From 58130b21a96d46406853472db231a33e7ba8e128 Mon Sep 17 00:00:00 2001 From: Egor Tsyganchuk Date: Tue, 23 Sep 2025 15:51:56 +0300 Subject: [PATCH] Initializing repository --- CONTRIBUTIONS | 174 + COPYING | 22 + README | 56 + hs20/client/.gitignore | 4 + hs20/client/Android.mk | 91 + hs20/client/Makefile | 81 + hs20/client/devdetail.xml | 47 + hs20/client/devinfo.xml | 7 + hs20/client/est.c | 742 + hs20/client/oma_dm_client.c | 1398 ++ hs20/client/osu_client.c | 3474 +++ hs20/client/osu_client.h | 121 + hs20/client/spp_client.c | 1003 + src/Makefile | 12 + src/ap/Makefile | 60 + src/ap/accounting.c | 547 + src/ap/accounting.h | 45 + src/ap/acs.c | 1518 ++ src/ap/acs.h | 35 + src/ap/airtime_policy.c | 273 + src/ap/airtime_policy.h | 48 + src/ap/ap_config.c | 1789 ++ src/ap/ap_config.h | 1401 ++ src/ap/ap_drv_ops.c | 1252 ++ src/ap/ap_drv_ops.h | 481 + src/ap/ap_list.c | 308 + src/ap/ap_list.h | 58 + src/ap/ap_mlme.c | 191 + src/ap/ap_mlme.h | 34 + src/ap/authsrv.c | 445 + src/ap/authsrv.h | 15 + src/ap/beacon.c | 2762 +++ src/ap/beacon.h | 39 + src/ap/bss_load.c | 99 + src/ap/bss_load.h | 17 + src/ap/comeback_token.c | 139 + src/ap/comeback_token.h | 21 + src/ap/ctrl_iface_ap.c | 1622 ++ src/ap/ctrl_iface_ap.h | 57 + src/ap/dfs.c | 1669 ++ src/ap/dfs.h | 36 + src/ap/dhcp_snoop.c | 160 + src/ap/dhcp_snoop.h | 30 + src/ap/dpp_hostapd.c | 4007 ++++ src/ap/dpp_hostapd.h | 54 + src/ap/drv_callbacks.c | 2798 +++ src/ap/eap_user_db.c | 290 + src/ap/eth_p_oui.c | 191 + src/ap/eth_p_oui.h | 28 + src/ap/fils_hlp.c | 654 + src/ap/fils_hlp.h | 27 + src/ap/gas_query_ap.c | 718 + src/ap/gas_query_ap.h | 43 + src/ap/gas_serv.c | 1895 ++ src/ap/gas_serv.h | 95 + src/ap/hostapd.c | 4951 +++++ src/ap/hostapd.h | 852 + src/ap/hs20.c | 255 + src/ap/hs20.h | 26 + src/ap/hw_features.c | 1393 ++ src/ap/hw_features.h | 108 + src/ap/ieee802_11.c | 8278 +++++++ src/ap/ieee802_11.h | 267 + src/ap/ieee802_11_auth.c | 751 + src/ap/ieee802_11_auth.h | 43 + src/ap/ieee802_11_eht.c | 1405 ++ src/ap/ieee802_11_he.c | 571 + src/ap/ieee802_11_ht.c | 534 + src/ap/ieee802_11_shared.c | 1228 ++ src/ap/ieee802_11_vht.c | 371 + src/ap/ieee802_1x.c | 3144 +++ src/ap/ieee802_1x.h | 69 + src/ap/mbo_ap.c | 244 + src/ap/mbo_ap.h | 51 + src/ap/nan_usd_ap.c | 267 + src/ap/nan_usd_ap.h | 46 + src/ap/ndisc_snoop.c | 189 + src/ap/ndisc_snoop.h | 36 + src/ap/neighbor_db.c | 366 + src/ap/neighbor_db.h | 28 + src/ap/p2p_hostapd.c | 113 + src/ap/p2p_hostapd.h | 35 + src/ap/pmksa_cache_auth.c | 751 + src/ap/pmksa_cache_auth.h | 83 + src/ap/preauth_auth.c | 273 + src/ap/preauth_auth.h | 52 + src/ap/rrm.c | 795 + src/ap/rrm.h | 35 + src/ap/sta_info.c | 1888 ++ src/ap/sta_info.h | 446 + src/ap/taxonomy.c | 292 + src/ap/taxonomy.h | 24 + src/ap/tkip_countermeasures.c | 110 + src/ap/tkip_countermeasures.h | 15 + src/ap/utils.c | 112 + src/ap/vlan.c | 34 + src/ap/vlan.h | 30 + src/ap/vlan_full.c | 801 + src/ap/vlan_ifconfig.c | 69 + src/ap/vlan_init.c | 267 + src/ap/vlan_init.h | 44 + src/ap/vlan_ioctl.c | 155 + src/ap/vlan_util.c | 174 + src/ap/vlan_util.h | 31 + src/ap/wmm.c | 385 + src/ap/wmm.h | 23 + src/ap/wnm_ap.c | 1095 + src/ap/wnm_ap.h | 30 + src/ap/wpa_auth.c | 7254 +++++++ src/ap/wpa_auth.h | 658 + src/ap/wpa_auth_ft.c | 4997 +++++ src/ap/wpa_auth_glue.c | 1837 ++ src/ap/wpa_auth_glue.h | 16 + src/ap/wpa_auth_i.h | 347 + src/ap/wpa_auth_ie.c | 1299 ++ src/ap/wpa_auth_ie.h | 16 + src/ap/wpa_auth_kay.c | 496 + src/ap/wpa_auth_kay.h | 51 + src/ap/wps_hostapd.c | 2285 ++ src/ap/wps_hostapd.h | 92 + src/ap/x_snoop.c | 136 + src/ap/x_snoop.h | 56 + src/build.rules | 109 + src/common/Makefile | 16 + src/common/brcm_vendor.h | 156 + src/common/cli.c | 267 + src/common/cli.h | 47 + src/common/common_module_tests.c | 794 + src/common/ctrl_iface_common.c | 209 + src/common/ctrl_iface_common.h | 42 + src/common/defs.h | 535 + src/common/dhcp.h | 263 + src/common/dpp.c | 5190 +++++ src/common/dpp.h | 859 + src/common/dpp_auth.c | 1975 ++ src/common/dpp_backup.c | 1210 ++ src/common/dpp_crypto.c | 2618 +++ src/common/dpp_i.h | 168 + src/common/dpp_pkex.c | 1388 ++ src/common/dpp_reconfig.c | 970 + src/common/dpp_tcp.c | 2661 +++ src/common/dragonfly.c | 252 + src/common/dragonfly.h | 33 + src/common/eapol_common.h | 92 + src/common/gas.c | 273 + src/common/gas.h | 40 + src/common/gas_server.c | 620 + src/common/gas_server.h | 52 + src/common/hw_features_common.c | 1035 + src/common/hw_features_common.h | 62 + src/common/ieee802_11_common.c | 3435 +++ src/common/ieee802_11_common.h | 373 + src/common/ieee802_11_defs.h | 3066 +++ src/common/ieee802_1x_defs.h | 86 + src/common/linux_bridge.h | 39 + src/common/linux_vlan.h | 52 + src/common/nan.h | 98 + src/common/nan_de.c | 1395 ++ src/common/nan_de.h | 145 + src/common/ocv.c | 174 + src/common/ocv.h | 47 + src/common/privsep_commands.h | 112 + src/common/ptksa_cache.c | 387 + src/common/ptksa_cache.h | 50 + src/common/qca-vendor-attr.h | 28 + src/common/qca-vendor.h | 17852 ++++++++++++++++ src/common/sae.c | 2472 +++ src/common/sae.h | 180 + src/common/sae_pk.c | 884 + src/common/tnc.h | 121 + src/common/version.h | 14 + src/common/wpa_common.c | 4262 ++++ src/common/wpa_common.h | 790 + src/common/wpa_ctrl.c | 776 + src/common/wpa_ctrl.h | 677 + src/common/wpa_helpers.c | 294 + src/common/wpa_helpers.h | 37 + src/crypto/Makefile | 60 + src/crypto/aes-cbc.c | 86 + src/crypto/aes-ccm.c | 212 + src/crypto/aes-ctr.c | 71 + src/crypto/aes-eax.c | 145 + src/crypto/aes-encblock.c | 32 + src/crypto/aes-gcm.c | 327 + src/crypto/aes-internal-dec.c | 163 + src/crypto/aes-internal-enc.c | 131 + src/crypto/aes-internal.c | 845 + src/crypto/aes-omac1.c | 173 + src/crypto/aes-siv.c | 208 + src/crypto/aes-unwrap.c | 80 + src/crypto/aes-wrap.c | 76 + src/crypto/aes.h | 21 + src/crypto/aes_i.h | 125 + src/crypto/aes_siv.h | 21 + src/crypto/aes_wrap.h | 73 + src/crypto/crypto.h | 1389 ++ src/crypto/crypto_gnutls.c | 511 + src/crypto/crypto_internal-cipher.c | 243 + src/crypto/crypto_internal-modexp.c | 122 + src/crypto/crypto_internal-rsa.c | 117 + src/crypto/crypto_internal.c | 333 + src/crypto/crypto_libtomcrypt.c | 773 + src/crypto/crypto_linux.c | 1014 + src/crypto/crypto_module_tests.c | 2598 +++ src/crypto/crypto_nettle.c | 474 + src/crypto/crypto_none.c | 29 + src/crypto/crypto_openssl.c | 5584 +++++ src/crypto/crypto_wolfssl.c | 3560 +++ src/crypto/des-internal.c | 494 + src/crypto/des_i.h | 25 + src/crypto/dh_group5.c | 41 + src/crypto/dh_group5.h | 18 + src/crypto/dh_groups.c | 1266 ++ src/crypto/dh_groups.h | 29 + src/crypto/fips_prf_internal.c | 66 + src/crypto/fips_prf_openssl.c | 114 + src/crypto/fips_prf_wolfssl.c | 87 + src/crypto/md4-internal.c | 275 + src/crypto/md5-internal.c | 290 + src/crypto/md5.c | 109 + src/crypto/md5.h | 19 + src/crypto/md5_i.h | 23 + src/crypto/milenage.c | 323 + src/crypto/milenage.h | 27 + src/crypto/ms_funcs.c | 531 + src/crypto/ms_funcs.h | 60 + src/crypto/random.c | 478 + src/crypto/random.h | 28 + src/crypto/rc4.c | 54 + src/crypto/sha1-internal.c | 306 + src/crypto/sha1-pbkdf2.c | 95 + src/crypto/sha1-prf.c | 67 + src/crypto/sha1-tlsprf.c | 101 + src/crypto/sha1-tprf.c | 72 + src/crypto/sha1.c | 108 + src/crypto/sha1.h | 27 + src/crypto/sha1_i.h | 23 + src/crypto/sha256-internal.c | 226 + src/crypto/sha256-kdf.c | 87 + src/crypto/sha256-prf.c | 108 + src/crypto/sha256-tlsprf.c | 71 + src/crypto/sha256.c | 113 + src/crypto/sha256.h | 30 + src/crypto/sha256_i.h | 25 + src/crypto/sha384-internal.c | 92 + src/crypto/sha384-kdf.c | 87 + src/crypto/sha384-prf.c | 108 + src/crypto/sha384-tlsprf.c | 71 + src/crypto/sha384.c | 104 + src/crypto/sha384.h | 30 + src/crypto/sha384_i.h | 23 + src/crypto/sha512-internal.c | 267 + src/crypto/sha512-kdf.c | 87 + src/crypto/sha512-prf.c | 108 + src/crypto/sha512.c | 104 + src/crypto/sha512.h | 27 + src/crypto/sha512_i.h | 25 + src/crypto/tls.h | 693 + src/crypto/tls_gnutls.c | 1787 ++ src/crypto/tls_internal.c | 804 + src/crypto/tls_none.c | 233 + src/crypto/tls_openssl.c | 6029 ++++++ src/crypto/tls_openssl.h | 19 + src/crypto/tls_openssl_ocsp.c | 842 + src/crypto/tls_wolfssl.c | 2314 ++ src/drivers/Makefile | 9 + src/drivers/android_drv.h | 56 + src/drivers/driver.h | 6994 ++++++ src/drivers/driver_atheros.c | 2286 ++ src/drivers/driver_bsd.c | 1769 ++ src/drivers/driver_common.c | 379 + src/drivers/driver_hostap.c | 1208 ++ src/drivers/driver_hostap.h | 208 + src/drivers/driver_macsec_linux.c | 1720 ++ src/drivers/driver_macsec_qca.c | 1071 + src/drivers/driver_ndis.c | 3232 +++ src/drivers/driver_ndis.h | 59 + src/drivers/driver_ndis_.c | 99 + src/drivers/driver_nl80211.c | 14158 ++++++++++++ src/drivers/driver_nl80211.h | 413 + src/drivers/driver_nl80211_android.c | 188 + src/drivers/driver_nl80211_capa.c | 2730 +++ src/drivers/driver_nl80211_event.c | 4272 ++++ src/drivers/driver_nl80211_monitor.c | 503 + src/drivers/driver_nl80211_scan.c | 1357 ++ src/drivers/driver_none.c | 77 + src/drivers/driver_openbsd.c | 139 + src/drivers/driver_privsep.c | 847 + src/drivers/driver_roboswitch.c | 487 + src/drivers/driver_wext.c | 2500 +++ src/drivers/driver_wext.h | 77 + src/drivers/driver_wired.c | 407 + src/drivers/driver_wired_common.c | 322 + src/drivers/driver_wired_common.h | 34 + src/drivers/drivers.c | 53 + src/drivers/drivers.mak | 220 + src/drivers/drivers.mk | 196 + src/drivers/linux_defines.h | 46 + src/drivers/linux_ioctl.c | 246 + src/drivers/linux_ioctl.h | 23 + src/drivers/linux_wext.h | 45 + src/drivers/ndis_events.c | 803 + src/drivers/netlink.c | 228 + src/drivers/netlink.h | 28 + src/drivers/nl80211_copy.h | 7872 +++++++ src/drivers/priv_netlink.h | 109 + src/drivers/rfkill.c | 224 + src/drivers/rfkill.h | 25 + src/eap_common/Makefile | 18 + src/eap_common/chap.c | 28 + src/eap_common/chap.h | 17 + src/eap_common/eap_common.c | 288 + src/eap_common/eap_common.h | 33 + src/eap_common/eap_defs.h | 119 + src/eap_common/eap_eke_common.c | 728 + src/eap_common/eap_eke_common.h | 114 + src/eap_common/eap_fast_common.c | 270 + src/eap_common/eap_fast_common.h | 107 + src/eap_common/eap_gpsk_common.c | 552 + src/eap_common/eap_gpsk_common.h | 66 + src/eap_common/eap_ikev2_common.c | 116 + src/eap_common/eap_ikev2_common.h | 29 + src/eap_common/eap_pax_common.c | 150 + src/eap_common/eap_pax_common.h | 92 + src/eap_common/eap_peap_common.c | 85 + src/eap_common/eap_peap_common.h | 16 + src/eap_common/eap_psk_common.c | 68 + src/eap_common/eap_psk_common.h | 72 + src/eap_common/eap_pwd_common.c | 490 + src/eap_common/eap_pwd_common.h | 77 + src/eap_common/eap_sake_common.c | 407 + src/eap_common/eap_sake_common.h | 96 + src/eap_common/eap_sim_common.c | 1254 ++ src/eap_common/eap_sim_common.h | 231 + src/eap_common/eap_teap_common.c | 744 + src/eap_common/eap_teap_common.h | 230 + src/eap_common/eap_tlv_common.h | 112 + src/eap_common/eap_ttls.h | 65 + src/eap_common/eap_wsc_common.c | 33 + src/eap_common/eap_wsc_common.h | 27 + src/eap_common/ikev2_common.c | 725 + src/eap_common/ikev2_common.h | 334 + src/eap_peer/.gitignore | 1 + src/eap_peer/Makefile | 7 + src/eap_peer/eap.c | 3292 +++ src/eap_peer/eap.h | 384 + src/eap_peer/eap_aka.c | 1768 ++ src/eap_peer/eap_config.h | 749 + src/eap_peer/eap_eke.c | 785 + src/eap_peer/eap_fast.c | 1832 ++ src/eap_peer/eap_fast_pac.c | 927 + src/eap_peer/eap_fast_pac.h | 50 + src/eap_peer/eap_gpsk.c | 783 + src/eap_peer/eap_gtc.c | 141 + src/eap_peer/eap_i.h | 415 + src/eap_peer/eap_ikev2.c | 530 + src/eap_peer/eap_leap.c | 416 + src/eap_peer/eap_md5.c | 116 + src/eap_peer/eap_methods.c | 378 + src/eap_peer/eap_methods.h | 113 + src/eap_peer/eap_mschapv2.c | 919 + src/eap_peer/eap_otp.c | 97 + src/eap_peer/eap_pax.c | 559 + src/eap_peer/eap_peap.c | 1408 ++ src/eap_peer/eap_proxy.h | 55 + src/eap_peer/eap_proxy_dummy.c | 94 + src/eap_peer/eap_psk.c | 494 + src/eap_peer/eap_pwd.c | 1172 + src/eap_peer/eap_sake.c | 521 + src/eap_peer/eap_sim.c | 1462 ++ src/eap_peer/eap_teap.c | 2153 ++ src/eap_peer/eap_teap_pac.c | 931 + src/eap_peer/eap_teap_pac.h | 50 + src/eap_peer/eap_tls.c | 508 + src/eap_peer/eap_tls_common.c | 1218 ++ src/eap_peer/eap_tls_common.h | 145 + src/eap_peer/eap_tnc.c | 424 + src/eap_peer/eap_ttls.c | 1903 ++ src/eap_peer/eap_vendor_test.c | 186 + src/eap_peer/eap_wsc.c | 603 + src/eap_peer/ikev2.c | 1242 ++ src/eap_peer/ikev2.h | 59 + src/eap_peer/mschapv2.c | 124 + src/eap_peer/mschapv2.h | 28 + src/eap_peer/tncc.c | 1316 ++ src/eap_peer/tncc.h | 36 + src/eap_server/Makefile | 8 + src/eap_server/eap.h | 307 + src/eap_server/eap_i.h | 210 + src/eap_server/eap_methods.h | 52 + src/eap_server/eap_server.c | 2104 ++ src/eap_server/eap_server_aka.c | 1506 ++ src/eap_server/eap_server_eke.c | 812 + src/eap_server/eap_server_fast.c | 1647 ++ src/eap_server/eap_server_gpsk.c | 649 + src/eap_server/eap_server_gtc.c | 219 + src/eap_server/eap_server_identity.c | 177 + src/eap_server/eap_server_ikev2.c | 571 + src/eap_server/eap_server_md5.c | 171 + src/eap_server/eap_server_methods.c | 178 + src/eap_server/eap_server_mschapv2.c | 616 + src/eap_server/eap_server_pax.c | 614 + src/eap_server/eap_server_peap.c | 1498 ++ src/eap_server/eap_server_psk.c | 527 + src/eap_server/eap_server_pwd.c | 1079 + src/eap_server/eap_server_sake.c | 550 + src/eap_server/eap_server_sim.c | 1018 + src/eap_server/eap_server_teap.c | 2141 ++ src/eap_server/eap_server_tls.c | 503 + src/eap_server/eap_server_tls_common.c | 582 + src/eap_server/eap_server_tnc.c | 572 + src/eap_server/eap_server_ttls.c | 1390 ++ src/eap_server/eap_server_vendor_test.c | 188 + src/eap_server/eap_server_wsc.c | 510 + src/eap_server/eap_sim_db.c | 1563 ++ src/eap_server/eap_sim_db.h | 95 + src/eap_server/eap_tls_common.h | 103 + src/eap_server/ikev2.c | 1198 ++ src/eap_server/ikev2.h | 61 + src/eap_server/tncs.c | 1199 ++ src/eap_server/tncs.h | 43 + src/eapol_auth/Makefile | 2 + src/eapol_auth/eapol_auth_dump.c | 289 + src/eapol_auth/eapol_auth_sm.c | 1281 ++ src/eapol_auth/eapol_auth_sm.h | 83 + src/eapol_auth/eapol_auth_sm_i.h | 180 + src/eapol_supp/Makefile | 5 + src/eapol_supp/eapol_supp_sm.c | 2288 ++ src/eapol_supp/eapol_supp_sm.h | 511 + src/fst/Makefile | 8 + src/fst/fst.c | 240 + src/fst/fst.h | 311 + src/fst/fst_ctrl_aux.c | 70 + src/fst/fst_ctrl_aux.h | 93 + src/fst/fst_ctrl_defs.h | 109 + src/fst/fst_ctrl_iface.c | 948 + src/fst/fst_ctrl_iface.h | 45 + src/fst/fst_defs.h | 87 + src/fst/fst_group.c | 526 + src/fst/fst_group.h | 69 + src/fst/fst_iface.c | 80 + src/fst/fst_iface.h | 136 + src/fst/fst_internal.h | 49 + src/fst/fst_session.c | 1607 ++ src/fst/fst_session.h | 80 + src/l2_packet/Makefile | 3 + src/l2_packet/l2_packet.h | 159 + src/l2_packet/l2_packet_freebsd.c | 340 + src/l2_packet/l2_packet_linux.c | 515 + src/l2_packet/l2_packet_ndis.c | 536 + src/l2_packet/l2_packet_none.c | 137 + src/l2_packet/l2_packet_pcap.c | 400 + src/l2_packet/l2_packet_privsep.c | 284 + src/l2_packet/l2_packet_winpcap.c | 350 + src/lib.rules | 29 + src/objs.mk | 3 + src/p2p/Makefile | 16 + src/p2p/p2p.c | 5690 +++++ src/p2p/p2p.h | 2433 +++ src/p2p/p2p_build.c | 841 + src/p2p/p2p_dev_disc.c | 329 + src/p2p/p2p_go_neg.c | 1599 ++ src/p2p/p2p_group.c | 1131 + src/p2p/p2p_i.h | 912 + src/p2p/p2p_invitation.c | 735 + src/p2p/p2p_parse.c | 911 + src/p2p/p2p_pd.c | 1782 ++ src/p2p/p2p_sd.c | 948 + src/p2p/p2p_utils.c | 613 + src/pae/Makefile | 8 + src/pae/ieee802_1x_cp.c | 738 + src/pae/ieee802_1x_cp.h | 39 + src/pae/ieee802_1x_kay.c | 4185 ++++ src/pae/ieee802_1x_kay.h | 284 + src/pae/ieee802_1x_kay_i.h | 425 + src/pae/ieee802_1x_key.c | 210 + src/pae/ieee802_1x_key.h | 26 + src/pae/ieee802_1x_secy_ops.c | 559 + src/pae/ieee802_1x_secy_ops.h | 63 + src/pasn/Makefile | 16 + src/pasn/pasn_common.c | 232 + src/pasn/pasn_common.h | 228 + src/pasn/pasn_initiator.c | 1406 ++ src/pasn/pasn_responder.c | 1032 + src/radius/Makefile | 9 + src/radius/radius.c | 1909 ++ src/radius/radius.h | 375 + src/radius/radius_client.c | 2099 ++ src/radius/radius_client.h | 292 + src/radius/radius_das.c | 608 + src/radius/radius_das.h | 63 + src/radius/radius_server.c | 2861 +++ src/radius/radius_server.h | 121 + src/rsn_supp/Makefile | 14 + src/rsn_supp/pmksa_cache.c | 950 + src/rsn_supp/pmksa_cache.h | 106 + src/rsn_supp/preauth.c | 555 + src/rsn_supp/preauth.h | 79 + src/rsn_supp/tdls.c | 3329 +++ src/rsn_supp/wpa.c | 6711 ++++++ src/rsn_supp/wpa.h | 625 + src/rsn_supp/wpa_ft.c | 1408 ++ src/rsn_supp/wpa_i.h | 546 + src/rsn_supp/wpa_ie.c | 411 + src/rsn_supp/wpa_ie.h | 18 + src/tls/Makefile | 25 + src/tls/asn1.c | 650 + src/tls/asn1.h | 212 + src/tls/bignum.c | 224 + src/tls/bignum.h | 32 + src/tls/libtommath.c | 3393 +++ src/tls/pkcs1.c | 341 + src/tls/pkcs1.h | 29 + src/tls/pkcs5.c | 628 + src/tls/pkcs5.h | 16 + src/tls/pkcs8.c | 168 + src/tls/pkcs8.h | 16 + src/tls/rsa.c | 366 + src/tls/rsa.h | 26 + src/tls/tlsv1_client.c | 918 + src/tls/tlsv1_client.h | 63 + src/tls/tlsv1_client_i.h | 102 + src/tls/tlsv1_client_ocsp.c | 759 + src/tls/tlsv1_client_read.c | 1562 ++ src/tls/tlsv1_client_write.c | 997 + src/tls/tlsv1_common.c | 525 + src/tls/tlsv1_common.h | 276 + src/tls/tlsv1_cred.c | 1212 ++ src/tls/tlsv1_cred.h | 48 + src/tls/tlsv1_record.c | 485 + src/tls/tlsv1_record.h | 71 + src/tls/tlsv1_server.c | 867 + src/tls/tlsv1_server.h | 58 + src/tls/tlsv1_server_i.h | 89 + src/tls/tlsv1_server_read.c | 1320 ++ src/tls/tlsv1_server_write.c | 1093 + src/tls/x509v3.c | 2291 ++ src/tls/x509v3.h | 164 + src/utils/Makefile | 30 + src/utils/base64.c | 208 + src/utils/base64.h | 18 + src/utils/bitfield.c | 89 + src/utils/bitfield.h | 21 + src/utils/browser-android.c | 126 + src/utils/browser-system.c | 119 + src/utils/browser-wpadebug.c | 139 + src/utils/browser.c | 402 + src/utils/browser.h | 21 + src/utils/build_config.h | 50 + src/utils/common.c | 1317 ++ src/utils/common.h | 636 + src/utils/config.c | 105 + src/utils/config.h | 29 + src/utils/const_time.h | 191 + src/utils/crc32.c | 85 + src/utils/crc32.h | 14 + src/utils/edit.c | 1174 + src/utils/edit.h | 21 + src/utils/edit_readline.c | 192 + src/utils/edit_simple.c | 98 + src/utils/eloop.c | 1359 ++ src/utils/eloop.h | 367 + src/utils/eloop_win.c | 700 + src/utils/ext_password.c | 115 + src/utils/ext_password.h | 33 + src/utils/ext_password_file.c | 136 + src/utils/ext_password_i.h | 33 + src/utils/ext_password_test.c | 90 + src/utils/http-utils.h | 64 + src/utils/http_curl.c | 1741 ++ src/utils/includes.h | 46 + src/utils/ip_addr.c | 72 + src/utils/ip_addr.h | 29 + src/utils/json.c | 690 + src/utils/json.h | 57 + src/utils/list.h | 97 + src/utils/module_tests.h | 20 + src/utils/os.h | 710 + src/utils/os_internal.c | 560 + src/utils/os_none.c | 248 + src/utils/os_unix.c | 872 + src/utils/os_win32.c | 295 + src/utils/pcsc_funcs.c | 1451 ++ src/utils/pcsc_funcs.h | 42 + src/utils/platform.h | 18 + src/utils/radiotap.c | 396 + src/utils/radiotap.h | 200 + src/utils/radiotap_iter.h | 96 + src/utils/state_machine.h | 138 + src/utils/trace.c | 420 + src/utils/trace.h | 71 + src/utils/utils_module_tests.c | 1234 ++ src/utils/uuid.c | 96 + src/utils/uuid.h | 19 + src/utils/wpa_debug.c | 902 + src/utils/wpa_debug.h | 371 + src/utils/wpabuf.c | 340 + src/utils/wpabuf.h | 197 + src/utils/xml-utils.c | 471 + src/utils/xml-utils.h | 97 + src/utils/xml_libxml2.c | 459 + src/wps/Makefile | 28 + src/wps/http.h | 29 + src/wps/http_client.c | 362 + src/wps/http_client.h | 40 + src/wps/http_server.c | 314 + src/wps/http_server.h | 33 + src/wps/httpread.c | 848 + src/wps/httpread.h | 117 + src/wps/ndef.c | 210 + src/wps/upnp_xml.c | 252 + src/wps/upnp_xml.h | 25 + src/wps/wps.c | 665 + src/wps/wps.h | 1107 + src/wps/wps_attr_build.c | 523 + src/wps/wps_attr_parse.c | 685 + src/wps/wps_attr_parse.h | 105 + src/wps/wps_attr_process.c | 283 + src/wps/wps_common.c | 911 + src/wps/wps_defs.h | 384 + src/wps/wps_dev_attr.c | 444 + src/wps/wps_dev_attr.h | 41 + src/wps/wps_enrollee.c | 1522 ++ src/wps/wps_er.c | 2105 ++ src/wps/wps_er.h | 112 + src/wps/wps_er_ssdp.c | 205 + src/wps/wps_i.h | 222 + src/wps/wps_module_tests.c | 338 + src/wps/wps_registrar.c | 3794 ++++ src/wps/wps_upnp.c | 1259 ++ src/wps/wps_upnp.h | 51 + src/wps/wps_upnp_ap.c | 85 + src/wps/wps_upnp_event.c | 423 + src/wps/wps_upnp_i.h | 194 + src/wps/wps_upnp_ssdp.c | 942 + src/wps/wps_upnp_web.c | 1420 ++ src/wps/wps_validate.c | 1977 ++ wpa_supplicant/.gitignore | 15 + wpa_supplicant/Android.mk | 2033 ++ wpa_supplicant/ChangeLog | 2550 +++ wpa_supplicant/Makefile | 2342 ++ wpa_supplicant/README | 1165 + wpa_supplicant/README-DPP | 204 + wpa_supplicant/README-HS20 | 685 + wpa_supplicant/README-NAN-USD | 147 + wpa_supplicant/README-P2P | 856 + wpa_supplicant/README-WPS | 393 + wpa_supplicant/README-Windows.txt | 299 + wpa_supplicant/android.config | 560 + wpa_supplicant/ap.c | 2184 ++ wpa_supplicant/ap.h | 126 + wpa_supplicant/autoscan.c | 162 + wpa_supplicant/autoscan.h | 59 + wpa_supplicant/autoscan_exponential.c | 104 + wpa_supplicant/autoscan_periodic.c | 85 + wpa_supplicant/bgscan.c | 109 + wpa_supplicant/bgscan.h | 82 + wpa_supplicant/bgscan_learn.c | 614 + wpa_supplicant/bgscan_simple.c | 333 + wpa_supplicant/binder/.clang-format | 9 + wpa_supplicant/binder/binder.cpp | 104 + wpa_supplicant/binder/binder.h | 46 + wpa_supplicant/binder/binder_constants.cpp | 18 + wpa_supplicant/binder/binder_constants.h | 21 + wpa_supplicant/binder/binder_i.h | 28 + wpa_supplicant/binder/binder_manager.cpp | 100 + wpa_supplicant/binder/binder_manager.h | 58 + wpa_supplicant/binder/iface.cpp | 16 + wpa_supplicant/binder/iface.h | 42 + wpa_supplicant/binder/supplicant.cpp | 127 + wpa_supplicant/binder/supplicant.h | 55 + wpa_supplicant/bss.c | 1868 ++ wpa_supplicant/bss.h | 229 + wpa_supplicant/bssid_ignore.c | 221 + wpa_supplicant/bssid_ignore.h | 33 + wpa_supplicant/config.c | 5760 +++++ wpa_supplicant/config.h | 1901 ++ wpa_supplicant/config_file.c | 1726 ++ wpa_supplicant/config_none.c | 57 + wpa_supplicant/config_ssid.h | 1288 ++ wpa_supplicant/config_winreg.c | 1060 + wpa_supplicant/ctrl_iface.c | 14298 +++++++++++++ wpa_supplicant/ctrl_iface.h | 169 + wpa_supplicant/ctrl_iface_named_pipe.c | 831 + wpa_supplicant/ctrl_iface_udp.c | 831 + wpa_supplicant/ctrl_iface_unix.c | 1434 ++ wpa_supplicant/dbus/.gitignore | 1 + wpa_supplicant/dbus/Makefile | 69 + wpa_supplicant/dbus/dbus-wpa_supplicant.conf | 17 + wpa_supplicant/dbus/dbus_common.c | 373 + wpa_supplicant/dbus/dbus_common.h | 20 + wpa_supplicant/dbus/dbus_common_i.h | 34 + wpa_supplicant/dbus/dbus_dict_helpers.c | 1161 + wpa_supplicant/dbus/dbus_dict_helpers.h | 161 + wpa_supplicant/dbus/dbus_new.c | 5245 +++++ wpa_supplicant/dbus/dbus_new.h | 672 + wpa_supplicant/dbus/dbus_new_handlers.c | 6464 ++++++ wpa_supplicant/dbus/dbus_new_handlers.h | 292 + wpa_supplicant/dbus/dbus_new_handlers_p2p.c | 3159 +++ wpa_supplicant/dbus/dbus_new_handlers_p2p.h | 152 + wpa_supplicant/dbus/dbus_new_handlers_wps.c | 804 + wpa_supplicant/dbus/dbus_new_helpers.c | 1202 ++ wpa_supplicant/dbus/dbus_new_helpers.h | 159 + wpa_supplicant/dbus/dbus_new_introspect.c | 286 + .../dbus/fi.w1.wpa_supplicant1.service.in | 5 + wpa_supplicant/defconfig | 688 + wpa_supplicant/doc/docbook/.gitignore | 7 + wpa_supplicant/doc/docbook/Makefile | 28 + wpa_supplicant/doc/docbook/eapol_test.sgml | 209 + .../doc/docbook/wpa_background.sgml | 105 + wpa_supplicant/doc/docbook/wpa_cli.sgml | 360 + wpa_supplicant/doc/docbook/wpa_gui.sgml | 106 + .../doc/docbook/wpa_passphrase.sgml | 77 + wpa_supplicant/doc/docbook/wpa_priv.sgml | 152 + .../doc/docbook/wpa_supplicant.conf.sgml | 243 + .../doc/docbook/wpa_supplicant.sgml | 786 + wpa_supplicant/dpp_supplicant.c | 5795 +++++ wpa_supplicant/dpp_supplicant.h | 50 + wpa_supplicant/driver_i.h | 1178 + wpa_supplicant/eap_proxy_dummy.mak | 0 wpa_supplicant/eap_proxy_dummy.mk | 0 wpa_supplicant/eap_register.c | 271 + wpa_supplicant/eap_testing.txt | 392 + wpa_supplicant/eapol_test.c | 1627 ++ wpa_supplicant/eapol_test.py | 159 + wpa_supplicant/events.c | 6970 ++++++ wpa_supplicant/examples/60_wpa_supplicant | 19 + wpa_supplicant/examples/dbus-listen-preq.py | 66 + wpa_supplicant/examples/dpp-nfc.py | 1192 ++ wpa_supplicant/examples/dpp-qrcode.py | 130 + wpa_supplicant/examples/ieee8021x.conf | 13 + wpa_supplicant/examples/openCryptoki.conf | 41 + wpa_supplicant/examples/p2p-action-udhcp.sh | 69 + wpa_supplicant/examples/p2p-action.sh | 96 + wpa_supplicant/examples/p2p-nfc.py | 654 + wpa_supplicant/examples/p2p/p2p_connect.py | 299 + wpa_supplicant/examples/p2p/p2p_disconnect.py | 169 + wpa_supplicant/examples/p2p/p2p_find.py | 192 + wpa_supplicant/examples/p2p/p2p_flush.py | 168 + wpa_supplicant/examples/p2p/p2p_group_add.py | 222 + wpa_supplicant/examples/p2p/p2p_invite.py | 201 + wpa_supplicant/examples/p2p/p2p_listen.py | 182 + wpa_supplicant/examples/p2p/p2p_stop_find.py | 174 + wpa_supplicant/examples/plaintext.conf | 8 + wpa_supplicant/examples/udhcpd-p2p.conf | 118 + wpa_supplicant/examples/wep.conf | 11 + wpa_supplicant/examples/wpa-psk-tkip.conf | 12 + wpa_supplicant/examples/wpa2-eap-ccmp.conf | 15 + .../examples/wpas-dbus-new-getall.py | 58 + .../examples/wpas-dbus-new-signals.py | 203 + wpa_supplicant/examples/wpas-dbus-new-wps.py | 80 + wpa_supplicant/examples/wpas-dbus-new.py | 149 + wpa_supplicant/examples/wps-ap-cli | 81 + wpa_supplicant/examples/wps-nfc.py | 525 + wpa_supplicant/gas_query.c | 913 + wpa_supplicant/gas_query.h | 59 + wpa_supplicant/hs20_supplicant.c | 1356 ++ wpa_supplicant/hs20_supplicant.h | 51 + wpa_supplicant/ibss_rsn.c | 958 + wpa_supplicant/ibss_rsn.h | 66 + wpa_supplicant/interworking.c | 3327 +++ wpa_supplicant/interworking.h | 37 + wpa_supplicant/libwpa_test.c | 32 + wpa_supplicant/main.c | 411 + wpa_supplicant/main_none.c | 40 + wpa_supplicant/main_winmain.c | 78 + wpa_supplicant/main_winsvc.c | 458 + wpa_supplicant/mbo.c | 682 + wpa_supplicant/mesh.c | 898 + wpa_supplicant/mesh.h | 49 + wpa_supplicant/mesh_mpm.c | 1439 ++ wpa_supplicant/mesh_mpm.h | 46 + wpa_supplicant/mesh_rsn.c | 814 + wpa_supplicant/mesh_rsn.h | 45 + wpa_supplicant/nan_usd.c | 513 + wpa_supplicant/nan_usd.h | 46 + wpa_supplicant/nfc_pw_token.c | 83 + wpa_supplicant/nmake.mak | 240 + wpa_supplicant/notify.c | 1060 + wpa_supplicant/notify.h | 179 + wpa_supplicant/offchannel.c | 488 + wpa_supplicant/offchannel.h | 35 + wpa_supplicant/op_classes.c | 644 + wpa_supplicant/p2p_supplicant.c | 10244 +++++++++ wpa_supplicant/p2p_supplicant.h | 356 + wpa_supplicant/p2p_supplicant_sd.c | 1283 ++ wpa_supplicant/pasn_supplicant.c | 980 + wpa_supplicant/preauth_test.c | 373 + wpa_supplicant/robust_av.c | 1738 ++ wpa_supplicant/rrm.c | 1650 ++ wpa_supplicant/scan.c | 3848 ++++ wpa_supplicant/scan.h | 108 + wpa_supplicant/sme.c | 3629 ++++ wpa_supplicant/sme.h | 131 + .../wpa_supplicant-nl80211.service.arg.in | 15 + .../wpa_supplicant-wired.service.arg.in | 15 + .../systemd/wpa_supplicant.service.arg.in | 15 + .../systemd/wpa_supplicant.service.in | 14 + wpa_supplicant/todo.txt | 78 + wpa_supplicant/twt.c | 142 + wpa_supplicant/utils/log2pcap.py | 55 + .../vs2005/win_if_list/win_if_list.vcproj | 203 + wpa_supplicant/vs2005/wpa_supplicant.sln | 52 + wpa_supplicant/vs2005/wpasvc/wpasvc.vcproj | 465 + wpa_supplicant/wifi_display.c | 431 + wpa_supplicant/wifi_display.h | 24 + wpa_supplicant/win_example.reg | 42 + wpa_supplicant/win_if_list.c | 173 + wpa_supplicant/wmm_ac.c | 987 + wpa_supplicant/wmm_ac.h | 176 + wpa_supplicant/wnm_sta.c | 2073 ++ wpa_supplicant/wnm_sta.h | 119 + wpa_supplicant/wpa_cli.c | 5197 +++++ wpa_supplicant/wpa_gui-qt4/.gitignore | 4 + wpa_supplicant/wpa_gui-qt4/addinterface.cpp | 239 + wpa_supplicant/wpa_gui-qt4/addinterface.h | 39 + wpa_supplicant/wpa_gui-qt4/eventhistory.cpp | 124 + wpa_supplicant/wpa_gui-qt4/eventhistory.h | 57 + wpa_supplicant/wpa_gui-qt4/eventhistory.ui | 61 + wpa_supplicant/wpa_gui-qt4/icons.qrc | 9 + wpa_supplicant/wpa_gui-qt4/icons/.gitignore | 2 + wpa_supplicant/wpa_gui-qt4/icons/Makefile | 37 + wpa_supplicant/wpa_gui-qt4/icons/README | 74 + wpa_supplicant/wpa_gui-qt4/icons/ap.svg | 832 + wpa_supplicant/wpa_gui-qt4/icons/group.svg | 616 + .../wpa_gui-qt4/icons/invitation.svg | 374 + wpa_supplicant/wpa_gui-qt4/icons/laptop.svg | 1568 ++ wpa_supplicant/wpa_gui-qt4/icons/wpa_gui.svg | 256 + wpa_supplicant/wpa_gui-qt4/icons_png.qrc | 9 + wpa_supplicant/wpa_gui-qt4/lang/.gitignore | 1 + wpa_supplicant/wpa_gui-qt4/lang/wpa_gui_de.ts | 1262 ++ wpa_supplicant/wpa_gui-qt4/main.cpp | 67 + wpa_supplicant/wpa_gui-qt4/networkconfig.cpp | 853 + wpa_supplicant/wpa_gui-qt4/networkconfig.h | 55 + wpa_supplicant/wpa_gui-qt4/networkconfig.ui | 435 + wpa_supplicant/wpa_gui-qt4/peers.cpp | 1885 ++ wpa_supplicant/wpa_gui-qt4/peers.h | 90 + wpa_supplicant/wpa_gui-qt4/peers.ui | 40 + wpa_supplicant/wpa_gui-qt4/scanresults.cpp | 141 + wpa_supplicant/wpa_gui-qt4/scanresults.h | 40 + wpa_supplicant/wpa_gui-qt4/scanresults.ui | 94 + .../wpa_gui-qt4/scanresultsitem.cpp | 18 + wpa_supplicant/wpa_gui-qt4/scanresultsitem.h | 21 + wpa_supplicant/wpa_gui-qt4/signalbar.cpp | 58 + wpa_supplicant/wpa_gui-qt4/signalbar.h | 28 + wpa_supplicant/wpa_gui-qt4/stringquery.cpp | 31 + wpa_supplicant/wpa_gui-qt4/stringquery.h | 28 + .../wpa_gui-qt4/userdatarequest.cpp | 94 + wpa_supplicant/wpa_gui-qt4/userdatarequest.h | 40 + wpa_supplicant/wpa_gui-qt4/userdatarequest.ui | 109 + wpa_supplicant/wpa_gui-qt4/wpa_gui.desktop | 10 + wpa_supplicant/wpa_gui-qt4/wpa_gui.pro | 73 + wpa_supplicant/wpa_gui-qt4/wpagui.cpp | 1913 ++ wpa_supplicant/wpa_gui-qt4/wpagui.h | 180 + wpa_supplicant/wpa_gui-qt4/wpagui.ui | 524 + wpa_supplicant/wpa_gui-qt4/wpamsg.h | 35 + wpa_supplicant/wpa_passphrase.c | 96 + wpa_supplicant/wpa_priv.c | 1297 ++ wpa_supplicant/wpa_supplicant.c | 9532 +++++++++ wpa_supplicant/wpa_supplicant.conf | 2160 ++ wpa_supplicant/wpa_supplicant_conf.mk | 34 + wpa_supplicant/wpa_supplicant_conf.sh | 16 + wpa_supplicant/wpa_supplicant_i.h | 2014 ++ wpa_supplicant/wpa_supplicant_template.conf | 9 + wpa_supplicant/wpas_glue.c | 1565 ++ wpa_supplicant/wpas_glue.h | 32 + wpa_supplicant/wpas_kay.c | 419 + wpa_supplicant/wpas_kay.h | 51 + wpa_supplicant/wpas_module_tests.c | 117 + wpa_supplicant/wps_supplicant.c | 3091 +++ wpa_supplicant/wps_supplicant.h | 177 + 871 files changed, 604586 insertions(+) create mode 100644 CONTRIBUTIONS create mode 100644 COPYING create mode 100644 README create mode 100644 hs20/client/.gitignore create mode 100644 hs20/client/Android.mk create mode 100644 hs20/client/Makefile create mode 100644 hs20/client/devdetail.xml create mode 100644 hs20/client/devinfo.xml create mode 100644 hs20/client/est.c create mode 100644 hs20/client/oma_dm_client.c create mode 100644 hs20/client/osu_client.c create mode 100644 hs20/client/osu_client.h create mode 100644 hs20/client/spp_client.c create mode 100644 src/Makefile create mode 100644 src/ap/Makefile create mode 100644 src/ap/accounting.c create mode 100644 src/ap/accounting.h create mode 100644 src/ap/acs.c create mode 100644 src/ap/acs.h create mode 100644 src/ap/airtime_policy.c create mode 100644 src/ap/airtime_policy.h create mode 100644 src/ap/ap_config.c create mode 100644 src/ap/ap_config.h create mode 100644 src/ap/ap_drv_ops.c create mode 100644 src/ap/ap_drv_ops.h create mode 100644 src/ap/ap_list.c create mode 100644 src/ap/ap_list.h create mode 100644 src/ap/ap_mlme.c create mode 100644 src/ap/ap_mlme.h create mode 100644 src/ap/authsrv.c create mode 100644 src/ap/authsrv.h create mode 100644 src/ap/beacon.c create mode 100644 src/ap/beacon.h create mode 100644 src/ap/bss_load.c create mode 100644 src/ap/bss_load.h create mode 100644 src/ap/comeback_token.c create mode 100644 src/ap/comeback_token.h create mode 100644 src/ap/ctrl_iface_ap.c create mode 100644 src/ap/ctrl_iface_ap.h create mode 100644 src/ap/dfs.c create mode 100644 src/ap/dfs.h create mode 100644 src/ap/dhcp_snoop.c create mode 100644 src/ap/dhcp_snoop.h create mode 100644 src/ap/dpp_hostapd.c create mode 100644 src/ap/dpp_hostapd.h create mode 100644 src/ap/drv_callbacks.c create mode 100644 src/ap/eap_user_db.c create mode 100644 src/ap/eth_p_oui.c create mode 100644 src/ap/eth_p_oui.h create mode 100644 src/ap/fils_hlp.c create mode 100644 src/ap/fils_hlp.h create mode 100644 src/ap/gas_query_ap.c create mode 100644 src/ap/gas_query_ap.h create mode 100644 src/ap/gas_serv.c create mode 100644 src/ap/gas_serv.h create mode 100644 src/ap/hostapd.c create mode 100644 src/ap/hostapd.h create mode 100644 src/ap/hs20.c create mode 100644 src/ap/hs20.h create mode 100644 src/ap/hw_features.c create mode 100644 src/ap/hw_features.h create mode 100644 src/ap/ieee802_11.c create mode 100644 src/ap/ieee802_11.h create mode 100644 src/ap/ieee802_11_auth.c create mode 100644 src/ap/ieee802_11_auth.h create mode 100644 src/ap/ieee802_11_eht.c create mode 100644 src/ap/ieee802_11_he.c create mode 100644 src/ap/ieee802_11_ht.c create mode 100644 src/ap/ieee802_11_shared.c create mode 100644 src/ap/ieee802_11_vht.c create mode 100644 src/ap/ieee802_1x.c create mode 100644 src/ap/ieee802_1x.h create mode 100644 src/ap/mbo_ap.c create mode 100644 src/ap/mbo_ap.h create mode 100644 src/ap/nan_usd_ap.c create mode 100644 src/ap/nan_usd_ap.h create mode 100644 src/ap/ndisc_snoop.c create mode 100644 src/ap/ndisc_snoop.h create mode 100644 src/ap/neighbor_db.c create mode 100644 src/ap/neighbor_db.h create mode 100644 src/ap/p2p_hostapd.c create mode 100644 src/ap/p2p_hostapd.h create mode 100644 src/ap/pmksa_cache_auth.c create mode 100644 src/ap/pmksa_cache_auth.h create mode 100644 src/ap/preauth_auth.c create mode 100644 src/ap/preauth_auth.h create mode 100644 src/ap/rrm.c create mode 100644 src/ap/rrm.h create mode 100644 src/ap/sta_info.c create mode 100644 src/ap/sta_info.h create mode 100644 src/ap/taxonomy.c create mode 100644 src/ap/taxonomy.h create mode 100644 src/ap/tkip_countermeasures.c create mode 100644 src/ap/tkip_countermeasures.h create mode 100644 src/ap/utils.c create mode 100644 src/ap/vlan.c create mode 100644 src/ap/vlan.h create mode 100644 src/ap/vlan_full.c create mode 100644 src/ap/vlan_ifconfig.c create mode 100644 src/ap/vlan_init.c create mode 100644 src/ap/vlan_init.h create mode 100644 src/ap/vlan_ioctl.c create mode 100644 src/ap/vlan_util.c create mode 100644 src/ap/vlan_util.h create mode 100644 src/ap/wmm.c create mode 100644 src/ap/wmm.h create mode 100644 src/ap/wnm_ap.c create mode 100644 src/ap/wnm_ap.h create mode 100644 src/ap/wpa_auth.c create mode 100644 src/ap/wpa_auth.h create mode 100644 src/ap/wpa_auth_ft.c create mode 100644 src/ap/wpa_auth_glue.c create mode 100644 src/ap/wpa_auth_glue.h create mode 100644 src/ap/wpa_auth_i.h create mode 100644 src/ap/wpa_auth_ie.c create mode 100644 src/ap/wpa_auth_ie.h create mode 100644 src/ap/wpa_auth_kay.c create mode 100644 src/ap/wpa_auth_kay.h create mode 100644 src/ap/wps_hostapd.c create mode 100644 src/ap/wps_hostapd.h create mode 100644 src/ap/x_snoop.c create mode 100644 src/ap/x_snoop.h create mode 100644 src/build.rules create mode 100644 src/common/Makefile create mode 100644 src/common/brcm_vendor.h create mode 100644 src/common/cli.c create mode 100644 src/common/cli.h create mode 100644 src/common/common_module_tests.c create mode 100644 src/common/ctrl_iface_common.c create mode 100644 src/common/ctrl_iface_common.h create mode 100644 src/common/defs.h create mode 100644 src/common/dhcp.h create mode 100644 src/common/dpp.c create mode 100644 src/common/dpp.h create mode 100644 src/common/dpp_auth.c create mode 100644 src/common/dpp_backup.c create mode 100644 src/common/dpp_crypto.c create mode 100644 src/common/dpp_i.h create mode 100644 src/common/dpp_pkex.c create mode 100644 src/common/dpp_reconfig.c create mode 100644 src/common/dpp_tcp.c create mode 100644 src/common/dragonfly.c create mode 100644 src/common/dragonfly.h create mode 100644 src/common/eapol_common.h create mode 100644 src/common/gas.c create mode 100644 src/common/gas.h create mode 100644 src/common/gas_server.c create mode 100644 src/common/gas_server.h create mode 100644 src/common/hw_features_common.c create mode 100644 src/common/hw_features_common.h create mode 100644 src/common/ieee802_11_common.c create mode 100644 src/common/ieee802_11_common.h create mode 100644 src/common/ieee802_11_defs.h create mode 100644 src/common/ieee802_1x_defs.h create mode 100644 src/common/linux_bridge.h create mode 100644 src/common/linux_vlan.h create mode 100644 src/common/nan.h create mode 100644 src/common/nan_de.c create mode 100644 src/common/nan_de.h create mode 100644 src/common/ocv.c create mode 100644 src/common/ocv.h create mode 100644 src/common/privsep_commands.h create mode 100644 src/common/ptksa_cache.c create mode 100644 src/common/ptksa_cache.h create mode 100644 src/common/qca-vendor-attr.h create mode 100644 src/common/qca-vendor.h create mode 100644 src/common/sae.c create mode 100644 src/common/sae.h create mode 100644 src/common/sae_pk.c create mode 100644 src/common/tnc.h create mode 100644 src/common/version.h create mode 100644 src/common/wpa_common.c create mode 100644 src/common/wpa_common.h create mode 100644 src/common/wpa_ctrl.c create mode 100644 src/common/wpa_ctrl.h create mode 100644 src/common/wpa_helpers.c create mode 100644 src/common/wpa_helpers.h create mode 100644 src/crypto/Makefile create mode 100644 src/crypto/aes-cbc.c create mode 100644 src/crypto/aes-ccm.c create mode 100644 src/crypto/aes-ctr.c create mode 100644 src/crypto/aes-eax.c create mode 100644 src/crypto/aes-encblock.c create mode 100644 src/crypto/aes-gcm.c create mode 100644 src/crypto/aes-internal-dec.c create mode 100644 src/crypto/aes-internal-enc.c create mode 100644 src/crypto/aes-internal.c create mode 100644 src/crypto/aes-omac1.c create mode 100644 src/crypto/aes-siv.c create mode 100644 src/crypto/aes-unwrap.c create mode 100644 src/crypto/aes-wrap.c create mode 100644 src/crypto/aes.h create mode 100644 src/crypto/aes_i.h create mode 100644 src/crypto/aes_siv.h create mode 100644 src/crypto/aes_wrap.h create mode 100644 src/crypto/crypto.h create mode 100644 src/crypto/crypto_gnutls.c create mode 100644 src/crypto/crypto_internal-cipher.c create mode 100644 src/crypto/crypto_internal-modexp.c create mode 100644 src/crypto/crypto_internal-rsa.c create mode 100644 src/crypto/crypto_internal.c create mode 100644 src/crypto/crypto_libtomcrypt.c create mode 100644 src/crypto/crypto_linux.c create mode 100644 src/crypto/crypto_module_tests.c create mode 100644 src/crypto/crypto_nettle.c create mode 100644 src/crypto/crypto_none.c create mode 100644 src/crypto/crypto_openssl.c create mode 100644 src/crypto/crypto_wolfssl.c create mode 100644 src/crypto/des-internal.c create mode 100644 src/crypto/des_i.h create mode 100644 src/crypto/dh_group5.c create mode 100644 src/crypto/dh_group5.h create mode 100644 src/crypto/dh_groups.c create mode 100644 src/crypto/dh_groups.h create mode 100644 src/crypto/fips_prf_internal.c create mode 100644 src/crypto/fips_prf_openssl.c create mode 100644 src/crypto/fips_prf_wolfssl.c create mode 100644 src/crypto/md4-internal.c create mode 100644 src/crypto/md5-internal.c create mode 100644 src/crypto/md5.c create mode 100644 src/crypto/md5.h create mode 100644 src/crypto/md5_i.h create mode 100644 src/crypto/milenage.c create mode 100644 src/crypto/milenage.h create mode 100644 src/crypto/ms_funcs.c create mode 100644 src/crypto/ms_funcs.h create mode 100644 src/crypto/random.c create mode 100644 src/crypto/random.h create mode 100644 src/crypto/rc4.c create mode 100644 src/crypto/sha1-internal.c create mode 100644 src/crypto/sha1-pbkdf2.c create mode 100644 src/crypto/sha1-prf.c create mode 100644 src/crypto/sha1-tlsprf.c create mode 100644 src/crypto/sha1-tprf.c create mode 100644 src/crypto/sha1.c create mode 100644 src/crypto/sha1.h create mode 100644 src/crypto/sha1_i.h create mode 100644 src/crypto/sha256-internal.c create mode 100644 src/crypto/sha256-kdf.c create mode 100644 src/crypto/sha256-prf.c create mode 100644 src/crypto/sha256-tlsprf.c create mode 100644 src/crypto/sha256.c create mode 100644 src/crypto/sha256.h create mode 100644 src/crypto/sha256_i.h create mode 100644 src/crypto/sha384-internal.c create mode 100644 src/crypto/sha384-kdf.c create mode 100644 src/crypto/sha384-prf.c create mode 100644 src/crypto/sha384-tlsprf.c create mode 100644 src/crypto/sha384.c create mode 100644 src/crypto/sha384.h create mode 100644 src/crypto/sha384_i.h create mode 100644 src/crypto/sha512-internal.c create mode 100644 src/crypto/sha512-kdf.c create mode 100644 src/crypto/sha512-prf.c create mode 100644 src/crypto/sha512.c create mode 100644 src/crypto/sha512.h create mode 100644 src/crypto/sha512_i.h create mode 100644 src/crypto/tls.h create mode 100644 src/crypto/tls_gnutls.c create mode 100644 src/crypto/tls_internal.c create mode 100644 src/crypto/tls_none.c create mode 100644 src/crypto/tls_openssl.c create mode 100644 src/crypto/tls_openssl.h create mode 100644 src/crypto/tls_openssl_ocsp.c create mode 100644 src/crypto/tls_wolfssl.c create mode 100644 src/drivers/Makefile create mode 100644 src/drivers/android_drv.h create mode 100644 src/drivers/driver.h create mode 100644 src/drivers/driver_atheros.c create mode 100644 src/drivers/driver_bsd.c create mode 100644 src/drivers/driver_common.c create mode 100644 src/drivers/driver_hostap.c create mode 100644 src/drivers/driver_hostap.h create mode 100644 src/drivers/driver_macsec_linux.c create mode 100644 src/drivers/driver_macsec_qca.c create mode 100644 src/drivers/driver_ndis.c create mode 100644 src/drivers/driver_ndis.h create mode 100644 src/drivers/driver_ndis_.c create mode 100644 src/drivers/driver_nl80211.c create mode 100644 src/drivers/driver_nl80211.h create mode 100644 src/drivers/driver_nl80211_android.c create mode 100644 src/drivers/driver_nl80211_capa.c create mode 100644 src/drivers/driver_nl80211_event.c create mode 100644 src/drivers/driver_nl80211_monitor.c create mode 100644 src/drivers/driver_nl80211_scan.c create mode 100644 src/drivers/driver_none.c create mode 100644 src/drivers/driver_openbsd.c create mode 100644 src/drivers/driver_privsep.c create mode 100644 src/drivers/driver_roboswitch.c create mode 100644 src/drivers/driver_wext.c create mode 100644 src/drivers/driver_wext.h create mode 100644 src/drivers/driver_wired.c create mode 100644 src/drivers/driver_wired_common.c create mode 100644 src/drivers/driver_wired_common.h create mode 100644 src/drivers/drivers.c create mode 100644 src/drivers/drivers.mak create mode 100644 src/drivers/drivers.mk create mode 100644 src/drivers/linux_defines.h create mode 100644 src/drivers/linux_ioctl.c create mode 100644 src/drivers/linux_ioctl.h create mode 100644 src/drivers/linux_wext.h create mode 100644 src/drivers/ndis_events.c create mode 100644 src/drivers/netlink.c create mode 100644 src/drivers/netlink.h create mode 100644 src/drivers/nl80211_copy.h create mode 100644 src/drivers/priv_netlink.h create mode 100644 src/drivers/rfkill.c create mode 100644 src/drivers/rfkill.h create mode 100644 src/eap_common/Makefile create mode 100644 src/eap_common/chap.c create mode 100644 src/eap_common/chap.h create mode 100644 src/eap_common/eap_common.c create mode 100644 src/eap_common/eap_common.h create mode 100644 src/eap_common/eap_defs.h create mode 100644 src/eap_common/eap_eke_common.c create mode 100644 src/eap_common/eap_eke_common.h create mode 100644 src/eap_common/eap_fast_common.c create mode 100644 src/eap_common/eap_fast_common.h create mode 100644 src/eap_common/eap_gpsk_common.c create mode 100644 src/eap_common/eap_gpsk_common.h create mode 100644 src/eap_common/eap_ikev2_common.c create mode 100644 src/eap_common/eap_ikev2_common.h create mode 100644 src/eap_common/eap_pax_common.c create mode 100644 src/eap_common/eap_pax_common.h create mode 100644 src/eap_common/eap_peap_common.c create mode 100644 src/eap_common/eap_peap_common.h create mode 100644 src/eap_common/eap_psk_common.c create mode 100644 src/eap_common/eap_psk_common.h create mode 100644 src/eap_common/eap_pwd_common.c create mode 100644 src/eap_common/eap_pwd_common.h create mode 100644 src/eap_common/eap_sake_common.c create mode 100644 src/eap_common/eap_sake_common.h create mode 100644 src/eap_common/eap_sim_common.c create mode 100644 src/eap_common/eap_sim_common.h create mode 100644 src/eap_common/eap_teap_common.c create mode 100644 src/eap_common/eap_teap_common.h create mode 100644 src/eap_common/eap_tlv_common.h create mode 100644 src/eap_common/eap_ttls.h create mode 100644 src/eap_common/eap_wsc_common.c create mode 100644 src/eap_common/eap_wsc_common.h create mode 100644 src/eap_common/ikev2_common.c create mode 100644 src/eap_common/ikev2_common.h create mode 100644 src/eap_peer/.gitignore create mode 100644 src/eap_peer/Makefile create mode 100644 src/eap_peer/eap.c create mode 100644 src/eap_peer/eap.h create mode 100644 src/eap_peer/eap_aka.c create mode 100644 src/eap_peer/eap_config.h create mode 100644 src/eap_peer/eap_eke.c create mode 100644 src/eap_peer/eap_fast.c create mode 100644 src/eap_peer/eap_fast_pac.c create mode 100644 src/eap_peer/eap_fast_pac.h create mode 100644 src/eap_peer/eap_gpsk.c create mode 100644 src/eap_peer/eap_gtc.c create mode 100644 src/eap_peer/eap_i.h create mode 100644 src/eap_peer/eap_ikev2.c create mode 100644 src/eap_peer/eap_leap.c create mode 100644 src/eap_peer/eap_md5.c create mode 100644 src/eap_peer/eap_methods.c create mode 100644 src/eap_peer/eap_methods.h create mode 100644 src/eap_peer/eap_mschapv2.c create mode 100644 src/eap_peer/eap_otp.c create mode 100644 src/eap_peer/eap_pax.c create mode 100644 src/eap_peer/eap_peap.c create mode 100644 src/eap_peer/eap_proxy.h create mode 100644 src/eap_peer/eap_proxy_dummy.c create mode 100644 src/eap_peer/eap_psk.c create mode 100644 src/eap_peer/eap_pwd.c create mode 100644 src/eap_peer/eap_sake.c create mode 100644 src/eap_peer/eap_sim.c create mode 100644 src/eap_peer/eap_teap.c create mode 100644 src/eap_peer/eap_teap_pac.c create mode 100644 src/eap_peer/eap_teap_pac.h create mode 100644 src/eap_peer/eap_tls.c create mode 100644 src/eap_peer/eap_tls_common.c create mode 100644 src/eap_peer/eap_tls_common.h create mode 100644 src/eap_peer/eap_tnc.c create mode 100644 src/eap_peer/eap_ttls.c create mode 100644 src/eap_peer/eap_vendor_test.c create mode 100644 src/eap_peer/eap_wsc.c create mode 100644 src/eap_peer/ikev2.c create mode 100644 src/eap_peer/ikev2.h create mode 100644 src/eap_peer/mschapv2.c create mode 100644 src/eap_peer/mschapv2.h create mode 100644 src/eap_peer/tncc.c create mode 100644 src/eap_peer/tncc.h create mode 100644 src/eap_server/Makefile create mode 100644 src/eap_server/eap.h create mode 100644 src/eap_server/eap_i.h create mode 100644 src/eap_server/eap_methods.h create mode 100644 src/eap_server/eap_server.c create mode 100644 src/eap_server/eap_server_aka.c create mode 100644 src/eap_server/eap_server_eke.c create mode 100644 src/eap_server/eap_server_fast.c create mode 100644 src/eap_server/eap_server_gpsk.c create mode 100644 src/eap_server/eap_server_gtc.c create mode 100644 src/eap_server/eap_server_identity.c create mode 100644 src/eap_server/eap_server_ikev2.c create mode 100644 src/eap_server/eap_server_md5.c create mode 100644 src/eap_server/eap_server_methods.c create mode 100644 src/eap_server/eap_server_mschapv2.c create mode 100644 src/eap_server/eap_server_pax.c create mode 100644 src/eap_server/eap_server_peap.c create mode 100644 src/eap_server/eap_server_psk.c create mode 100644 src/eap_server/eap_server_pwd.c create mode 100644 src/eap_server/eap_server_sake.c create mode 100644 src/eap_server/eap_server_sim.c create mode 100644 src/eap_server/eap_server_teap.c create mode 100644 src/eap_server/eap_server_tls.c create mode 100644 src/eap_server/eap_server_tls_common.c create mode 100644 src/eap_server/eap_server_tnc.c create mode 100644 src/eap_server/eap_server_ttls.c create mode 100644 src/eap_server/eap_server_vendor_test.c create mode 100644 src/eap_server/eap_server_wsc.c create mode 100644 src/eap_server/eap_sim_db.c create mode 100644 src/eap_server/eap_sim_db.h create mode 100644 src/eap_server/eap_tls_common.h create mode 100644 src/eap_server/ikev2.c create mode 100644 src/eap_server/ikev2.h create mode 100644 src/eap_server/tncs.c create mode 100644 src/eap_server/tncs.h create mode 100644 src/eapol_auth/Makefile create mode 100644 src/eapol_auth/eapol_auth_dump.c create mode 100644 src/eapol_auth/eapol_auth_sm.c create mode 100644 src/eapol_auth/eapol_auth_sm.h create mode 100644 src/eapol_auth/eapol_auth_sm_i.h create mode 100644 src/eapol_supp/Makefile create mode 100644 src/eapol_supp/eapol_supp_sm.c create mode 100644 src/eapol_supp/eapol_supp_sm.h create mode 100644 src/fst/Makefile create mode 100644 src/fst/fst.c create mode 100644 src/fst/fst.h create mode 100644 src/fst/fst_ctrl_aux.c create mode 100644 src/fst/fst_ctrl_aux.h create mode 100644 src/fst/fst_ctrl_defs.h create mode 100644 src/fst/fst_ctrl_iface.c create mode 100644 src/fst/fst_ctrl_iface.h create mode 100644 src/fst/fst_defs.h create mode 100644 src/fst/fst_group.c create mode 100644 src/fst/fst_group.h create mode 100644 src/fst/fst_iface.c create mode 100644 src/fst/fst_iface.h create mode 100644 src/fst/fst_internal.h create mode 100644 src/fst/fst_session.c create mode 100644 src/fst/fst_session.h create mode 100644 src/l2_packet/Makefile create mode 100644 src/l2_packet/l2_packet.h create mode 100644 src/l2_packet/l2_packet_freebsd.c create mode 100644 src/l2_packet/l2_packet_linux.c create mode 100644 src/l2_packet/l2_packet_ndis.c create mode 100644 src/l2_packet/l2_packet_none.c create mode 100644 src/l2_packet/l2_packet_pcap.c create mode 100644 src/l2_packet/l2_packet_privsep.c create mode 100644 src/l2_packet/l2_packet_winpcap.c create mode 100644 src/lib.rules create mode 100644 src/objs.mk create mode 100644 src/p2p/Makefile create mode 100644 src/p2p/p2p.c create mode 100644 src/p2p/p2p.h create mode 100644 src/p2p/p2p_build.c create mode 100644 src/p2p/p2p_dev_disc.c create mode 100644 src/p2p/p2p_go_neg.c create mode 100644 src/p2p/p2p_group.c create mode 100644 src/p2p/p2p_i.h create mode 100644 src/p2p/p2p_invitation.c create mode 100644 src/p2p/p2p_parse.c create mode 100644 src/p2p/p2p_pd.c create mode 100644 src/p2p/p2p_sd.c create mode 100644 src/p2p/p2p_utils.c create mode 100644 src/pae/Makefile create mode 100644 src/pae/ieee802_1x_cp.c create mode 100644 src/pae/ieee802_1x_cp.h create mode 100644 src/pae/ieee802_1x_kay.c create mode 100644 src/pae/ieee802_1x_kay.h create mode 100644 src/pae/ieee802_1x_kay_i.h create mode 100644 src/pae/ieee802_1x_key.c create mode 100644 src/pae/ieee802_1x_key.h create mode 100644 src/pae/ieee802_1x_secy_ops.c create mode 100644 src/pae/ieee802_1x_secy_ops.h create mode 100644 src/pasn/Makefile create mode 100644 src/pasn/pasn_common.c create mode 100644 src/pasn/pasn_common.h create mode 100644 src/pasn/pasn_initiator.c create mode 100644 src/pasn/pasn_responder.c create mode 100644 src/radius/Makefile create mode 100644 src/radius/radius.c create mode 100644 src/radius/radius.h create mode 100644 src/radius/radius_client.c create mode 100644 src/radius/radius_client.h create mode 100644 src/radius/radius_das.c create mode 100644 src/radius/radius_das.h create mode 100644 src/radius/radius_server.c create mode 100644 src/radius/radius_server.h create mode 100644 src/rsn_supp/Makefile create mode 100644 src/rsn_supp/pmksa_cache.c create mode 100644 src/rsn_supp/pmksa_cache.h create mode 100644 src/rsn_supp/preauth.c create mode 100644 src/rsn_supp/preauth.h create mode 100644 src/rsn_supp/tdls.c create mode 100644 src/rsn_supp/wpa.c create mode 100644 src/rsn_supp/wpa.h create mode 100644 src/rsn_supp/wpa_ft.c create mode 100644 src/rsn_supp/wpa_i.h create mode 100644 src/rsn_supp/wpa_ie.c create mode 100644 src/rsn_supp/wpa_ie.h create mode 100644 src/tls/Makefile create mode 100644 src/tls/asn1.c create mode 100644 src/tls/asn1.h create mode 100644 src/tls/bignum.c create mode 100644 src/tls/bignum.h create mode 100644 src/tls/libtommath.c create mode 100644 src/tls/pkcs1.c create mode 100644 src/tls/pkcs1.h create mode 100644 src/tls/pkcs5.c create mode 100644 src/tls/pkcs5.h create mode 100644 src/tls/pkcs8.c create mode 100644 src/tls/pkcs8.h create mode 100644 src/tls/rsa.c create mode 100644 src/tls/rsa.h create mode 100644 src/tls/tlsv1_client.c create mode 100644 src/tls/tlsv1_client.h create mode 100644 src/tls/tlsv1_client_i.h create mode 100644 src/tls/tlsv1_client_ocsp.c create mode 100644 src/tls/tlsv1_client_read.c create mode 100644 src/tls/tlsv1_client_write.c create mode 100644 src/tls/tlsv1_common.c create mode 100644 src/tls/tlsv1_common.h create mode 100644 src/tls/tlsv1_cred.c create mode 100644 src/tls/tlsv1_cred.h create mode 100644 src/tls/tlsv1_record.c create mode 100644 src/tls/tlsv1_record.h create mode 100644 src/tls/tlsv1_server.c create mode 100644 src/tls/tlsv1_server.h create mode 100644 src/tls/tlsv1_server_i.h create mode 100644 src/tls/tlsv1_server_read.c create mode 100644 src/tls/tlsv1_server_write.c create mode 100644 src/tls/x509v3.c create mode 100644 src/tls/x509v3.h create mode 100644 src/utils/Makefile create mode 100644 src/utils/base64.c create mode 100644 src/utils/base64.h create mode 100644 src/utils/bitfield.c create mode 100644 src/utils/bitfield.h create mode 100644 src/utils/browser-android.c create mode 100644 src/utils/browser-system.c create mode 100644 src/utils/browser-wpadebug.c create mode 100644 src/utils/browser.c create mode 100644 src/utils/browser.h create mode 100644 src/utils/build_config.h create mode 100644 src/utils/common.c create mode 100644 src/utils/common.h create mode 100644 src/utils/config.c create mode 100644 src/utils/config.h create mode 100644 src/utils/const_time.h create mode 100644 src/utils/crc32.c create mode 100644 src/utils/crc32.h create mode 100644 src/utils/edit.c create mode 100644 src/utils/edit.h create mode 100644 src/utils/edit_readline.c create mode 100644 src/utils/edit_simple.c create mode 100644 src/utils/eloop.c create mode 100644 src/utils/eloop.h create mode 100644 src/utils/eloop_win.c create mode 100644 src/utils/ext_password.c create mode 100644 src/utils/ext_password.h create mode 100644 src/utils/ext_password_file.c create mode 100644 src/utils/ext_password_i.h create mode 100644 src/utils/ext_password_test.c create mode 100644 src/utils/http-utils.h create mode 100644 src/utils/http_curl.c create mode 100644 src/utils/includes.h create mode 100644 src/utils/ip_addr.c create mode 100644 src/utils/ip_addr.h create mode 100644 src/utils/json.c create mode 100644 src/utils/json.h create mode 100644 src/utils/list.h create mode 100644 src/utils/module_tests.h create mode 100644 src/utils/os.h create mode 100644 src/utils/os_internal.c create mode 100644 src/utils/os_none.c create mode 100644 src/utils/os_unix.c create mode 100644 src/utils/os_win32.c create mode 100644 src/utils/pcsc_funcs.c create mode 100644 src/utils/pcsc_funcs.h create mode 100644 src/utils/platform.h create mode 100644 src/utils/radiotap.c create mode 100644 src/utils/radiotap.h create mode 100644 src/utils/radiotap_iter.h create mode 100644 src/utils/state_machine.h create mode 100644 src/utils/trace.c create mode 100644 src/utils/trace.h create mode 100644 src/utils/utils_module_tests.c create mode 100644 src/utils/uuid.c create mode 100644 src/utils/uuid.h create mode 100644 src/utils/wpa_debug.c create mode 100644 src/utils/wpa_debug.h create mode 100644 src/utils/wpabuf.c create mode 100644 src/utils/wpabuf.h create mode 100644 src/utils/xml-utils.c create mode 100644 src/utils/xml-utils.h create mode 100644 src/utils/xml_libxml2.c create mode 100644 src/wps/Makefile create mode 100644 src/wps/http.h create mode 100644 src/wps/http_client.c create mode 100644 src/wps/http_client.h create mode 100644 src/wps/http_server.c create mode 100644 src/wps/http_server.h create mode 100644 src/wps/httpread.c create mode 100644 src/wps/httpread.h create mode 100644 src/wps/ndef.c create mode 100644 src/wps/upnp_xml.c create mode 100644 src/wps/upnp_xml.h create mode 100644 src/wps/wps.c create mode 100644 src/wps/wps.h create mode 100644 src/wps/wps_attr_build.c create mode 100644 src/wps/wps_attr_parse.c create mode 100644 src/wps/wps_attr_parse.h create mode 100644 src/wps/wps_attr_process.c create mode 100644 src/wps/wps_common.c create mode 100644 src/wps/wps_defs.h create mode 100644 src/wps/wps_dev_attr.c create mode 100644 src/wps/wps_dev_attr.h create mode 100644 src/wps/wps_enrollee.c create mode 100644 src/wps/wps_er.c create mode 100644 src/wps/wps_er.h create mode 100644 src/wps/wps_er_ssdp.c create mode 100644 src/wps/wps_i.h create mode 100644 src/wps/wps_module_tests.c create mode 100644 src/wps/wps_registrar.c create mode 100644 src/wps/wps_upnp.c create mode 100644 src/wps/wps_upnp.h create mode 100644 src/wps/wps_upnp_ap.c create mode 100644 src/wps/wps_upnp_event.c create mode 100644 src/wps/wps_upnp_i.h create mode 100644 src/wps/wps_upnp_ssdp.c create mode 100644 src/wps/wps_upnp_web.c create mode 100644 src/wps/wps_validate.c create mode 100644 wpa_supplicant/.gitignore create mode 100644 wpa_supplicant/Android.mk create mode 100644 wpa_supplicant/ChangeLog create mode 100644 wpa_supplicant/Makefile create mode 100644 wpa_supplicant/README create mode 100644 wpa_supplicant/README-DPP create mode 100644 wpa_supplicant/README-HS20 create mode 100644 wpa_supplicant/README-NAN-USD create mode 100644 wpa_supplicant/README-P2P create mode 100644 wpa_supplicant/README-WPS create mode 100644 wpa_supplicant/README-Windows.txt create mode 100644 wpa_supplicant/android.config create mode 100644 wpa_supplicant/ap.c create mode 100644 wpa_supplicant/ap.h create mode 100644 wpa_supplicant/autoscan.c create mode 100644 wpa_supplicant/autoscan.h create mode 100644 wpa_supplicant/autoscan_exponential.c create mode 100644 wpa_supplicant/autoscan_periodic.c create mode 100644 wpa_supplicant/bgscan.c create mode 100644 wpa_supplicant/bgscan.h create mode 100644 wpa_supplicant/bgscan_learn.c create mode 100644 wpa_supplicant/bgscan_simple.c create mode 100644 wpa_supplicant/binder/.clang-format create mode 100644 wpa_supplicant/binder/binder.cpp create mode 100644 wpa_supplicant/binder/binder.h create mode 100644 wpa_supplicant/binder/binder_constants.cpp create mode 100644 wpa_supplicant/binder/binder_constants.h create mode 100644 wpa_supplicant/binder/binder_i.h create mode 100644 wpa_supplicant/binder/binder_manager.cpp create mode 100644 wpa_supplicant/binder/binder_manager.h create mode 100644 wpa_supplicant/binder/iface.cpp create mode 100644 wpa_supplicant/binder/iface.h create mode 100644 wpa_supplicant/binder/supplicant.cpp create mode 100644 wpa_supplicant/binder/supplicant.h create mode 100644 wpa_supplicant/bss.c create mode 100644 wpa_supplicant/bss.h create mode 100644 wpa_supplicant/bssid_ignore.c create mode 100644 wpa_supplicant/bssid_ignore.h create mode 100644 wpa_supplicant/config.c create mode 100644 wpa_supplicant/config.h create mode 100644 wpa_supplicant/config_file.c create mode 100644 wpa_supplicant/config_none.c create mode 100644 wpa_supplicant/config_ssid.h create mode 100644 wpa_supplicant/config_winreg.c create mode 100644 wpa_supplicant/ctrl_iface.c create mode 100644 wpa_supplicant/ctrl_iface.h create mode 100644 wpa_supplicant/ctrl_iface_named_pipe.c create mode 100644 wpa_supplicant/ctrl_iface_udp.c create mode 100644 wpa_supplicant/ctrl_iface_unix.c create mode 100644 wpa_supplicant/dbus/.gitignore create mode 100644 wpa_supplicant/dbus/Makefile create mode 100644 wpa_supplicant/dbus/dbus-wpa_supplicant.conf create mode 100644 wpa_supplicant/dbus/dbus_common.c create mode 100644 wpa_supplicant/dbus/dbus_common.h create mode 100644 wpa_supplicant/dbus/dbus_common_i.h create mode 100644 wpa_supplicant/dbus/dbus_dict_helpers.c create mode 100644 wpa_supplicant/dbus/dbus_dict_helpers.h create mode 100644 wpa_supplicant/dbus/dbus_new.c create mode 100644 wpa_supplicant/dbus/dbus_new.h create mode 100644 wpa_supplicant/dbus/dbus_new_handlers.c create mode 100644 wpa_supplicant/dbus/dbus_new_handlers.h create mode 100644 wpa_supplicant/dbus/dbus_new_handlers_p2p.c create mode 100644 wpa_supplicant/dbus/dbus_new_handlers_p2p.h create mode 100644 wpa_supplicant/dbus/dbus_new_handlers_wps.c create mode 100644 wpa_supplicant/dbus/dbus_new_helpers.c create mode 100644 wpa_supplicant/dbus/dbus_new_helpers.h create mode 100644 wpa_supplicant/dbus/dbus_new_introspect.c create mode 100644 wpa_supplicant/dbus/fi.w1.wpa_supplicant1.service.in create mode 100644 wpa_supplicant/defconfig create mode 100644 wpa_supplicant/doc/docbook/.gitignore create mode 100644 wpa_supplicant/doc/docbook/Makefile create mode 100644 wpa_supplicant/doc/docbook/eapol_test.sgml create mode 100644 wpa_supplicant/doc/docbook/wpa_background.sgml create mode 100644 wpa_supplicant/doc/docbook/wpa_cli.sgml create mode 100644 wpa_supplicant/doc/docbook/wpa_gui.sgml create mode 100644 wpa_supplicant/doc/docbook/wpa_passphrase.sgml create mode 100644 wpa_supplicant/doc/docbook/wpa_priv.sgml create mode 100644 wpa_supplicant/doc/docbook/wpa_supplicant.conf.sgml create mode 100644 wpa_supplicant/doc/docbook/wpa_supplicant.sgml create mode 100644 wpa_supplicant/dpp_supplicant.c create mode 100644 wpa_supplicant/dpp_supplicant.h create mode 100644 wpa_supplicant/driver_i.h create mode 100644 wpa_supplicant/eap_proxy_dummy.mak create mode 100644 wpa_supplicant/eap_proxy_dummy.mk create mode 100644 wpa_supplicant/eap_register.c create mode 100644 wpa_supplicant/eap_testing.txt create mode 100644 wpa_supplicant/eapol_test.c create mode 100755 wpa_supplicant/eapol_test.py create mode 100644 wpa_supplicant/events.c create mode 100755 wpa_supplicant/examples/60_wpa_supplicant create mode 100755 wpa_supplicant/examples/dbus-listen-preq.py create mode 100755 wpa_supplicant/examples/dpp-nfc.py create mode 100755 wpa_supplicant/examples/dpp-qrcode.py create mode 100644 wpa_supplicant/examples/ieee8021x.conf create mode 100644 wpa_supplicant/examples/openCryptoki.conf create mode 100755 wpa_supplicant/examples/p2p-action-udhcp.sh create mode 100755 wpa_supplicant/examples/p2p-action.sh create mode 100755 wpa_supplicant/examples/p2p-nfc.py create mode 100644 wpa_supplicant/examples/p2p/p2p_connect.py create mode 100644 wpa_supplicant/examples/p2p/p2p_disconnect.py create mode 100644 wpa_supplicant/examples/p2p/p2p_find.py create mode 100644 wpa_supplicant/examples/p2p/p2p_flush.py create mode 100644 wpa_supplicant/examples/p2p/p2p_group_add.py create mode 100644 wpa_supplicant/examples/p2p/p2p_invite.py create mode 100644 wpa_supplicant/examples/p2p/p2p_listen.py create mode 100644 wpa_supplicant/examples/p2p/p2p_stop_find.py create mode 100644 wpa_supplicant/examples/plaintext.conf create mode 100644 wpa_supplicant/examples/udhcpd-p2p.conf create mode 100644 wpa_supplicant/examples/wep.conf create mode 100644 wpa_supplicant/examples/wpa-psk-tkip.conf create mode 100644 wpa_supplicant/examples/wpa2-eap-ccmp.conf create mode 100755 wpa_supplicant/examples/wpas-dbus-new-getall.py create mode 100755 wpa_supplicant/examples/wpas-dbus-new-signals.py create mode 100755 wpa_supplicant/examples/wpas-dbus-new-wps.py create mode 100755 wpa_supplicant/examples/wpas-dbus-new.py create mode 100755 wpa_supplicant/examples/wps-ap-cli create mode 100755 wpa_supplicant/examples/wps-nfc.py create mode 100644 wpa_supplicant/gas_query.c create mode 100644 wpa_supplicant/gas_query.h create mode 100644 wpa_supplicant/hs20_supplicant.c create mode 100644 wpa_supplicant/hs20_supplicant.h create mode 100644 wpa_supplicant/ibss_rsn.c create mode 100644 wpa_supplicant/ibss_rsn.h create mode 100644 wpa_supplicant/interworking.c create mode 100644 wpa_supplicant/interworking.h create mode 100644 wpa_supplicant/libwpa_test.c create mode 100644 wpa_supplicant/main.c create mode 100644 wpa_supplicant/main_none.c create mode 100644 wpa_supplicant/main_winmain.c create mode 100644 wpa_supplicant/main_winsvc.c create mode 100644 wpa_supplicant/mbo.c create mode 100644 wpa_supplicant/mesh.c create mode 100644 wpa_supplicant/mesh.h create mode 100644 wpa_supplicant/mesh_mpm.c create mode 100644 wpa_supplicant/mesh_mpm.h create mode 100644 wpa_supplicant/mesh_rsn.c create mode 100644 wpa_supplicant/mesh_rsn.h create mode 100644 wpa_supplicant/nan_usd.c create mode 100644 wpa_supplicant/nan_usd.h create mode 100644 wpa_supplicant/nfc_pw_token.c create mode 100644 wpa_supplicant/nmake.mak create mode 100644 wpa_supplicant/notify.c create mode 100644 wpa_supplicant/notify.h create mode 100644 wpa_supplicant/offchannel.c create mode 100644 wpa_supplicant/offchannel.h create mode 100644 wpa_supplicant/op_classes.c create mode 100644 wpa_supplicant/p2p_supplicant.c create mode 100644 wpa_supplicant/p2p_supplicant.h create mode 100644 wpa_supplicant/p2p_supplicant_sd.c create mode 100644 wpa_supplicant/pasn_supplicant.c create mode 100644 wpa_supplicant/preauth_test.c create mode 100644 wpa_supplicant/robust_av.c create mode 100644 wpa_supplicant/rrm.c create mode 100644 wpa_supplicant/scan.c create mode 100644 wpa_supplicant/scan.h create mode 100644 wpa_supplicant/sme.c create mode 100644 wpa_supplicant/sme.h create mode 100644 wpa_supplicant/systemd/wpa_supplicant-nl80211.service.arg.in create mode 100644 wpa_supplicant/systemd/wpa_supplicant-wired.service.arg.in create mode 100644 wpa_supplicant/systemd/wpa_supplicant.service.arg.in create mode 100644 wpa_supplicant/systemd/wpa_supplicant.service.in create mode 100644 wpa_supplicant/todo.txt create mode 100644 wpa_supplicant/twt.c create mode 100755 wpa_supplicant/utils/log2pcap.py create mode 100755 wpa_supplicant/vs2005/win_if_list/win_if_list.vcproj create mode 100755 wpa_supplicant/vs2005/wpa_supplicant.sln create mode 100755 wpa_supplicant/vs2005/wpasvc/wpasvc.vcproj create mode 100644 wpa_supplicant/wifi_display.c create mode 100644 wpa_supplicant/wifi_display.h create mode 100755 wpa_supplicant/win_example.reg create mode 100644 wpa_supplicant/win_if_list.c create mode 100644 wpa_supplicant/wmm_ac.c create mode 100644 wpa_supplicant/wmm_ac.h create mode 100644 wpa_supplicant/wnm_sta.c create mode 100644 wpa_supplicant/wnm_sta.h create mode 100644 wpa_supplicant/wpa_cli.c create mode 100644 wpa_supplicant/wpa_gui-qt4/.gitignore create mode 100644 wpa_supplicant/wpa_gui-qt4/addinterface.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/addinterface.h create mode 100644 wpa_supplicant/wpa_gui-qt4/eventhistory.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/eventhistory.h create mode 100644 wpa_supplicant/wpa_gui-qt4/eventhistory.ui create mode 100644 wpa_supplicant/wpa_gui-qt4/icons.qrc create mode 100644 wpa_supplicant/wpa_gui-qt4/icons/.gitignore create mode 100644 wpa_supplicant/wpa_gui-qt4/icons/Makefile create mode 100644 wpa_supplicant/wpa_gui-qt4/icons/README create mode 100644 wpa_supplicant/wpa_gui-qt4/icons/ap.svg create mode 100644 wpa_supplicant/wpa_gui-qt4/icons/group.svg create mode 100644 wpa_supplicant/wpa_gui-qt4/icons/invitation.svg create mode 100644 wpa_supplicant/wpa_gui-qt4/icons/laptop.svg create mode 100644 wpa_supplicant/wpa_gui-qt4/icons/wpa_gui.svg create mode 100644 wpa_supplicant/wpa_gui-qt4/icons_png.qrc create mode 100644 wpa_supplicant/wpa_gui-qt4/lang/.gitignore create mode 100644 wpa_supplicant/wpa_gui-qt4/lang/wpa_gui_de.ts create mode 100644 wpa_supplicant/wpa_gui-qt4/main.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/networkconfig.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/networkconfig.h create mode 100644 wpa_supplicant/wpa_gui-qt4/networkconfig.ui create mode 100644 wpa_supplicant/wpa_gui-qt4/peers.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/peers.h create mode 100644 wpa_supplicant/wpa_gui-qt4/peers.ui create mode 100644 wpa_supplicant/wpa_gui-qt4/scanresults.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/scanresults.h create mode 100644 wpa_supplicant/wpa_gui-qt4/scanresults.ui create mode 100644 wpa_supplicant/wpa_gui-qt4/scanresultsitem.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/scanresultsitem.h create mode 100644 wpa_supplicant/wpa_gui-qt4/signalbar.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/signalbar.h create mode 100644 wpa_supplicant/wpa_gui-qt4/stringquery.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/stringquery.h create mode 100644 wpa_supplicant/wpa_gui-qt4/userdatarequest.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/userdatarequest.h create mode 100644 wpa_supplicant/wpa_gui-qt4/userdatarequest.ui create mode 100644 wpa_supplicant/wpa_gui-qt4/wpa_gui.desktop create mode 100644 wpa_supplicant/wpa_gui-qt4/wpa_gui.pro create mode 100644 wpa_supplicant/wpa_gui-qt4/wpagui.cpp create mode 100644 wpa_supplicant/wpa_gui-qt4/wpagui.h create mode 100644 wpa_supplicant/wpa_gui-qt4/wpagui.ui create mode 100644 wpa_supplicant/wpa_gui-qt4/wpamsg.h create mode 100644 wpa_supplicant/wpa_passphrase.c create mode 100644 wpa_supplicant/wpa_priv.c create mode 100644 wpa_supplicant/wpa_supplicant.c create mode 100644 wpa_supplicant/wpa_supplicant.conf create mode 100644 wpa_supplicant/wpa_supplicant_conf.mk create mode 100755 wpa_supplicant/wpa_supplicant_conf.sh create mode 100644 wpa_supplicant/wpa_supplicant_i.h create mode 100644 wpa_supplicant/wpa_supplicant_template.conf create mode 100644 wpa_supplicant/wpas_glue.c create mode 100644 wpa_supplicant/wpas_glue.h create mode 100644 wpa_supplicant/wpas_kay.c create mode 100644 wpa_supplicant/wpas_kay.h create mode 100644 wpa_supplicant/wpas_module_tests.c create mode 100644 wpa_supplicant/wps_supplicant.c create mode 100644 wpa_supplicant/wps_supplicant.h diff --git a/CONTRIBUTIONS b/CONTRIBUTIONS new file mode 100644 index 0000000..6c8187c --- /dev/null +++ b/CONTRIBUTIONS @@ -0,0 +1,174 @@ +Contributions to hostap.git +--------------------------- + +This software is distributed under a permissive open source license to +allow it to be used in any projects, whether open source or proprietary. +Contributions to the project are welcome and it is important to maintain +clear record of contributions and terms under which they are licensed. +To help with this, following procedure is used to allow acceptance and +recording of the terms. + +All contributions are expected to be licensed under the modified BSD +license (see below). Acknowledgment of the terms is tracked through +inclusion of Signed-off-by tag in the contributions at the end of the +commit log message. This tag indicates that the contributor agrees with +the Developer Certificate of Origin (DCO) version 1.1 terms (see below; +also available from http://developercertificate.org/). + + +The current requirements for contributions to hostap.git +-------------------------------------------------------- + +To indicate your acceptance of Developer's Certificate of Origin 1.1 +terms, please add the following line to the end of the commit message +for each contribution you make to the project: + +Signed-off-by: Your Name + +using your real name. Pseudonyms or anonymous contributions cannot +unfortunately be accepted. + + +The preferred method of submitting the contribution to the project is by +email to the hostap mailing list: +hostap@lists.infradead.org +Note that the list may require subscription before accepting message +without moderation. You can subscribe to the list at this address: +http://lists.infradead.org/mailman/listinfo/hostap + +The message should contain an inlined patch against the current +development branch (i.e., the main branch of +git://w1.fi/hostap.git). Please make sure the software you use for +sending the patch does not corrupt whitespace. If that cannot be fixed +for some reason, it is better to include an attached version of the +patch file than just send a whitespace damaged version in the message +body. + +The patches should be separate logical changes rather than doing +everything in a single patch. In other words, please keep cleanup, new +features, and bug fixes all in their own patches. Each patch needs a +commit log that describes the changes (what the changes fix, what +functionality is added, why the changes are useful, etc.). + +Please try to follow the coding style used in the project. + +In general, the best way of generating a suitable formatted patch file +is by committing the changes to a cloned git repository and using git +format-patch. The patch can then be sent, e.g., with git send-email. + +A list of pending patches waiting for review is available in +Patchwork: https://patchwork.ozlabs.org/project/hostap/list/ + + +History of license and contributions terms +------------------------------------------ + +Until February 11, 2012, in case of most files in hostap.git, "under the +open source license indicated in the file" means that the contribution +is licensed both under GPL v2 and modified BSD license (see below) and +the choice between these licenses is given to anyone who redistributes +or uses the software. As such, the contribution has to be licensed under +both options to allow this choice. + +As of February 11, 2012, the project has chosen to use only the BSD +license option for future distribution. As such, the GPL v2 license +option is no longer used and the contributions are not required to be +licensed until GPL v2. In case of most files in hostap.git, "under the +open source license indicated in the file" means that the contribution +is licensed under the modified BSD license (see below). + +Until February 13, 2014, the project used an extended version of the DCO +that included the identical items (a) through (d) from DCO 1.1 and an +additional item (e): + +(e) The contribution can be licensed under the modified BSD license + as shown below even in case of files that are currently licensed + under other terms. + +This was used during the period when some of the files included the old +license terms. Acceptance of this extended DCO version was indicated +with a Signed-hostap tag in the commit message. This additional item (e) +was used to collect explicit approval to license the contribution with +only the modified BSD license (see below), i.e., without the GPL v2 +option. This was done to allow simpler licensing terms to be used in the +future. It should be noted that the modified BSD license is compatible +with GNU GPL and as such, this possible move to simpler licensing option +does not prevent use of this software in GPL projects. + + +===[ start quote from http://developercertificate.org/ ]======================= + +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + +===[ end quote from http://developercertificate.org/ ]========================= + + +The license terms used for hostap.git files +------------------------------------------- + +Modified BSD license (no advertisement clause): + +Copyright (c) 2002-2022, Jouni Malinen and contributors +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name(s) of the above-listed copyright holder(s) nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..7ca3030 --- /dev/null +++ b/COPYING @@ -0,0 +1,22 @@ +wpa_supplicant and hostapd +-------------------------- + +Copyright (c) 2002-2022, Jouni Malinen and contributors +All Rights Reserved. + + +See the README file for the current license terms. + +This software was previously distributed under BSD/GPL v2 dual license +terms that allowed either of those license alternatives to be +selected. As of February 11, 2012, the project has chosen to use only +the BSD license option for future distribution. As such, the GPL v2 +license option is no longer used. It should be noted that the BSD +license option (the one with advertisement clause removed) is compatible +with GPL and as such, does not prevent use of this software in projects +that use GPL. + +Some of the files may still include pointers to GPL version 2 license +terms. However, such copyright and license notifications are maintained +only for attribution purposes and any distribution of this software +after February 11, 2012 is no longer under the GPL v2 option. diff --git a/README b/README new file mode 100644 index 0000000..8392bb3 --- /dev/null +++ b/README @@ -0,0 +1,56 @@ +wpa_supplicant and hostapd +-------------------------- + +Copyright (c) 2002-2024, Jouni Malinen and contributors +All Rights Reserved. + +These programs are licensed under the BSD license (the one with +advertisement clause removed). + +If you are submitting changes to the project, please see CONTRIBUTIONS +file for more instructions. + + +This package may include either wpa_supplicant, hostapd, or both. See +README file respective subdirectories (wpa_supplicant/README or +hostapd/README) for more details. + +Source code files were moved around in v0.6.x releases and compared to +earlier releases, the programs are now built by first going to a +subdirectory (wpa_supplicant or hostapd) and creating build +configuration (.config) and running 'make' there (for Linux/BSD/cygwin +builds). + + +License +------- + +This software may be distributed, used, and modified under the terms of +BSD license: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name(s) of the above-listed copyright holder(s) nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/hs20/client/.gitignore b/hs20/client/.gitignore new file mode 100644 index 0000000..f6c13d3 --- /dev/null +++ b/hs20/client/.gitignore @@ -0,0 +1,4 @@ +hs20-osu-client +SP +osu-ca.pem +spp.xsd diff --git a/hs20/client/Android.mk b/hs20/client/Android.mk new file mode 100644 index 0000000..8d208b2 --- /dev/null +++ b/hs20/client/Android.mk @@ -0,0 +1,91 @@ +LOCAL_PATH := $(call my-dir) + +INCLUDES = $(LOCAL_PATH) +INCLUDES += $(LOCAL_PATH)/../../src/utils +INCLUDES += $(LOCAL_PATH)/../../src/common +INCLUDES += $(LOCAL_PATH)/../../src +INCLUDES += external/libxml2/include +INCLUDES += external/curl/include +INCLUDES += external/webkit/Source/WebKit/gtk + +# We try to keep this compiling against older platform versions. +# The new icu location (external/icu) exports its own headers, but +# the older versions in external/icu4c don't, and we need to add those +# headers to the include path by hand. +ifeq ($(wildcard external/icu),) +INCLUDES += external/icu4c/common +else +# The LOCAL_EXPORT_C_INCLUDE_DIRS from ICU did not seem to fully resolve the +# build (e.g., "mm -B" failed to build, but following that with "mm" allowed +# the build to complete). For now, add the include directory manually here for +# Android 5.0. +ver = $(filter 5.0%,$(PLATFORM_VERSION)) +ifneq (,$(strip $(ver))) +INCLUDES += external/icu/icu4c/source/common +endif +endif + + +L_CFLAGS += -DCONFIG_CTRL_IFACE +L_CFLAGS += -DCONFIG_CTRL_IFACE_UNIX +L_CFLAGS += -DCONFIG_CTRL_IFACE_CLIENT_DIR=\"/data/misc/wifi/sockets\" + +OBJS = spp_client.c +OBJS += oma_dm_client.c +OBJS += osu_client.c +OBJS += est.c +OBJS += ../../src/common/wpa_ctrl.c +OBJS += ../../src/common/wpa_helpers.c +OBJS += ../../src/utils/xml-utils.c +#OBJS += ../../src/utils/browser-android.c +OBJS += ../../src/utils/browser-wpadebug.c +OBJS += ../../src/utils/wpabuf.c +OBJS += ../../src/utils/eloop.c +OBJS += ../../src/wps/httpread.c +OBJS += ../../src/wps/http_server.c +OBJS += ../../src/utils/xml_libxml2.c +OBJS += ../../src/utils/http_curl.c +OBJS += ../../src/utils/base64.c +OBJS += ../../src/utils/os_unix.c +L_CFLAGS += -DCONFIG_DEBUG_FILE +OBJS += ../../src/utils/wpa_debug.c +OBJS += ../../src/utils/common.c +OBJS += ../../src/crypto/crypto_internal.c +OBJS += ../../src/crypto/md5-internal.c +OBJS += ../../src/crypto/sha1-internal.c +OBJS += ../../src/crypto/sha256-internal.c +OBJS += ../../src/crypto/tls_openssl_ocsp.c + +L_CFLAGS += -DEAP_TLS_OPENSSL + +L_CFLAGS += -Wno-unused-parameter + +ifeq ($(shell test $(PLATFORM_VERSION_LAST_STABLE) -ge 8 ; echo $$?), 0) +L_CFLAGS += -DCONFIG_ANDROID_LOG +L_CFLAGS += -DANDROID_LOG_NAME='"hs20-osu-client"' +endif + +######################## +include $(CLEAR_VARS) +LOCAL_MODULE := hs20-osu-client +LOCAL_MODULE_TAGS := optional + +LOCAL_SHARED_LIBRARIES := libc libcutils +LOCAL_SHARED_LIBRARIES += libcrypto libssl +ifeq ($(shell test $(PLATFORM_VERSION_LAST_STABLE) -ge 8 ; echo $$?), 0) +LOCAL_VENDOR_MODULE := true +LOCAL_SHARED_LIBRARIES += libxml2 +LOCAL_SHARED_LIBRARIES += liblog +else +#LOCAL_SHARED_LIBRARIES += libxml2 +LOCAL_STATIC_LIBRARIES += libxml2 +LOCAL_SHARED_LIBRARIES += libicuuc +endif # End of check for platform version +LOCAL_SHARED_LIBRARIES += libcurl + +LOCAL_CFLAGS := $(L_CFLAGS) +LOCAL_SRC_FILES := $(OBJS) +LOCAL_C_INCLUDES := $(INCLUDES) +include $(BUILD_EXECUTABLE) + +######################## diff --git a/hs20/client/Makefile b/hs20/client/Makefile new file mode 100644 index 0000000..4dcfe2d --- /dev/null +++ b/hs20/client/Makefile @@ -0,0 +1,81 @@ +ALL=hs20-osu-client + +include ../../src/build.rules + +CFLAGS += -I../../src/utils +CFLAGS += -I../../src/common +CFLAGS += -I../../src + +ifndef CONFIG_NO_BROWSER +ifndef CONFIG_BROWSER_SYSTEM +TEST_WK := $(shell pkg-config --silence-errors --cflags webkitgtk-3.0) +ifeq ($(TEST_WK),) +# Try webkit2 +GTKCFLAGS := $(shell pkg-config --cflags gtk+-3.0 webkit2gtk-4.0) +GTKLIBS := $(shell pkg-config --libs gtk+-3.0 webkit2gtk-4.0) +CFLAGS += -DUSE_WEBKIT2 +else +GTKCFLAGS := $(shell pkg-config --cflags gtk+-3.0 webkitgtk-3.0) +GTKLIBS := $(shell pkg-config --libs gtk+-3.0 webkitgtk-3.0) +endif + +CFLAGS += $(GTKCFLAGS) +LIBS += $(GTKLIBS) +endif +endif + +OBJS=spp_client.o +OBJS += oma_dm_client.o +OBJS += osu_client.o +OBJS += est.o +OBJS += ../../src/utils/xml-utils.o +CFLAGS += -DCONFIG_CTRL_IFACE +CFLAGS += -DCONFIG_CTRL_IFACE_UNIX +OBJS += ../../src/common/wpa_ctrl.o ../../src/common/wpa_helpers.o +ifdef CONFIG_NO_BROWSER +CFLAGS += -DCONFIG_NO_BROWSER +else +ifdef CONFIG_BROWSER_SYSTEM +OBJS += ../../src/utils/eloop.o +OBJS += ../../src/utils/wpabuf.o +OBJS += ../../src/wps/httpread.o +OBJS += ../../src/wps/http_server.o +OBJS += ../../src/utils/browser-system.o +else +OBJS += ../../src/utils/browser.o +endif +endif +OBJS += ../../src/utils/xml_libxml2.o +OBJS += ../../src/utils/http_curl.o +OBJS += ../../src/utils/base64.o +OBJS += ../../src/utils/os_unix.o +CFLAGS += -DCONFIG_DEBUG_FILE +OBJS += ../../src/utils/wpa_debug.o +OBJS += ../../src/utils/common.o +OBJS += ../../src/crypto/crypto_internal.o +OBJS += ../../src/crypto/md5-internal.o +OBJS += ../../src/crypto/sha1-internal.o +OBJS += ../../src/crypto/sha256-internal.o + +CFLAGS += $(shell xml2-config --cflags) +LIBS += $(shell xml2-config --libs) + +# Allow static/custom linking of libcurl. +ifdef CUST_CURL_LINKAGE +LIBS += ${CUST_CURL_LINKAGE} +else +LIBS += -lcurl +endif + +CFLAGS += -DEAP_TLS_OPENSSL +OBJS += ../../src/crypto/tls_openssl_ocsp.o +LIBS += -lssl -lcrypto + +_OBJS_VAR := OBJS +include ../../src/objs.mk +hs20-osu-client: $(OBJS) + $(Q)$(LDO) $(LDFLAGS) -o hs20-osu-client $(OBJS) $(LIBS) + @$(E) " LD " $@ + +clean: common-clean + rm -f core *~ diff --git a/hs20/client/devdetail.xml b/hs20/client/devdetail.xml new file mode 100644 index 0000000..6d0389e --- /dev/null +++ b/hs20/client/devdetail.xml @@ -0,0 +1,47 @@ + + + + + + + 13 + + + 21 + MS-CHAP-V2 + + + 18 + + + 23 + + + 50 + + + false + 020102030405 + 310026000000000 + imei:490123456789012 + http://localhost:12345/ + + + + + + + + + + 0 + 0 + 0 + + MobilePhone + Manufacturer + 1.0 + 1.0 + 1.0 + false + diff --git a/hs20/client/devinfo.xml b/hs20/client/devinfo.xml new file mode 100644 index 0000000..d48a520 --- /dev/null +++ b/hs20/client/devinfo.xml @@ -0,0 +1,7 @@ + + urn:Example:HS20-station:123456 + Manufacturer + HS20-station + 1.2 + en + diff --git a/hs20/client/est.c b/hs20/client/est.c new file mode 100644 index 0000000..425b72d --- /dev/null +++ b/hs20/client/est.c @@ -0,0 +1,742 @@ +/* + * Hotspot 2.0 OSU client - EST client + * Copyright (c) 2012-2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "utils/base64.h" +#include "utils/xml-utils.h" +#include "utils/http-utils.h" +#include "osu_client.h" + + +static int pkcs7_to_cert(struct hs20_osu_client *ctx, const u8 *pkcs7, + size_t len, char *pem_file, char *der_file) +{ +#ifdef OPENSSL_IS_BORINGSSL + CBS pkcs7_cbs; +#else /* OPENSSL_IS_BORINGSSL */ + PKCS7 *p7 = NULL; + const unsigned char *p = pkcs7; +#endif /* OPENSSL_IS_BORINGSSL */ + STACK_OF(X509) *certs; + int i, num, ret = -1; + BIO *out = NULL; + +#ifdef OPENSSL_IS_BORINGSSL + certs = sk_X509_new_null(); + if (!certs) + goto fail; + CBS_init(&pkcs7_cbs, pkcs7, len); + if (!PKCS7_get_certificates(certs, &pkcs7_cbs)) { + wpa_printf(MSG_INFO, "Could not parse PKCS#7 object: %s", + ERR_error_string(ERR_get_error(), NULL)); + write_result(ctx, "Could not parse PKCS#7 object from EST"); + goto fail; + } +#else /* OPENSSL_IS_BORINGSSL */ + p7 = d2i_PKCS7(NULL, &p, len); + if (p7 == NULL) { + wpa_printf(MSG_INFO, "Could not parse PKCS#7 object: %s", + ERR_error_string(ERR_get_error(), NULL)); + write_result(ctx, "Could not parse PKCS#7 object from EST"); + goto fail; + } + + switch (OBJ_obj2nid(p7->type)) { + case NID_pkcs7_signed: + certs = p7->d.sign->cert; + break; + case NID_pkcs7_signedAndEnveloped: + certs = p7->d.signed_and_enveloped->cert; + break; + default: + certs = NULL; + break; + } +#endif /* OPENSSL_IS_BORINGSSL */ + + if (!certs || ((num = sk_X509_num(certs)) == 0)) { + wpa_printf(MSG_INFO, "No certificates found in PKCS#7 object"); + write_result(ctx, "No certificates found in PKCS#7 object"); + goto fail; + } + + if (der_file) { + FILE *f = fopen(der_file, "wb"); + if (f == NULL) + goto fail; + i2d_X509_fp(f, sk_X509_value(certs, 0)); + fclose(f); + } + + if (pem_file) { + out = BIO_new(BIO_s_file()); + if (out == NULL || + BIO_write_filename(out, pem_file) <= 0) + goto fail; + + for (i = 0; i < num; i++) { + X509 *cert = sk_X509_value(certs, i); + X509_print(out, cert); + PEM_write_bio_X509(out, cert); + BIO_puts(out, "\n"); + } + } + + ret = 0; + +fail: +#ifdef OPENSSL_IS_BORINGSSL + if (certs) + sk_X509_pop_free(certs, X509_free); +#else /* OPENSSL_IS_BORINGSSL */ + PKCS7_free(p7); +#endif /* OPENSSL_IS_BORINGSSL */ + if (out) + BIO_free_all(out); + + return ret; +} + + +int est_load_cacerts(struct hs20_osu_client *ctx, const char *url) +{ + char *buf, *resp; + size_t buflen; + unsigned char *pkcs7; + size_t pkcs7_len, resp_len; + int res; + + buflen = os_strlen(url) + 100; + buf = os_malloc(buflen); + if (buf == NULL) + return -1; + + os_snprintf(buf, buflen, "%s/cacerts", url); + wpa_printf(MSG_INFO, "Download EST cacerts from %s", buf); + write_summary(ctx, "Download EST cacerts from %s", buf); + ctx->no_osu_cert_validation = 1; + http_ocsp_set(ctx->http, 1); + res = http_download_file(ctx->http, buf, "Cert/est-cacerts.txt", + ctx->ca_fname); + http_ocsp_set(ctx->http, + (ctx->workarounds & WORKAROUND_OCSP_OPTIONAL) ? 1 : 2); + ctx->no_osu_cert_validation = 0; + if (res < 0) { + wpa_printf(MSG_INFO, "Failed to download EST cacerts from %s", + buf); + write_result(ctx, "Failed to download EST cacerts from %s", + buf); + os_free(buf); + return -1; + } + os_free(buf); + + resp = os_readfile("Cert/est-cacerts.txt", &resp_len); + if (resp == NULL) { + wpa_printf(MSG_INFO, "Could not read Cert/est-cacerts.txt"); + write_result(ctx, "Could not read EST cacerts"); + return -1; + } + + pkcs7 = base64_decode(resp, resp_len, &pkcs7_len); + if (pkcs7 && pkcs7_len < resp_len / 2) { + wpa_printf(MSG_INFO, "Too short base64 decode (%u bytes; downloaded %u bytes) - assume this was binary", + (unsigned int) pkcs7_len, (unsigned int) resp_len); + os_free(pkcs7); + pkcs7 = NULL; + } + if (pkcs7 == NULL) { + wpa_printf(MSG_INFO, "EST workaround - Could not decode base64, assume this is DER encoded PKCS7"); + pkcs7 = os_malloc(resp_len); + if (pkcs7) { + os_memcpy(pkcs7, resp, resp_len); + pkcs7_len = resp_len; + } + } + os_free(resp); + + if (pkcs7 == NULL) { + wpa_printf(MSG_INFO, "Could not fetch PKCS7 cacerts"); + write_result(ctx, "Could not fetch EST PKCS#7 cacerts"); + return -1; + } + + res = pkcs7_to_cert(ctx, pkcs7, pkcs7_len, "Cert/est-cacerts.pem", + NULL); + os_free(pkcs7); + if (res < 0) { + wpa_printf(MSG_INFO, "Could not parse CA certs from PKCS#7 cacerts response"); + write_result(ctx, "Could not parse CA certs from EST PKCS#7 cacerts response"); + return -1; + } + unlink("Cert/est-cacerts.txt"); + + return 0; +} + + +/* + * CsrAttrs ::= SEQUENCE SIZE (0..MAX) OF AttrOrOID + * + * AttrOrOID ::= CHOICE { + * oid OBJECT IDENTIFIER, + * attribute Attribute } + * + * Attribute ::= SEQUENCE { + * type OBJECT IDENTIFIER, + * values SET SIZE(1..MAX) OF OBJECT IDENTIFIER } + */ + +typedef struct { + ASN1_OBJECT *type; + STACK_OF(ASN1_OBJECT) *values; +} Attribute; + +typedef struct { + int type; + union { + ASN1_OBJECT *oid; + Attribute *attribute; + } d; +} AttrOrOID; + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +DEFINE_STACK_OF(AttrOrOID) +#endif + +typedef struct { + int type; + STACK_OF(AttrOrOID) *attrs; +} CsrAttrs; + +ASN1_SEQUENCE(Attribute) = { + ASN1_SIMPLE(Attribute, type, ASN1_OBJECT), + ASN1_SET_OF(Attribute, values, ASN1_OBJECT) +} ASN1_SEQUENCE_END(Attribute); + +ASN1_CHOICE(AttrOrOID) = { + ASN1_SIMPLE(AttrOrOID, d.oid, ASN1_OBJECT), + ASN1_SIMPLE(AttrOrOID, d.attribute, Attribute) +} ASN1_CHOICE_END(AttrOrOID); + +ASN1_CHOICE(CsrAttrs) = { + ASN1_SEQUENCE_OF(CsrAttrs, attrs, AttrOrOID) +} ASN1_CHOICE_END(CsrAttrs); + +IMPLEMENT_ASN1_FUNCTIONS(CsrAttrs); + + +static void add_csrattrs_oid(struct hs20_osu_client *ctx, ASN1_OBJECT *oid, + STACK_OF(X509_EXTENSION) *exts) +{ + char txt[100]; + int res; + + if (!oid) + return; + + res = OBJ_obj2txt(txt, sizeof(txt), oid, 1); + if (res < 0 || res >= (int) sizeof(txt)) + return; + + if (os_strcmp(txt, "1.2.840.113549.1.9.7") == 0) { + wpa_printf(MSG_INFO, "TODO: csrattr challengePassword"); + } else if (os_strcmp(txt, "1.2.840.113549.1.1.11") == 0) { + wpa_printf(MSG_INFO, "csrattr sha256WithRSAEncryption"); + } else { + wpa_printf(MSG_INFO, "Ignore unsupported csrattr oid %s", txt); + } +} + + +static void add_csrattrs_ext_req(struct hs20_osu_client *ctx, + STACK_OF(ASN1_OBJECT) *values, + STACK_OF(X509_EXTENSION) *exts) +{ + char txt[100]; + int i, num, res; + + num = sk_ASN1_OBJECT_num(values); + for (i = 0; i < num; i++) { + ASN1_OBJECT *oid = sk_ASN1_OBJECT_value(values, i); + + res = OBJ_obj2txt(txt, sizeof(txt), oid, 1); + if (res < 0 || res >= (int) sizeof(txt)) + continue; + + if (os_strcmp(txt, "1.3.6.1.1.1.1.22") == 0) { + wpa_printf(MSG_INFO, "TODO: extReq macAddress"); + } else if (os_strcmp(txt, "1.3.6.1.4.1.40808.1.1.3") == 0) { + wpa_printf(MSG_INFO, "TODO: extReq imei"); + } else if (os_strcmp(txt, "1.3.6.1.4.1.40808.1.1.4") == 0) { + wpa_printf(MSG_INFO, "TODO: extReq meid"); + } else if (os_strcmp(txt, "1.3.6.1.4.1.40808.1.1.5") == 0) { + wpa_printf(MSG_INFO, "TODO: extReq DevId"); + } else { + wpa_printf(MSG_INFO, "Ignore unsupported cstattr extensionsRequest %s", + txt); + } + } +} + + +static void add_csrattrs_attr(struct hs20_osu_client *ctx, Attribute *attr, + STACK_OF(X509_EXTENSION) *exts) +{ + char txt[100], txt2[100]; + int i, num, res; + + if (!attr || !attr->type || !attr->values) + return; + + res = OBJ_obj2txt(txt, sizeof(txt), attr->type, 1); + if (res < 0 || res >= (int) sizeof(txt)) + return; + + if (os_strcmp(txt, "1.2.840.113549.1.9.14") == 0) { + add_csrattrs_ext_req(ctx, attr->values, exts); + return; + } + + num = sk_ASN1_OBJECT_num(attr->values); + for (i = 0; i < num; i++) { + ASN1_OBJECT *oid = sk_ASN1_OBJECT_value(attr->values, i); + + res = OBJ_obj2txt(txt2, sizeof(txt2), oid, 1); + if (res < 0 || res >= (int) sizeof(txt2)) + continue; + + wpa_printf(MSG_INFO, "Ignore unsupported cstattr::attr %s oid %s", + txt, txt2); + } +} + + +static void add_csrattrs(struct hs20_osu_client *ctx, CsrAttrs *csrattrs, + STACK_OF(X509_EXTENSION) *exts) +{ + int i, num; + + if (!csrattrs || ! csrattrs->attrs) + return; + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + num = sk_AttrOrOID_num(csrattrs->attrs); +#else + num = SKM_sk_num(AttrOrOID, csrattrs->attrs); +#endif + for (i = 0; i < num; i++) { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + AttrOrOID *ao = sk_AttrOrOID_value(csrattrs->attrs, i); +#else + AttrOrOID *ao = SKM_sk_value(AttrOrOID, csrattrs->attrs, i); +#endif + switch (ao->type) { + case 0: + add_csrattrs_oid(ctx, ao->d.oid, exts); + break; + case 1: + add_csrattrs_attr(ctx, ao->d.attribute, exts); + break; + } + } +} + + +static int generate_csr(struct hs20_osu_client *ctx, char *key_pem, + char *csr_pem, char *est_req, char *old_cert, + CsrAttrs *csrattrs) +{ + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL; + X509_REQ *req = NULL; + int ret = -1; + unsigned int val; + X509_NAME *subj = NULL; + char name[100]; + STACK_OF(X509_EXTENSION) *exts = NULL; + X509_EXTENSION *ex; + BIO *out; + CONF *ctmp = NULL; + + wpa_printf(MSG_INFO, "Generate RSA private key"); + write_summary(ctx, "Generate RSA private key"); + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!pctx) + return -1; + + if (EVP_PKEY_keygen_init(pctx) <= 0) + goto fail; + + if (EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048) <= 0) + goto fail; + + if (EVP_PKEY_keygen(pctx, &pkey) <= 0) + goto fail; + EVP_PKEY_CTX_free(pctx); + pctx = NULL; + + if (key_pem) { + FILE *f = fopen(key_pem, "wb"); + if (f == NULL) + goto fail; + if (!PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL)) { + wpa_printf(MSG_INFO, "Could not write private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + fclose(f); + goto fail; + } + fclose(f); + } + + wpa_printf(MSG_INFO, "Generate CSR"); + write_summary(ctx, "Generate CSR"); + req = X509_REQ_new(); + if (req == NULL) + goto fail; + + if (old_cert) { + FILE *f; + X509 *cert; + int res; + + f = fopen(old_cert, "r"); + if (f == NULL) + goto fail; + cert = PEM_read_X509(f, NULL, NULL, NULL); + fclose(f); + + if (cert == NULL) + goto fail; + res = X509_REQ_set_subject_name(req, + X509_get_subject_name(cert)); + X509_free(cert); + if (!res) + goto fail; + } else { + os_get_random((u8 *) &val, sizeof(val)); + os_snprintf(name, sizeof(name), "cert-user-%u", val); + subj = X509_NAME_new(); + if (subj == NULL || + !X509_NAME_add_entry_by_txt(subj, "CN", MBSTRING_ASC, + (unsigned char *) name, + -1, -1, 0) || + !X509_REQ_set_subject_name(req, subj)) + goto fail; + X509_NAME_free(subj); + subj = NULL; + } + + if (!X509_REQ_set_pubkey(req, pkey)) + goto fail; + + exts = sk_X509_EXTENSION_new_null(); + if (!exts) + goto fail; + + ex = X509V3_EXT_nconf_nid(ctmp, NULL, NID_basic_constraints, + "CA:FALSE"); + if (ex == NULL || + !sk_X509_EXTENSION_push(exts, ex)) + goto fail; + + ex = X509V3_EXT_nconf_nid(ctmp, NULL, NID_key_usage, + "nonRepudiation,digitalSignature,keyEncipherment"); + if (ex == NULL || + !sk_X509_EXTENSION_push(exts, ex)) + goto fail; + + ex = X509V3_EXT_nconf_nid(ctmp, NULL, NID_ext_key_usage, + "1.3.6.1.4.1.40808.1.1.2"); + if (ex == NULL || + !sk_X509_EXTENSION_push(exts, ex)) + goto fail; + + add_csrattrs(ctx, csrattrs, exts); + + if (!X509_REQ_add_extensions(req, exts)) + goto fail; + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + exts = NULL; + + if (!X509_REQ_sign(req, pkey, EVP_sha256())) + goto fail; + + out = BIO_new(BIO_s_mem()); + if (out) { + char *txt; + size_t rlen; + +#if !defined(ANDROID) || !defined(OPENSSL_IS_BORINGSSL) + X509_REQ_print(out, req); +#endif + rlen = BIO_ctrl_pending(out); + txt = os_malloc(rlen + 1); + if (txt) { + int res = BIO_read(out, txt, rlen); + if (res > 0) { + txt[res] = '\0'; + wpa_printf(MSG_MSGDUMP, "OpenSSL: Certificate request:\n%s", + txt); + } + os_free(txt); + } + BIO_free(out); + } + + if (csr_pem) { + FILE *f = fopen(csr_pem, "w"); + if (f == NULL) + goto fail; +#if !defined(ANDROID) || !defined(OPENSSL_IS_BORINGSSL) + X509_REQ_print_fp(f, req); +#endif + if (!PEM_write_X509_REQ(f, req)) { + fclose(f); + goto fail; + } + fclose(f); + } + + if (est_req) { + BIO *mem = BIO_new(BIO_s_mem()); + BUF_MEM *ptr; + char *pos, *end, *buf_end; + FILE *f; + + if (mem == NULL) + goto fail; + if (!PEM_write_bio_X509_REQ(mem, req)) { + BIO_free(mem); + goto fail; + } + + BIO_get_mem_ptr(mem, &ptr); + pos = ptr->data; + buf_end = pos + ptr->length; + + /* Remove START/END lines */ + while (pos < buf_end && *pos != '\n') + pos++; + if (pos == buf_end) { + BIO_free(mem); + goto fail; + } + pos++; + + end = pos; + while (end < buf_end && *end != '-') + end++; + + f = fopen(est_req, "w"); + if (f == NULL) { + BIO_free(mem); + goto fail; + } + fwrite(pos, end - pos, 1, f); + fclose(f); + + BIO_free(mem); + } + + ret = 0; +fail: + if (exts) + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + if (subj) + X509_NAME_free(subj); + if (req) + X509_REQ_free(req); + if (pkey) + EVP_PKEY_free(pkey); + if (pctx) + EVP_PKEY_CTX_free(pctx); + return ret; +} + + +int est_build_csr(struct hs20_osu_client *ctx, const char *url) +{ + char *buf; + size_t buflen; + int res; + char old_cert_buf[200]; + char *old_cert = NULL; + CsrAttrs *csrattrs = NULL; + + buflen = os_strlen(url) + 100; + buf = os_malloc(buflen); + if (buf == NULL) + return -1; + + os_snprintf(buf, buflen, "%s/csrattrs", url); + wpa_printf(MSG_INFO, "Download csrattrs from %s", buf); + write_summary(ctx, "Download EST csrattrs from %s", buf); + ctx->no_osu_cert_validation = 1; + http_ocsp_set(ctx->http, 1); + res = http_download_file(ctx->http, buf, "Cert/est-csrattrs.txt", + ctx->ca_fname); + http_ocsp_set(ctx->http, + (ctx->workarounds & WORKAROUND_OCSP_OPTIONAL) ? 1 : 2); + ctx->no_osu_cert_validation = 0; + os_free(buf); + if (res < 0) { + wpa_printf(MSG_INFO, "Failed to download EST csrattrs - assume no extra attributes are needed"); + } else { + size_t resp_len; + char *resp; + unsigned char *attrs; + const unsigned char *pos; + size_t attrs_len; + + resp = os_readfile("Cert/est-csrattrs.txt", &resp_len); + if (resp == NULL) { + wpa_printf(MSG_INFO, "Could not read csrattrs"); + return -1; + } + + attrs = base64_decode(resp, resp_len, &attrs_len); + os_free(resp); + + if (attrs == NULL) { + wpa_printf(MSG_INFO, "Could not base64 decode csrattrs"); + return -1; + } + unlink("Cert/est-csrattrs.txt"); + + pos = attrs; + csrattrs = d2i_CsrAttrs(NULL, &pos, attrs_len); + os_free(attrs); + if (csrattrs == NULL) { + wpa_printf(MSG_INFO, "Failed to parse csrattrs ASN.1"); + /* Continue assuming no additional requirements */ + } + } + + if (ctx->client_cert_present) { + os_snprintf(old_cert_buf, sizeof(old_cert_buf), + "SP/%s/client-cert.pem", ctx->fqdn); + old_cert = old_cert_buf; + } + + res = generate_csr(ctx, "Cert/privkey-plain.pem", "Cert/est-req.pem", + "Cert/est-req.b64", old_cert, csrattrs); + if (csrattrs) + CsrAttrs_free(csrattrs); + + return res; +} + + +int est_simple_enroll(struct hs20_osu_client *ctx, const char *url, + const char *user, const char *pw) +{ + char *buf, *resp, *req, *req2; + size_t buflen, resp_len, len, pkcs7_len; + unsigned char *pkcs7; + char client_cert_buf[200]; + char client_key_buf[200]; + const char *client_cert = NULL, *client_key = NULL; + int res; + + req = os_readfile("Cert/est-req.b64", &len); + if (req == NULL) { + wpa_printf(MSG_INFO, "Could not read Cert/req.b64"); + return -1; + } + req2 = os_realloc(req, len + 1); + if (req2 == NULL) { + os_free(req); + return -1; + } + req2[len] = '\0'; + req = req2; + wpa_printf(MSG_DEBUG, "EST simpleenroll request: %s", req); + + buflen = os_strlen(url) + 100; + buf = os_malloc(buflen); + if (buf == NULL) { + os_free(req); + return -1; + } + + if (ctx->client_cert_present) { + os_snprintf(buf, buflen, "%s/simplereenroll", url); + os_snprintf(client_cert_buf, sizeof(client_cert_buf), + "SP/%s/client-cert.pem", ctx->fqdn); + client_cert = client_cert_buf; + os_snprintf(client_key_buf, sizeof(client_key_buf), + "SP/%s/client-key.pem", ctx->fqdn); + client_key = client_key_buf; + } else + os_snprintf(buf, buflen, "%s/simpleenroll", url); + wpa_printf(MSG_INFO, "EST simpleenroll URL: %s", buf); + write_summary(ctx, "EST simpleenroll URL: %s", buf); + ctx->no_osu_cert_validation = 1; + http_ocsp_set(ctx->http, 1); + resp = http_post(ctx->http, buf, req, "application/pkcs10", + "Content-Transfer-Encoding: base64", + ctx->ca_fname, user, pw, client_cert, client_key, + &resp_len); + http_ocsp_set(ctx->http, + (ctx->workarounds & WORKAROUND_OCSP_OPTIONAL) ? 1 : 2); + ctx->no_osu_cert_validation = 0; + os_free(buf); + if (resp == NULL) { + wpa_printf(MSG_INFO, "EST certificate enrollment failed"); + write_result(ctx, "EST certificate enrollment failed"); + return -1; + } + wpa_printf(MSG_DEBUG, "EST simpleenroll response: %s", resp); + + pkcs7 = base64_decode(resp, resp_len, &pkcs7_len); + if (pkcs7 == NULL) { + wpa_printf(MSG_INFO, "EST workaround - Could not decode base64, assume this is DER encoded PKCS7"); + pkcs7 = os_malloc(resp_len); + if (pkcs7) { + os_memcpy(pkcs7, resp, resp_len); + pkcs7_len = resp_len; + } + } + os_free(resp); + + if (pkcs7 == NULL) { + wpa_printf(MSG_INFO, "Failed to parse simpleenroll base64 response"); + write_result(ctx, "Failed to parse EST simpleenroll base64 response"); + return -1; + } + + res = pkcs7_to_cert(ctx, pkcs7, pkcs7_len, "Cert/est_cert.pem", + "Cert/est_cert.der"); + os_free(pkcs7); + + if (res < 0) { + wpa_printf(MSG_INFO, "EST: Failed to extract certificate from PKCS7 file"); + write_result(ctx, "EST: Failed to extract certificate from EST PKCS7 file"); + return -1; + } + + wpa_printf(MSG_INFO, "EST simple%senroll completed successfully", + ctx->client_cert_present ? "re" : ""); + write_summary(ctx, "EST simple%senroll completed successfully", + ctx->client_cert_present ? "re" : ""); + + return 0; +} diff --git a/hs20/client/oma_dm_client.c b/hs20/client/oma_dm_client.c new file mode 100644 index 0000000..bcd68b8 --- /dev/null +++ b/hs20/client/oma_dm_client.c @@ -0,0 +1,1398 @@ +/* + * Hotspot 2.0 - OMA DM client + * Copyright (c) 2013-2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "wpa_helpers.h" +#include "xml-utils.h" +#include "http-utils.h" +#include "utils/browser.h" +#include "osu_client.h" + + +#define DM_SERVER_INITIATED_MGMT 1200 +#define DM_CLIENT_INITIATED_MGMT 1201 +#define DM_GENERIC_ALERT 1226 + +/* OMA-TS-SyncML-RepPro-V1_2_2 - 10. Response Status Codes */ +#define DM_RESP_OK 200 +#define DM_RESP_AUTH_ACCEPTED 212 +#define DM_RESP_CHUNKED_ITEM_ACCEPTED 213 +#define DM_RESP_NOT_EXECUTED 215 +#define DM_RESP_ATOMIC_ROLL_BACK_OK 216 +#define DM_RESP_NOT_MODIFIED 304 +#define DM_RESP_BAD_REQUEST 400 +#define DM_RESP_UNAUTHORIZED 401 +#define DM_RESP_FORBIDDEN 403 +#define DM_RESP_NOT_FOUND 404 +#define DM_RESP_COMMAND_NOT_ALLOWED 405 +#define DM_RESP_OPTIONAL_FEATURE_NOT_SUPPORTED 406 +#define DM_RESP_MISSING_CREDENTIALS 407 +#define DM_RESP_CONFLICT 409 +#define DM_RESP_GONE 410 +#define DM_RESP_INCOMPLETE_COMMAND 412 +#define DM_RESP_REQ_ENTITY_TOO_LARGE 413 +#define DM_RESP_URI_TOO_LONG 414 +#define DM_RESP_UNSUPPORTED_MEDIA_TYPE_OR_FORMAT 415 +#define DM_RESP_REQ_TOO_BIG 416 +#define DM_RESP_ALREADY_EXISTS 418 +#define DM_RESP_DEVICE_FULL 420 +#define DM_RESP_SIZE_MISMATCH 424 +#define DM_RESP_PERMISSION_DENIED 425 +#define DM_RESP_COMMAND_FAILED 500 +#define DM_RESP_COMMAND_NOT_IMPLEMENTED 501 +#define DM_RESP_ATOMIC_ROLL_BACK_FAILED 516 + +#define DM_HS20_SUBSCRIPTION_CREATION \ + "org.wi-fi.hotspot2dot0.SubscriptionCreation" +#define DM_HS20_SUBSCRIPTION_PROVISIONING \ + "org.wi-fi.hotspot2dot0.SubscriptionProvisioning" +#define DM_HS20_SUBSCRIPTION_REMEDIATION \ + "org.wi-fi.hotspot2dot0.SubscriptionRemediation" +#define DM_HS20_POLICY_UPDATE \ + "org.wi-fi.hotspot2dot0.PolicyUpdate" + +#define DM_URI_PPS "./Wi-Fi/org.wi-fi/PerProviderSubscription" +#define DM_URI_LAUNCH_BROWSER \ + "./DevDetail/Ext/org.wi-fi/Wi-Fi/Ops/launchBrowserToURI" + + +static void add_item(struct hs20_osu_client *ctx, xml_node_t *parent, + const char *locuri, const char *data); + + +static const char * int2str(int val) +{ + static char buf[20]; + snprintf(buf, sizeof(buf), "%d", val); + return buf; +} + + +static char * oma_dm_get_target_locuri(struct hs20_osu_client *ctx, + xml_node_t *node) +{ + xml_node_t *locuri; + char *uri, *ret = NULL; + + locuri = get_node(ctx->xml, node, "Item/Target/LocURI"); + if (locuri == NULL) + return NULL; + + uri = xml_node_get_text(ctx->xml, locuri); + if (uri) + ret = os_strdup(uri); + xml_node_get_text_free(ctx->xml, uri); + return ret; +} + + +static void oma_dm_add_locuri(struct hs20_osu_client *ctx, xml_node_t *parent, + const char *element, const char *uri) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, element); + if (node == NULL) + return; + xml_node_create_text(ctx->xml, node, NULL, "LocURI", uri); +} + + +static xml_node_t * oma_dm_build_hdr(struct hs20_osu_client *ctx, + const char *url, int msgid) +{ + xml_node_t *syncml, *synchdr; + xml_namespace_t *ns; + + if (!ctx->devid) { + wpa_printf(MSG_ERROR, + "DevId from devinfo.xml is not available - cannot use OMA DM"); + return NULL; + } + + syncml = xml_node_create_root(ctx->xml, "SYNCML:SYNCML1.2", NULL, &ns, + "SyncML"); + + synchdr = xml_node_create(ctx->xml, syncml, NULL, "SyncHdr"); + xml_node_create_text(ctx->xml, synchdr, NULL, "VerDTD", "1.2"); + xml_node_create_text(ctx->xml, synchdr, NULL, "VerProto", "DM/1.2"); + xml_node_create_text(ctx->xml, synchdr, NULL, "SessionID", "1"); + xml_node_create_text(ctx->xml, synchdr, NULL, "MsgID", int2str(msgid)); + + oma_dm_add_locuri(ctx, synchdr, "Target", url); + oma_dm_add_locuri(ctx, synchdr, "Source", ctx->devid); + + return syncml; +} + + +static void oma_dm_add_cmdid(struct hs20_osu_client *ctx, xml_node_t *parent, + int cmdid) +{ + xml_node_create_text(ctx->xml, parent, NULL, "CmdID", int2str(cmdid)); +} + + +static xml_node_t * add_alert(struct hs20_osu_client *ctx, xml_node_t *parent, + int cmdid, int data) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, "Alert"); + if (node == NULL) + return NULL; + oma_dm_add_cmdid(ctx, node, cmdid); + xml_node_create_text(ctx->xml, node, NULL, "Data", int2str(data)); + + return node; +} + + +static xml_node_t * add_status(struct hs20_osu_client *ctx, xml_node_t *parent, + int msgref, int cmdref, int cmdid, + const char *cmd, int data, const char *targetref) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, "Status"); + if (node == NULL) + return NULL; + oma_dm_add_cmdid(ctx, node, cmdid); + xml_node_create_text(ctx->xml, node, NULL, "MsgRef", int2str(msgref)); + if (cmdref) + xml_node_create_text(ctx->xml, node, NULL, "CmdRef", + int2str(cmdref)); + xml_node_create_text(ctx->xml, node, NULL, "Cmd", cmd); + xml_node_create_text(ctx->xml, node, NULL, "Data", int2str(data)); + if (targetref) { + xml_node_create_text(ctx->xml, node, NULL, "TargetRef", + targetref); + } + + return node; +} + + +static xml_node_t * add_results(struct hs20_osu_client *ctx, xml_node_t *parent, + int msgref, int cmdref, int cmdid, + const char *locuri, const char *data) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, "Results"); + if (node == NULL) + return NULL; + + oma_dm_add_cmdid(ctx, node, cmdid); + xml_node_create_text(ctx->xml, node, NULL, "MsgRef", int2str(msgref)); + xml_node_create_text(ctx->xml, node, NULL, "CmdRef", int2str(cmdref)); + add_item(ctx, node, locuri, data); + + return node; +} + + +static char * mo_str(struct hs20_osu_client *ctx, const char *urn, + const char *fname) +{ + xml_node_t *fnode, *tnds; + char *str; + + fnode = node_from_file(ctx->xml, fname); + if (!fnode) + return NULL; + tnds = mo_to_tnds(ctx->xml, fnode, 0, urn, "syncml:dmddf1.2"); + xml_node_free(ctx->xml, fnode); + if (!tnds) + return NULL; + + str = xml_node_to_str(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (str == NULL) + return NULL; + wpa_printf(MSG_INFO, "MgmtTree: %s", str); + + return str; +} + + +static void add_item(struct hs20_osu_client *ctx, xml_node_t *parent, + const char *locuri, const char *data) +{ + xml_node_t *item, *node; + + item = xml_node_create(ctx->xml, parent, NULL, "Item"); + oma_dm_add_locuri(ctx, item, "Source", locuri); + node = xml_node_create(ctx->xml, item, NULL, "Meta"); + xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Format", + "Chr"); + xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Type", + "text/plain"); + xml_node_create_text(ctx->xml, item, NULL, "Data", data); +} + + +static void add_replace_devinfo(struct hs20_osu_client *ctx, xml_node_t *parent, + int cmdid) +{ + xml_node_t *info, *child, *replace; + const char *name; + char locuri[200], *txt; + + info = node_from_file(ctx->xml, "devinfo.xml"); + if (info == NULL) { + wpa_printf(MSG_INFO, "Could not read devinfo.xml"); + return; + } + + replace = xml_node_create(ctx->xml, parent, NULL, "Replace"); + if (replace == NULL) { + xml_node_free(ctx->xml, info); + return; + } + oma_dm_add_cmdid(ctx, replace, cmdid); + + xml_node_for_each_child(ctx->xml, child, info) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + os_snprintf(locuri, sizeof(locuri), "./DevInfo/%s", name); + txt = xml_node_get_text(ctx->xml, child); + if (txt) { + add_item(ctx, replace, locuri, txt); + xml_node_get_text_free(ctx->xml, txt); + } + } + + xml_node_free(ctx->xml, info); +} + + +static void oma_dm_add_hs20_generic_alert(struct hs20_osu_client *ctx, + xml_node_t *syncbody, + int cmdid, const char *oper, + const char *data) +{ + xml_node_t *node, *item; + char buf[200]; + + node = add_alert(ctx, syncbody, cmdid, DM_GENERIC_ALERT); + + item = xml_node_create(ctx->xml, node, NULL, "Item"); + oma_dm_add_locuri(ctx, item, "Source", DM_URI_PPS); + node = xml_node_create(ctx->xml, item, NULL, "Meta"); + snprintf(buf, sizeof(buf), "Reversed-Domain-Name: %s", oper); + xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Type", buf); + xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Format", + "xml"); + xml_node_create_text(ctx->xml, item, NULL, "Data", data); +} + + +static xml_node_t * build_oma_dm_1(struct hs20_osu_client *ctx, + const char *url, int msgid, const char *oper) +{ + xml_node_t *syncml, *syncbody; + char *str; + int cmdid = 0; + + syncml = oma_dm_build_hdr(ctx, url, msgid); + if (syncml == NULL) + return NULL; + + syncbody = xml_node_create(ctx->xml, syncml, NULL, "SyncBody"); + if (syncbody == NULL) { + xml_node_free(ctx->xml, syncml); + return NULL; + } + + cmdid++; + add_alert(ctx, syncbody, cmdid, DM_CLIENT_INITIATED_MGMT); + + str = mo_str(ctx, NULL, "devdetail.xml"); + if (str == NULL) { + xml_node_free(ctx->xml, syncml); + return NULL; + } + cmdid++; + oma_dm_add_hs20_generic_alert(ctx, syncbody, cmdid, oper, str); + os_free(str); + + cmdid++; + add_replace_devinfo(ctx, syncbody, cmdid); + + xml_node_create(ctx->xml, syncbody, NULL, "Final"); + + return syncml; +} + + +static xml_node_t * build_oma_dm_1_sub_reg(struct hs20_osu_client *ctx, + const char *url, int msgid) +{ + xml_node_t *syncml; + + syncml = build_oma_dm_1(ctx, url, msgid, DM_HS20_SUBSCRIPTION_CREATION); + if (syncml) + debug_dump_node(ctx, "OMA-DM Package 1 (sub reg)", syncml); + + return syncml; +} + + +static xml_node_t * build_oma_dm_1_sub_prov(struct hs20_osu_client *ctx, + const char *url, int msgid) +{ + xml_node_t *syncml; + + syncml = build_oma_dm_1(ctx, url, msgid, + DM_HS20_SUBSCRIPTION_PROVISIONING); + if (syncml) + debug_dump_node(ctx, "OMA-DM Package 1 (sub prov)", syncml); + + return syncml; +} + + +static xml_node_t * build_oma_dm_1_pol_upd(struct hs20_osu_client *ctx, + const char *url, int msgid) +{ + xml_node_t *syncml; + + syncml = build_oma_dm_1(ctx, url, msgid, DM_HS20_POLICY_UPDATE); + if (syncml) + debug_dump_node(ctx, "OMA-DM Package 1 (pol upd)", syncml); + + return syncml; +} + + +static xml_node_t * build_oma_dm_1_sub_rem(struct hs20_osu_client *ctx, + const char *url, int msgid) +{ + xml_node_t *syncml; + + syncml = build_oma_dm_1(ctx, url, msgid, + DM_HS20_SUBSCRIPTION_REMEDIATION); + if (syncml) + debug_dump_node(ctx, "OMA-DM Package 1 (sub rem)", syncml); + + return syncml; +} + + +static int oma_dm_exec_browser(struct hs20_osu_client *ctx, xml_node_t *exec) +{ + xml_node_t *node; + char *data; + int res; + + node = get_node(ctx->xml, exec, "Item/Data"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Data node found"); + return DM_RESP_BAD_REQUEST; + } + + data = xml_node_get_text(ctx->xml, node); + if (data == NULL) { + wpa_printf(MSG_INFO, "Invalid data"); + return DM_RESP_BAD_REQUEST; + } + wpa_printf(MSG_INFO, "Data: %s", data); + wpa_printf(MSG_INFO, "Launch browser to URI '%s'", data); + write_summary(ctx, "Launch browser to URI '%s'", data); + res = hs20_web_browser(data, 1); + xml_node_get_text_free(ctx->xml, data); + if (res > 0) { + wpa_printf(MSG_INFO, "User response in browser completed successfully"); + write_summary(ctx, "User response in browser completed successfully"); + return DM_RESP_OK; + } else { + wpa_printf(MSG_INFO, "Failed to receive user response"); + write_summary(ctx, "Failed to receive user response"); + return DM_RESP_COMMAND_FAILED; + } +} + + +static int oma_dm_exec_get_cert(struct hs20_osu_client *ctx, xml_node_t *exec) +{ + xml_node_t *node, *getcert; + char *data; + const char *name; + int res; + + wpa_printf(MSG_INFO, "Client certificate enrollment"); + write_summary(ctx, "Client certificate enrollment"); + + node = get_node(ctx->xml, exec, "Item/Data"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Data node found"); + return DM_RESP_BAD_REQUEST; + } + + data = xml_node_get_text(ctx->xml, node); + if (data == NULL) { + wpa_printf(MSG_INFO, "Invalid data"); + return DM_RESP_BAD_REQUEST; + } + wpa_printf(MSG_INFO, "Data: %s", data); + getcert = xml_node_from_buf(ctx->xml, data); + xml_node_get_text_free(ctx->xml, data); + + if (getcert == NULL) { + wpa_printf(MSG_INFO, "Could not parse Item/Data node contents"); + return DM_RESP_BAD_REQUEST; + } + + debug_dump_node(ctx, "OMA-DM getCertificate", getcert); + + name = xml_node_get_localname(ctx->xml, getcert); + if (name == NULL || os_strcasecmp(name, "getCertificate") != 0) { + wpa_printf(MSG_INFO, "Unexpected getCertificate node name '%s'", + name); + return DM_RESP_BAD_REQUEST; + } + + res = osu_get_certificate(ctx, getcert); + + xml_node_free(ctx->xml, getcert); + + return res == 0 ? DM_RESP_OK : DM_RESP_COMMAND_FAILED; +} + + +static int oma_dm_exec(struct hs20_osu_client *ctx, xml_node_t *exec) +{ + char *locuri; + int ret; + + locuri = oma_dm_get_target_locuri(ctx, exec); + if (locuri == NULL) { + wpa_printf(MSG_INFO, "No Target LocURI node found"); + return DM_RESP_BAD_REQUEST; + } + + wpa_printf(MSG_INFO, "Target LocURI: %s", locuri); + + if (os_strcasecmp(locuri, "./DevDetail/Ext/org.wi-fi/Wi-Fi/Ops/" + "launchBrowserToURI") == 0) { + ret = oma_dm_exec_browser(ctx, exec); + } else if (os_strcasecmp(locuri, "./DevDetail/Ext/org.wi-fi/Wi-Fi/Ops/" + "getCertificate") == 0) { + ret = oma_dm_exec_get_cert(ctx, exec); + } else { + wpa_printf(MSG_INFO, "Unsupported exec Target LocURI"); + ret = DM_RESP_NOT_FOUND; + } + os_free(locuri); + + return ret; +} + + +static int oma_dm_run_add(struct hs20_osu_client *ctx, const char *locuri, + xml_node_t *add, xml_node_t *pps, + const char *pps_fname) +{ + const char *pos; + size_t fqdn_len; + xml_node_t *node, *tnds, *unode, *pps_node; + char *data, *uri, *upos, *end; + int use_tnds = 0; + size_t uri_len; + + wpa_printf(MSG_INFO, "Add command target LocURI: %s", locuri); + + if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Do not allow Add outside ./Wi-Fi"); + return DM_RESP_PERMISSION_DENIED; + } + pos = locuri + 8; + + if (ctx->fqdn == NULL) + return DM_RESP_COMMAND_FAILED; + fqdn_len = os_strlen(ctx->fqdn); + if (os_strncasecmp(pos, ctx->fqdn, fqdn_len) != 0 || + pos[fqdn_len] != '/') { + wpa_printf(MSG_INFO, "Do not allow Add outside ./Wi-Fi/%s", + ctx->fqdn); + return DM_RESP_PERMISSION_DENIED; + } + pos += fqdn_len + 1; + + if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) { + wpa_printf(MSG_INFO, + "Do not allow Add outside ./Wi-Fi/%s/PerProviderSubscription", + ctx->fqdn); + return DM_RESP_PERMISSION_DENIED; + } + pos += 24; + + wpa_printf(MSG_INFO, "Add command for PPS node %s", pos); + + pps_node = get_node(ctx->xml, pps, pos); + if (pps_node) { + wpa_printf(MSG_INFO, "Specified PPS node exists already"); + return DM_RESP_ALREADY_EXISTS; + } + + uri = os_strdup(pos); + if (uri == NULL) + return DM_RESP_COMMAND_FAILED; + while (!pps_node) { + upos = os_strrchr(uri, '/'); + if (!upos) + break; + upos[0] = '\0'; + pps_node = get_node(ctx->xml, pps, uri); + wpa_printf(MSG_INFO, "Node %s %s", uri, + pps_node ? "exists" : "does not exist"); + } + + wpa_printf(MSG_INFO, "Parent URI: %s", uri); + + if (!pps_node) { + /* Add at root of PPS MO */ + pps_node = pps; + } + + uri_len = os_strlen(uri); + os_strlcpy(uri, pos + uri_len, os_strlen(pos)); + upos = uri; + while (*upos == '/') + upos++; + wpa_printf(MSG_INFO, "Nodes to add: %s", upos); + + for (;;) { + end = os_strchr(upos, '/'); + if (!end) + break; + *end = '\0'; + wpa_printf(MSG_INFO, "Adding interim node %s", upos); + pps_node = xml_node_create(ctx->xml, pps_node, NULL, upos); + if (pps_node == NULL) { + os_free(uri); + return DM_RESP_COMMAND_FAILED; + } + upos = end + 1; + } + + wpa_printf(MSG_INFO, "Adding node %s", upos); + + node = get_node(ctx->xml, add, "Item/Meta/Type"); + if (node) { + char *type; + type = xml_node_get_text(ctx->xml, node); + if (type == NULL) { + wpa_printf(MSG_ERROR, "Could not find type text"); + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + use_tnds = node && + os_strstr(type, "application/vnd.syncml.dmtnds+xml"); + } + + node = get_node(ctx->xml, add, "Item/Data"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Add/Item/Data found"); + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + + data = xml_node_get_text(ctx->xml, node); + if (data == NULL) { + wpa_printf(MSG_INFO, "Could not get Add/Item/Data text"); + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + + wpa_printf(MSG_DEBUG, "Add/Item/Data: %s", data); + + if (use_tnds) { + tnds = xml_node_from_buf(ctx->xml, data); + xml_node_get_text_free(ctx->xml, data); + if (tnds == NULL) { + wpa_printf(MSG_INFO, + "Could not parse Add/Item/Data text"); + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + + unode = tnds_to_mo(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (unode == NULL) { + wpa_printf(MSG_INFO, "Could not parse TNDS text"); + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + + debug_dump_node(ctx, "Parsed TNDS", unode); + + xml_node_add_child(ctx->xml, pps_node, unode); + } else { + /* TODO: What to do here? */ + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + + os_free(uri); + + if (update_pps_file(ctx, pps_fname, pps) < 0) + return DM_RESP_COMMAND_FAILED; + + ctx->pps_updated = 1; + + return DM_RESP_OK; +} + + +static int oma_dm_add(struct hs20_osu_client *ctx, xml_node_t *add, + xml_node_t *pps, const char *pps_fname) +{ + xml_node_t *node; + char *locuri; + char fname[300]; + int ret; + + node = get_node(ctx->xml, add, "Item/Target/LocURI"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Target LocURI node found"); + return DM_RESP_BAD_REQUEST; + } + locuri = xml_node_get_text(ctx->xml, node); + if (locuri == NULL) { + wpa_printf(MSG_ERROR, "No LocURI node text found"); + return DM_RESP_BAD_REQUEST; + } + wpa_printf(MSG_INFO, "Target LocURI: %s", locuri); + if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Unsupported Add Target LocURI"); + xml_node_get_text_free(ctx->xml, locuri); + return DM_RESP_PERMISSION_DENIED; + } + + node = get_node(ctx->xml, add, "Item/Data"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Data node found"); + xml_node_get_text_free(ctx->xml, locuri); + return DM_RESP_BAD_REQUEST; + } + + if (pps_fname && os_file_exists(pps_fname)) { + ret = oma_dm_run_add(ctx, locuri, add, pps, pps_fname); + if (ret != DM_RESP_OK) { + xml_node_get_text_free(ctx->xml, locuri); + return ret; + } + ret = 0; + os_strlcpy(fname, pps_fname, sizeof(fname)); + } else + ret = hs20_add_pps_mo(ctx, locuri, node, fname, sizeof(fname)); + xml_node_get_text_free(ctx->xml, locuri); + if (ret < 0) + return ret == -2 ? DM_RESP_ALREADY_EXISTS : + DM_RESP_COMMAND_FAILED; + + if (ctx->no_reconnect == 2) { + os_snprintf(ctx->pps_fname, sizeof(ctx->pps_fname), "%s", + fname); + ctx->pps_cred_set = 1; + return DM_RESP_OK; + } + + wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials"); + cmd_set_pps(ctx, fname); + + if (ctx->no_reconnect) + return DM_RESP_OK; + + wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration"); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) + wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect"); + + return DM_RESP_OK; +} + + +static int oma_dm_replace(struct hs20_osu_client *ctx, xml_node_t *replace, + xml_node_t *pps, const char *pps_fname) +{ + char *locuri, *pos; + size_t fqdn_len; + xml_node_t *node, *tnds, *unode, *pps_node, *parent; + char *data; + int use_tnds = 0; + + locuri = oma_dm_get_target_locuri(ctx, replace); + if (locuri == NULL) + return DM_RESP_BAD_REQUEST; + + wpa_printf(MSG_INFO, "Replace command target LocURI: %s", locuri); + if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Do not allow Replace outside ./Wi-Fi"); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos = locuri + 8; + + if (ctx->fqdn == NULL) { + os_free(locuri); + return DM_RESP_COMMAND_FAILED; + } + fqdn_len = os_strlen(ctx->fqdn); + if (os_strncasecmp(pos, ctx->fqdn, fqdn_len) != 0 || + pos[fqdn_len] != '/') { + wpa_printf(MSG_INFO, "Do not allow Replace outside ./Wi-Fi/%s", + ctx->fqdn); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos += fqdn_len + 1; + + if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) { + wpa_printf(MSG_INFO, + "Do not allow Replace outside ./Wi-Fi/%s/PerProviderSubscription", + ctx->fqdn); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos += 24; + + wpa_printf(MSG_INFO, "Replace command for PPS node %s", pos); + + pps_node = get_node(ctx->xml, pps, pos); + if (pps_node == NULL) { + wpa_printf(MSG_INFO, "Specified PPS node not found"); + os_free(locuri); + return DM_RESP_NOT_FOUND; + } + + node = get_node(ctx->xml, replace, "Item/Meta/Type"); + if (node) { + char *type; + type = xml_node_get_text(ctx->xml, node); + if (type == NULL) { + wpa_printf(MSG_INFO, "Could not find type text"); + os_free(locuri); + return DM_RESP_BAD_REQUEST; + } + use_tnds = node && + os_strstr(type, "application/vnd.syncml.dmtnds+xml"); + } + + node = get_node(ctx->xml, replace, "Item/Data"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Replace/Item/Data found"); + os_free(locuri); + return DM_RESP_BAD_REQUEST; + } + + data = xml_node_get_text(ctx->xml, node); + if (data == NULL) { + wpa_printf(MSG_INFO, "Could not get Replace/Item/Data text"); + os_free(locuri); + return DM_RESP_BAD_REQUEST; + } + + wpa_printf(MSG_DEBUG, "Replace/Item/Data: %s", data); + + if (use_tnds) { + tnds = xml_node_from_buf(ctx->xml, data); + xml_node_get_text_free(ctx->xml, data); + if (tnds == NULL) { + wpa_printf(MSG_INFO, + "Could not parse Replace/Item/Data text"); + os_free(locuri); + return DM_RESP_BAD_REQUEST; + } + + unode = tnds_to_mo(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (unode == NULL) { + wpa_printf(MSG_INFO, "Could not parse TNDS text"); + os_free(locuri); + return DM_RESP_BAD_REQUEST; + } + + debug_dump_node(ctx, "Parsed TNDS", unode); + + parent = xml_node_get_parent(ctx->xml, pps_node); + xml_node_detach(ctx->xml, pps_node); + xml_node_add_child(ctx->xml, parent, unode); + } else { + xml_node_set_text(ctx->xml, pps_node, data); + xml_node_get_text_free(ctx->xml, data); + } + + os_free(locuri); + + if (update_pps_file(ctx, pps_fname, pps) < 0) + return DM_RESP_COMMAND_FAILED; + + ctx->pps_updated = 1; + + return DM_RESP_OK; +} + + +static int oma_dm_get(struct hs20_osu_client *ctx, xml_node_t *get, + xml_node_t *pps, const char *pps_fname, char **value) +{ + char *locuri, *pos; + size_t fqdn_len; + xml_node_t *pps_node; + const char *name; + + *value = NULL; + + locuri = oma_dm_get_target_locuri(ctx, get); + if (locuri == NULL) + return DM_RESP_BAD_REQUEST; + + wpa_printf(MSG_INFO, "Get command target LocURI: %s", locuri); + if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Do not allow Get outside ./Wi-Fi"); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos = locuri + 8; + + if (ctx->fqdn == NULL) + return DM_RESP_COMMAND_FAILED; + fqdn_len = os_strlen(ctx->fqdn); + if (os_strncasecmp(pos, ctx->fqdn, fqdn_len) != 0 || + pos[fqdn_len] != '/') { + wpa_printf(MSG_INFO, "Do not allow Get outside ./Wi-Fi/%s", + ctx->fqdn); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos += fqdn_len + 1; + + if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) { + wpa_printf(MSG_INFO, + "Do not allow Get outside ./Wi-Fi/%s/PerProviderSubscription", + ctx->fqdn); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos += 24; + + wpa_printf(MSG_INFO, "Get command for PPS node %s", pos); + + pps_node = get_node(ctx->xml, pps, pos); + if (pps_node == NULL) { + wpa_printf(MSG_INFO, "Specified PPS node not found"); + os_free(locuri); + return DM_RESP_NOT_FOUND; + } + + name = xml_node_get_localname(ctx->xml, pps_node); + wpa_printf(MSG_INFO, "Get command returned node with name '%s'", name); + if (os_strcasecmp(name, "Password") == 0) { + wpa_printf(MSG_INFO, "Do not allow Get for Password node"); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + + /* + * TODO: No support for DMTNDS, so if interior node, reply with a + * list of children node names in Results element. The child list type is + * defined in [DMTND]. + */ + + *value = xml_node_get_text(ctx->xml, pps_node); + if (*value == NULL) + return DM_RESP_COMMAND_FAILED; + + return DM_RESP_OK; +} + + +static int oma_dm_get_cmdid(struct hs20_osu_client *ctx, xml_node_t *node) +{ + xml_node_t *cnode; + char *str; + int ret; + + cnode = get_node(ctx->xml, node, "CmdID"); + if (cnode == NULL) + return 0; + + str = xml_node_get_text(ctx->xml, cnode); + if (str == NULL) + return 0; + ret = atoi(str); + xml_node_get_text_free(ctx->xml, str); + return ret; +} + + +static xml_node_t * oma_dm_send_recv(struct hs20_osu_client *ctx, + const char *url, xml_node_t *syncml, + const char *ext_hdr, + const char *username, const char *password, + const char *client_cert, + const char *client_key) +{ + xml_node_t *resp; + char *str, *res; + char *resp_uri = NULL; + + str = xml_node_to_str(ctx->xml, syncml); + xml_node_free(ctx->xml, syncml); + if (str == NULL) + return NULL; + + wpa_printf(MSG_INFO, "Send OMA DM Package"); + write_summary(ctx, "Send OMA DM Package"); + os_free(ctx->server_url); + ctx->server_url = os_strdup(url); + res = http_post(ctx->http, url, str, "application/vnd.syncml.dm+xml", + ext_hdr, ctx->ca_fname, username, password, + client_cert, client_key, NULL); + os_free(str); + os_free(resp_uri); + resp_uri = NULL; + + if (res == NULL) { + const char *err = http_get_err(ctx->http); + if (err) { + wpa_printf(MSG_INFO, "HTTP error: %s", err); + write_result(ctx, "HTTP error: %s", err); + } else { + write_summary(ctx, "Failed to send OMA DM Package"); + } + return NULL; + } + wpa_printf(MSG_DEBUG, "Server response: %s", res); + + wpa_printf(MSG_INFO, "Process OMA DM Package"); + write_summary(ctx, "Process received OMA DM Package"); + resp = xml_node_from_buf(ctx->xml, res); + os_free(res); + if (resp == NULL) { + wpa_printf(MSG_INFO, "Failed to parse OMA DM response"); + return NULL; + } + + debug_dump_node(ctx, "OMA DM Package", resp); + + return resp; +} + + +static xml_node_t * oma_dm_process(struct hs20_osu_client *ctx, const char *url, + xml_node_t *resp, int msgid, + char **ret_resp_uri, + xml_node_t *pps, const char *pps_fname) +{ + xml_node_t *syncml, *syncbody, *hdr, *body, *child; + const char *name; + char *resp_uri = NULL; + int server_msgid = 0; + int cmdid = 0; + int server_cmdid; + int resp_needed = 0; + char *tmp; + int final = 0; + char *locuri; + + *ret_resp_uri = NULL; + + name = xml_node_get_localname(ctx->xml, resp); + if (name == NULL || os_strcasecmp(name, "SyncML") != 0) { + wpa_printf(MSG_INFO, "SyncML node not found"); + return NULL; + } + + hdr = get_node(ctx->xml, resp, "SyncHdr"); + body = get_node(ctx->xml, resp, "SyncBody"); + if (hdr == NULL || body == NULL) { + wpa_printf(MSG_INFO, "Could not find SyncHdr or SyncBody"); + return NULL; + } + + xml_node_for_each_child(ctx->xml, child, hdr) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + wpa_printf(MSG_INFO, "SyncHdr %s", name); + if (os_strcasecmp(name, "RespURI") == 0) { + tmp = xml_node_get_text(ctx->xml, child); + if (tmp) + resp_uri = os_strdup(tmp); + xml_node_get_text_free(ctx->xml, tmp); + } else if (os_strcasecmp(name, "MsgID") == 0) { + tmp = xml_node_get_text(ctx->xml, child); + if (tmp) + server_msgid = atoi(tmp); + xml_node_get_text_free(ctx->xml, tmp); + } + } + + wpa_printf(MSG_INFO, "Server MsgID: %d", server_msgid); + if (resp_uri) + wpa_printf(MSG_INFO, "RespURI: %s", resp_uri); + + syncml = oma_dm_build_hdr(ctx, resp_uri ? resp_uri : url, msgid); + if (syncml == NULL) { + os_free(resp_uri); + return NULL; + } + + syncbody = xml_node_create(ctx->xml, syncml, NULL, "SyncBody"); + cmdid++; + add_status(ctx, syncbody, server_msgid, 0, cmdid, "SyncHdr", + DM_RESP_AUTH_ACCEPTED, NULL); + + xml_node_for_each_child(ctx->xml, child, body) { + xml_node_for_each_check(ctx->xml, child); + server_cmdid = oma_dm_get_cmdid(ctx, child); + name = xml_node_get_localname(ctx->xml, child); + wpa_printf(MSG_INFO, "SyncBody CmdID=%d - %s", + server_cmdid, name); + if (os_strcasecmp(name, "Exec") == 0) { + int res = oma_dm_exec(ctx, child); + cmdid++; + locuri = oma_dm_get_target_locuri(ctx, child); + if (locuri == NULL) + res = DM_RESP_BAD_REQUEST; + add_status(ctx, syncbody, server_msgid, server_cmdid, + cmdid, name, res, locuri); + os_free(locuri); + resp_needed = 1; + } else if (os_strcasecmp(name, "Add") == 0) { + int res = oma_dm_add(ctx, child, pps, pps_fname); + cmdid++; + locuri = oma_dm_get_target_locuri(ctx, child); + if (locuri == NULL) + res = DM_RESP_BAD_REQUEST; + add_status(ctx, syncbody, server_msgid, server_cmdid, + cmdid, name, res, locuri); + os_free(locuri); + resp_needed = 1; + } else if (os_strcasecmp(name, "Replace") == 0) { + int res; + res = oma_dm_replace(ctx, child, pps, pps_fname); + cmdid++; + locuri = oma_dm_get_target_locuri(ctx, child); + if (locuri == NULL) + res = DM_RESP_BAD_REQUEST; + add_status(ctx, syncbody, server_msgid, server_cmdid, + cmdid, name, res, locuri); + os_free(locuri); + resp_needed = 1; + } else if (os_strcasecmp(name, "Status") == 0) { + /* TODO: Verify success */ + } else if (os_strcasecmp(name, "Get") == 0) { + int res; + char *value; + res = oma_dm_get(ctx, child, pps, pps_fname, &value); + cmdid++; + locuri = oma_dm_get_target_locuri(ctx, child); + if (locuri == NULL) + res = DM_RESP_BAD_REQUEST; + add_status(ctx, syncbody, server_msgid, server_cmdid, + cmdid, name, res, locuri); + if (res == DM_RESP_OK && value) { + cmdid++; + add_results(ctx, syncbody, server_msgid, + server_cmdid, cmdid, locuri, value); + } + os_free(locuri); + xml_node_get_text_free(ctx->xml, value); + resp_needed = 1; +#if 0 /* TODO: MUST support */ + } else if (os_strcasecmp(name, "Delete") == 0) { +#endif +#if 0 /* TODO: MUST support */ + } else if (os_strcasecmp(name, "Sequence") == 0) { +#endif + } else if (os_strcasecmp(name, "Final") == 0) { + final = 1; + break; + } else { + locuri = oma_dm_get_target_locuri(ctx, child); + add_status(ctx, syncbody, server_msgid, server_cmdid, + cmdid, name, DM_RESP_COMMAND_NOT_IMPLEMENTED, + locuri); + os_free(locuri); + resp_needed = 1; + } + } + + if (!final) { + wpa_printf(MSG_INFO, "Final node not found"); + xml_node_free(ctx->xml, syncml); + os_free(resp_uri); + return NULL; + } + + if (!resp_needed) { + wpa_printf(MSG_INFO, "Exchange completed - no response needed"); + xml_node_free(ctx->xml, syncml); + os_free(resp_uri); + return NULL; + } + + xml_node_create(ctx->xml, syncbody, NULL, "Final"); + + debug_dump_node(ctx, "OMA-DM Package 3", syncml); + + *ret_resp_uri = resp_uri; + return syncml; +} + + +int cmd_oma_dm_prov(struct hs20_osu_client *ctx, const char *url) +{ + xml_node_t *syncml, *resp; + char *resp_uri = NULL; + int msgid = 0; + + if (url == NULL) { + wpa_printf(MSG_INFO, "Invalid prov command (missing URL)"); + return -1; + } + + wpa_printf(MSG_INFO, "OMA-DM credential provisioning requested"); + write_summary(ctx, "OMA-DM credential provisioning"); + + msgid++; + syncml = build_oma_dm_1_sub_reg(ctx, url, msgid); + if (syncml == NULL) + return -1; + + while (syncml) { + resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : url, + syncml, NULL, NULL, NULL, NULL, NULL); + if (resp == NULL) + return -1; + + msgid++; + syncml = oma_dm_process(ctx, url, resp, msgid, &resp_uri, + NULL, NULL); + xml_node_free(ctx->xml, resp); + } + + os_free(resp_uri); + + return ctx->pps_cred_set ? 0 : -1; +} + + +int cmd_oma_dm_sim_prov(struct hs20_osu_client *ctx, const char *url) +{ + xml_node_t *syncml, *resp; + char *resp_uri = NULL; + int msgid = 0; + + if (url == NULL) { + wpa_printf(MSG_INFO, "Invalid prov command (missing URL)"); + return -1; + } + + wpa_printf(MSG_INFO, "OMA-DM SIM provisioning requested"); + ctx->no_reconnect = 2; + + wpa_printf(MSG_INFO, "Wait for IP address before starting SIM provisioning"); + write_summary(ctx, "Wait for IP address before starting SIM provisioning"); + + if (wait_ip_addr(ctx->ifname, 15) < 0) { + wpa_printf(MSG_INFO, "Could not get IP address for WLAN - try connection anyway"); + } + write_summary(ctx, "OMA-DM SIM provisioning"); + + msgid++; + syncml = build_oma_dm_1_sub_prov(ctx, url, msgid); + if (syncml == NULL) + return -1; + + while (syncml) { + resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : url, + syncml, NULL, NULL, NULL, NULL, NULL); + if (resp == NULL) + return -1; + + msgid++; + syncml = oma_dm_process(ctx, url, resp, msgid, &resp_uri, + NULL, NULL); + xml_node_free(ctx->xml, resp); + } + + os_free(resp_uri); + + if (ctx->pps_cred_set) { + wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials"); + cmd_set_pps(ctx, ctx->pps_fname); + + wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration"); + write_summary(ctx, "Requesting reconnection with updated configuration"); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) { + wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect"); + write_summary(ctx, "Failed to request wpa_supplicant to reconnect"); + return -1; + } + } + + return ctx->pps_cred_set ? 0 : -1; +} + + +void oma_dm_pol_upd(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps) +{ + xml_node_t *syncml, *resp; + char *resp_uri = NULL; + int msgid = 0; + + wpa_printf(MSG_INFO, "OMA-DM policy update"); + write_summary(ctx, "OMA-DM policy update"); + + msgid++; + syncml = build_oma_dm_1_pol_upd(ctx, address, msgid); + if (syncml == NULL) + return; + + while (syncml) { + resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : address, + syncml, NULL, cred_username, + cred_password, client_cert, client_key); + if (resp == NULL) + return; + + msgid++; + syncml = oma_dm_process(ctx, address, resp, msgid, &resp_uri, + pps, pps_fname); + xml_node_free(ctx->xml, resp); + } + + os_free(resp_uri); + + if (ctx->pps_updated) { + wpa_printf(MSG_INFO, "Update wpa_supplicant credential based on updated PPS MO"); + write_summary(ctx, "Update wpa_supplicant credential based on updated PPS MO and request connection"); + cmd_set_pps(ctx, pps_fname); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) { + wpa_printf(MSG_INFO, + "Failed to request wpa_supplicant to reconnect"); + write_summary(ctx, + "Failed to request wpa_supplicant to reconnect"); + } + } +} + + +void oma_dm_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps) +{ + xml_node_t *syncml, *resp; + char *resp_uri = NULL; + int msgid = 0; + + wpa_printf(MSG_INFO, "OMA-DM subscription remediation"); + write_summary(ctx, "OMA-DM subscription remediation"); + + msgid++; + syncml = build_oma_dm_1_sub_rem(ctx, address, msgid); + if (syncml == NULL) + return; + + while (syncml) { + resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : address, + syncml, NULL, cred_username, + cred_password, client_cert, client_key); + if (resp == NULL) + return; + + msgid++; + syncml = oma_dm_process(ctx, address, resp, msgid, &resp_uri, + pps, pps_fname); + xml_node_free(ctx->xml, resp); + } + + os_free(resp_uri); + + wpa_printf(MSG_INFO, "Update wpa_supplicant credential based on updated PPS MO and request reconnection"); + write_summary(ctx, "Update wpa_supplicant credential based on updated PPS MO and request reconnection"); + cmd_set_pps(ctx, pps_fname); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) { + wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect"); + write_summary(ctx, "Failed to request wpa_supplicant to reconnect"); + } +} + + +void cmd_oma_dm_add(struct hs20_osu_client *ctx, const char *pps_fname, + const char *add_fname) +{ + xml_node_t *pps, *add; + int res; + + ctx->fqdn = os_strdup("wi-fi.org"); + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "PPS file %s could not be parsed", + pps_fname); + return; + } + + add = node_from_file(ctx->xml, add_fname); + if (add == NULL) { + wpa_printf(MSG_INFO, "Add file %s could not be parsed", + add_fname); + xml_node_free(ctx->xml, pps); + return; + } + + res = oma_dm_add(ctx, add, pps, pps_fname); + wpa_printf(MSG_INFO, "oma_dm_add --> %d", res); + + xml_node_free(ctx->xml, pps); + xml_node_free(ctx->xml, add); +} + + +void cmd_oma_dm_replace(struct hs20_osu_client *ctx, const char *pps_fname, + const char *replace_fname) +{ + xml_node_t *pps, *replace; + int res; + + ctx->fqdn = os_strdup("wi-fi.org"); + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "PPS file %s could not be parsed", + pps_fname); + return; + } + + replace = node_from_file(ctx->xml, replace_fname); + if (replace == NULL) { + wpa_printf(MSG_INFO, "Replace file %s could not be parsed", + replace_fname); + xml_node_free(ctx->xml, pps); + return; + } + + res = oma_dm_replace(ctx, replace, pps, pps_fname); + wpa_printf(MSG_INFO, "oma_dm_replace --> %d", res); + + xml_node_free(ctx->xml, pps); + xml_node_free(ctx->xml, replace); +} diff --git a/hs20/client/osu_client.c b/hs20/client/osu_client.c new file mode 100644 index 0000000..2ca85f9 --- /dev/null +++ b/hs20/client/osu_client.c @@ -0,0 +1,3474 @@ +/* + * Hotspot 2.0 OSU client + * Copyright (c) 2012-2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" +#include +#include +#ifdef ANDROID +#include "private/android_filesystem_config.h" +#endif /* ANDROID */ + +#include "common.h" +#include "utils/browser.h" +#include "utils/base64.h" +#include "utils/xml-utils.h" +#include "utils/http-utils.h" +#include "common/wpa_ctrl.h" +#include "common/wpa_helpers.h" +#include "eap_common/eap_defs.h" +#include "crypto/crypto.h" +#include "crypto/sha256.h" +#include "osu_client.h" + +const char *spp_xsd_fname = "spp.xsd"; + + +void write_result(struct hs20_osu_client *ctx, const char *fmt, ...) +{ + va_list ap; + FILE *f; + char buf[500]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + write_summary(ctx, "%s", buf); + + if (!ctx->result_file) + return; + + f = fopen(ctx->result_file, "w"); + if (f == NULL) + return; + + va_start(ap, fmt); + vfprintf(f, fmt, ap); + va_end(ap); + fprintf(f, "\n"); + fclose(f); +} + + +void write_summary(struct hs20_osu_client *ctx, const char *fmt, ...) +{ + va_list ap; + FILE *f; + + if (!ctx->summary_file) + return; + + f = fopen(ctx->summary_file, "a"); + if (f == NULL) + return; + + va_start(ap, fmt); + vfprintf(f, fmt, ap); + va_end(ap); + fprintf(f, "\n"); + fclose(f); +} + + +void debug_dump_node(struct hs20_osu_client *ctx, const char *title, + xml_node_t *node) +{ + char *str = xml_node_to_str(ctx->xml, node); + wpa_printf(MSG_DEBUG, "[hs20] %s: '%s'", title, str); + free(str); +} + + +static int valid_fqdn(const char *fqdn) +{ + const char *pos; + + /* TODO: could make this more complete.. */ + if (strchr(fqdn, '.') == 0 || strlen(fqdn) > 255) + return 0; + for (pos = fqdn; *pos; pos++) { + if (*pos >= 'a' && *pos <= 'z') + continue; + if (*pos >= 'A' && *pos <= 'Z') + continue; + if (*pos >= '0' && *pos <= '9') + continue; + if (*pos == '-' || *pos == '.' || *pos == '_') + continue; + return 0; + } + return 1; +} + + +static int android_update_permission(const char *path, mode_t mode) +{ +#ifdef ANDROID + /* we need to change file/folder permission for Android */ + + if (!path) { + wpa_printf(MSG_ERROR, "file path null"); + return -1; + } + + /* Allow processes running with Group ID as AID_WIFI, + * to read files from SP, SP/, Cert and osu-info directories */ + if (lchown(path, -1, AID_WIFI)) { + wpa_printf(MSG_INFO, "CTRL: Could not lchown directory: %s", + strerror(errno)); + return -1; + } + + if (chmod(path, mode) < 0) { + wpa_printf(MSG_INFO, "CTRL: Could not chmod directory: %s", + strerror(errno)); + return -1; + } +#endif /* ANDROID */ + + return 0; +} + + +int osu_get_certificate(struct hs20_osu_client *ctx, xml_node_t *getcert) +{ + xml_node_t *node; + char *url, *user = NULL, *pw = NULL; + char *proto; + int ret = -1; + + proto = xml_node_get_attr_value(ctx->xml, getcert, + "enrollmentProtocol"); + if (!proto) + return -1; + wpa_printf(MSG_INFO, "getCertificate - enrollmentProtocol=%s", proto); + write_summary(ctx, "getCertificate - enrollmentProtocol=%s", proto); + if (os_strcasecmp(proto, "EST") != 0) { + wpa_printf(MSG_INFO, "Unsupported enrollmentProtocol"); + xml_node_get_attr_value_free(ctx->xml, proto); + return -1; + } + xml_node_get_attr_value_free(ctx->xml, proto); + + node = get_node(ctx->xml, getcert, "enrollmentServerURI"); + if (node == NULL) { + wpa_printf(MSG_INFO, "Could not find enrollmentServerURI node"); + xml_node_get_attr_value_free(ctx->xml, proto); + return -1; + } + url = xml_node_get_text(ctx->xml, node); + if (url == NULL) { + wpa_printf(MSG_INFO, "Could not get URL text"); + return -1; + } + wpa_printf(MSG_INFO, "enrollmentServerURI: %s", url); + write_summary(ctx, "enrollmentServerURI: %s", url); + + node = get_node(ctx->xml, getcert, "estUserID"); + if (node == NULL && !ctx->client_cert_present) { + wpa_printf(MSG_INFO, "Could not find estUserID node"); + goto fail; + } + if (node) { + user = xml_node_get_text(ctx->xml, node); + if (user == NULL) { + wpa_printf(MSG_INFO, "Could not get estUserID text"); + goto fail; + } + wpa_printf(MSG_INFO, "estUserID: %s", user); + write_summary(ctx, "estUserID: %s", user); + } + + node = get_node(ctx->xml, getcert, "estPassword"); + if (node == NULL && !ctx->client_cert_present) { + wpa_printf(MSG_INFO, "Could not find estPassword node"); + goto fail; + } + if (node) { + pw = xml_node_get_base64_text(ctx->xml, node, NULL); + if (pw == NULL) { + wpa_printf(MSG_INFO, "Could not get estPassword text"); + goto fail; + } + wpa_printf(MSG_INFO, "estPassword: %s", pw); + } + + mkdir("Cert", S_IRWXU); + android_update_permission("Cert", S_IRWXU | S_IRWXG); + + if (est_load_cacerts(ctx, url) < 0 || + est_build_csr(ctx, url) < 0 || + est_simple_enroll(ctx, url, user, pw) < 0) + goto fail; + + ret = 0; +fail: + xml_node_get_text_free(ctx->xml, url); + xml_node_get_text_free(ctx->xml, user); + xml_node_get_text_free(ctx->xml, pw); + + return ret; +} + + +static int process_est_cert(struct hs20_osu_client *ctx, xml_node_t *cert, + const char *fqdn) +{ + u8 digest1[SHA256_MAC_LEN], digest2[SHA256_MAC_LEN]; + char *der, *pem; + size_t der_len, pem_len; + char *fingerprint; + char buf[200]; + + wpa_printf(MSG_INFO, "PPS for certificate credential - fqdn=%s", fqdn); + + fingerprint = xml_node_get_text(ctx->xml, cert); + if (fingerprint == NULL) + return -1; + if (hexstr2bin(fingerprint, digest1, SHA256_MAC_LEN) < 0) { + wpa_printf(MSG_INFO, "Invalid SHA256 hash value"); + write_result(ctx, "Invalid client certificate SHA256 hash value in PPS"); + xml_node_get_text_free(ctx->xml, fingerprint); + return -1; + } + xml_node_get_text_free(ctx->xml, fingerprint); + + der = os_readfile("Cert/est_cert.der", &der_len); + if (der == NULL) { + wpa_printf(MSG_INFO, "Could not find client certificate from EST"); + write_result(ctx, "Could not find client certificate from EST"); + return -1; + } + + if (sha256_vector(1, (const u8 **) &der, &der_len, digest2) < 0) { + os_free(der); + return -1; + } + os_free(der); + + if (os_memcmp(digest1, digest2, sizeof(digest1)) != 0) { + wpa_printf(MSG_INFO, "Client certificate from EST does not match fingerprint from PPS MO"); + write_result(ctx, "Client certificate from EST does not match fingerprint from PPS MO"); + return -1; + } + + wpa_printf(MSG_INFO, "Client certificate from EST matches PPS MO"); + unlink("Cert/est_cert.der"); + + os_snprintf(buf, sizeof(buf), "SP/%s/client-ca.pem", fqdn); + if (rename("Cert/est-cacerts.pem", buf) < 0) { + wpa_printf(MSG_INFO, "Could not move est-cacerts.pem to client-ca.pem: %s", + strerror(errno)); + return -1; + } + pem = os_readfile(buf, &pem_len); + + os_snprintf(buf, sizeof(buf), "SP/%s/client-cert.pem", fqdn); + if (rename("Cert/est_cert.pem", buf) < 0) { + wpa_printf(MSG_INFO, "Could not move est_cert.pem to client-cert.pem: %s", + strerror(errno)); + os_free(pem); + return -1; + } + + if (pem) { + FILE *f = fopen(buf, "a"); + if (f) { + fwrite(pem, pem_len, 1, f); + fclose(f); + } + os_free(pem); + } + + os_snprintf(buf, sizeof(buf), "SP/%s/client-key.pem", fqdn); + if (rename("Cert/privkey-plain.pem", buf) < 0) { + wpa_printf(MSG_INFO, "Could not move privkey-plain.pem to client-key.pem: %s", + strerror(errno)); + return -1; + } + + unlink("Cert/est-req.b64"); + unlink("Cert/est-req.pem"); + rmdir("Cert"); + + return 0; +} + + +#define TMP_CERT_DL_FILE "tmp-cert-download" + +static int download_cert(struct hs20_osu_client *ctx, xml_node_t *params, + const char *fname) +{ + xml_node_t *url_node, *hash_node; + char *url, *hash; + char *cert; + size_t len; + u8 digest1[SHA256_MAC_LEN], digest2[SHA256_MAC_LEN]; + int res; + char *b64; + FILE *f; + + url_node = get_node(ctx->xml, params, "CertURL"); + hash_node = get_node(ctx->xml, params, "CertSHA256Fingerprint"); + if (url_node == NULL || hash_node == NULL) + return -1; + url = xml_node_get_text(ctx->xml, url_node); + hash = xml_node_get_text(ctx->xml, hash_node); + if (url == NULL || hash == NULL) { + xml_node_get_text_free(ctx->xml, url); + xml_node_get_text_free(ctx->xml, hash); + return -1; + } + + wpa_printf(MSG_INFO, "CertURL: %s", url); + wpa_printf(MSG_INFO, "SHA256 hash: %s", hash); + + if (hexstr2bin(hash, digest1, SHA256_MAC_LEN) < 0) { + wpa_printf(MSG_INFO, "Invalid SHA256 hash value"); + write_result(ctx, "Invalid SHA256 hash value for downloaded certificate"); + xml_node_get_text_free(ctx->xml, hash); + return -1; + } + xml_node_get_text_free(ctx->xml, hash); + + write_summary(ctx, "Download certificate from %s", url); + ctx->no_osu_cert_validation = 1; + http_ocsp_set(ctx->http, 1); + res = http_download_file(ctx->http, url, TMP_CERT_DL_FILE, NULL); + http_ocsp_set(ctx->http, + (ctx->workarounds & WORKAROUND_OCSP_OPTIONAL) ? 1 : 2); + ctx->no_osu_cert_validation = 0; + xml_node_get_text_free(ctx->xml, url); + if (res < 0) + return -1; + + cert = os_readfile(TMP_CERT_DL_FILE, &len); + remove(TMP_CERT_DL_FILE); + if (cert == NULL) + return -1; + + if (sha256_vector(1, (const u8 **) &cert, &len, digest2) < 0) { + os_free(cert); + return -1; + } + + if (os_memcmp(digest1, digest2, sizeof(digest1)) != 0) { + wpa_printf(MSG_INFO, "Downloaded certificate fingerprint did not match"); + write_result(ctx, "Downloaded certificate fingerprint did not match"); + os_free(cert); + return -1; + } + + b64 = base64_encode(cert, len, NULL); + os_free(cert); + if (b64 == NULL) + return -1; + + f = fopen(fname, "wb"); + if (f == NULL) { + os_free(b64); + return -1; + } + + fprintf(f, "-----BEGIN CERTIFICATE-----\n" + "%s" + "-----END CERTIFICATE-----\n", + b64); + + os_free(b64); + fclose(f); + + wpa_printf(MSG_INFO, "Downloaded certificate into %s and validated fingerprint", + fname); + write_summary(ctx, "Downloaded certificate into %s and validated fingerprint", + fname); + + return 0; +} + + +static int cmd_dl_osu_ca(struct hs20_osu_client *ctx, const char *pps_fname, + const char *ca_fname) +{ + xml_node_t *pps, *node; + int ret; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", pps_fname); + return -1; + } + + node = get_child_node(ctx->xml, pps, + "SubscriptionUpdate/TrustRoot"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No SubscriptionUpdate/TrustRoot/CertURL found from PPS"); + xml_node_free(ctx->xml, pps); + return -1; + } + + ret = download_cert(ctx, node, ca_fname); + xml_node_free(ctx->xml, pps); + + return ret; +} + + +static int cmd_dl_polupd_ca(struct hs20_osu_client *ctx, const char *pps_fname, + const char *ca_fname) +{ + xml_node_t *pps, *node; + int ret; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", pps_fname); + return -1; + } + + node = get_child_node(ctx->xml, pps, + "Policy/PolicyUpdate/TrustRoot"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Policy/PolicyUpdate/TrustRoot/CertURL found from PPS"); + xml_node_free(ctx->xml, pps); + return -2; + } + + ret = download_cert(ctx, node, ca_fname); + xml_node_free(ctx->xml, pps); + + return ret; +} + + +static int cmd_dl_aaa_ca(struct hs20_osu_client *ctx, const char *pps_fname, + const char *ca_fname) +{ + xml_node_t *pps, *node, *aaa; + int ret; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", pps_fname); + return -1; + } + + node = get_child_node(ctx->xml, pps, + "AAAServerTrustRoot"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No AAAServerTrustRoot/CertURL found from PPS"); + xml_node_free(ctx->xml, pps); + return -2; + } + + aaa = xml_node_first_child(ctx->xml, node); + if (aaa == NULL) { + wpa_printf(MSG_INFO, "No AAAServerTrustRoot/CertURL found from PPS"); + xml_node_free(ctx->xml, pps); + return -1; + } + + ret = download_cert(ctx, aaa, ca_fname); + xml_node_free(ctx->xml, pps); + + return ret; +} + + +static int download_trust_roots(struct hs20_osu_client *ctx, + const char *pps_fname) +{ + char *dir, *pos; + char fname[300]; + int ret, ret1; + + dir = os_strdup(pps_fname); + if (dir == NULL) + return -1; + pos = os_strrchr(dir, '/'); + if (pos == NULL) { + os_free(dir); + return -1; + } + *pos = '\0'; + + snprintf(fname, sizeof(fname), "%s/ca.pem", dir); + ret = cmd_dl_osu_ca(ctx, pps_fname, fname); + snprintf(fname, sizeof(fname), "%s/polupd-ca.pem", dir); + ret1 = cmd_dl_polupd_ca(ctx, pps_fname, fname); + if (ret == 0 && ret1 == -1) + ret = -1; + snprintf(fname, sizeof(fname), "%s/aaa-ca.pem", dir); + ret1 = cmd_dl_aaa_ca(ctx, pps_fname, fname); + if (ret == 0 && ret1 == -1) + ret = -1; + + os_free(dir); + + return ret; +} + + +static int server_dnsname_suffix_match(struct hs20_osu_client *ctx, + const char *fqdn) +{ + size_t match_len, len, i; + const char *val; + + match_len = os_strlen(fqdn); + + for (i = 0; i < ctx->server_dnsname_count; i++) { + wpa_printf(MSG_INFO, + "Checking suffix match against server dNSName %s", + ctx->server_dnsname[i]); + val = ctx->server_dnsname[i]; + len = os_strlen(val); + + if (match_len > len) + continue; + + if (os_strncasecmp(val + len - match_len, fqdn, match_len) != 0) + continue; /* no match */ + + if (match_len == len) + return 1; /* exact match */ + + if (val[len - match_len - 1] == '.') + return 1; /* full label match completes suffix match */ + + /* Reject due to incomplete label match */ + } + + /* None of the dNSName(s) matched */ + return 0; +} + + +int hs20_add_pps_mo(struct hs20_osu_client *ctx, const char *uri, + xml_node_t *add_mo, char *fname, size_t fname_len) +{ + char *str; + char *fqdn, *pos; + xml_node_t *tnds, *mo, *cert; + const char *name; + int ret; + + if (strncmp(uri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Unsupported location for addMO to add PPS MO: '%s'", + uri); + write_result(ctx, "Unsupported location for addMO to add PPS MO: '%s'", + uri); + return -1; + } + + fqdn = strdup(uri + 8); + if (fqdn == NULL) + return -1; + pos = strchr(fqdn, '/'); + if (pos) { + if (os_strcasecmp(pos, "/PerProviderSubscription") != 0) { + wpa_printf(MSG_INFO, "Unsupported location for addMO to add PPS MO (extra directory): '%s'", + uri); + write_result(ctx, "Unsupported location for addMO to " + "add PPS MO (extra directory): '%s'", uri); + free(fqdn); + return -1; + } + *pos = '\0'; /* remove trailing slash and PPS node name */ + } + wpa_printf(MSG_INFO, "SP FQDN: %s", fqdn); + + if (!server_dnsname_suffix_match(ctx, fqdn)) { + wpa_printf(MSG_INFO, + "FQDN '%s' for new PPS MO did not have suffix match with server's dNSName values, count: %d", + fqdn, (int) ctx->server_dnsname_count); + write_result(ctx, "FQDN '%s' for new PPS MO did not have suffix match with server's dNSName values", + fqdn); + free(fqdn); + return -1; + } + + if (!valid_fqdn(fqdn)) { + wpa_printf(MSG_INFO, "Invalid FQDN '%s'", fqdn); + write_result(ctx, "Invalid FQDN '%s'", fqdn); + free(fqdn); + return -1; + } + + mkdir("SP", S_IRWXU); + snprintf(fname, fname_len, "SP/%s", fqdn); + if (mkdir(fname, S_IRWXU) < 0) { + if (errno != EEXIST) { + int err = errno; + wpa_printf(MSG_INFO, "mkdir(%s) failed: %s", + fname, strerror(err)); + free(fqdn); + return -1; + } + } + + android_update_permission("SP", S_IRWXU | S_IRWXG); + android_update_permission(fname, S_IRWXU | S_IRWXG); + + snprintf(fname, fname_len, "SP/%s/pps.xml", fqdn); + + if (os_file_exists(fname)) { + wpa_printf(MSG_INFO, "PPS file '%s' exists - reject addMO", + fname); + write_result(ctx, "PPS file '%s' exists - reject addMO", + fname); + free(fqdn); + return -2; + } + wpa_printf(MSG_INFO, "Using PPS file: %s", fname); + + str = xml_node_get_text(ctx->xml, add_mo); + if (str == NULL) { + wpa_printf(MSG_INFO, "Could not extract MO text"); + free(fqdn); + return -1; + } + wpa_printf(MSG_DEBUG, "[hs20] addMO text: '%s'", str); + + tnds = xml_node_from_buf(ctx->xml, str); + xml_node_get_text_free(ctx->xml, str); + if (tnds == NULL) { + wpa_printf(MSG_INFO, "[hs20] Could not parse addMO text"); + free(fqdn); + return -1; + } + + mo = tnds_to_mo(ctx->xml, tnds); + if (mo == NULL) { + wpa_printf(MSG_INFO, "[hs20] Could not parse addMO TNDS text"); + free(fqdn); + return -1; + } + + debug_dump_node(ctx, "Parsed TNDS", mo); + + name = xml_node_get_localname(ctx->xml, mo); + if (os_strcasecmp(name, "PerProviderSubscription") != 0) { + wpa_printf(MSG_INFO, "[hs20] Unexpected PPS MO root node name '%s'", + name); + free(fqdn); + return -1; + } + + cert = get_child_node(ctx->xml, mo, + "Credential/DigitalCertificate/" + "CertSHA256Fingerprint"); + if (cert && process_est_cert(ctx, cert, fqdn) < 0) { + xml_node_free(ctx->xml, mo); + free(fqdn); + return -1; + } + free(fqdn); + + if (node_to_file(ctx->xml, fname, mo) < 0) { + wpa_printf(MSG_INFO, "Could not write MO to file"); + xml_node_free(ctx->xml, mo); + return -1; + } + xml_node_free(ctx->xml, mo); + + wpa_printf(MSG_INFO, "A new PPS MO added as '%s'", fname); + write_summary(ctx, "A new PPS MO added as '%s'", fname); + + ret = download_trust_roots(ctx, fname); + if (ret < 0) { + wpa_printf(MSG_INFO, "Remove invalid PPS MO file"); + write_summary(ctx, "Remove invalid PPS MO file"); + unlink(fname); + } + + return ret; +} + + +int update_pps_file(struct hs20_osu_client *ctx, const char *pps_fname, + xml_node_t *pps) +{ + char *str; + FILE *f; + char backup[300]; + + if (ctx->client_cert_present) { + xml_node_t *cert; + cert = get_child_node(ctx->xml, pps, + "Credential/DigitalCertificate/" + "CertSHA256Fingerprint"); + if (cert && os_file_exists("Cert/est_cert.der") && + process_est_cert(ctx, cert, ctx->fqdn) < 0) { + wpa_printf(MSG_INFO, "EST certificate update processing failed on PPS MO update"); + return -1; + } + } + + wpa_printf(MSG_INFO, "Updating PPS MO %s", pps_fname); + + str = xml_node_to_str(ctx->xml, pps); + if (str == NULL) { + wpa_printf(MSG_ERROR, "No node found"); + return -1; + } + wpa_printf(MSG_MSGDUMP, "[hs20] Updated PPS: '%s'", str); + + snprintf(backup, sizeof(backup), "%s.bak", pps_fname); + rename(pps_fname, backup); + f = fopen(pps_fname, "w"); + if (f == NULL) { + wpa_printf(MSG_INFO, "Could not write PPS"); + rename(backup, pps_fname); + free(str); + return -1; + } + fprintf(f, "%s\n", str); + fclose(f); + + free(str); + + return 0; +} + + +void get_user_pw(struct hs20_osu_client *ctx, xml_node_t *pps, + const char *alt_loc, char **user, char **pw) +{ + xml_node_t *node; + + node = get_child_node(ctx->xml, pps, + "Credential/UsernamePassword/Username"); + if (node) + *user = xml_node_get_text(ctx->xml, node); + + node = get_child_node(ctx->xml, pps, + "Credential/UsernamePassword/Password"); + if (node) + *pw = xml_node_get_base64_text(ctx->xml, node, NULL); + + node = get_child_node(ctx->xml, pps, alt_loc); + if (node) { + xml_node_t *a; + a = get_node(ctx->xml, node, "Username"); + if (a) { + xml_node_get_text_free(ctx->xml, *user); + *user = xml_node_get_text(ctx->xml, a); + wpa_printf(MSG_INFO, "Use OSU username '%s'", *user); + } + + a = get_node(ctx->xml, node, "Password"); + if (a) { + free(*pw); + *pw = xml_node_get_base64_text(ctx->xml, a, NULL); + wpa_printf(MSG_INFO, "Use OSU password"); + } + } +} + + +/* Remove old credentials based on HomeSP/FQDN */ +static void remove_sp_creds(struct hs20_osu_client *ctx, const char *fqdn) +{ + char cmd[300]; + os_snprintf(cmd, sizeof(cmd), "REMOVE_CRED provisioning_sp=%s", fqdn); + if (wpa_command(ctx->ifname, cmd) < 0) + wpa_printf(MSG_INFO, "Failed to remove old credential(s)"); +} + + +static void set_pps_cred_policy_spe(struct hs20_osu_client *ctx, int id, + xml_node_t *spe) +{ + xml_node_t *ssid; + char *txt; + + ssid = get_node(ctx->xml, spe, "SSID"); + if (ssid == NULL) + return; + txt = xml_node_get_text(ctx->xml, ssid); + if (txt == NULL) + return; + wpa_printf(MSG_DEBUG, "- Policy/SPExclusionList//SSID = %s", txt); + if (set_cred_quoted(ctx->ifname, id, "excluded_ssid", txt) < 0) + wpa_printf(MSG_INFO, "Failed to set cred excluded_ssid"); + xml_node_get_text_free(ctx->xml, txt); +} + + +static void set_pps_cred_policy_spel(struct hs20_osu_client *ctx, int id, + xml_node_t *spel) +{ + xml_node_t *child; + + xml_node_for_each_child(ctx->xml, child, spel) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_policy_spe(ctx, id, child); + } +} + + +static void set_pps_cred_policy_prp(struct hs20_osu_client *ctx, int id, + xml_node_t *prp) +{ + xml_node_t *node; + char *txt = NULL, *pos; + char *prio, *country_buf = NULL; + const char *country; + char val[200]; + int priority; + + node = get_node(ctx->xml, prp, "Priority"); + if (node == NULL) + return; + prio = xml_node_get_text(ctx->xml, node); + if (prio == NULL) + return; + wpa_printf(MSG_INFO, "- Policy/PreferredRoamingPartnerList//Priority = %s", + prio); + priority = atoi(prio); + xml_node_get_text_free(ctx->xml, prio); + + node = get_node(ctx->xml, prp, "Country"); + if (node) { + country_buf = xml_node_get_text(ctx->xml, node); + if (country_buf == NULL) + return; + country = country_buf; + wpa_printf(MSG_INFO, "- Policy/PreferredRoamingPartnerList//Country = %s", + country); + } else { + country = "*"; + } + + node = get_node(ctx->xml, prp, "FQDN_Match"); + if (node == NULL) + goto out; + txt = xml_node_get_text(ctx->xml, node); + if (txt == NULL) + goto out; + wpa_printf(MSG_INFO, "- Policy/PreferredRoamingPartnerList//FQDN_Match = %s", + txt); + pos = strrchr(txt, ','); + if (pos == NULL) + goto out; + *pos++ = '\0'; + + snprintf(val, sizeof(val), "%s,%d,%d,%s", txt, + strcmp(pos, "includeSubdomains") != 0, priority, country); + if (set_cred_quoted(ctx->ifname, id, "roaming_partner", val) < 0) + wpa_printf(MSG_INFO, "Failed to set cred roaming_partner"); +out: + xml_node_get_text_free(ctx->xml, country_buf); + xml_node_get_text_free(ctx->xml, txt); +} + + +static void set_pps_cred_policy_prpl(struct hs20_osu_client *ctx, int id, + xml_node_t *prpl) +{ + xml_node_t *child; + + xml_node_for_each_child(ctx->xml, child, prpl) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_policy_prp(ctx, id, child); + } +} + + +static void set_pps_cred_policy_min_backhaul(struct hs20_osu_client *ctx, int id, + xml_node_t *min_backhaul) +{ + xml_node_t *node; + char *type, *dl = NULL, *ul = NULL; + int home; + + node = get_node(ctx->xml, min_backhaul, "NetworkType"); + if (node == NULL) { + wpa_printf(MSG_INFO, "Ignore MinBackhaulThreshold without mandatory NetworkType node"); + return; + } + + type = xml_node_get_text(ctx->xml, node); + if (type == NULL) + return; + wpa_printf(MSG_INFO, "- Policy/MinBackhaulThreshold//NetworkType = %s", + type); + if (os_strcasecmp(type, "home") == 0) + home = 1; + else if (os_strcasecmp(type, "roaming") == 0) + home = 0; + else { + wpa_printf(MSG_INFO, "Ignore MinBackhaulThreshold with invalid NetworkType"); + xml_node_get_text_free(ctx->xml, type); + return; + } + xml_node_get_text_free(ctx->xml, type); + + node = get_node(ctx->xml, min_backhaul, "DLBandwidth"); + if (node) + dl = xml_node_get_text(ctx->xml, node); + + node = get_node(ctx->xml, min_backhaul, "ULBandwidth"); + if (node) + ul = xml_node_get_text(ctx->xml, node); + + if (dl == NULL && ul == NULL) { + wpa_printf(MSG_INFO, "Ignore MinBackhaulThreshold without either DLBandwidth or ULBandwidth nodes"); + return; + } + + if (dl) + wpa_printf(MSG_INFO, "- Policy/MinBackhaulThreshold//DLBandwidth = %s", + dl); + if (ul) + wpa_printf(MSG_INFO, "- Policy/MinBackhaulThreshold//ULBandwidth = %s", + ul); + + if (home) { + if (dl && + set_cred(ctx->ifname, id, "min_dl_bandwidth_home", dl) < 0) + wpa_printf(MSG_INFO, "Failed to set cred bandwidth limit"); + if (ul && + set_cred(ctx->ifname, id, "min_ul_bandwidth_home", ul) < 0) + wpa_printf(MSG_INFO, "Failed to set cred bandwidth limit"); + } else { + if (dl && + set_cred(ctx->ifname, id, "min_dl_bandwidth_roaming", dl) < + 0) + wpa_printf(MSG_INFO, "Failed to set cred bandwidth limit"); + if (ul && + set_cred(ctx->ifname, id, "min_ul_bandwidth_roaming", ul) < + 0) + wpa_printf(MSG_INFO, "Failed to set cred bandwidth limit"); + } + + xml_node_get_text_free(ctx->xml, dl); + xml_node_get_text_free(ctx->xml, ul); +} + + +static void set_pps_cred_policy_min_backhaul_list(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + xml_node_t *child; + + wpa_printf(MSG_INFO, "- Policy/MinBackhaulThreshold"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_policy_min_backhaul(ctx, id, child); + } +} + + +static void set_pps_cred_policy_update(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + wpa_printf(MSG_INFO, "- Policy/PolicyUpdate"); + /* Not used in wpa_supplicant */ +} + + +static void set_pps_cred_policy_required_proto_port(struct hs20_osu_client *ctx, + int id, xml_node_t *tuple) +{ + xml_node_t *node; + char *proto, *port; + char *buf; + size_t buflen; + + node = get_node(ctx->xml, tuple, "IPProtocol"); + if (node == NULL) { + wpa_printf(MSG_INFO, "Ignore RequiredProtoPortTuple without mandatory IPProtocol node"); + return; + } + + proto = xml_node_get_text(ctx->xml, node); + if (proto == NULL) + return; + + wpa_printf(MSG_INFO, "- Policy/RequiredProtoPortTuple//IPProtocol = %s", + proto); + + node = get_node(ctx->xml, tuple, "PortNumber"); + port = node ? xml_node_get_text(ctx->xml, node) : NULL; + if (port) { + wpa_printf(MSG_INFO, "- Policy/RequiredProtoPortTuple//PortNumber = %s", + port); + buflen = os_strlen(proto) + os_strlen(port) + 10; + buf = os_malloc(buflen); + if (buf) + os_snprintf(buf, buflen, "%s:%s", proto, port); + xml_node_get_text_free(ctx->xml, port); + } else { + buflen = os_strlen(proto) + 10; + buf = os_malloc(buflen); + if (buf) + os_snprintf(buf, buflen, "%s", proto); + } + + xml_node_get_text_free(ctx->xml, proto); + + if (buf == NULL) + return; + + if (set_cred(ctx->ifname, id, "req_conn_capab", buf) < 0) + wpa_printf(MSG_INFO, "Could not set req_conn_capab"); + + os_free(buf); +} + + +static void set_pps_cred_policy_required_proto_ports(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + xml_node_t *child; + + wpa_printf(MSG_INFO, "- Policy/RequiredProtoPortTuple"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_policy_required_proto_port(ctx, id, child); + } +} + + +static void set_pps_cred_policy_max_bss_load(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Policy/MaximumBSSLoadValue - %s", str); + if (set_cred(ctx->ifname, id, "max_bss_load", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred max_bss_load limit"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_policy(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + const char *name; + + wpa_printf(MSG_INFO, "- Policy"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "PreferredRoamingPartnerList") == 0) + set_pps_cred_policy_prpl(ctx, id, child); + else if (os_strcasecmp(name, "MinBackhaulThreshold") == 0) + set_pps_cred_policy_min_backhaul_list(ctx, id, child); + else if (os_strcasecmp(name, "PolicyUpdate") == 0) + set_pps_cred_policy_update(ctx, id, child); + else if (os_strcasecmp(name, "SPExclusionList") == 0) + set_pps_cred_policy_spel(ctx, id, child); + else if (os_strcasecmp(name, "RequiredProtoPortTuple") == 0) + set_pps_cred_policy_required_proto_ports(ctx, id, child); + else if (os_strcasecmp(name, "MaximumBSSLoadValue") == 0) + set_pps_cred_policy_max_bss_load(ctx, id, child); + else + wpa_printf(MSG_INFO, "Unknown Policy node '%s'", name); + } +} + + +static void set_pps_cred_priority(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- CredentialPriority = %s", str); + if (set_cred(ctx->ifname, id, "sp_priority", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred sp_priority"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_aaa_server_trust_root(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + wpa_printf(MSG_INFO, "- AAAServerTrustRoot - TODO"); +} + + +static void set_pps_cred_sub_update(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + wpa_printf(MSG_INFO, "- SubscriptionUpdate"); + /* not used within wpa_supplicant */ +} + + +static void set_pps_cred_home_sp_network_id(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + xml_node_t *ssid_node, *hessid_node; + char *ssid, *hessid; + + ssid_node = get_node(ctx->xml, node, "SSID"); + if (ssid_node == NULL) { + wpa_printf(MSG_INFO, "Ignore HomeSP/NetworkID without mandatory SSID node"); + return; + } + + hessid_node = get_node(ctx->xml, node, "HESSID"); + + ssid = xml_node_get_text(ctx->xml, ssid_node); + if (ssid == NULL) + return; + hessid = hessid_node ? xml_node_get_text(ctx->xml, hessid_node) : NULL; + + wpa_printf(MSG_INFO, "- HomeSP/NetworkID//SSID = %s", ssid); + if (hessid) + wpa_printf(MSG_INFO, "- HomeSP/NetworkID//HESSID = %s", + hessid); + + /* TODO: Configure to wpa_supplicant */ + + xml_node_get_text_free(ctx->xml, ssid); + xml_node_get_text_free(ctx->xml, hessid); +} + + +static void set_pps_cred_home_sp_network_ids(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + xml_node_t *child; + + wpa_printf(MSG_INFO, "- HomeSP/NetworkID"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_home_sp_network_id(ctx, id, child); + } +} + + +static void set_pps_cred_home_sp_friendly_name(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- HomeSP/FriendlyName = %s", str); + /* not used within wpa_supplicant(?) */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_home_sp_icon_url(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- HomeSP/IconURL = %s", str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_home_sp_fqdn(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- HomeSP/FQDN = %s", str); + if (set_cred_quoted(ctx->ifname, id, "domain", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred domain"); + if (set_cred_quoted(ctx->ifname, id, "domain_suffix_match", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred domain_suffix_match"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_home_sp_oi(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + const char *name; + char *homeoi = NULL; + int required = 0; + char *str; + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (strcasecmp(name, "HomeOI") == 0 && !homeoi) { + homeoi = xml_node_get_text(ctx->xml, child); + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList//HomeOI = %s", + homeoi); + } else if (strcasecmp(name, "HomeOIRequired") == 0) { + str = xml_node_get_text(ctx->xml, child); + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList//HomeOIRequired = '%s'", + str); + if (str == NULL) + continue; + required = strcasecmp(str, "true") == 0; + xml_node_get_text_free(ctx->xml, str); + } else + wpa_printf(MSG_INFO, "Unknown HomeOIList node '%s'", + name); + } + + if (homeoi == NULL) { + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList/ without HomeOI ignored"); + return; + } + + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList/ '%s' required=%d", + homeoi, required); + + if (required) { + if (set_cred_quoted(ctx->ifname, id, "required_home_ois", + homeoi) < 0) + wpa_printf(MSG_INFO, + "Failed to set cred required_home_ois"); + } else { + if (set_cred_quoted(ctx->ifname, id, "home_ois", homeoi) < 0) + wpa_printf(MSG_INFO, "Failed to set cred home_ois"); + } + + xml_node_get_text_free(ctx->xml, homeoi); +} + + +static void set_pps_cred_home_sp_oi_list(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_home_sp_oi(ctx, id, child); + } +} + + +static void set_pps_cred_home_sp_other_partner(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + xml_node_t *child; + const char *name; + char *fqdn = NULL; + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "FQDN") == 0 && !fqdn) { + fqdn = xml_node_get_text(ctx->xml, child); + wpa_printf(MSG_INFO, "- HomeSP/OtherHomePartners//FQDN = %s", + fqdn); + } else + wpa_printf(MSG_INFO, "Unknown OtherHomePartners node '%s'", + name); + } + + if (fqdn == NULL) { + wpa_printf(MSG_INFO, "- HomeSP/OtherHomePartners/ without FQDN ignored"); + return; + } + + if (set_cred_quoted(ctx->ifname, id, "domain", fqdn) < 0) + wpa_printf(MSG_INFO, "Failed to set cred domain for OtherHomePartners node"); + + xml_node_get_text_free(ctx->xml, fqdn); +} + + +static void set_pps_cred_home_sp_other_partners(struct hs20_osu_client *ctx, + int id, + xml_node_t *node) +{ + xml_node_t *child; + + wpa_printf(MSG_INFO, "- HomeSP/OtherHomePartners"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_home_sp_other_partner(ctx, id, child); + } +} + + +static void set_pps_cred_home_sp_roaming_consortium_oi( + struct hs20_osu_client *ctx, int id, xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- HomeSP/RoamingConsortiumOI = %s", str); + if (set_cred_quoted(ctx->ifname, id, "roaming_consortiums", + str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred roaming_consortiums"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_home_sp(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + const char *name; + + wpa_printf(MSG_INFO, "- HomeSP"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "NetworkID") == 0) + set_pps_cred_home_sp_network_ids(ctx, id, child); + else if (os_strcasecmp(name, "FriendlyName") == 0) + set_pps_cred_home_sp_friendly_name(ctx, id, child); + else if (os_strcasecmp(name, "IconURL") == 0) + set_pps_cred_home_sp_icon_url(ctx, id, child); + else if (os_strcasecmp(name, "FQDN") == 0) + set_pps_cred_home_sp_fqdn(ctx, id, child); + else if (os_strcasecmp(name, "HomeOIList") == 0) + set_pps_cred_home_sp_oi_list(ctx, id, child); + else if (os_strcasecmp(name, "OtherHomePartners") == 0) + set_pps_cred_home_sp_other_partners(ctx, id, child); + else if (os_strcasecmp(name, "RoamingConsortiumOI") == 0) + set_pps_cred_home_sp_roaming_consortium_oi(ctx, id, + child); + else + wpa_printf(MSG_INFO, "Unknown HomeSP node '%s'", name); + } +} + + +static void set_pps_cred_sub_params(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + wpa_printf(MSG_INFO, "- SubscriptionParameters"); + /* not used within wpa_supplicant */ +} + + +static void set_pps_cred_creation_date(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/CreationDate = %s", str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_expiration_date(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/ExpirationDate = %s", str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_username(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/Username = %s", + str); + if (set_cred_quoted(ctx->ifname, id, "username", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred username"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_password(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + int len, i; + char *pw, *hex, *pos, *end; + + pw = xml_node_get_base64_text(ctx->xml, node, &len); + if (pw == NULL) + return; + + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/Password = %s", pw); + + hex = malloc(len * 2 + 1); + if (hex == NULL) { + free(pw); + return; + } + end = hex + len * 2 + 1; + pos = hex; + for (i = 0; i < len; i++) { + snprintf(pos, end - pos, "%02x", pw[i]); + pos += 2; + } + free(pw); + + if (set_cred(ctx->ifname, id, "password", hex) < 0) + wpa_printf(MSG_INFO, "Failed to set cred password"); + free(hex); +} + + +static void set_pps_cred_machine_managed(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/MachineManaged = %s", + str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_soft_token_app(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/SoftTokenApp = %s", + str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_able_to_share(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/AbleToShare = %s", + str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_eap_method_eap_type(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + int type; + const char *eap_method = NULL; + + if (!str) + return; + wpa_printf(MSG_INFO, + "- Credential/UsernamePassword/EAPMethod/EAPType = %s", str); + type = atoi(str); + switch (type) { + case EAP_TYPE_TLS: + eap_method = "TLS"; + break; + case EAP_TYPE_TTLS: + eap_method = "TTLS"; + break; + case EAP_TYPE_PEAP: + eap_method = "PEAP"; + break; + case EAP_TYPE_PWD: + eap_method = "PWD"; + break; + } + xml_node_get_text_free(ctx->xml, str); + if (!eap_method) { + wpa_printf(MSG_INFO, "Unknown EAPType value"); + return; + } + + if (set_cred(ctx->ifname, id, "eap", eap_method) < 0) + wpa_printf(MSG_INFO, "Failed to set cred eap"); +} + + +static void set_pps_cred_eap_method_inner_method(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + const char *phase2 = NULL; + + if (!str) + return; + wpa_printf(MSG_INFO, + "- Credential/UsernamePassword/EAPMethod/InnerMethod = %s", + str); + if (os_strcmp(str, "PAP") == 0) + phase2 = "auth=PAP"; + else if (os_strcmp(str, "CHAP") == 0) + phase2 = "auth=CHAP"; + else if (os_strcmp(str, "MS-CHAP") == 0) + phase2 = "auth=MSCHAP"; + else if (os_strcmp(str, "MS-CHAP-V2") == 0) + phase2 = "auth=MSCHAPV2"; + xml_node_get_text_free(ctx->xml, str); + if (!phase2) { + wpa_printf(MSG_INFO, "Unknown InnerMethod value"); + return; + } + + if (set_cred_quoted(ctx->ifname, id, "phase2", phase2) < 0) + wpa_printf(MSG_INFO, "Failed to set cred phase2"); +} + + +static void set_pps_cred_eap_method(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + const char *name; + + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/EAPMethod"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "EAPType") == 0) + set_pps_cred_eap_method_eap_type(ctx, id, child); + else if (os_strcasecmp(name, "InnerMethod") == 0) + set_pps_cred_eap_method_inner_method(ctx, id, child); + else + wpa_printf(MSG_INFO, "Unknown Credential/UsernamePassword/EAPMethod node '%s'", + name); + } +} + + +static void set_pps_cred_username_password(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + const char *name; + + wpa_printf(MSG_INFO, "- Credential/UsernamePassword"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "Username") == 0) + set_pps_cred_username(ctx, id, child); + else if (os_strcasecmp(name, "Password") == 0) + set_pps_cred_password(ctx, id, child); + else if (os_strcasecmp(name, "MachineManaged") == 0) + set_pps_cred_machine_managed(ctx, id, child); + else if (os_strcasecmp(name, "SoftTokenApp") == 0) + set_pps_cred_soft_token_app(ctx, id, child); + else if (os_strcasecmp(name, "AbleToShare") == 0) + set_pps_cred_able_to_share(ctx, id, child); + else if (os_strcasecmp(name, "EAPMethod") == 0) + set_pps_cred_eap_method(ctx, id, child); + else + wpa_printf(MSG_INFO, "Unknown Credential/UsernamePassword node '%s'", + name); + } +} + + +static void set_pps_cred_digital_cert(struct hs20_osu_client *ctx, int id, + xml_node_t *node, const char *fqdn) +{ + char buf[200], dir[200]; + int res; + + wpa_printf(MSG_INFO, "- Credential/DigitalCertificate"); + + if (getcwd(dir, sizeof(dir)) == NULL) + return; + + /* TODO: could build username from Subject of Subject AltName */ + if (set_cred_quoted(ctx->ifname, id, "username", "cert") < 0) { + wpa_printf(MSG_INFO, "Failed to set username"); + } + + res = os_snprintf(buf, sizeof(buf), "%s/SP/%s/client-cert.pem", dir, + fqdn); + if (os_snprintf_error(sizeof(buf), res)) + return; + if (os_file_exists(buf)) { + if (set_cred_quoted(ctx->ifname, id, "client_cert", buf) < 0) { + wpa_printf(MSG_INFO, "Failed to set client_cert"); + } + } + + res = os_snprintf(buf, sizeof(buf), "%s/SP/%s/client-key.pem", dir, + fqdn); + if (os_snprintf_error(sizeof(buf), res)) + return; + if (os_file_exists(buf)) { + if (set_cred_quoted(ctx->ifname, id, "private_key", buf) < 0) { + wpa_printf(MSG_INFO, "Failed to set private_key"); + } + } +} + + +static void set_pps_cred_realm(struct hs20_osu_client *ctx, int id, + xml_node_t *node, const char *fqdn, int sim) +{ + char *str = xml_node_get_text(ctx->xml, node); + char buf[200], dir[200]; + int res; + + if (str == NULL) + return; + + wpa_printf(MSG_INFO, "- Credential/Realm = %s", str); + if (set_cred_quoted(ctx->ifname, id, "realm", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred realm"); + xml_node_get_text_free(ctx->xml, str); + + if (sim) + return; + + if (getcwd(dir, sizeof(dir)) == NULL) + return; + res = os_snprintf(buf, sizeof(buf), "%s/SP/%s/aaa-ca.pem", dir, fqdn); + if (os_snprintf_error(sizeof(buf), res)) + return; + if (os_file_exists(buf)) { + if (set_cred_quoted(ctx->ifname, id, "ca_cert", buf) < 0) { + wpa_printf(MSG_INFO, "Failed to set CA cert"); + } + } +} + + +static void set_pps_cred_check_aaa_cert_status(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + + if (str == NULL) + return; + + wpa_printf(MSG_INFO, "- Credential/CheckAAAServerCertStatus = %s", str); + if (os_strcasecmp(str, "true") == 0 && + set_cred(ctx->ifname, id, "ocsp", "2") < 0) + wpa_printf(MSG_INFO, "Failed to set cred ocsp"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_sim(struct hs20_osu_client *ctx, int id, + xml_node_t *sim, xml_node_t *realm) +{ + xml_node_t *node; + char *imsi, *eaptype, *str, buf[20]; + int type; + int mnc_len = 3; + size_t imsi_len; + + node = get_node(ctx->xml, sim, "EAPType"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No SIM/EAPType node in credential"); + return; + } + eaptype = xml_node_get_text(ctx->xml, node); + if (eaptype == NULL) { + wpa_printf(MSG_INFO, "Could not extract SIM/EAPType"); + return; + } + wpa_printf(MSG_INFO, " - Credential/SIM/EAPType = %s", eaptype); + type = atoi(eaptype); + xml_node_get_text_free(ctx->xml, eaptype); + + switch (type) { + case EAP_TYPE_SIM: + if (set_cred(ctx->ifname, id, "eap", "SIM") < 0) + wpa_printf(MSG_INFO, "Could not set eap=SIM"); + break; + case EAP_TYPE_AKA: + if (set_cred(ctx->ifname, id, "eap", "AKA") < 0) + wpa_printf(MSG_INFO, "Could not set eap=SIM"); + break; + case EAP_TYPE_AKA_PRIME: + if (set_cred(ctx->ifname, id, "eap", "AKA'") < 0) + wpa_printf(MSG_INFO, "Could not set eap=SIM"); + break; + default: + wpa_printf(MSG_INFO, "Unsupported SIM/EAPType %d", type); + return; + } + + node = get_node(ctx->xml, sim, "IMSI"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No SIM/IMSI node in credential"); + return; + } + imsi = xml_node_get_text(ctx->xml, node); + if (imsi == NULL) { + wpa_printf(MSG_INFO, "Could not extract SIM/IMSI"); + return; + } + wpa_printf(MSG_INFO, " - Credential/SIM/IMSI = %s", imsi); + imsi_len = os_strlen(imsi); + if (imsi_len < 7 || imsi_len + 2 > sizeof(buf)) { + wpa_printf(MSG_INFO, "Invalid IMSI length"); + xml_node_get_text_free(ctx->xml, imsi); + return; + } + + str = xml_node_get_text(ctx->xml, node); + if (str) { + char *pos; + pos = os_strstr(str, "mnc"); + if (pos && os_strlen(pos) >= 6) { + if (os_strncmp(imsi + 3, pos + 3, 3) == 0) + mnc_len = 3; + else if (os_strncmp(imsi + 3, pos + 4, 2) == 0) + mnc_len = 2; + } + xml_node_get_text_free(ctx->xml, str); + } + + os_memcpy(buf, imsi, 3 + mnc_len); + buf[3 + mnc_len] = '-'; + os_strlcpy(buf + 3 + mnc_len + 1, imsi + 3 + mnc_len, + sizeof(buf) - 3 - mnc_len - 1); + + xml_node_get_text_free(ctx->xml, imsi); + + if (set_cred_quoted(ctx->ifname, id, "imsi", buf) < 0) + wpa_printf(MSG_INFO, "Could not set IMSI"); + + if (set_cred_quoted(ctx->ifname, id, "milenage", + "90dca4eda45b53cf0f12d7c9c3bc6a89:" + "cb9cccc4b9258e6dca4760379fb82581:000000000123") < + 0) + wpa_printf(MSG_INFO, "Could not set Milenage parameters"); +} + + +static void set_pps_cred_credential(struct hs20_osu_client *ctx, int id, + xml_node_t *node, const char *fqdn) +{ + xml_node_t *child, *sim, *realm; + const char *name; + + wpa_printf(MSG_INFO, "- Credential"); + + sim = get_node(ctx->xml, node, "SIM"); + realm = get_node(ctx->xml, node, "Realm"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "CreationDate") == 0) + set_pps_cred_creation_date(ctx, id, child); + else if (os_strcasecmp(name, "ExpirationDate") == 0) + set_pps_cred_expiration_date(ctx, id, child); + else if (os_strcasecmp(name, "UsernamePassword") == 0) + set_pps_cred_username_password(ctx, id, child); + else if (os_strcasecmp(name, "DigitalCertificate") == 0) + set_pps_cred_digital_cert(ctx, id, child, fqdn); + else if (os_strcasecmp(name, "Realm") == 0) + set_pps_cred_realm(ctx, id, child, fqdn, sim != NULL); + else if (os_strcasecmp(name, "CheckAAAServerCertStatus") == 0) + set_pps_cred_check_aaa_cert_status(ctx, id, child); + else if (os_strcasecmp(name, "SIM") == 0) + set_pps_cred_sim(ctx, id, child, realm); + else + wpa_printf(MSG_INFO, "Unknown Credential node '%s'", + name); + } +} + + +static void set_pps_credential(struct hs20_osu_client *ctx, int id, + xml_node_t *cred, const char *fqdn) +{ + xml_node_t *child; + const char *name; + + xml_node_for_each_child(ctx->xml, child, cred) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "Policy") == 0) + set_pps_cred_policy(ctx, id, child); + else if (os_strcasecmp(name, "CredentialPriority") == 0) + set_pps_cred_priority(ctx, id, child); + else if (os_strcasecmp(name, "AAAServerTrustRoot") == 0) + set_pps_cred_aaa_server_trust_root(ctx, id, child); + else if (os_strcasecmp(name, "SubscriptionUpdate") == 0) + set_pps_cred_sub_update(ctx, id, child); + else if (os_strcasecmp(name, "HomeSP") == 0) + set_pps_cred_home_sp(ctx, id, child); + else if (os_strcasecmp(name, "SubscriptionParameters") == 0) + set_pps_cred_sub_params(ctx, id, child); + else if (os_strcasecmp(name, "Credential") == 0) + set_pps_cred_credential(ctx, id, child, fqdn); + else + wpa_printf(MSG_INFO, "Unknown credential node '%s'", + name); + } +} + + +static void set_pps(struct hs20_osu_client *ctx, xml_node_t *pps, + const char *fqdn) +{ + xml_node_t *child; + const char *name; + int id; + char *update_identifier = NULL; + + /* + * TODO: Could consider more complex mechanism that would remove + * credentials only if there are changes in the information sent to + * wpa_supplicant. + */ + remove_sp_creds(ctx, fqdn); + + xml_node_for_each_child(ctx->xml, child, pps) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "UpdateIdentifier") == 0) { + update_identifier = xml_node_get_text(ctx->xml, child); + if (update_identifier) { + wpa_printf(MSG_INFO, "- UpdateIdentifier = %s", + update_identifier); + break; + } + } + } + + xml_node_for_each_child(ctx->xml, child, pps) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "UpdateIdentifier") == 0) + continue; + id = add_cred(ctx->ifname); + if (id < 0) { + wpa_printf(MSG_INFO, "Failed to add credential to wpa_supplicant"); + write_summary(ctx, "Failed to add credential to wpa_supplicant"); + break; + } + write_summary(ctx, "Add a credential to wpa_supplicant"); + if (update_identifier && + set_cred(ctx->ifname, id, "update_identifier", + update_identifier) < 0) + wpa_printf(MSG_INFO, "Failed to set update_identifier"); + if (set_cred_quoted(ctx->ifname, id, "provisioning_sp", fqdn) < + 0) + wpa_printf(MSG_INFO, "Failed to set provisioning_sp"); + wpa_printf(MSG_INFO, "credential localname: '%s'", name); + set_pps_credential(ctx, id, child, fqdn); + ctx->pps_cred_set = 1; + } + + xml_node_get_text_free(ctx->xml, update_identifier); +} + + +void cmd_set_pps(struct hs20_osu_client *ctx, const char *pps_fname) +{ + xml_node_t *pps; + const char *fqdn; + char *fqdn_buf = NULL, *pos; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", pps_fname); + return; + } + + fqdn = os_strstr(pps_fname, "SP/"); + if (fqdn) { + fqdn_buf = os_strdup(fqdn + 3); + if (fqdn_buf == NULL) + return; + pos = os_strchr(fqdn_buf, '/'); + if (pos) + *pos = '\0'; + fqdn = fqdn_buf; + } else + fqdn = "wi-fi.org"; + + wpa_printf(MSG_INFO, "Set PPS MO info to wpa_supplicant - SP FQDN %s", + fqdn); + set_pps(ctx, pps, fqdn); + + os_free(fqdn_buf); + xml_node_free(ctx->xml, pps); +} + + +static int cmd_get_fqdn(struct hs20_osu_client *ctx, const char *pps_fname) +{ + xml_node_t *pps, *node; + char *fqdn = NULL; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", pps_fname); + return -1; + } + + node = get_child_node(ctx->xml, pps, "HomeSP/FQDN"); + if (node) + fqdn = xml_node_get_text(ctx->xml, node); + + xml_node_free(ctx->xml, pps); + + if (fqdn) { + FILE *f = fopen("pps-fqdn", "w"); + if (f) { + fprintf(f, "%s", fqdn); + fclose(f); + } + xml_node_get_text_free(ctx->xml, fqdn); + return 0; + } + + xml_node_get_text_free(ctx->xml, fqdn); + return -1; +} + + +static void cmd_to_tnds(struct hs20_osu_client *ctx, const char *in_fname, + const char *out_fname, const char *urn, int use_path) +{ + xml_node_t *mo, *node; + + mo = node_from_file(ctx->xml, in_fname); + if (mo == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", in_fname); + return; + } + + node = mo_to_tnds(ctx->xml, mo, use_path, urn, NULL); + if (node) { + node_to_file(ctx->xml, out_fname, node); + xml_node_free(ctx->xml, node); + } + + xml_node_free(ctx->xml, mo); +} + + +static void cmd_from_tnds(struct hs20_osu_client *ctx, const char *in_fname, + const char *out_fname) +{ + xml_node_t *tnds, *mo; + + tnds = node_from_file(ctx->xml, in_fname); + if (tnds == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", in_fname); + return; + } + + mo = tnds_to_mo(ctx->xml, tnds); + if (mo) { + node_to_file(ctx->xml, out_fname, mo); + xml_node_free(ctx->xml, mo); + } + + xml_node_free(ctx->xml, tnds); +} + + +struct osu_icon { + int id; + char lang[4]; + char mime_type[256]; + char filename[256]; +}; + +struct osu_data { + char bssid[20]; + char url[256]; + unsigned int methods; + char osu_ssid[33]; + char osu_ssid2[33]; + char osu_nai[256]; + char osu_nai2[256]; + struct osu_lang_text friendly_name[MAX_OSU_VALS]; + size_t friendly_name_count; + struct osu_lang_text serv_desc[MAX_OSU_VALS]; + size_t serv_desc_count; + struct osu_icon icon[MAX_OSU_VALS]; + size_t icon_count; +}; + + +static struct osu_data * parse_osu_providers(const char *fname, size_t *count) +{ + FILE *f; + char buf[1000]; + struct osu_data *osu = NULL, *last = NULL; + size_t osu_count = 0; + char *pos, *end; + int res; + + f = fopen(fname, "r"); + if (f == NULL) { + wpa_printf(MSG_ERROR, "Could not open %s", fname); + return NULL; + } + + while (fgets(buf, sizeof(buf), f)) { + pos = strchr(buf, '\n'); + if (pos) + *pos = '\0'; + + if (strncmp(buf, "OSU-PROVIDER ", 13) == 0) { + last = realloc(osu, (osu_count + 1) * sizeof(*osu)); + if (last == NULL) + break; + osu = last; + last = &osu[osu_count++]; + memset(last, 0, sizeof(*last)); + res = os_snprintf(last->bssid, sizeof(last->bssid), + "%s", buf + 13); + if (os_snprintf_error(sizeof(last->bssid), res)) + break; + continue; + } + if (!last) + continue; + + if (strncmp(buf, "uri=", 4) == 0) { + res = os_snprintf(last->url, sizeof(last->url), + "%s", buf + 4); + if (os_snprintf_error(sizeof(last->url), res)) + break; + continue; + } + + if (strncmp(buf, "methods=", 8) == 0) { + last->methods = strtol(buf + 8, NULL, 16); + continue; + } + + if (strncmp(buf, "osu_ssid=", 9) == 0) { + res = os_snprintf(last->osu_ssid, + sizeof(last->osu_ssid), + "%s", buf + 9); + if (os_snprintf_error(sizeof(last->osu_ssid), res)) + break; + continue; + } + + if (strncmp(buf, "osu_ssid2=", 10) == 0) { + res = os_snprintf(last->osu_ssid2, + sizeof(last->osu_ssid2), + "%s", buf + 10); + if (os_snprintf_error(sizeof(last->osu_ssid2), res)) + break; + continue; + } + + if (os_strncmp(buf, "osu_nai=", 8) == 0) { + res = os_snprintf(last->osu_nai, sizeof(last->osu_nai), + "%s", buf + 8); + if (os_snprintf_error(sizeof(last->osu_nai), res)) + break; + continue; + } + + if (os_strncmp(buf, "osu_nai2=", 9) == 0) { + res = os_snprintf(last->osu_nai2, + sizeof(last->osu_nai2), + "%s", buf + 9); + if (os_snprintf_error(sizeof(last->osu_nai2), res)) + break; + continue; + } + + if (strncmp(buf, "friendly_name=", 14) == 0) { + struct osu_lang_text *txt; + if (last->friendly_name_count == MAX_OSU_VALS) + continue; + pos = strchr(buf + 14, ':'); + if (pos == NULL) + continue; + *pos++ = '\0'; + txt = &last->friendly_name[last->friendly_name_count++]; + res = os_snprintf(txt->lang, sizeof(txt->lang), + "%s", buf + 14); + if (os_snprintf_error(sizeof(txt->lang), res)) + break; + res = os_snprintf(txt->text, sizeof(txt->text), + "%s", pos); + if (os_snprintf_error(sizeof(txt->text), res)) + break; + } + + if (strncmp(buf, "desc=", 5) == 0) { + struct osu_lang_text *txt; + if (last->serv_desc_count == MAX_OSU_VALS) + continue; + pos = strchr(buf + 5, ':'); + if (pos == NULL) + continue; + *pos++ = '\0'; + txt = &last->serv_desc[last->serv_desc_count++]; + res = os_snprintf(txt->lang, sizeof(txt->lang), + "%s", buf + 5); + if (os_snprintf_error(sizeof(txt->lang), res)) + break; + res = os_snprintf(txt->text, sizeof(txt->text), + "%s", pos); + if (os_snprintf_error(sizeof(txt->text), res)) + break; + } + + if (strncmp(buf, "icon=", 5) == 0) { + struct osu_icon *icon; + if (last->icon_count == MAX_OSU_VALS) + continue; + icon = &last->icon[last->icon_count++]; + icon->id = atoi(buf + 5); + pos = strchr(buf, ':'); + if (pos == NULL) + continue; + pos = strchr(pos + 1, ':'); + if (pos == NULL) + continue; + pos = strchr(pos + 1, ':'); + if (pos == NULL) + continue; + pos++; + end = strchr(pos, ':'); + if (!end) + continue; + *end = '\0'; + res = os_snprintf(icon->lang, sizeof(icon->lang), + "%s", pos); + if (os_snprintf_error(sizeof(icon->lang), res)) + break; + pos = end + 1; + + end = strchr(pos, ':'); + if (end) + *end = '\0'; + res = os_snprintf(icon->mime_type, + sizeof(icon->mime_type), "%s", pos); + if (os_snprintf_error(sizeof(icon->mime_type), res)) + break; + if (!end) + continue; + pos = end + 1; + + end = strchr(pos, ':'); + if (end) + *end = '\0'; + res = os_snprintf(icon->filename, + sizeof(icon->filename), "%s", pos); + if (os_snprintf_error(sizeof(icon->filename), res)) + break; + continue; + } + } + + fclose(f); + + *count = osu_count; + return osu; +} + + +static int osu_connect(struct hs20_osu_client *ctx, const char *bssid, + const char *ssid, const char *ssid2, const char *url, + unsigned int methods, int no_prod_assoc, + const char *osu_nai, const char *osu_nai2) +{ + int id; + const char *ifname = ctx->ifname; + char buf[200]; + struct wpa_ctrl *mon; + int res; + + if (ssid2 && ssid2[0] == '\0') + ssid2 = NULL; + + if (ctx->osu_ssid) { + if (os_strcmp(ssid, ctx->osu_ssid) == 0) { + wpa_printf(MSG_DEBUG, + "Enforced OSU SSID matches ANQP info"); + ssid2 = NULL; + } else if (ssid2 && os_strcmp(ssid2, ctx->osu_ssid) == 0) { + wpa_printf(MSG_DEBUG, + "Enforced OSU SSID matches RSN[OSEN] info"); + ssid = ssid2; + } else { + wpa_printf(MSG_INFO, "Enforced OSU SSID did not match"); + write_summary(ctx, "Enforced OSU SSID did not match"); + return -1; + } + } + + id = add_network(ifname); + if (id < 0) + return -1; + if (set_network_quoted(ifname, id, "ssid", ssid) < 0) + return -1; + if (ssid2) + osu_nai = osu_nai2; + if (osu_nai && os_strlen(osu_nai) > 0) { + char dir[255], fname[300]; + if (getcwd(dir, sizeof(dir)) == NULL) + return -1; + os_snprintf(fname, sizeof(fname), "%s/osu-ca.pem", dir); + + if (ssid2 && set_network_quoted(ifname, id, "ssid", ssid2) < 0) + return -1; + + if (set_network(ifname, id, "proto", "OSEN") < 0 || + set_network(ifname, id, "key_mgmt", "OSEN") < 0 || + set_network(ifname, id, "pairwise", "CCMP") < 0 || + set_network(ifname, id, "group", "GTK_NOT_USED CCMP") < 0 || + set_network(ifname, id, "eap", "WFA-UNAUTH-TLS") < 0 || + set_network(ifname, id, "ocsp", "2") < 0 || + set_network_quoted(ifname, id, "identity", osu_nai) < 0 || + set_network_quoted(ifname, id, "ca_cert", fname) < 0) + return -1; + } else if (ssid2) { + wpa_printf(MSG_INFO, "No OSU_NAI set for RSN[OSEN]"); + write_summary(ctx, "No OSU_NAI set for RSN[OSEN]"); + return -1; + } else { + if (set_network(ifname, id, "key_mgmt", "NONE") < 0) + return -1; + } + + mon = open_wpa_mon(ifname); + if (mon == NULL) + return -1; + + wpa_printf(MSG_INFO, "Associate with OSU SSID"); + write_summary(ctx, "Associate with OSU SSID"); + snprintf(buf, sizeof(buf), "SELECT_NETWORK %d", id); + if (wpa_command(ifname, buf) < 0) + return -1; + + res = get_wpa_cli_event(mon, "CTRL-EVENT-CONNECTED", + buf, sizeof(buf)); + + wpa_ctrl_detach(mon); + wpa_ctrl_close(mon); + + if (res < 0) { + wpa_printf(MSG_INFO, "Could not connect to OSU network"); + write_summary(ctx, "Could not connect to OSU network"); + wpa_printf(MSG_INFO, "Remove OSU network connection"); + snprintf(buf, sizeof(buf), "REMOVE_NETWORK %d", id); + wpa_command(ifname, buf); + return -1; + } + + write_summary(ctx, "Waiting for IP address for subscription registration"); + if (wait_ip_addr(ifname, 15) < 0) { + wpa_printf(MSG_INFO, "Could not get IP address for WLAN - try connection anyway"); + } + + if (no_prod_assoc) { + if (res < 0) + return -1; + wpa_printf(MSG_INFO, "No production connection used for testing purposes"); + write_summary(ctx, "No production connection used for testing purposes"); + return 0; + } + + ctx->no_reconnect = 1; + if (methods & 0x02) { + wpa_printf(MSG_DEBUG, "Calling cmd_prov from osu_connect"); + res = cmd_prov(ctx, url); + } else if (methods & 0x01) { + wpa_printf(MSG_DEBUG, + "Calling cmd_oma_dm_prov from osu_connect"); + res = cmd_oma_dm_prov(ctx, url); + } + + wpa_printf(MSG_INFO, "Remove OSU network connection"); + write_summary(ctx, "Remove OSU network connection"); + snprintf(buf, sizeof(buf), "REMOVE_NETWORK %d", id); + wpa_command(ifname, buf); + + if (res < 0) + return -1; + + wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration"); + write_summary(ctx, "Requesting reconnection with updated configuration"); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) { + wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect"); + write_summary(ctx, "Failed to request wpa_supplicant to reconnect"); + return -1; + } + + return 0; +} + + +static int cmd_osu_select(struct hs20_osu_client *ctx, const char *dir, + int connect, int no_prod_assoc, + const char *friendly_name) +{ + char fname[255]; + FILE *f; + struct osu_data *osu = NULL, *last = NULL; + size_t osu_count = 0, i, j; + int ret; + + write_summary(ctx, "OSU provider selection"); + + if (dir == NULL) { + wpa_printf(MSG_INFO, "Missing dir parameter to osu_select"); + return -1; + } + + snprintf(fname, sizeof(fname), "%s/osu-providers.txt", dir); + osu = parse_osu_providers(fname, &osu_count); + if (osu == NULL) { + wpa_printf(MSG_INFO, "Could not find any OSU providers from %s", + fname); + write_result(ctx, "No OSU providers available"); + return -1; + } + + if (friendly_name) { + for (i = 0; i < osu_count; i++) { + last = &osu[i]; + for (j = 0; j < last->friendly_name_count; j++) { + if (os_strcmp(last->friendly_name[j].text, + friendly_name) == 0) + break; + } + if (j < last->friendly_name_count) + break; + } + if (i == osu_count) { + wpa_printf(MSG_INFO, "Requested operator friendly name '%s' not found in the list of available providers", + friendly_name); + write_summary(ctx, "Requested operator friendly name '%s' not found in the list of available providers", + friendly_name); + free(osu); + return -1; + } + + wpa_printf(MSG_INFO, "OSU Provider selected based on requested operator friendly name '%s'", + friendly_name); + write_summary(ctx, "OSU Provider selected based on requested operator friendly name '%s'", + friendly_name); + ret = i + 1; + goto selected; + } + + snprintf(fname, sizeof(fname), "%s/osu-providers.html", dir); + f = fopen(fname, "w"); + if (f == NULL) { + wpa_printf(MSG_INFO, "Could not open %s", fname); + free(osu); + return -1; + } + + fprintf(f, "" + "Select service operator" + "

Select service operator

\n"); + + if (osu_count == 0) + fprintf(f, "No online signup available\n"); + + for (i = 0; i < osu_count; i++) { + last = &osu[i]; +#ifdef ANDROID + fprintf(f, "

\n" + "" + "
", (int) i + 1); +#else /* ANDROID */ + fprintf(f, "

\n" + "" + "
", (int) i + 1); +#endif /* ANDROID */ + for (j = 0; j < last->icon_count; j++) { + fprintf(f, "\n", + last->icon[j].id, + strcasecmp(last->icon[j].mime_type, + "image/png") == 0 ? "png" : "icon"); + } + fprintf(f, ""); + for (j = 0; j < last->friendly_name_count; j++) { + fprintf(f, "[%s] %s
\n", + last->friendly_name[j].lang, + last->friendly_name[j].text); + } + fprintf(f, "
"); + for (j = 0; j < last->serv_desc_count; j++) { + fprintf(f, "[%s] %s
\n", + last->serv_desc[j].lang, + last->serv_desc[j].text); + } + fprintf(f, "

BSSID: %s
\n" + "SSID: %s
\n", + last->bssid, last->osu_ssid); + if (last->osu_ssid2[0]) + fprintf(f, "SSID2: %s
\n", last->osu_ssid2); + if (last->osu_nai[0]) + fprintf(f, "NAI: %s
\n", last->osu_nai); + if (last->osu_nai2[0]) + fprintf(f, "NAI2: %s
\n", last->osu_nai2); + fprintf(f, "URL: %s
\n" + "methods:%s%s
\n" + "

\n", + last->url, + last->methods & 0x01 ? " OMA-DM" : "", + last->methods & 0x02 ? " SOAP-XML-SPP" : ""); + } + + fprintf(f, "\n"); + + fclose(f); + + snprintf(fname, sizeof(fname), "file://%s/osu-providers.html", dir); + write_summary(ctx, "Start web browser with OSU provider selection page"); + ret = hs20_web_browser(fname, 0); + +selected: + if (ret > 0 && (size_t) ret <= osu_count) { + char *data; + size_t data_len; + + wpa_printf(MSG_INFO, "Selected OSU id=%d", ret); + last = &osu[ret - 1]; + ret = 0; + wpa_printf(MSG_INFO, "BSSID: %s", last->bssid); + wpa_printf(MSG_INFO, "SSID: %s", last->osu_ssid); + if (last->osu_ssid2[0]) + wpa_printf(MSG_INFO, "SSID2: %s", last->osu_ssid2); + wpa_printf(MSG_INFO, "URL: %s", last->url); + write_summary(ctx, "Selected OSU provider id=%d BSSID=%s SSID=%s URL=%s", + ret, last->bssid, last->osu_ssid, last->url); + + ctx->friendly_name_count = last->friendly_name_count; + for (j = 0; j < last->friendly_name_count; j++) { + wpa_printf(MSG_INFO, "FRIENDLY_NAME: [%s]%s", + last->friendly_name[j].lang, + last->friendly_name[j].text); + os_strlcpy(ctx->friendly_name[j].lang, + last->friendly_name[j].lang, + sizeof(ctx->friendly_name[j].lang)); + os_strlcpy(ctx->friendly_name[j].text, + last->friendly_name[j].text, + sizeof(ctx->friendly_name[j].text)); + } + + ctx->icon_count = last->icon_count; + for (j = 0; j < last->icon_count; j++) { + char fname[256]; + + os_snprintf(fname, sizeof(fname), "%s/osu-icon-%d.%s", + dir, last->icon[j].id, + strcasecmp(last->icon[j].mime_type, + "image/png") == 0 ? + "png" : "icon"); + wpa_printf(MSG_INFO, "ICON: %s (%s)", + fname, last->icon[j].filename); + os_strlcpy(ctx->icon_filename[j], + last->icon[j].filename, + sizeof(ctx->icon_filename[j])); + + data = os_readfile(fname, &data_len); + if (data) { + sha256_vector(1, (const u8 **) &data, &data_len, + ctx->icon_hash[j]); + os_free(data); + } + } + + if (connect == 2) { + if (last->methods & 0x02) { + wpa_printf(MSG_DEBUG, + "Calling cmd_prov from cmd_osu_select"); + ret = cmd_prov(ctx, last->url); + } else if (last->methods & 0x01) { + wpa_printf(MSG_DEBUG, + "Calling cmd_oma_dm_prov from cmd_osu_select"); + ret = cmd_oma_dm_prov(ctx, last->url); + } else { + wpa_printf(MSG_DEBUG, + "No supported OSU provisioning method"); + ret = -1; + } + } else if (connect) { + ret = osu_connect(ctx, last->bssid, last->osu_ssid, + last->osu_ssid2, + last->url, last->methods, + no_prod_assoc, last->osu_nai, + last->osu_nai2); + } + } else + ret = -1; + + free(osu); + + return ret; +} + + +static int cmd_signup(struct hs20_osu_client *ctx, int no_prod_assoc, + const char *friendly_name) +{ + char dir[255]; + char fname[300], buf[400]; + struct wpa_ctrl *mon; + const char *ifname; + int res; + + ifname = ctx->ifname; + + if (getcwd(dir, sizeof(dir)) == NULL) + return -1; + + snprintf(fname, sizeof(fname), "%s/osu-info", dir); + if (mkdir(fname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0 && + errno != EEXIST) { + wpa_printf(MSG_INFO, "mkdir(%s) failed: %s", + fname, strerror(errno)); + return -1; + } + + android_update_permission(fname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + + snprintf(buf, sizeof(buf), "SET osu_dir %s", fname); + if (wpa_command(ifname, buf) < 0) { + wpa_printf(MSG_INFO, "Failed to configure osu_dir to wpa_supplicant"); + return -1; + } + + mon = open_wpa_mon(ifname); + if (mon == NULL) + return -1; + + wpa_printf(MSG_INFO, "Starting OSU fetch"); + write_summary(ctx, "Starting OSU provider information fetch"); + if (wpa_command(ifname, "FETCH_OSU") < 0) { + wpa_printf(MSG_INFO, "Could not start OSU fetch"); + wpa_ctrl_detach(mon); + wpa_ctrl_close(mon); + return -1; + } + res = get_wpa_cli_event(mon, "OSU provider fetch completed", + buf, sizeof(buf)); + + wpa_ctrl_detach(mon); + wpa_ctrl_close(mon); + + if (res < 0) { + wpa_printf(MSG_INFO, "OSU fetch did not complete"); + write_summary(ctx, "OSU fetch did not complete"); + return -1; + } + wpa_printf(MSG_INFO, "OSU provider fetch completed"); + + return cmd_osu_select(ctx, fname, 1, no_prod_assoc, friendly_name); +} + + +static int cmd_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname) +{ + xml_node_t *pps, *node; + char pps_fname_buf[300]; + char ca_fname_buf[200]; + char *cred_username = NULL; + char *cred_password = NULL; + char *sub_rem_uri = NULL; + char client_cert_buf[200]; + char *client_cert = NULL; + char client_key_buf[200]; + char *client_key = NULL; + int spp; + + wpa_printf(MSG_INFO, "Subscription remediation requested with Server URL: %s", + address); + + if (!pps_fname) { + char buf[256]; + wpa_printf(MSG_INFO, "Determining PPS file based on Home SP information"); + if (os_strncmp(address, "fqdn=", 5) == 0) { + wpa_printf(MSG_INFO, "Use requested FQDN from command line"); + os_snprintf(buf, sizeof(buf), "%s", address + 5); + address = NULL; + } else if (get_wpa_status(ctx->ifname, "provisioning_sp", buf, + sizeof(buf)) < 0) { + wpa_printf(MSG_INFO, "Could not get provisioning Home SP FQDN from wpa_supplicant"); + return -1; + } + os_free(ctx->fqdn); + ctx->fqdn = os_strdup(buf); + if (ctx->fqdn == NULL) + return -1; + wpa_printf(MSG_INFO, "Home SP FQDN for current credential: %s", + buf); + os_snprintf(pps_fname_buf, sizeof(pps_fname_buf), + "SP/%s/pps.xml", ctx->fqdn); + pps_fname = pps_fname_buf; + + os_snprintf(ca_fname_buf, sizeof(ca_fname_buf), "SP/%s/ca.pem", + ctx->fqdn); + ca_fname = ca_fname_buf; + } + + if (!os_file_exists(pps_fname)) { + wpa_printf(MSG_INFO, "PPS file '%s' does not exist or is not accessible", + pps_fname); + return -1; + } + wpa_printf(MSG_INFO, "Using PPS file: %s", pps_fname); + + if (ca_fname && !os_file_exists(ca_fname)) { + wpa_printf(MSG_INFO, "CA file '%s' does not exist or is not accessible", + ca_fname); + return -1; + } + wpa_printf(MSG_INFO, "Using server trust root: %s", ca_fname); + ctx->ca_fname = ca_fname; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read PPS MO"); + return -1; + } + + if (!ctx->fqdn) { + char *tmp; + node = get_child_node(ctx->xml, pps, "HomeSP/FQDN"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No HomeSP/FQDN found from PPS"); + return -1; + } + tmp = xml_node_get_text(ctx->xml, node); + if (tmp == NULL) { + wpa_printf(MSG_INFO, "No HomeSP/FQDN text found from PPS"); + return -1; + } + ctx->fqdn = os_strdup(tmp); + xml_node_get_text_free(ctx->xml, tmp); + if (!ctx->fqdn) { + wpa_printf(MSG_INFO, "No FQDN known"); + return -1; + } + } + + node = get_child_node(ctx->xml, pps, + "SubscriptionUpdate/UpdateMethod"); + if (node) { + char *tmp; + tmp = xml_node_get_text(ctx->xml, node); + if (tmp && os_strcasecmp(tmp, "OMA-DM-ClientInitiated") == 0) + spp = 0; + else + spp = 1; + } else { + wpa_printf(MSG_INFO, "No UpdateMethod specified - assume SPP"); + spp = 1; + } + + get_user_pw(ctx, pps, "SubscriptionUpdate/UsernamePassword", + &cred_username, &cred_password); + if (cred_username) + wpa_printf(MSG_INFO, "Using username: %s", cred_username); + if (cred_password) + wpa_printf(MSG_DEBUG, "Using password: %s", cred_password); + + if (cred_username == NULL && cred_password == NULL && + get_child_node(ctx->xml, pps, "Credential/DigitalCertificate")) { + wpa_printf(MSG_INFO, "Using client certificate"); + os_snprintf(client_cert_buf, sizeof(client_cert_buf), + "SP/%s/client-cert.pem", ctx->fqdn); + client_cert = client_cert_buf; + os_snprintf(client_key_buf, sizeof(client_key_buf), + "SP/%s/client-key.pem", ctx->fqdn); + client_key = client_key_buf; + ctx->client_cert_present = 1; + } + + node = get_child_node(ctx->xml, pps, "SubscriptionUpdate/URI"); + if (node) { + sub_rem_uri = xml_node_get_text(ctx->xml, node); + if (sub_rem_uri && + (!address || os_strcmp(address, sub_rem_uri) != 0)) { + wpa_printf(MSG_INFO, "Override sub rem URI based on PPS: %s", + sub_rem_uri); + address = sub_rem_uri; + } + } + if (!address) { + wpa_printf(MSG_INFO, "Server URL not known"); + return -1; + } + + write_summary(ctx, "Wait for IP address for subscriptiom remediation"); + wpa_printf(MSG_INFO, "Wait for IP address before starting subscription remediation"); + + if (wait_ip_addr(ctx->ifname, 15) < 0) { + wpa_printf(MSG_INFO, "Could not get IP address for WLAN - try connection anyway"); + } + + if (spp) + spp_sub_rem(ctx, address, pps_fname, + client_cert, client_key, + cred_username, cred_password, pps); + else + oma_dm_sub_rem(ctx, address, pps_fname, + client_cert, client_key, + cred_username, cred_password, pps); + + xml_node_get_text_free(ctx->xml, sub_rem_uri); + xml_node_get_text_free(ctx->xml, cred_username); + str_clear_free(cred_password); + xml_node_free(ctx->xml, pps); + return 0; +} + + +static int cmd_pol_upd(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname) +{ + xml_node_t *pps; + xml_node_t *node; + char pps_fname_buf[300]; + char ca_fname_buf[200]; + char *uri = NULL; + char *cred_username = NULL; + char *cred_password = NULL; + char client_cert_buf[200]; + char *client_cert = NULL; + char client_key_buf[200]; + char *client_key = NULL; + int spp; + + wpa_printf(MSG_INFO, "Policy update requested"); + + if (!pps_fname) { + char buf[256]; + int res; + + wpa_printf(MSG_INFO, "Determining PPS file based on Home SP information"); + if (address && os_strncmp(address, "fqdn=", 5) == 0) { + wpa_printf(MSG_INFO, "Use requested FQDN from command line"); + os_snprintf(buf, sizeof(buf), "%s", address + 5); + address = NULL; + } else if (get_wpa_status(ctx->ifname, "provisioning_sp", buf, + sizeof(buf)) < 0) { + wpa_printf(MSG_INFO, "Could not get provisioning Home SP FQDN from wpa_supplicant"); + return -1; + } + os_free(ctx->fqdn); + ctx->fqdn = os_strdup(buf); + if (ctx->fqdn == NULL) + return -1; + wpa_printf(MSG_INFO, "Home SP FQDN for current credential: %s", + buf); + os_snprintf(pps_fname_buf, sizeof(pps_fname_buf), + "SP/%s/pps.xml", ctx->fqdn); + pps_fname = pps_fname_buf; + + res = os_snprintf(ca_fname_buf, sizeof(ca_fname_buf), + "SP/%s/ca.pem", buf); + if (os_snprintf_error(sizeof(ca_fname_buf), res)) { + os_free(ctx->fqdn); + ctx->fqdn = NULL; + return -1; + } + ca_fname = ca_fname_buf; + } + + if (!os_file_exists(pps_fname)) { + wpa_printf(MSG_INFO, "PPS file '%s' does not exist or is not accessible", + pps_fname); + return -1; + } + wpa_printf(MSG_INFO, "Using PPS file: %s", pps_fname); + + if (ca_fname && !os_file_exists(ca_fname)) { + wpa_printf(MSG_INFO, "CA file '%s' does not exist or is not accessible", + ca_fname); + return -1; + } + wpa_printf(MSG_INFO, "Using server trust root: %s", ca_fname); + ctx->ca_fname = ca_fname; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read PPS MO"); + return -1; + } + + if (!ctx->fqdn) { + char *tmp; + node = get_child_node(ctx->xml, pps, "HomeSP/FQDN"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No HomeSP/FQDN found from PPS"); + return -1; + } + tmp = xml_node_get_text(ctx->xml, node); + if (tmp == NULL) { + wpa_printf(MSG_INFO, "No HomeSP/FQDN text found from PPS"); + return -1; + } + ctx->fqdn = os_strdup(tmp); + xml_node_get_text_free(ctx->xml, tmp); + if (!ctx->fqdn) { + wpa_printf(MSG_INFO, "No FQDN known"); + return -1; + } + } + + node = get_child_node(ctx->xml, pps, + "Policy/PolicyUpdate/UpdateMethod"); + if (node) { + char *tmp; + tmp = xml_node_get_text(ctx->xml, node); + if (tmp && os_strcasecmp(tmp, "OMA-DM-ClientInitiated") == 0) + spp = 0; + else + spp = 1; + } else { + wpa_printf(MSG_INFO, "No UpdateMethod specified - assume SPP"); + spp = 1; + } + + get_user_pw(ctx, pps, "Policy/PolicyUpdate/UsernamePassword", + &cred_username, &cred_password); + if (cred_username) + wpa_printf(MSG_INFO, "Using username: %s", cred_username); + if (cred_password) + wpa_printf(MSG_DEBUG, "Using password: %s", cred_password); + + if (cred_username == NULL && cred_password == NULL && + get_child_node(ctx->xml, pps, "Credential/DigitalCertificate")) { + wpa_printf(MSG_INFO, "Using client certificate"); + os_snprintf(client_cert_buf, sizeof(client_cert_buf), + "SP/%s/client-cert.pem", ctx->fqdn); + client_cert = client_cert_buf; + os_snprintf(client_key_buf, sizeof(client_key_buf), + "SP/%s/client-key.pem", ctx->fqdn); + client_key = client_key_buf; + } + + if (!address) { + node = get_child_node(ctx->xml, pps, "Policy/PolicyUpdate/URI"); + if (node) { + uri = xml_node_get_text(ctx->xml, node); + wpa_printf(MSG_INFO, "URI based on PPS: %s", uri); + address = uri; + } + } + if (!address) { + wpa_printf(MSG_INFO, "Server URL not known"); + return -1; + } + + if (spp) + spp_pol_upd(ctx, address, pps_fname, + client_cert, client_key, + cred_username, cred_password, pps); + else + oma_dm_pol_upd(ctx, address, pps_fname, + client_cert, client_key, + cred_username, cred_password, pps); + + xml_node_get_text_free(ctx->xml, uri); + xml_node_get_text_free(ctx->xml, cred_username); + str_clear_free(cred_password); + xml_node_free(ctx->xml, pps); + + return 0; +} + + +static char * get_hostname(const char *url) +{ + const char *pos, *end, *end2; + char *ret; + + if (url == NULL) + return NULL; + + pos = os_strchr(url, '/'); + if (pos == NULL) + return NULL; + pos++; + if (*pos != '/') + return NULL; + pos++; + + end = os_strchr(pos, '/'); + end2 = os_strchr(pos, ':'); + if ((end && end2 && end2 < end) || (!end && end2)) + end = end2; + if (end) + end--; + else { + end = pos; + while (*end) + end++; + if (end > pos) + end--; + } + + ret = os_malloc(end - pos + 2); + if (ret == NULL) + return NULL; + + os_memcpy(ret, pos, end - pos + 1); + ret[end - pos + 1] = '\0'; + + return ret; +} + + +static int osu_cert_cb(void *_ctx, struct http_cert *cert) +{ + struct hs20_osu_client *ctx = _ctx; + size_t i, j; + int found; + char *host = NULL; + + wpa_printf(MSG_INFO, "osu_cert_cb(osu_cert_validation=%d, url=%s server_url=%s)", + !ctx->no_osu_cert_validation, cert->url ? cert->url : "N/A", + ctx->server_url); + + if (ctx->no_osu_cert_validation && cert->url) + host = get_hostname(cert->url); + else + host = get_hostname(ctx->server_url); + + if (!ctx->no_osu_cert_validation) { + for (i = 0; i < ctx->server_dnsname_count; i++) + os_free(ctx->server_dnsname[i]); + os_free(ctx->server_dnsname); + ctx->server_dnsname = os_calloc(cert->num_dnsname, + sizeof(char *)); + ctx->server_dnsname_count = 0; + } + + found = 0; + for (i = 0; i < cert->num_dnsname; i++) { + if (!ctx->no_osu_cert_validation && ctx->server_dnsname) { + ctx->server_dnsname[ctx->server_dnsname_count] = + os_strdup(cert->dnsname[i]); + if (ctx->server_dnsname[ctx->server_dnsname_count]) + ctx->server_dnsname_count++; + } + if (host && os_strcasecmp(host, cert->dnsname[i]) == 0) + found = 1; + wpa_printf(MSG_INFO, "dNSName '%s'", cert->dnsname[i]); + } + + if (host && !found) { + wpa_printf(MSG_INFO, "Server name from URL (%s) did not match any dNSName - abort connection", + host); + write_result(ctx, "Server name from URL (%s) did not match any dNSName - abort connection", + host); + os_free(host); + return -1; + } + + os_free(host); + + for (i = 0; i < cert->num_othername; i++) { + if (os_strcmp(cert->othername[i].oid, + "1.3.6.1.4.1.40808.1.1.1") == 0) { + wpa_hexdump_ascii(MSG_INFO, + "id-wfa-hotspot-friendlyName", + cert->othername[i].data, + cert->othername[i].len); + } + } + + for (j = 0; !ctx->no_osu_cert_validation && + j < ctx->friendly_name_count; j++) { + int found = 0; + for (i = 0; i < cert->num_othername; i++) { + if (os_strcmp(cert->othername[i].oid, + "1.3.6.1.4.1.40808.1.1.1") != 0) + continue; + if (cert->othername[i].len < 3) + continue; + if (os_strncasecmp((char *) cert->othername[i].data, + ctx->friendly_name[j].lang, 3) != 0) + continue; + if (os_strncmp((char *) cert->othername[i].data + 3, + ctx->friendly_name[j].text, + cert->othername[i].len - 3) == 0) { + found = 1; + break; + } + } + + if (!found) { + wpa_printf(MSG_INFO, "No friendly name match found for '[%s]%s'", + ctx->friendly_name[j].lang, + ctx->friendly_name[j].text); + write_result(ctx, "No friendly name match found for '[%s]%s'", + ctx->friendly_name[j].lang, + ctx->friendly_name[j].text); + return -1; + } + } + + for (i = 0; i < cert->num_logo; i++) { + struct http_logo *logo = &cert->logo[i]; + + wpa_printf(MSG_INFO, "logo hash alg %s uri '%s'", + logo->alg_oid, logo->uri); + wpa_hexdump_ascii(MSG_INFO, "hashValue", + logo->hash, logo->hash_len); + } + + for (j = 0; !ctx->no_osu_cert_validation && j < ctx->icon_count; j++) { + int found = 0; + char *name = ctx->icon_filename[j]; + size_t name_len = os_strlen(name); + + wpa_printf(MSG_INFO, + "[%zu] Looking for icon file name '%s' match", + j, name); + for (i = 0; i < cert->num_logo; i++) { + struct http_logo *logo = &cert->logo[i]; + size_t uri_len = os_strlen(logo->uri); + char *pos; + + wpa_printf(MSG_INFO, + "[%zu] Comparing to '%s' uri_len=%d name_len=%d", + i, logo->uri, (int) uri_len, (int) name_len); + if (uri_len < 1 + name_len) { + wpa_printf(MSG_INFO, "URI Length is too short"); + continue; + } + pos = &logo->uri[uri_len - name_len - 1]; + if (*pos != '/') + continue; + pos++; + if (os_strcmp(pos, name) == 0) { + found = 1; + break; + } + } + + if (!found) { + wpa_printf(MSG_INFO, "No icon filename match found for '%s'", + name); + write_result(ctx, + "No icon filename match found for '%s'", + name); + return -1; + } + } + + for (j = 0; !ctx->no_osu_cert_validation && j < ctx->icon_count; j++) { + int found = 0; + + for (i = 0; i < cert->num_logo; i++) { + struct http_logo *logo = &cert->logo[i]; + + if (logo->hash_len != 32) { + wpa_printf(MSG_INFO, + "[%zu][%zu] Icon hash length invalid (should be 32): %d", + j, i, (int) logo->hash_len); + continue; + } + if (os_memcmp(logo->hash, ctx->icon_hash[j], 32) == 0) { + found = 1; + break; + } + + wpa_printf(MSG_DEBUG, + "[%zu][%zu] Icon hash did not match", j, i); + wpa_hexdump_ascii(MSG_DEBUG, "logo->hash", + logo->hash, 32); + wpa_hexdump_ascii(MSG_DEBUG, "ctx->icon_hash[j]", + ctx->icon_hash[j], 32); + } + + if (!found) { + wpa_printf(MSG_INFO, + "No icon hash match (by hash) found"); + write_result(ctx, + "No icon hash match (by hash) found"); + return -1; + } + } + + return 0; +} + + +static int init_ctx(struct hs20_osu_client *ctx) +{ + xml_node_t *devinfo, *devid; + + os_memset(ctx, 0, sizeof(*ctx)); + ctx->ifname = "wlan0"; + ctx->xml = xml_node_init_ctx(ctx, NULL); + if (ctx->xml == NULL) + return -1; + + devinfo = node_from_file(ctx->xml, "devinfo.xml"); + if (devinfo) { + devid = get_node(ctx->xml, devinfo, "DevId"); + if (devid) { + char *tmp = xml_node_get_text(ctx->xml, devid); + + if (tmp) { + ctx->devid = os_strdup(tmp); + xml_node_get_text_free(ctx->xml, tmp); + } + } + xml_node_free(ctx->xml, devinfo); + } + + ctx->http = http_init_ctx(ctx, ctx->xml); + if (ctx->http == NULL) { + xml_node_deinit_ctx(ctx->xml); + return -1; + } + http_ocsp_set(ctx->http, 2); + http_set_cert_cb(ctx->http, osu_cert_cb, ctx); + + return 0; +} + + +static void deinit_ctx(struct hs20_osu_client *ctx) +{ + size_t i; + + http_deinit_ctx(ctx->http); + xml_node_deinit_ctx(ctx->xml); + os_free(ctx->fqdn); + os_free(ctx->server_url); + os_free(ctx->devid); + + for (i = 0; i < ctx->server_dnsname_count; i++) + os_free(ctx->server_dnsname[i]); + os_free(ctx->server_dnsname); +} + + +static void check_workarounds(struct hs20_osu_client *ctx) +{ + FILE *f; + char buf[100]; + unsigned long int val = 0; + + f = fopen("hs20-osu-client.workarounds", "r"); + if (f == NULL) + return; + + if (fgets(buf, sizeof(buf), f)) + val = strtoul(buf, NULL, 16); + + fclose(f); + + if (val) { + wpa_printf(MSG_INFO, "Workarounds enabled: 0x%lx", val); + ctx->workarounds = val; + if (ctx->workarounds & WORKAROUND_OCSP_OPTIONAL) + http_ocsp_set(ctx->http, 1); + } +} + + +static void usage(void) +{ + printf("usage: hs20-osu-client [-dddqqKtT] [-S] \\\n" + " [-w] " + "[-r] [-f] \\\n" + " [-s] \\\n" + " [-x] \\\n" + " [arguments..]\n" + "commands:\n" + "- to_tnds [URN]\n" + "- to_tnds2 \n" + "- from_tnds \n" + "- set_pps \n" + "- get_fqdn \n" + "- pol_upd [Server URL] [PPS] [CA cert]\n" + "- sub_rem [PPS] [CA cert]\n" + "- prov [CA cert]\n" + "- oma_dm_prov [CA cert]\n" + "- sim_prov [CA cert]\n" + "- oma_dm_sim_prov [CA cert]\n" + "- signup [CA cert]\n" + "- dl_osu_ca \n" + "- dl_polupd_ca \n" + "- dl_aaa_ca \n" + "- browser \n" + "- parse_cert \n" + "- osu_select [CA cert]\n"); +} + + +int main(int argc, char *argv[]) +{ + struct hs20_osu_client ctx; + int c; + int ret = 0; + int no_prod_assoc = 0; + const char *friendly_name = NULL; + const char *wpa_debug_file_path = NULL; + extern char *wpas_ctrl_path; + extern int wpa_debug_level; + extern int wpa_debug_show_keys; + extern int wpa_debug_timestamp; + + if (init_ctx(&ctx) < 0) + return -1; + + for (;;) { + c = getopt(argc, argv, "df:hKNo:O:qr:s:S:tTw:x:"); + if (c < 0) + break; + switch (c) { + case 'd': + if (wpa_debug_level > 0) + wpa_debug_level--; + break; + case 'f': + wpa_debug_file_path = optarg; + break; + case 'K': + wpa_debug_show_keys++; + break; + case 'N': + no_prod_assoc = 1; + break; + case 'o': + ctx.osu_ssid = optarg; + break; + case 'O': + friendly_name = optarg; + break; + case 'q': + wpa_debug_level++; + break; + case 'r': + ctx.result_file = optarg; + break; + case 's': + ctx.summary_file = optarg; + break; + case 'S': + ctx.ifname = optarg; + break; + case 't': + wpa_debug_timestamp++; + break; + case 'T': + ctx.ignore_tls = 1; + break; + case 'w': + wpas_ctrl_path = optarg; + break; + case 'x': + spp_xsd_fname = optarg; + break; + case 'h': + default: + usage(); + exit(0); + } + } + + if (argc - optind < 1) { + usage(); + exit(0); + } + + wpa_debug_open_file(wpa_debug_file_path); + +#ifdef __linux__ + setlinebuf(stdout); +#endif /* __linux__ */ + + if (ctx.result_file) + unlink(ctx.result_file); + wpa_printf(MSG_DEBUG, "===[hs20-osu-client START - command: %s ]======" + "================", argv[optind]); + check_workarounds(&ctx); + + if (strcmp(argv[optind], "to_tnds") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_to_tnds(&ctx, argv[optind + 1], argv[optind + 2], + argc > optind + 3 ? argv[optind + 3] : NULL, + 0); + } else if (strcmp(argv[optind], "to_tnds2") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_to_tnds(&ctx, argv[optind + 1], argv[optind + 2], + argc > optind + 3 ? argv[optind + 3] : NULL, + 1); + } else if (strcmp(argv[optind], "from_tnds") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_from_tnds(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "sub_rem") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + ret = cmd_sub_rem(&ctx, argv[optind + 1], + argc > optind + 2 ? argv[optind + 2] : NULL, + argc > optind + 3 ? argv[optind + 3] : NULL); + } else if (strcmp(argv[optind], "pol_upd") == 0) { + ret = cmd_pol_upd(&ctx, + argc > optind + 1 ? argv[optind + 1] : NULL, + argc > optind + 2 ? argv[optind + 2] : NULL, + argc > optind + 3 ? argv[optind + 3] : NULL); + } else if (strcmp(argv[optind], "prov") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + ctx.ca_fname = argv[optind + 2]; + wpa_printf(MSG_DEBUG, "Calling cmd_prov from main"); + cmd_prov(&ctx, argv[optind + 1]); + } else if (strcmp(argv[optind], "sim_prov") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + ctx.ca_fname = argv[optind + 2]; + cmd_sim_prov(&ctx, argv[optind + 1]); + } else if (strcmp(argv[optind], "dl_osu_ca") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_dl_osu_ca(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "dl_polupd_ca") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_dl_polupd_ca(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "dl_aaa_ca") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_dl_aaa_ca(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "osu_select") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + ctx.ca_fname = argc > optind + 2 ? argv[optind + 2] : NULL; + cmd_osu_select(&ctx, argv[optind + 1], 2, 1, NULL); + } else if (strcmp(argv[optind], "signup") == 0) { + ctx.ca_fname = argc > optind + 1 ? argv[optind + 1] : NULL; + ret = cmd_signup(&ctx, no_prod_assoc, friendly_name); + } else if (strcmp(argv[optind], "set_pps") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_set_pps(&ctx, argv[optind + 1]); + } else if (strcmp(argv[optind], "get_fqdn") == 0) { + if (argc - optind < 1) { + usage(); + exit(0); + } + ret = cmd_get_fqdn(&ctx, argv[optind + 1]); + } else if (strcmp(argv[optind], "oma_dm_prov") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + ctx.ca_fname = argv[optind + 2]; + cmd_oma_dm_prov(&ctx, argv[optind + 1]); + } else if (strcmp(argv[optind], "oma_dm_sim_prov") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + ctx.ca_fname = argv[optind + 2]; + if (cmd_oma_dm_sim_prov(&ctx, argv[optind + 1]) < 0) { + write_summary(&ctx, "Failed to complete OMA DM SIM provisioning"); + return -1; + } + } else if (strcmp(argv[optind], "oma_dm_add") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_oma_dm_add(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "oma_dm_replace") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_oma_dm_replace(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "est_csr") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + mkdir("Cert", S_IRWXU); + est_build_csr(&ctx, argv[optind + 1]); + } else if (strcmp(argv[optind], "browser") == 0) { + int ret; + + if (argc - optind < 2) { + usage(); + exit(0); + } + + wpa_printf(MSG_INFO, "Launch web browser to URL %s", + argv[optind + 1]); + ret = hs20_web_browser(argv[optind + 1], ctx.ignore_tls); + wpa_printf(MSG_INFO, "Web browser result: %d", ret); + } else if (strcmp(argv[optind], "parse_cert") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + + wpa_debug_level = MSG_MSGDUMP; + http_parse_x509_certificate(ctx.http, argv[optind + 1]); + wpa_debug_level = MSG_INFO; + } else { + wpa_printf(MSG_INFO, "Unknown command '%s'", argv[optind]); + } + + deinit_ctx(&ctx); + wpa_printf(MSG_DEBUG, + "===[hs20-osu-client END ]======================"); + + wpa_debug_close_file(); + + return ret; +} diff --git a/hs20/client/osu_client.h b/hs20/client/osu_client.h new file mode 100644 index 0000000..9b45b03 --- /dev/null +++ b/hs20/client/osu_client.h @@ -0,0 +1,121 @@ +/* + * Hotspot 2.0 - OSU client + * Copyright (c) 2013-2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef OSU_CLIENT_H +#define OSU_CLIENT_H + +#define SPP_NS_URI "http://www.wi-fi.org/specifications/hotspot2dot0/v1.0/spp" + +#define URN_OMA_DM_DEVINFO "urn:oma:mo:oma-dm-devinfo:1.0" +#define URN_OMA_DM_DEVDETAIL "urn:oma:mo:oma-dm-devdetail:1.0" +#define URN_HS20_DEVDETAIL_EXT "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext:1.0" +#define URN_HS20_PPS "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0" + + +#define MAX_OSU_VALS 10 + +struct osu_lang_text { + char lang[4]; + char text[253]; +}; + +struct hs20_osu_client { + struct xml_node_ctx *xml; + struct http_ctx *http; + int no_reconnect; + char pps_fname[300]; + char *devid; + const char *result_file; + const char *summary_file; + const char *ifname; + const char *ca_fname; + int no_osu_cert_validation; /* for EST operations */ + char *fqdn; + char *server_url; + struct osu_lang_text friendly_name[MAX_OSU_VALS]; + size_t friendly_name_count; + size_t icon_count; + char icon_filename[MAX_OSU_VALS][256]; + u8 icon_hash[MAX_OSU_VALS][32]; + int pps_cred_set; + int pps_updated; + int client_cert_present; + char **server_dnsname; + size_t server_dnsname_count; + const char *osu_ssid; /* Enforced OSU_SSID for testing purposes */ +#define WORKAROUND_OCSP_OPTIONAL 0x00000001 + unsigned long int workarounds; + int ignore_tls; /* whether to ignore TLS validation issues with HTTPS + * server certificate */ +}; + + +/* osu_client.c */ + +void write_result(struct hs20_osu_client *ctx, const char *fmt, ...) + __attribute__ ((format (printf, 2, 3))); +void write_summary(struct hs20_osu_client *ctx, const char *fmt, ...) + __attribute__ ((format (printf, 2, 3))); + +void debug_dump_node(struct hs20_osu_client *ctx, const char *title, + xml_node_t *node); +int osu_get_certificate(struct hs20_osu_client *ctx, xml_node_t *getcert); +int hs20_add_pps_mo(struct hs20_osu_client *ctx, const char *uri, + xml_node_t *add_mo, char *fname, size_t fname_len); +void get_user_pw(struct hs20_osu_client *ctx, xml_node_t *pps, + const char *alt_loc, char **user, char **pw); +int update_pps_file(struct hs20_osu_client *ctx, const char *pps_fname, + xml_node_t *pps); +void cmd_set_pps(struct hs20_osu_client *ctx, const char *pps_fname); + + +/* spp_client.c */ + +void spp_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps); +void spp_pol_upd(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps); +int cmd_prov(struct hs20_osu_client *ctx, const char *url); +int cmd_sim_prov(struct hs20_osu_client *ctx, const char *url); + + +/* oma_dm_client.c */ + +int cmd_oma_dm_prov(struct hs20_osu_client *ctx, const char *url); +int cmd_oma_dm_sim_prov(struct hs20_osu_client *ctx, const char *url); +void oma_dm_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps); +void oma_dm_pol_upd(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps); +void cmd_oma_dm_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname); +void cmd_oma_dm_add(struct hs20_osu_client *ctx, const char *pps_fname, + const char *add_fname); +void cmd_oma_dm_replace(struct hs20_osu_client *ctx, const char *pps_fname, + const char *replace_fname); + +/* est.c */ + +int est_load_cacerts(struct hs20_osu_client *ctx, const char *url); +int est_build_csr(struct hs20_osu_client *ctx, const char *url); +int est_simple_enroll(struct hs20_osu_client *ctx, const char *url, + const char *user, const char *pw); + +#endif /* OSU_CLIENT_H */ diff --git a/hs20/client/spp_client.c b/hs20/client/spp_client.c new file mode 100644 index 0000000..194518e --- /dev/null +++ b/hs20/client/spp_client.c @@ -0,0 +1,1003 @@ +/* + * Hotspot 2.0 SPP client + * Copyright (c) 2012-2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" +#include + +#include "common.h" +#include "browser.h" +#include "wpa_ctrl.h" +#include "wpa_helpers.h" +#include "xml-utils.h" +#include "http-utils.h" +#include "utils/base64.h" +#include "crypto/crypto.h" +#include "crypto/sha256.h" +#include "osu_client.h" + + +extern const char *spp_xsd_fname; + +static int hs20_spp_update_response(struct hs20_osu_client *ctx, + const char *session_id, + const char *spp_status, + const char *error_code); +static void hs20_policy_update_complete( + struct hs20_osu_client *ctx, const char *pps_fname); + + +static char * get_spp_attr_value(struct xml_node_ctx *ctx, xml_node_t *node, + char *attr_name) +{ + return xml_node_get_attr_value_ns(ctx, node, SPP_NS_URI, attr_name); +} + + +static int hs20_spp_validate(struct hs20_osu_client *ctx, xml_node_t *node, + const char *expected_name) +{ + struct xml_node_ctx *xctx = ctx->xml; + const char *name; + char *err; + int ret; + + if (!xml_node_is_element(xctx, node)) + return -1; + + name = xml_node_get_localname(xctx, node); + if (name == NULL) + return -1; + + if (strcmp(expected_name, name) != 0) { + wpa_printf(MSG_INFO, "Unexpected SOAP method name '%s' (expected '%s')", + name, expected_name); + write_summary(ctx, "Unexpected SOAP method name '%s' (expected '%s')", + name, expected_name); + return -1; + } + + ret = xml_validate(xctx, node, spp_xsd_fname, &err); + if (ret < 0) { + wpa_printf(MSG_INFO, "XML schema validation error(s)\n%s", err); + write_summary(ctx, "SPP XML schema validation failed"); + os_free(err); + } + return ret; +} + + +static void add_mo_container(struct xml_node_ctx *ctx, xml_namespace_t *ns, + xml_node_t *parent, const char *urn, + const char *fname) +{ + xml_node_t *node; + xml_node_t *fnode, *tnds; + char *str; + + errno = 0; + fnode = node_from_file(ctx, fname); + if (!fnode) { + wpa_printf(MSG_ERROR, + "Failed to create XML node from file: %s, possible error: %s", + fname, strerror(errno)); + return; + } + tnds = mo_to_tnds(ctx, fnode, 0, urn, "syncml:dmddf1.2"); + xml_node_free(ctx, fnode); + if (!tnds) + return; + + str = xml_node_to_str(ctx, tnds); + xml_node_free(ctx, tnds); + if (str == NULL) + return; + + node = xml_node_create_text(ctx, parent, ns, "moContainer", str); + if (node) + xml_node_add_attr(ctx, node, ns, "moURN", urn); + os_free(str); +} + + +static xml_node_t * build_spp_post_dev_data(struct hs20_osu_client *ctx, + xml_namespace_t **ret_ns, + const char *session_id, + const char *reason) +{ + xml_namespace_t *ns; + xml_node_t *spp_node; + + write_summary(ctx, "Building sppPostDevData requestReason='%s'", + reason); + spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, + "sppPostDevData"); + if (spp_node == NULL) + return NULL; + if (ret_ns) + *ret_ns = ns; + + xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0"); + xml_node_add_attr(ctx->xml, spp_node, NULL, "requestReason", reason); + if (session_id) + xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", + session_id); + xml_node_add_attr(ctx->xml, spp_node, NULL, "redirectURI", + "http://localhost:12345/"); + + xml_node_create_text(ctx->xml, spp_node, ns, "supportedSPPVersions", + "1.0"); + xml_node_create_text(ctx->xml, spp_node, ns, "supportedMOList", + URN_HS20_PPS " " URN_OMA_DM_DEVINFO " " + URN_OMA_DM_DEVDETAIL " " URN_HS20_DEVDETAIL_EXT); + + add_mo_container(ctx->xml, ns, spp_node, URN_OMA_DM_DEVINFO, + "devinfo.xml"); + add_mo_container(ctx->xml, ns, spp_node, URN_OMA_DM_DEVDETAIL, + "devdetail.xml"); + + return spp_node; +} + + +static int process_update_node(struct hs20_osu_client *ctx, xml_node_t *pps, + xml_node_t *update) +{ + xml_node_t *node, *parent, *tnds, *unode; + char *str; + const char *name; + char *uri, *pos; + char *cdata, *cdata_end; + size_t fqdn_len; + + wpa_printf(MSG_INFO, "Processing updateNode"); + debug_dump_node(ctx, "updateNode", update); + + uri = get_spp_attr_value(ctx->xml, update, "managementTreeURI"); + if (uri == NULL) { + wpa_printf(MSG_INFO, "No managementTreeURI present"); + return -1; + } + wpa_printf(MSG_INFO, "managementTreeUri: '%s'", uri); + + name = os_strrchr(uri, '/'); + if (name == NULL) { + wpa_printf(MSG_INFO, "Unexpected URI"); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + name++; + wpa_printf(MSG_INFO, "Update interior node: '%s'", name); + + str = xml_node_get_text(ctx->xml, update); + if (str == NULL) { + wpa_printf(MSG_INFO, "Could not extract MO text"); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + wpa_printf(MSG_DEBUG, "[hs20] nodeContainer text: '%s'", str); + cdata = strstr(str, ""); + if (cdata && cdata_end && cdata_end > cdata && + cdata < strstr(str, "MgmtTree") && + cdata_end > strstr(str, "/MgmtTree")) { + char *tmp; + wpa_printf(MSG_DEBUG, "[hs20] Removing extra CDATA container"); + tmp = strdup(cdata + 9); + if (tmp) { + cdata_end = strstr(tmp, "]]>"); + if (cdata_end) + *cdata_end = '\0'; + wpa_printf(MSG_DEBUG, "[hs20] nodeContainer text with CDATA container removed: '%s'", + tmp); + tnds = xml_node_from_buf(ctx->xml, tmp); + free(tmp); + } else + tnds = NULL; + } else + tnds = xml_node_from_buf(ctx->xml, str); + xml_node_get_text_free(ctx->xml, str); + if (tnds == NULL) { + wpa_printf(MSG_INFO, "[hs20] Could not parse nodeContainer text"); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + + unode = tnds_to_mo(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (unode == NULL) { + wpa_printf(MSG_INFO, "[hs20] Could not parse nodeContainer TNDS text"); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + + debug_dump_node(ctx, "Parsed TNDS", unode); + + if (get_node_uri(ctx->xml, unode, name) == NULL) { + wpa_printf(MSG_INFO, "[hs20] %s node not found", name); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + + if (os_strncasecmp(uri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Do not allow update outside ./Wi-Fi"); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + pos = uri + 8; + + if (ctx->fqdn == NULL) { + wpa_printf(MSG_INFO, "FQDN not known"); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + fqdn_len = os_strlen(ctx->fqdn); + if (os_strncasecmp(pos, ctx->fqdn, fqdn_len) != 0 || + pos[fqdn_len] != '/') { + wpa_printf(MSG_INFO, "Do not allow update outside ./Wi-Fi/%s", + ctx->fqdn); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + pos += fqdn_len + 1; + + if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) { + wpa_printf(MSG_INFO, "Do not allow update outside ./Wi-Fi/%s/PerProviderSubscription", + ctx->fqdn); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + pos += 24; + + wpa_printf(MSG_INFO, "Update command for PPS node %s", pos); + + node = get_node(ctx->xml, pps, pos); + if (node) { + parent = xml_node_get_parent(ctx->xml, node); + xml_node_detach(ctx->xml, node); + wpa_printf(MSG_INFO, "Replace '%s' node", name); + } else { + char *pos2; + pos2 = os_strrchr(pos, '/'); + if (pos2 == NULL) { + parent = pps; + } else { + *pos2 = '\0'; + parent = get_node(ctx->xml, pps, pos); + } + if (parent == NULL) { + wpa_printf(MSG_INFO, "Could not find parent %s", pos); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + wpa_printf(MSG_INFO, "Add '%s' node", name); + } + xml_node_add_child(ctx->xml, parent, unode); + + xml_node_get_attr_value_free(ctx->xml, uri); + + return 0; +} + + +static int update_pps(struct hs20_osu_client *ctx, xml_node_t *update, + const char *pps_fname, xml_node_t *pps) +{ + wpa_printf(MSG_INFO, "Updating PPS based on updateNode element(s)"); + xml_node_for_each_sibling(ctx->xml, update) { + xml_node_for_each_check(ctx->xml, update); + if (process_update_node(ctx, pps, update) < 0) + return -1; + } + + return update_pps_file(ctx, pps_fname, pps); +} + + +static void hs20_sub_rem_complete(struct hs20_osu_client *ctx, + const char *pps_fname) +{ + /* + * Update wpa_supplicant credentials and reconnect using updated + * information. + */ + wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials"); + cmd_set_pps(ctx, pps_fname); + + if (ctx->no_reconnect) + return; + + wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration"); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) + wpa_printf(MSG_ERROR, "Failed to request wpa_supplicant to reconnect"); +} + + +static xml_node_t * hs20_spp_upload_mo(struct hs20_osu_client *ctx, + xml_node_t *cmd, + const char *session_id, + const char *pps_fname) +{ + xml_namespace_t *ns; + xml_node_t *node, *ret_node; + char *urn; + + urn = get_spp_attr_value(ctx->xml, cmd, "moURN"); + if (!urn) { + wpa_printf(MSG_INFO, "No URN included"); + return NULL; + } + wpa_printf(MSG_INFO, "Upload MO request - URN=%s", urn); + if (strcasecmp(urn, URN_HS20_PPS) != 0) { + wpa_printf(MSG_INFO, "Unsupported moURN"); + xml_node_get_attr_value_free(ctx->xml, urn); + return NULL; + } + xml_node_get_attr_value_free(ctx->xml, urn); + + if (!pps_fname) { + wpa_printf(MSG_INFO, "PPS file name no known"); + return NULL; + } + + node = build_spp_post_dev_data(ctx, &ns, session_id, + "MO upload"); + if (node == NULL) + return NULL; + add_mo_container(ctx->xml, ns, node, URN_HS20_PPS, pps_fname); + + ret_node = soap_send_receive(ctx->http, node); + if (ret_node == NULL) + return NULL; + + debug_dump_node(ctx, "Received response to MO upload", ret_node); + + if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) { + wpa_printf(MSG_INFO, "SPP validation failed"); + xml_node_free(ctx->xml, ret_node); + return NULL; + } + + return ret_node; +} + + +static int hs20_add_mo(struct hs20_osu_client *ctx, xml_node_t *add_mo, + char *fname, size_t fname_len) +{ + char *uri, *urn; + int ret; + + debug_dump_node(ctx, "Received addMO", add_mo); + + urn = get_spp_attr_value(ctx->xml, add_mo, "moURN"); + if (urn == NULL) { + wpa_printf(MSG_INFO, "[hs20] No moURN in addMO"); + return -1; + } + wpa_printf(MSG_INFO, "addMO - moURN: '%s'", urn); + if (strcasecmp(urn, URN_HS20_PPS) != 0) { + wpa_printf(MSG_INFO, "[hs20] Unsupported MO in addMO"); + xml_node_get_attr_value_free(ctx->xml, urn); + return -1; + } + xml_node_get_attr_value_free(ctx->xml, urn); + + uri = get_spp_attr_value(ctx->xml, add_mo, "managementTreeURI"); + if (uri == NULL) { + wpa_printf(MSG_INFO, "[hs20] No managementTreeURI in addMO"); + return -1; + } + wpa_printf(MSG_INFO, "addMO - managementTreeURI: '%s'", uri); + + ret = hs20_add_pps_mo(ctx, uri, add_mo, fname, fname_len); + xml_node_get_attr_value_free(ctx->xml, uri); + return ret; +} + + +static int process_spp_user_input_response(struct hs20_osu_client *ctx, + const char *session_id, + xml_node_t *add_mo) +{ + int ret; + char fname[300]; + + debug_dump_node(ctx, "addMO", add_mo); + + wpa_printf(MSG_INFO, "Subscription registration completed"); + + if (hs20_add_mo(ctx, add_mo, fname, sizeof(fname)) < 0) { + wpa_printf(MSG_INFO, "Could not add MO"); + ret = hs20_spp_update_response( + ctx, session_id, + "Error occurred", + "MO addition or update failed"); + return 0; + } + + ret = hs20_spp_update_response(ctx, session_id, "OK", NULL); + if (ret == 0) + hs20_sub_rem_complete(ctx, fname); + + return 0; +} + + +static xml_node_t * hs20_spp_user_input_completed(struct hs20_osu_client *ctx, + const char *session_id) +{ + xml_node_t *node, *ret_node; + + node = build_spp_post_dev_data(ctx, NULL, session_id, + "User input completed"); + if (node == NULL) + return NULL; + + ret_node = soap_send_receive(ctx->http, node); + if (!ret_node) { + if (soap_reinit_client(ctx->http) < 0) + return NULL; + wpa_printf(MSG_INFO, "Try to finish with re-opened connection"); + node = build_spp_post_dev_data(ctx, NULL, session_id, + "User input completed"); + if (node == NULL) + return NULL; + ret_node = soap_send_receive(ctx->http, node); + if (ret_node == NULL) + return NULL; + wpa_printf(MSG_INFO, "Continue with new connection"); + } + + if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) { + wpa_printf(MSG_INFO, "SPP validation failed"); + xml_node_free(ctx->xml, ret_node); + return NULL; + } + + return ret_node; +} + + +static xml_node_t * hs20_spp_get_certificate(struct hs20_osu_client *ctx, + xml_node_t *cmd, + const char *session_id, + const char *pps_fname) +{ + xml_namespace_t *ns; + xml_node_t *node, *ret_node; + int res; + + wpa_printf(MSG_INFO, "Client certificate enrollment"); + + res = osu_get_certificate(ctx, cmd); + if (res < 0) + wpa_printf(MSG_INFO, "EST simpleEnroll failed"); + + node = build_spp_post_dev_data(ctx, &ns, session_id, + res == 0 ? + "Certificate enrollment completed" : + "Certificate enrollment failed"); + if (node == NULL) + return NULL; + + ret_node = soap_send_receive(ctx->http, node); + if (ret_node == NULL) + return NULL; + + debug_dump_node(ctx, "Received response to certificate enrollment " + "completed", ret_node); + + if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) { + wpa_printf(MSG_INFO, "SPP validation failed"); + xml_node_free(ctx->xml, ret_node); + return NULL; + } + + return ret_node; +} + + +static int hs20_spp_exec(struct hs20_osu_client *ctx, xml_node_t *exec, + const char *session_id, const char *pps_fname, + xml_node_t *pps, xml_node_t **ret_node) +{ + xml_node_t *cmd; + const char *name; + char *uri; + char *id = strdup(session_id); + + if (id == NULL) + return -1; + + *ret_node = NULL; + + debug_dump_node(ctx, "exec", exec); + + xml_node_for_each_child(ctx->xml, cmd, exec) { + xml_node_for_each_check(ctx->xml, cmd); + break; + } + if (!cmd) { + wpa_printf(MSG_INFO, "exec command element not found (cmd=%p)", + cmd); + free(id); + return -1; + } + + name = xml_node_get_localname(ctx->xml, cmd); + + if (strcasecmp(name, "launchBrowserToURI") == 0) { + int res; + uri = xml_node_get_text(ctx->xml, cmd); + if (!uri) { + wpa_printf(MSG_INFO, "No URI found"); + free(id); + return -1; + } + wpa_printf(MSG_INFO, "Launch browser to URI '%s'", uri); + write_summary(ctx, "Launch browser to URI '%s'", uri); + res = hs20_web_browser(uri, 1); + xml_node_get_text_free(ctx->xml, uri); + if (res > 0) { + wpa_printf(MSG_INFO, "User response in browser completed successfully - sessionid='%s'", + id); + write_summary(ctx, "User response in browser completed successfully"); + *ret_node = hs20_spp_user_input_completed(ctx, id); + free(id); + return *ret_node ? 0 : -1; + } else { + wpa_printf(MSG_INFO, "Failed to receive user response"); + write_summary(ctx, "Failed to receive user response"); + hs20_spp_update_response( + ctx, id, "Error occurred", "Other"); + free(id); + return -1; + } + } + + if (strcasecmp(name, "uploadMO") == 0) { + if (pps_fname == NULL) + return -1; + *ret_node = hs20_spp_upload_mo(ctx, cmd, id, + pps_fname); + free(id); + return *ret_node ? 0 : -1; + } + + if (strcasecmp(name, "getCertificate") == 0) { + *ret_node = hs20_spp_get_certificate(ctx, cmd, id, + pps_fname); + free(id); + return *ret_node ? 0 : -1; + } + + wpa_printf(MSG_INFO, "Unsupported exec command: '%s'", name); + free(id); + return -1; +} + + +enum spp_post_dev_data_use { + SPP_SUBSCRIPTION_REMEDIATION, + SPP_POLICY_UPDATE, + SPP_SUBSCRIPTION_REGISTRATION, +}; + +static void process_spp_post_dev_data_response( + struct hs20_osu_client *ctx, + enum spp_post_dev_data_use use, xml_node_t *node, + const char *pps_fname, xml_node_t *pps) +{ + xml_node_t *child; + char *status = NULL; + xml_node_t *update = NULL, *exec = NULL, *add_mo = NULL, *no_mo = NULL; + char *session_id = NULL; + + debug_dump_node(ctx, "sppPostDevDataResponse node", node); + + status = get_spp_attr_value(ctx->xml, node, "sppStatus"); + if (status == NULL) { + wpa_printf(MSG_INFO, "No sppStatus attribute"); + goto out; + } + write_summary(ctx, "Received sppPostDevDataResponse sppStatus='%s'", + status); + + session_id = get_spp_attr_value(ctx->xml, node, "sessionID"); + if (session_id == NULL) { + wpa_printf(MSG_INFO, "No sessionID attribute"); + goto out; + } + + wpa_printf(MSG_INFO, "[hs20] sppPostDevDataResponse - sppStatus: '%s' sessionID: '%s'", + status, session_id); + + xml_node_for_each_child(ctx->xml, child, node) { + const char *name; + xml_node_for_each_check(ctx->xml, child); + debug_dump_node(ctx, "child", child); + name = xml_node_get_localname(ctx->xml, child); + wpa_printf(MSG_INFO, "localname: '%s'", name); + if (!update && strcasecmp(name, "updateNode") == 0) + update = child; + if (!exec && strcasecmp(name, "exec") == 0) + exec = child; + if (!add_mo && strcasecmp(name, "addMO") == 0) + add_mo = child; + if (!no_mo && strcasecmp(name, "noMOUpdate") == 0) + no_mo = child; + } + + if (use == SPP_SUBSCRIPTION_REMEDIATION && + strcasecmp(status, + "Remediation complete, request sppUpdateResponse") == 0) + { + int res, ret; + if (!update && !no_mo) { + wpa_printf(MSG_INFO, "No updateNode or noMOUpdate element"); + goto out; + } + wpa_printf(MSG_INFO, "Subscription remediation completed"); + res = update_pps(ctx, update, pps_fname, pps); + if (res < 0) + wpa_printf(MSG_INFO, "Failed to update PPS MO"); + ret = hs20_spp_update_response( + ctx, session_id, + res < 0 ? "Error occurred" : "OK", + res < 0 ? "MO addition or update failed" : NULL); + if (res == 0 && ret == 0) + hs20_sub_rem_complete(ctx, pps_fname); + goto out; + } + + if (use == SPP_SUBSCRIPTION_REMEDIATION && + strcasecmp(status, "Exchange complete, release TLS connection") == + 0) { + if (!no_mo) { + wpa_printf(MSG_INFO, "No noMOUpdate element"); + goto out; + } + wpa_printf(MSG_INFO, "Subscription remediation completed (no MO update)"); + goto out; + } + + if (use == SPP_POLICY_UPDATE && + strcasecmp(status, "Update complete, request sppUpdateResponse") == + 0) { + int res, ret; + wpa_printf(MSG_INFO, "Policy update received - update PPS"); + res = update_pps(ctx, update, pps_fname, pps); + ret = hs20_spp_update_response( + ctx, session_id, + res < 0 ? "Error occurred" : "OK", + res < 0 ? "MO addition or update failed" : NULL); + if (res == 0 && ret == 0) + hs20_policy_update_complete(ctx, pps_fname); + goto out; + } + + if (use == SPP_SUBSCRIPTION_REGISTRATION && + strcasecmp(status, "Provisioning complete, request " + "sppUpdateResponse") == 0) { + if (!add_mo) { + wpa_printf(MSG_INFO, "No addMO element - not sure what to do next"); + goto out; + } + process_spp_user_input_response(ctx, session_id, add_mo); + node = NULL; + goto out; + } + + if (strcasecmp(status, "No update available at this time") == 0) { + wpa_printf(MSG_INFO, "No update available at this time"); + goto out; + } + + if (strcasecmp(status, "OK") == 0) { + int res; + xml_node_t *ret; + + if (!exec) { + wpa_printf(MSG_INFO, "No exec element - not sure what to do next"); + goto out; + } + res = hs20_spp_exec(ctx, exec, session_id, + pps_fname, pps, &ret); + /* xml_node_free(ctx->xml, node); */ + node = NULL; + if (res == 0 && ret) + process_spp_post_dev_data_response(ctx, use, + ret, pps_fname, pps); + goto out; + } + + if (strcasecmp(status, "Error occurred") == 0) { + xml_node_t *err; + char *code = NULL; + err = get_node(ctx->xml, node, "sppError"); + if (err) + code = xml_node_get_attr_value(ctx->xml, err, + "errorCode"); + wpa_printf(MSG_INFO, "Error occurred - errorCode=%s", + code ? code : "N/A"); + xml_node_get_attr_value_free(ctx->xml, code); + goto out; + } + + wpa_printf(MSG_INFO, + "[hs20] Unsupported sppPostDevDataResponse sppStatus '%s'", + status); +out: + xml_node_get_attr_value_free(ctx->xml, status); + xml_node_get_attr_value_free(ctx->xml, session_id); + xml_node_free(ctx->xml, node); +} + + +static int spp_post_dev_data(struct hs20_osu_client *ctx, + enum spp_post_dev_data_use use, + const char *reason, + const char *pps_fname, xml_node_t *pps) +{ + xml_node_t *payload; + xml_node_t *ret_node; + + payload = build_spp_post_dev_data(ctx, NULL, NULL, reason); + if (payload == NULL) + return -1; + + ret_node = soap_send_receive(ctx->http, payload); + if (!ret_node) { + const char *err = http_get_err(ctx->http); + if (err) { + wpa_printf(MSG_INFO, "HTTP error: %s", err); + write_result(ctx, "HTTP error: %s", err); + } else { + write_summary(ctx, "Failed to send SOAP message"); + } + return -1; + } + + if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) { + wpa_printf(MSG_INFO, "SPP validation failed"); + xml_node_free(ctx->xml, ret_node); + return -1; + } + + process_spp_post_dev_data_response(ctx, use, ret_node, + pps_fname, pps); + return 0; +} + + +void spp_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps) +{ + wpa_printf(MSG_INFO, "SPP subscription remediation"); + write_summary(ctx, "SPP subscription remediation"); + + os_free(ctx->server_url); + ctx->server_url = os_strdup(address); + + if (soap_init_client(ctx->http, address, ctx->ca_fname, + cred_username, cred_password, client_cert, + client_key) == 0) { + spp_post_dev_data(ctx, SPP_SUBSCRIPTION_REMEDIATION, + "Subscription remediation", pps_fname, pps); + } +} + + +static void hs20_policy_update_complete(struct hs20_osu_client *ctx, + const char *pps_fname) +{ + wpa_printf(MSG_INFO, "Policy update completed"); + + /* + * Update wpa_supplicant credentials and reconnect using updated + * information. + */ + wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials"); + cmd_set_pps(ctx, pps_fname); + + wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration"); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) + wpa_printf(MSG_ERROR, "Failed to request wpa_supplicant to reconnect"); +} + + +static int process_spp_exchange_complete(struct hs20_osu_client *ctx, + xml_node_t *node) +{ + char *status, *session_id; + + debug_dump_node(ctx, "sppExchangeComplete", node); + + status = get_spp_attr_value(ctx->xml, node, "sppStatus"); + if (status == NULL) { + wpa_printf(MSG_INFO, "No sppStatus attribute"); + return -1; + } + write_summary(ctx, "Received sppExchangeComplete sppStatus='%s'", + status); + + session_id = get_spp_attr_value(ctx->xml, node, "sessionID"); + if (session_id == NULL) { + wpa_printf(MSG_INFO, "No sessionID attribute"); + xml_node_get_attr_value_free(ctx->xml, status); + return -1; + } + + wpa_printf(MSG_INFO, "[hs20] sppStatus: '%s' sessionID: '%s'", + status, session_id); + xml_node_get_attr_value_free(ctx->xml, session_id); + + if (strcasecmp(status, "Exchange complete, release TLS connection") == + 0) { + xml_node_get_attr_value_free(ctx->xml, status); + return 0; + } + + wpa_printf(MSG_INFO, "Unexpected sppStatus '%s'", status); + write_summary(ctx, "Unexpected sppStatus '%s'", status); + xml_node_get_attr_value_free(ctx->xml, status); + return -1; +} + + +static xml_node_t * build_spp_update_response(struct hs20_osu_client *ctx, + const char *session_id, + const char *spp_status, + const char *error_code) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node; + + spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, + "sppUpdateResponse"); + if (spp_node == NULL) + return NULL; + + xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0"); + xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", session_id); + xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", spp_status); + + if (error_code) { + node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); + if (node) + xml_node_add_attr(ctx->xml, node, NULL, "errorCode", + error_code); + } + + return spp_node; +} + + +static int hs20_spp_update_response(struct hs20_osu_client *ctx, + const char *session_id, + const char *spp_status, + const char *error_code) +{ + xml_node_t *node, *ret_node; + int ret; + + write_summary(ctx, "Building sppUpdateResponse sppStatus='%s' error_code='%s'", + spp_status, error_code); + node = build_spp_update_response(ctx, session_id, spp_status, + error_code); + if (node == NULL) + return -1; + ret_node = soap_send_receive(ctx->http, node); + if (!ret_node) { + if (soap_reinit_client(ctx->http) < 0) + return -1; + wpa_printf(MSG_INFO, "Try to finish with re-opened connection"); + node = build_spp_update_response(ctx, session_id, spp_status, + error_code); + if (node == NULL) + return -1; + ret_node = soap_send_receive(ctx->http, node); + if (ret_node == NULL) + return -1; + wpa_printf(MSG_INFO, "Continue with new connection"); + } + + if (hs20_spp_validate(ctx, ret_node, "sppExchangeComplete") < 0) { + wpa_printf(MSG_INFO, "SPP validation failed"); + xml_node_free(ctx->xml, ret_node); + return -1; + } + + ret = process_spp_exchange_complete(ctx, ret_node); + xml_node_free(ctx->xml, ret_node); + return ret; +} + + +void spp_pol_upd(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps) +{ + wpa_printf(MSG_INFO, "SPP policy update"); + write_summary(ctx, "SPP policy update"); + + os_free(ctx->server_url); + ctx->server_url = os_strdup(address); + + if (soap_init_client(ctx->http, address, ctx->ca_fname, cred_username, + cred_password, client_cert, client_key) == 0) { + spp_post_dev_data(ctx, SPP_POLICY_UPDATE, "Policy update", + pps_fname, pps); + } +} + + +int cmd_prov(struct hs20_osu_client *ctx, const char *url) +{ + unlink("Cert/est_cert.der"); + unlink("Cert/est_cert.pem"); + + if (url == NULL) { + wpa_printf(MSG_INFO, "Invalid prov command (missing URL)"); + return -1; + } + + wpa_printf(MSG_INFO, + "Credential provisioning requested - URL: %s ca_fname: %s", + url, ctx->ca_fname ? ctx->ca_fname : "N/A"); + + os_free(ctx->server_url); + ctx->server_url = os_strdup(url); + + if (soap_init_client(ctx->http, url, ctx->ca_fname, NULL, NULL, NULL, + NULL) < 0) + return -1; + spp_post_dev_data(ctx, SPP_SUBSCRIPTION_REGISTRATION, + "Subscription registration", NULL, NULL); + + return ctx->pps_cred_set ? 0 : -1; +} + + +int cmd_sim_prov(struct hs20_osu_client *ctx, const char *url) +{ + if (url == NULL) { + wpa_printf(MSG_INFO, "Invalid prov command (missing URL)"); + return -1; + } + + wpa_printf(MSG_INFO, "SIM provisioning requested"); + + os_free(ctx->server_url); + ctx->server_url = os_strdup(url); + + wpa_printf(MSG_INFO, "Wait for IP address before starting SIM provisioning"); + + if (wait_ip_addr(ctx->ifname, 15) < 0) { + wpa_printf(MSG_INFO, "Could not get IP address for WLAN - try connection anyway"); + } + + if (soap_init_client(ctx->http, url, ctx->ca_fname, NULL, NULL, NULL, + NULL) < 0) + return -1; + spp_post_dev_data(ctx, SPP_SUBSCRIPTION_REGISTRATION, + "Subscription provisioning", NULL, NULL); + + return ctx->pps_cred_set ? 0 : -1; +} diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..d15cf32 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,12 @@ +SUBDIRS=ap common crypto drivers eapol_auth eapol_supp eap_common eap_peer eap_server l2_packet p2p pae pasn radius rsn_supp tls utils wps +SUBDIRS += fst + +all: + for d in $(SUBDIRS); do [ -d $$d ] && $(MAKE) -C $$d; done + +clean: + $(Q)for d in $(SUBDIRS); do [ -d $$d ] && $(MAKE) -C $$d clean; done + $(Q)rm -f *~ + +install: + for d in $(SUBDIRS); do [ -d $$d ] && $(MAKE) -C $$d install; done diff --git a/src/ap/Makefile b/src/ap/Makefile new file mode 100644 index 0000000..a1e9b7c --- /dev/null +++ b/src/ap/Makefile @@ -0,0 +1,60 @@ +CFLAGS += -DHOSTAPD +CFLAGS += -DNEED_AP_MLME +CFLAGS += -DCONFIG_ETH_P_OUI +CFLAGS += -DCONFIG_HS20 +CFLAGS += -DCONFIG_INTERWORKING +CFLAGS += -DCONFIG_IEEE80211R +CFLAGS += -DCONFIG_IEEE80211R_AP +CFLAGS += -DCONFIG_WPS +CFLAGS += -DCONFIG_PROXYARP +CFLAGS += -DCONFIG_IPV6 +CFLAGS += -DCONFIG_AIRTIME_POLICY + +LIB_OBJS= \ + accounting.o \ + ap_config.o \ + ap_drv_ops.o \ + ap_list.o \ + ap_mlme.o \ + airtime_policy.o \ + authsrv.o \ + beacon.o \ + bss_load.o \ + ctrl_iface_ap.o \ + dfs.o \ + dhcp_snoop.o \ + drv_callbacks.o \ + eap_user_db.o \ + eth_p_oui.o \ + gas_serv.o \ + hostapd.o \ + hs20.o \ + hw_features.o \ + ieee802_11_auth.o \ + ieee802_11.o \ + ieee802_11_ht.o \ + ieee802_11_shared.o \ + ieee802_11_vht.o \ + ieee802_1x.o \ + neighbor_db.o \ + ndisc_snoop.o \ + p2p_hostapd.o \ + pmksa_cache_auth.o \ + preauth_auth.o \ + rrm.o \ + sta_info.o \ + tkip_countermeasures.o \ + utils.o \ + vlan.o \ + vlan_ifconfig.o \ + vlan_init.o \ + wmm.o \ + wnm_ap.o \ + wpa_auth.o \ + wpa_auth_ft.o \ + wpa_auth_glue.o \ + wpa_auth_ie.o \ + wps_hostapd.o \ + x_snoop.o + +include ../lib.rules diff --git a/src/ap/accounting.c b/src/ap/accounting.c new file mode 100644 index 0000000..9fc1886 --- /dev/null +++ b/src/ap/accounting.c @@ -0,0 +1,547 @@ +/* + * hostapd / RADIUS Accounting + * Copyright (c) 2002-2009, 2012-2015, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "eapol_auth/eapol_auth_sm_i.h" +#include "radius/radius.h" +#include "radius/radius_client.h" +#include "hostapd.h" +#include "ieee802_1x.h" +#include "ap_config.h" +#include "sta_info.h" +#include "ap_drv_ops.h" +#include "accounting.h" + + +/* Default interval in seconds for polling TX/RX octets from the driver if + * STA is not using interim accounting. This detects wrap arounds for + * input/output octets and updates Acct-{Input,Output}-Gigawords. */ +#define ACCT_DEFAULT_UPDATE_INTERVAL 300 + +static void accounting_sta_interim(struct hostapd_data *hapd, + struct sta_info *sta); + + +static struct radius_msg * accounting_msg(struct hostapd_data *hapd, + struct sta_info *sta, + int status_type) +{ + struct radius_msg *msg; + char buf[128]; + u8 *val; + size_t len; + int i; + struct wpabuf *b; + struct os_time now; + + msg = radius_msg_new(RADIUS_CODE_ACCOUNTING_REQUEST, + radius_client_get_id(hapd->radius)); + if (msg == NULL) { + wpa_printf(MSG_INFO, "Could not create new RADIUS packet"); + return NULL; + } + + if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_STATUS_TYPE, + status_type)) { + wpa_printf(MSG_INFO, "Could not add Acct-Status-Type"); + goto fail; + } + + if (sta) { + if (!hostapd_config_get_radius_attr( + hapd->conf->radius_acct_req_attr, + RADIUS_ATTR_ACCT_AUTHENTIC) && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_AUTHENTIC, + hapd->conf->ieee802_1x ? + RADIUS_ACCT_AUTHENTIC_RADIUS : + RADIUS_ACCT_AUTHENTIC_LOCAL)) { + wpa_printf(MSG_INFO, "Could not add Acct-Authentic"); + goto fail; + } + + /* Use 802.1X identity if available */ + val = ieee802_1x_get_identity(sta->eapol_sm, &len); + + /* Use RADIUS ACL identity if 802.1X provides no identity */ + if (!val && sta->identity) { + val = (u8 *) sta->identity; + len = os_strlen(sta->identity); + } + + /* Use STA MAC if neither 802.1X nor RADIUS ACL provided + * identity */ + if (!val) { + os_snprintf(buf, sizeof(buf), RADIUS_ADDR_FORMAT, + MAC2STR(sta->addr)); + val = (u8 *) buf; + len = os_strlen(buf); + } + + if (!radius_msg_add_attr(msg, RADIUS_ATTR_USER_NAME, val, + len)) { + wpa_printf(MSG_INFO, "Could not add User-Name"); + goto fail; + } + } + + if (add_common_radius_attr(hapd, hapd->conf->radius_acct_req_attr, sta, + msg) < 0) + goto fail; + + if (sta && add_sqlite_radius_attr(hapd, sta, msg, 1) < 0) + goto fail; + + if (sta) { + for (i = 0; ; i++) { + val = ieee802_1x_get_radius_class(sta->eapol_sm, &len, + i); + if (val == NULL) + break; + + if (!radius_msg_add_attr(msg, RADIUS_ATTR_CLASS, + val, len)) { + wpa_printf(MSG_INFO, "Could not add Class"); + goto fail; + } + } + + b = ieee802_1x_get_radius_cui(sta->eapol_sm); + if (b && + !radius_msg_add_attr(msg, + RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, + wpabuf_head(b), wpabuf_len(b))) { + wpa_printf(MSG_ERROR, "Could not add CUI"); + goto fail; + } + + if (!b && sta->radius_cui && + !radius_msg_add_attr(msg, + RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, + (u8 *) sta->radius_cui, + os_strlen(sta->radius_cui))) { + wpa_printf(MSG_ERROR, "Could not add CUI from ACL"); + goto fail; + } + + if (sta->ipaddr && + !radius_msg_add_attr_int32(msg, + RADIUS_ATTR_FRAMED_IP_ADDRESS, + be_to_host32(sta->ipaddr))) { + wpa_printf(MSG_ERROR, + "Could not add Framed-IP-Address"); + goto fail; + } + } + + os_get_time(&now); + if (now.sec > 1000000000 && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_EVENT_TIMESTAMP, + now.sec)) { + wpa_printf(MSG_INFO, "Could not add Event-Timestamp"); + goto fail; + } + + /* + * Add Acct-Delay-Time with zero value for the first transmission. This + * will be updated within radius_client.c when retransmitting the frame. + */ + if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_DELAY_TIME, 0)) { + wpa_printf(MSG_INFO, "Could not add Acct-Delay-Time"); + goto fail; + } + + return msg; + + fail: + radius_msg_free(msg); + return NULL; +} + + +static int accounting_sta_update_stats(struct hostapd_data *hapd, + struct sta_info *sta, + struct hostap_sta_driver_data *data) +{ + if (hostapd_drv_read_sta_data(hapd, data, sta->addr)) + return -1; + + if (!data->bytes_64bit) { + /* Extend 32-bit counters from the driver to 64-bit counters */ + if (sta->last_rx_bytes_lo > data->rx_bytes) + sta->last_rx_bytes_hi++; + sta->last_rx_bytes_lo = data->rx_bytes; + + if (sta->last_tx_bytes_lo > data->tx_bytes) + sta->last_tx_bytes_hi++; + sta->last_tx_bytes_lo = data->tx_bytes; + } + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS, + HOSTAPD_LEVEL_DEBUG, + "updated TX/RX stats: rx_bytes=%llu [%u:%u] tx_bytes=%llu [%u:%u] bytes_64bit=%d", + data->rx_bytes, sta->last_rx_bytes_hi, + sta->last_rx_bytes_lo, + data->tx_bytes, sta->last_tx_bytes_hi, + sta->last_tx_bytes_lo, + data->bytes_64bit); + + return 0; +} + + +static void accounting_interim_update(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + int interval; + + if (sta->acct_interim_interval) { + accounting_sta_interim(hapd, sta); + interval = sta->acct_interim_interval; + } else { + struct hostap_sta_driver_data data; + accounting_sta_update_stats(hapd, sta, &data); + interval = ACCT_DEFAULT_UPDATE_INTERVAL; + } + + eloop_register_timeout(interval, 0, accounting_interim_update, + hapd, sta); +} + + +/** + * accounting_sta_start - Start STA accounting + * @hapd: hostapd BSS data + * @sta: The station + */ +void accounting_sta_start(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct radius_msg *msg; + int interval; + + if (sta->acct_session_started) + return; + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS, + HOSTAPD_LEVEL_INFO, + "starting accounting session %016llX", + (unsigned long long) sta->acct_session_id); + + os_get_reltime(&sta->acct_session_start); + sta->last_rx_bytes_hi = 0; + sta->last_rx_bytes_lo = 0; + sta->last_tx_bytes_hi = 0; + sta->last_tx_bytes_lo = 0; + hostapd_drv_sta_clear_stats(hapd, sta->addr); + + if (!hapd->conf->radius->acct_server) + return; + + if (sta->acct_interim_interval) + interval = sta->acct_interim_interval; + else + interval = ACCT_DEFAULT_UPDATE_INTERVAL; + eloop_register_timeout(interval, 0, accounting_interim_update, + hapd, sta); + + msg = accounting_msg(hapd, sta, RADIUS_ACCT_STATUS_TYPE_START); + if (msg && + radius_client_send(hapd->radius, msg, RADIUS_ACCT, sta->addr) < 0) + radius_msg_free(msg); + + sta->acct_session_started = 1; +} + + +static void accounting_sta_report(struct hostapd_data *hapd, + struct sta_info *sta, int stop) +{ + struct radius_msg *msg; + int cause = sta->acct_terminate_cause; + struct hostap_sta_driver_data data; + struct os_reltime now_r, diff; + u64 bytes; + + if (!hapd->conf->radius->acct_server) + return; + + msg = accounting_msg(hapd, sta, + stop ? RADIUS_ACCT_STATUS_TYPE_STOP : + RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE); + if (!msg) { + wpa_printf(MSG_INFO, "Could not create RADIUS Accounting message"); + return; + } + + os_get_reltime(&now_r); + os_reltime_sub(&now_r, &sta->acct_session_start, &diff); + if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_SESSION_TIME, + diff.sec)) { + wpa_printf(MSG_INFO, "Could not add Acct-Session-Time"); + goto fail; + } + + if (accounting_sta_update_stats(hapd, sta, &data) == 0) { + if (!radius_msg_add_attr_int32(msg, + RADIUS_ATTR_ACCT_INPUT_PACKETS, + data.rx_packets)) { + wpa_printf(MSG_INFO, "Could not add Acct-Input-Packets"); + goto fail; + } + if (!radius_msg_add_attr_int32(msg, + RADIUS_ATTR_ACCT_OUTPUT_PACKETS, + data.tx_packets)) { + wpa_printf(MSG_INFO, "Could not add Acct-Output-Packets"); + goto fail; + } + if (data.bytes_64bit) + bytes = data.rx_bytes; + else + bytes = ((u64) sta->last_rx_bytes_hi << 32) | + sta->last_rx_bytes_lo; + if (!radius_msg_add_attr_int32(msg, + RADIUS_ATTR_ACCT_INPUT_OCTETS, + (u32) bytes)) { + wpa_printf(MSG_INFO, "Could not add Acct-Input-Octets"); + goto fail; + } + if (!radius_msg_add_attr_int32(msg, + RADIUS_ATTR_ACCT_INPUT_GIGAWORDS, + (u32) (bytes >> 32))) { + wpa_printf(MSG_INFO, "Could not add Acct-Input-Gigawords"); + goto fail; + } + if (data.bytes_64bit) + bytes = data.tx_bytes; + else + bytes = ((u64) sta->last_tx_bytes_hi << 32) | + sta->last_tx_bytes_lo; + if (!radius_msg_add_attr_int32(msg, + RADIUS_ATTR_ACCT_OUTPUT_OCTETS, + (u32) bytes)) { + wpa_printf(MSG_INFO, "Could not add Acct-Output-Octets"); + goto fail; + } + if (!radius_msg_add_attr_int32(msg, + RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS, + (u32) (bytes >> 32))) { + wpa_printf(MSG_INFO, "Could not add Acct-Output-Gigawords"); + goto fail; + } + } + + if (eloop_terminated()) + cause = RADIUS_ACCT_TERMINATE_CAUSE_ADMIN_REBOOT; + + if (stop && cause && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_TERMINATE_CAUSE, + cause)) { + wpa_printf(MSG_INFO, "Could not add Acct-Terminate-Cause"); + goto fail; + } + + if (radius_client_send(hapd->radius, msg, + stop ? RADIUS_ACCT : RADIUS_ACCT_INTERIM, + sta->addr) < 0) + goto fail; + return; + + fail: + radius_msg_free(msg); +} + + +/** + * accounting_sta_interim - Send a interim STA accounting report + * @hapd: hostapd BSS data + * @sta: The station + */ +static void accounting_sta_interim(struct hostapd_data *hapd, + struct sta_info *sta) +{ + if (sta->acct_session_started) + accounting_sta_report(hapd, sta, 0); +} + + +/** + * accounting_sta_stop - Stop STA accounting + * @hapd: hostapd BSS data + * @sta: The station + */ +void accounting_sta_stop(struct hostapd_data *hapd, struct sta_info *sta) +{ + if (sta->acct_session_started) { + accounting_sta_report(hapd, sta, 1); + eloop_cancel_timeout(accounting_interim_update, hapd, sta); + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS, + HOSTAPD_LEVEL_INFO, + "stopped accounting session %016llX", + (unsigned long long) sta->acct_session_id); + sta->acct_session_started = 0; + } +} + + +int accounting_sta_get_id(struct hostapd_data *hapd, struct sta_info *sta) +{ + return radius_gen_session_id((u8 *) &sta->acct_session_id, + sizeof(sta->acct_session_id)); +} + + +/** + * accounting_receive - Process the RADIUS frames from Accounting Server + * @msg: RADIUS response message + * @req: RADIUS request message + * @shared_secret: RADIUS shared secret + * @shared_secret_len: Length of shared_secret in octets + * @data: Context data (struct hostapd_data *) + * Returns: Processing status + */ +static RadiusRxResult +accounting_receive(struct radius_msg *msg, struct radius_msg *req, + const u8 *shared_secret, size_t shared_secret_len, + void *data) +{ + if (radius_msg_get_hdr(msg)->code != RADIUS_CODE_ACCOUNTING_RESPONSE) { + wpa_printf(MSG_INFO, "Unknown RADIUS message code"); + return RADIUS_RX_UNKNOWN; + } + + if (radius_msg_verify(msg, shared_secret, shared_secret_len, req, 0)) { + wpa_printf(MSG_INFO, "Incoming RADIUS packet did not have correct Authenticator - dropped"); + return RADIUS_RX_INVALID_AUTHENTICATOR; + } + + return RADIUS_RX_PROCESSED; +} + + +static void accounting_report_state(struct hostapd_data *hapd, int on) +{ + struct radius_msg *msg; + + if (!hapd->conf->radius->acct_server || hapd->radius == NULL) + return; + + /* Inform RADIUS server that accounting will start/stop so that the + * server can close old accounting sessions. */ + msg = accounting_msg(hapd, NULL, + on ? RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON : + RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_OFF); + if (!msg) + return; + + if (hapd->acct_session_id) { + char buf[20]; + + os_snprintf(buf, sizeof(buf), "%016llX", + (unsigned long long) hapd->acct_session_id); + if (!radius_msg_add_attr(msg, RADIUS_ATTR_ACCT_SESSION_ID, + (u8 *) buf, os_strlen(buf))) + wpa_printf(MSG_ERROR, "Could not add Acct-Session-Id"); + } + + if (radius_client_send(hapd->radius, msg, RADIUS_ACCT, NULL) < 0) + radius_msg_free(msg); +} + + +static void accounting_interim_error_cb(const u8 *addr, void *ctx) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + unsigned int i, wait_time; + int res; + + sta = ap_get_sta(hapd, addr); + if (!sta) + return; + sta->acct_interim_errors++; + if (sta->acct_interim_errors > 10 /* RADIUS_CLIENT_MAX_RETRIES */) { + wpa_printf(MSG_DEBUG, + "Interim RADIUS accounting update failed for " MACSTR + " - too many errors, abandon this interim accounting update", + MAC2STR(addr)); + sta->acct_interim_errors = 0; + /* Next update will be tried after normal update interval */ + return; + } + + /* + * Use a shorter update interval as an improved retransmission mechanism + * for failed interim accounting updates. This allows the statistics to + * be updated for each retransmission. + * + * RADIUS client code has already waited RADIUS_CLIENT_FIRST_WAIT. + * Schedule the first retry attempt immediately and every following one + * with exponential backoff. + */ + if (sta->acct_interim_errors == 1) { + wait_time = 0; + } else { + wait_time = 3; /* RADIUS_CLIENT_FIRST_WAIT */ + for (i = 1; i < sta->acct_interim_errors; i++) + wait_time *= 2; + } + res = eloop_deplete_timeout(wait_time, 0, accounting_interim_update, + hapd, sta); + if (res == 1) + wpa_printf(MSG_DEBUG, + "Interim RADIUS accounting update failed for " MACSTR + " (error count: %u) - schedule next update in %u seconds", + MAC2STR(addr), sta->acct_interim_errors, wait_time); + else if (res == 0) + wpa_printf(MSG_DEBUG, + "Interim RADIUS accounting update failed for " MACSTR + " (error count: %u)", MAC2STR(addr), + sta->acct_interim_errors); + else + wpa_printf(MSG_DEBUG, + "Interim RADIUS accounting update failed for " MACSTR + " (error count: %u) - no timer found", MAC2STR(addr), + sta->acct_interim_errors); +} + + +/** + * accounting_init: Initialize accounting + * @hapd: hostapd BSS data + * Returns: 0 on success, -1 on failure + */ +int accounting_init(struct hostapd_data *hapd) +{ + if (radius_gen_session_id((u8 *) &hapd->acct_session_id, + sizeof(hapd->acct_session_id)) < 0) + return -1; + + if (radius_client_register(hapd->radius, RADIUS_ACCT, + accounting_receive, hapd)) + return -1; + radius_client_set_interim_error_cb(hapd->radius, + accounting_interim_error_cb, hapd); + + accounting_report_state(hapd, 1); + + return 0; +} + + +/** + * accounting_deinit: Deinitialize accounting + * @hapd: hostapd BSS data + */ +void accounting_deinit(struct hostapd_data *hapd) +{ + accounting_report_state(hapd, 0); +} diff --git a/src/ap/accounting.h b/src/ap/accounting.h new file mode 100644 index 0000000..de5a33f --- /dev/null +++ b/src/ap/accounting.h @@ -0,0 +1,45 @@ +/* + * hostapd / RADIUS Accounting + * Copyright (c) 2002-2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef ACCOUNTING_H +#define ACCOUNTING_H + +#ifdef CONFIG_NO_ACCOUNTING +static inline int accounting_sta_get_id(struct hostapd_data *hapd, + struct sta_info *sta) +{ + return 0; +} + +static inline void accounting_sta_start(struct hostapd_data *hapd, + struct sta_info *sta) +{ +} + +static inline void accounting_sta_stop(struct hostapd_data *hapd, + struct sta_info *sta) +{ +} + +static inline int accounting_init(struct hostapd_data *hapd) +{ + return 0; +} + +static inline void accounting_deinit(struct hostapd_data *hapd) +{ +} +#else /* CONFIG_NO_ACCOUNTING */ +int accounting_sta_get_id(struct hostapd_data *hapd, struct sta_info *sta); +void accounting_sta_start(struct hostapd_data *hapd, struct sta_info *sta); +void accounting_sta_stop(struct hostapd_data *hapd, struct sta_info *sta); +int accounting_init(struct hostapd_data *hapd); +void accounting_deinit(struct hostapd_data *hapd); +#endif /* CONFIG_NO_ACCOUNTING */ + +#endif /* ACCOUNTING_H */ diff --git a/src/ap/acs.c b/src/ap/acs.c new file mode 100644 index 0000000..f5b36d3 --- /dev/null +++ b/src/ap/acs.c @@ -0,0 +1,1518 @@ +/* + * ACS - Automatic Channel Selection module + * Copyright (c) 2011, Atheros Communications + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include + +#include "utils/common.h" +#include "utils/list.h" +#include "utils/eloop.h" +#include "common/ieee802_11_defs.h" +#include "common/hw_features_common.h" +#include "common/wpa_ctrl.h" +#include "drivers/driver.h" +#include "hostapd.h" +#include "ap_drv_ops.h" +#include "ap_config.h" +#include "hw_features.h" +#include "acs.h" + +/* + * Automatic Channel Selection + * =========================== + * + * More info at + * ------------ + * http://wireless.kernel.org/en/users/Documentation/acs + * + * How to use + * ---------- + * - make sure you have CONFIG_ACS=y in hostapd's .config + * - use channel=0 or channel=acs to enable ACS + * + * How does it work + * ---------------- + * 1. passive scans are used to collect survey data + * (it is assumed that scan trigger collection of survey data in driver) + * 2. interference factor is calculated for each channel + * 3. ideal channel is picked depending on channel width by using adjacent + * channel interference factors + * + * Known limitations + * ----------------- + * - Current implementation depends heavily on the amount of time willing to + * spend gathering survey data during hostapd startup. Short traffic bursts + * may be missed and a suboptimal channel may be picked. + * - Ideal channel may end up overlapping a channel with 40 MHz intolerant BSS + * + * Todo / Ideas + * ------------ + * - implement other interference computation methods + * - BSS/RSSI based + * - spectral scan based + * (should be possibly to hook this up with current ACS scans) + * - add wpa_supplicant support (for P2P) + * - collect a histogram of interference over time allowing more educated + * guess about an ideal channel (perhaps CSA could be used to migrate AP to a + * new "better" channel while running) + * - include neighboring BSS scan to avoid conflicts with 40 MHz intolerant BSSs + * when choosing the ideal channel + * + * Survey interference factor implementation details + * ------------------------------------------------- + * Generic interference_factor in struct hostapd_channel_data is used. + * + * The survey interference factor is defined as the ratio of the + * observed busy time over the time we spent on the channel, + * this value is then amplified by the observed noise floor on + * the channel in comparison to the lowest noise floor observed + * on the entire band. + * + * This corresponds to: + * --- + * (busy time - tx time) / (active time - tx time) * 2^(chan_nf - band_min_nf) + * --- + * + * The coefficient of 2 reflects the way power in "far-field" + * radiation decreases as the square of distance from the antenna [1]. + * What this does is it decreases the observed busy time ratio if the + * noise observed was low but increases it if the noise was high, + * proportionally to the way "far field" radiation changes over + * distance. + * + * If channel busy time is not available the fallback is to use channel RX time. + * + * Since noise floor is in dBm it is necessary to convert it into Watts so that + * combined channel interference (e.g., HT40, which uses two channels) can be + * calculated easily. + * --- + * (busy time - tx time) / (active time - tx time) * + * 2^(10^(chan_nf/10) - 10^(band_min_nf/10)) + * --- + * + * However to account for cases where busy/rx time is 0 (channel load is then + * 0%) channel noise floor signal power is combined into the equation so a + * channel with lower noise floor is preferred. The equation becomes: + * --- + * 10^(chan_nf/5) + (busy time - tx time) / (active time - tx time) * + * 2^(10^(chan_nf/10) - 10^(band_min_nf/10)) + * --- + * + * All this "interference factor" is purely subjective and only time + * will tell how usable this is. By using the minimum noise floor we + * remove any possible issues due to card calibration. The computation + * of the interference factor then is dependent on what the card itself + * picks up as the minimum noise, not an actual real possible card + * noise value. + * + * Total interference computation details + * -------------------------------------- + * The above channel interference factor is calculated with no respect to + * target operational bandwidth. + * + * To find an ideal channel the above data is combined by taking into account + * the target operational bandwidth and selected band. E.g., on 2.4 GHz channels + * overlap with 20 MHz bandwidth, but there is no overlap for 20 MHz bandwidth + * on 5 GHz. + * + * Each valid and possible channel spec (i.e., channel + width) is taken and its + * interference factor is computed by summing up interferences of each channel + * it overlaps. The one with least total interference is picked up. + * + * Note: This implies base channel interference factor must be non-negative + * allowing easy summing up. + * + * Example ACS analysis printout + * ----------------------------- + * + * ACS: Trying survey-based ACS + * ACS: Survey analysis for channel 1 (2412 MHz) + * ACS: 1: min_nf=-113 interference_factor=0.0802469 nf=-113 time=162 busy=0 rx=13 + * ACS: 2: min_nf=-113 interference_factor=0.0745342 nf=-113 time=161 busy=0 rx=12 + * ACS: 3: min_nf=-113 interference_factor=0.0679012 nf=-113 time=162 busy=0 rx=11 + * ACS: 4: min_nf=-113 interference_factor=0.0310559 nf=-113 time=161 busy=0 rx=5 + * ACS: 5: min_nf=-113 interference_factor=0.0248447 nf=-113 time=161 busy=0 rx=4 + * ACS: * interference factor average: 0.0557166 + * ACS: Survey analysis for channel 2 (2417 MHz) + * ACS: 1: min_nf=-113 interference_factor=0.0185185 nf=-113 time=162 busy=0 rx=3 + * ACS: 2: min_nf=-113 interference_factor=0.0246914 nf=-113 time=162 busy=0 rx=4 + * ACS: 3: min_nf=-113 interference_factor=0.037037 nf=-113 time=162 busy=0 rx=6 + * ACS: 4: min_nf=-113 interference_factor=0.149068 nf=-113 time=161 busy=0 rx=24 + * ACS: 5: min_nf=-113 interference_factor=0.0248447 nf=-113 time=161 busy=0 rx=4 + * ACS: * interference factor average: 0.050832 + * ACS: Survey analysis for channel 3 (2422 MHz) + * ACS: 1: min_nf=-113 interference_factor=2.51189e-23 nf=-113 time=162 busy=0 rx=0 + * ACS: 2: min_nf=-113 interference_factor=0.0185185 nf=-113 time=162 busy=0 rx=3 + * ACS: 3: min_nf=-113 interference_factor=0.0186335 nf=-113 time=161 busy=0 rx=3 + * ACS: 4: min_nf=-113 interference_factor=0.0186335 nf=-113 time=161 busy=0 rx=3 + * ACS: 5: min_nf=-113 interference_factor=0.0186335 nf=-113 time=161 busy=0 rx=3 + * ACS: * interference factor average: 0.0148838 + * ACS: Survey analysis for channel 4 (2427 MHz) + * ACS: 1: min_nf=-114 interference_factor=1.58489e-23 nf=-114 time=162 busy=0 rx=0 + * ACS: 2: min_nf=-114 interference_factor=0.0555556 nf=-114 time=162 busy=0 rx=9 + * ACS: 3: min_nf=-114 interference_factor=1.58489e-23 nf=-114 time=161 busy=0 rx=0 + * ACS: 4: min_nf=-114 interference_factor=0.0186335 nf=-114 time=161 busy=0 rx=3 + * ACS: 5: min_nf=-114 interference_factor=0.00621118 nf=-114 time=161 busy=0 rx=1 + * ACS: * interference factor average: 0.0160801 + * ACS: Survey analysis for channel 5 (2432 MHz) + * ACS: 1: min_nf=-114 interference_factor=0.409938 nf=-113 time=161 busy=0 rx=66 + * ACS: 2: min_nf=-114 interference_factor=0.0432099 nf=-113 time=162 busy=0 rx=7 + * ACS: 3: min_nf=-114 interference_factor=0.0124224 nf=-113 time=161 busy=0 rx=2 + * ACS: 4: min_nf=-114 interference_factor=0.677019 nf=-113 time=161 busy=0 rx=109 + * ACS: 5: min_nf=-114 interference_factor=0.0186335 nf=-114 time=161 busy=0 rx=3 + * ACS: * interference factor average: 0.232244 + * ACS: Survey analysis for channel 6 (2437 MHz) + * ACS: 1: min_nf=-113 interference_factor=0.552795 nf=-113 time=161 busy=0 rx=89 + * ACS: 2: min_nf=-113 interference_factor=0.0807453 nf=-112 time=161 busy=0 rx=13 + * ACS: 3: min_nf=-113 interference_factor=0.0310559 nf=-113 time=161 busy=0 rx=5 + * ACS: 4: min_nf=-113 interference_factor=0.434783 nf=-112 time=161 busy=0 rx=70 + * ACS: 5: min_nf=-113 interference_factor=0.0621118 nf=-113 time=161 busy=0 rx=10 + * ACS: * interference factor average: 0.232298 + * ACS: Survey analysis for channel 7 (2442 MHz) + * ACS: 1: min_nf=-113 interference_factor=0.440994 nf=-112 time=161 busy=0 rx=71 + * ACS: 2: min_nf=-113 interference_factor=0.385093 nf=-113 time=161 busy=0 rx=62 + * ACS: 3: min_nf=-113 interference_factor=0.0372671 nf=-113 time=161 busy=0 rx=6 + * ACS: 4: min_nf=-113 interference_factor=0.0372671 nf=-113 time=161 busy=0 rx=6 + * ACS: 5: min_nf=-113 interference_factor=0.0745342 nf=-113 time=161 busy=0 rx=12 + * ACS: * interference factor average: 0.195031 + * ACS: Survey analysis for channel 8 (2447 MHz) + * ACS: 1: min_nf=-114 interference_factor=0.0496894 nf=-112 time=161 busy=0 rx=8 + * ACS: 2: min_nf=-114 interference_factor=0.0496894 nf=-114 time=161 busy=0 rx=8 + * ACS: 3: min_nf=-114 interference_factor=0.0372671 nf=-113 time=161 busy=0 rx=6 + * ACS: 4: min_nf=-114 interference_factor=0.12963 nf=-113 time=162 busy=0 rx=21 + * ACS: 5: min_nf=-114 interference_factor=0.166667 nf=-114 time=162 busy=0 rx=27 + * ACS: * interference factor average: 0.0865885 + * ACS: Survey analysis for channel 9 (2452 MHz) + * ACS: 1: min_nf=-114 interference_factor=0.0124224 nf=-114 time=161 busy=0 rx=2 + * ACS: 2: min_nf=-114 interference_factor=0.0310559 nf=-114 time=161 busy=0 rx=5 + * ACS: 3: min_nf=-114 interference_factor=1.58489e-23 nf=-114 time=161 busy=0 rx=0 + * ACS: 4: min_nf=-114 interference_factor=0.00617284 nf=-114 time=162 busy=0 rx=1 + * ACS: 5: min_nf=-114 interference_factor=1.58489e-23 nf=-114 time=162 busy=0 rx=0 + * ACS: * interference factor average: 0.00993022 + * ACS: Survey analysis for channel 10 (2457 MHz) + * ACS: 1: min_nf=-114 interference_factor=0.00621118 nf=-114 time=161 busy=0 rx=1 + * ACS: 2: min_nf=-114 interference_factor=0.00621118 nf=-114 time=161 busy=0 rx=1 + * ACS: 3: min_nf=-114 interference_factor=0.00621118 nf=-114 time=161 busy=0 rx=1 + * ACS: 4: min_nf=-114 interference_factor=0.0493827 nf=-114 time=162 busy=0 rx=8 + * ACS: 5: min_nf=-114 interference_factor=1.58489e-23 nf=-114 time=162 busy=0 rx=0 + * ACS: * interference factor average: 0.0136033 + * ACS: Survey analysis for channel 11 (2462 MHz) + * ACS: 1: min_nf=-114 interference_factor=1.58489e-23 nf=-114 time=161 busy=0 rx=0 + * ACS: 2: min_nf=-114 interference_factor=2.51189e-23 nf=-113 time=161 busy=0 rx=0 + * ACS: 3: min_nf=-114 interference_factor=2.51189e-23 nf=-113 time=161 busy=0 rx=0 + * ACS: 4: min_nf=-114 interference_factor=0.0432099 nf=-114 time=162 busy=0 rx=7 + * ACS: 5: min_nf=-114 interference_factor=0.0925926 nf=-114 time=162 busy=0 rx=15 + * ACS: * interference factor average: 0.0271605 + * ACS: Survey analysis for channel 12 (2467 MHz) + * ACS: 1: min_nf=-114 interference_factor=0.0621118 nf=-113 time=161 busy=0 rx=10 + * ACS: 2: min_nf=-114 interference_factor=0.00621118 nf=-114 time=161 busy=0 rx=1 + * ACS: 3: min_nf=-114 interference_factor=2.51189e-23 nf=-113 time=162 busy=0 rx=0 + * ACS: 4: min_nf=-114 interference_factor=2.51189e-23 nf=-113 time=162 busy=0 rx=0 + * ACS: 5: min_nf=-114 interference_factor=0.00617284 nf=-113 time=162 busy=0 rx=1 + * ACS: * interference factor average: 0.0148992 + * ACS: Survey analysis for channel 13 (2472 MHz) + * ACS: 1: min_nf=-114 interference_factor=0.0745342 nf=-114 time=161 busy=0 rx=12 + * ACS: 2: min_nf=-114 interference_factor=0.0555556 nf=-114 time=162 busy=0 rx=9 + * ACS: 3: min_nf=-114 interference_factor=1.58489e-23 nf=-114 time=162 busy=0 rx=0 + * ACS: 4: min_nf=-114 interference_factor=1.58489e-23 nf=-114 time=162 busy=0 rx=0 + * ACS: 5: min_nf=-114 interference_factor=1.58489e-23 nf=-114 time=162 busy=0 rx=0 + * ACS: * interference factor average: 0.0260179 + * ACS: Survey analysis for selected bandwidth 20MHz + * ACS: * channel 1: total interference = 0.121432 + * ACS: * channel 2: total interference = 0.137512 + * ACS: * channel 3: total interference = 0.369757 + * ACS: * channel 4: total interference = 0.546338 + * ACS: * channel 5: total interference = 0.690538 + * ACS: * channel 6: total interference = 0.762242 + * ACS: * channel 7: total interference = 0.756092 + * ACS: * channel 8: total interference = 0.537451 + * ACS: * channel 9: total interference = 0.332313 + * ACS: * channel 10: total interference = 0.152182 + * ACS: * channel 11: total interference = 0.0916111 + * ACS: * channel 12: total interference = 0.0816809 + * ACS: * channel 13: total interference = 0.0680776 + * ACS: Ideal channel is 13 (2472 MHz) with total interference factor of 0.0680776 + * + * [1] http://en.wikipedia.org/wiki/Near_and_far_field + */ + +enum bw_type { + ACS_BW40, + ACS_BW80, + ACS_BW160, + ACS_BW320_1, + ACS_BW320_2, +}; + +struct bw_item { + int first; + int last; + int center_chan; +}; + +static const struct bw_item bw_40[] = { + { 5180, 5200, 38 }, { 5220, 5240, 46 }, { 5260, 5280, 54 }, + { 5300, 5320, 62 }, { 5500, 5520, 102 }, { 5540, 5560, 110 }, + { 5580, 5600, 118 }, { 5620, 5640, 126 }, { 5660, 5680, 134 }, + { 5700, 5720, 142 }, { 5745, 5765, 151 }, { 5785, 5805, 159 }, + { 5825, 5845, 167 }, { 5865, 5885, 175 }, + { 5955, 5975, 3 }, { 5995, 6015, 11 }, { 6035, 6055, 19 }, + { 6075, 6095, 27 }, { 6115, 6135, 35 }, { 6155, 6175, 43 }, + { 6195, 6215, 51 }, { 6235, 6255, 59 }, { 6275, 6295, 67 }, + { 6315, 6335, 75 }, { 6355, 6375, 83 }, { 6395, 6415, 91 }, + { 6435, 6455, 99 }, { 6475, 6495, 107 }, { 6515, 6535, 115 }, + { 6555, 6575, 123 }, { 6595, 6615, 131 }, { 6635, 6655, 139 }, + { 6675, 6695, 147 }, { 6715, 6735, 155 }, { 6755, 6775, 163 }, + { 6795, 6815, 171 }, { 6835, 6855, 179 }, { 6875, 6895, 187 }, + { 6915, 6935, 195 }, { 6955, 6975, 203 }, { 6995, 7015, 211 }, + { 7035, 7055, 219 }, { 7075, 7095, 227}, { -1, -1, -1 } +}; +static const struct bw_item bw_80[] = { + { 5180, 5240, 42 }, { 5260, 5320, 58 }, { 5500, 5560, 106 }, + { 5580, 5640, 122 }, { 5660, 5720, 138 }, { 5745, 5805, 155 }, + { 5825, 5885, 171}, + { 5955, 6015, 7 }, { 6035, 6095, 23 }, { 6115, 6175, 39 }, + { 6195, 6255, 55 }, { 6275, 6335, 71 }, { 6355, 6415, 87 }, + { 6435, 6495, 103 }, { 6515, 6575, 119 }, { 6595, 6655, 135 }, + { 6675, 6735, 151 }, { 6755, 6815, 167 }, { 6835, 6895, 183 }, + { 6915, 6975, 199 }, { 6995, 7055, 215 }, { -1, -1, -1 } +}; +static const struct bw_item bw_160[] = { + { 5180, 5320, 50 }, { 5500, 5640, 114 }, { 5745, 5885, 163 }, + { 5955, 6095, 15 }, { 6115, 6255, 47 }, { 6275, 6415, 79 }, + { 6435, 6575, 111 }, { 6595, 6735, 143 }, + { 6755, 6895, 175 }, { 6915, 7055, 207 }, { -1, -1, -1 } +}; +static const struct bw_item bw_320_1[] = { + { 5955, 6255, 31 }, { 6275, 6575, 95 }, { 6595, 6895, 159 }, + { -1, -1, -1 } +}; +static const struct bw_item bw_320_2[] = { + { 6115, 6415, 63 }, { 6435, 6735, 127 }, { 6755, 7055, 191 }, + { -1, -1, -1 } +}; +static const struct bw_item *bw_desc[] = { + [ACS_BW40] = bw_40, + [ACS_BW80] = bw_80, + [ACS_BW160] = bw_160, + [ACS_BW320_1] = bw_320_1, + [ACS_BW320_2] = bw_320_2, +}; + + +static int acs_request_scan(struct hostapd_iface *iface); +static int acs_survey_is_sufficient(struct freq_survey *survey); +static void acs_scan_retry(void *eloop_data, void *user_data); + + +static void acs_clean_chan_surveys(struct hostapd_channel_data *chan) +{ + struct freq_survey *survey, *tmp; + + if (dl_list_empty(&chan->survey_list)) + return; + + dl_list_for_each_safe(survey, tmp, &chan->survey_list, + struct freq_survey, list) { + dl_list_del(&survey->list); + os_free(survey); + } +} + + +static void acs_cleanup_mode(struct hostapd_hw_modes *mode) +{ + int i; + struct hostapd_channel_data *chan; + + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + + if (chan->flag & HOSTAPD_CHAN_SURVEY_LIST_INITIALIZED) + acs_clean_chan_surveys(chan); + + dl_list_init(&chan->survey_list); + chan->flag |= HOSTAPD_CHAN_SURVEY_LIST_INITIALIZED; + chan->min_nf = 0; + chan->punct_bitmap = 0; + } +} + + +void acs_cleanup(struct hostapd_iface *iface) +{ + int i; + + for (i = 0; i < iface->num_hw_features; i++) + acs_cleanup_mode(&iface->hw_features[i]); + + iface->chans_surveyed = 0; + iface->acs_num_completed_scans = 0; + iface->acs_num_retries = 0; + eloop_cancel_timeout(acs_scan_retry, iface, NULL); +} + + +static void acs_fail(struct hostapd_iface *iface) +{ + wpa_printf(MSG_ERROR, "ACS: Failed to start"); + acs_cleanup(iface); + hostapd_disable_iface(iface); +} + + +static long double +acs_survey_interference_factor(struct freq_survey *survey, s8 min_nf) +{ + long double factor, busy, total; + + if (survey->filled & SURVEY_HAS_CHAN_TIME_BUSY) + busy = survey->channel_time_busy; + else if (survey->filled & SURVEY_HAS_CHAN_TIME_RX) + busy = survey->channel_time_rx; + else { + wpa_printf(MSG_ERROR, "ACS: Survey data missing"); + return 0; + } + + total = survey->channel_time; + + if (survey->filled & SURVEY_HAS_CHAN_TIME_TX) { + busy -= survey->channel_time_tx; + total -= survey->channel_time_tx; + } + + /* TODO: figure out the best multiplier for noise floor base */ + factor = pow(10, survey->nf / 5.0L) + + (total ? (busy / total) : 0) * + pow(2, pow(10, (long double) survey->nf / 10.0L) - + pow(10, (long double) min_nf / 10.0L)); + + return factor; +} + + +static void +acs_survey_chan_interference_factor(struct hostapd_iface *iface, + struct hostapd_channel_data *chan) +{ + struct freq_survey *survey; + unsigned int i = 0; + long double int_factor = 0; + unsigned count = 0; + + if (dl_list_empty(&chan->survey_list) || + (chan->flag & HOSTAPD_CHAN_DISABLED)) + return; + + chan->interference_factor = 0; + + dl_list_for_each(survey, &chan->survey_list, struct freq_survey, list) + { + i++; + + if (!acs_survey_is_sufficient(survey)) { + wpa_printf(MSG_DEBUG, "ACS: %d: insufficient data", i); + continue; + } + + count++; + int_factor = acs_survey_interference_factor(survey, + iface->lowest_nf); + chan->interference_factor += int_factor; + wpa_printf(MSG_DEBUG, "ACS: %d: min_nf=%d interference_factor=%Lg nf=%d time=%lu busy=%lu rx=%lu", + i, chan->min_nf, int_factor, + survey->nf, (unsigned long) survey->channel_time, + (unsigned long) survey->channel_time_busy, + (unsigned long) survey->channel_time_rx); + } + + if (count) + chan->interference_factor /= count; +} + + +static bool acs_usable_bw_chan(const struct hostapd_channel_data *chan, + enum bw_type bw) +{ + unsigned int i = 0; + + while (bw_desc[bw][i].first != -1) { + if (chan->freq == bw_desc[bw][i].first) + return true; + i++; + } + + return false; +} + + +static int acs_get_bw_center_chan(int freq, enum bw_type bw) +{ + unsigned int i = 0; + + while (bw_desc[bw][i].first != -1) { + if (freq >= bw_desc[bw][i].first && + freq <= bw_desc[bw][i].last) + return bw_desc[bw][i].center_chan; + i++; + } + + return 0; +} + + +static int acs_survey_is_sufficient(struct freq_survey *survey) +{ + if (!(survey->filled & SURVEY_HAS_NF)) { + wpa_printf(MSG_INFO, + "ACS: Survey for freq %d is missing noise floor", + survey->freq); + return 0; + } + + if (!(survey->filled & SURVEY_HAS_CHAN_TIME)) { + wpa_printf(MSG_INFO, + "ACS: Survey for freq %d is missing channel time", + survey->freq); + return 0; + } + + if (!(survey->filled & SURVEY_HAS_CHAN_TIME_BUSY) && + !(survey->filled & SURVEY_HAS_CHAN_TIME_RX)) { + wpa_printf(MSG_INFO, + "ACS: Survey for freq %d is missing RX and busy time (at least one is required)", + survey->freq); + return 0; + } + + return 1; +} + + +static int acs_survey_list_is_sufficient(struct hostapd_channel_data *chan) +{ + struct freq_survey *survey; + int ret = -1; + + dl_list_for_each(survey, &chan->survey_list, struct freq_survey, list) + { + if (acs_survey_is_sufficient(survey)) { + ret = 1; + break; + } + ret = 0; + } + + if (ret == -1) + ret = 0; /* no survey list entries */ + + if (!ret) { + wpa_printf(MSG_INFO, + "ACS: Channel %d has insufficient survey data", + chan->chan); + } + + return ret; +} + + +static int acs_surveys_are_sufficient_mode(struct hostapd_hw_modes *mode) +{ + int i; + struct hostapd_channel_data *chan; + + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + if (!(chan->flag & HOSTAPD_CHAN_DISABLED) && + acs_survey_list_is_sufficient(chan)) + return 1; + } + + return 0; +} + + +static int acs_surveys_are_sufficient(struct hostapd_iface *iface) +{ + int i; + struct hostapd_hw_modes *mode; + + for (i = 0; i < iface->num_hw_features; i++) { + mode = &iface->hw_features[i]; + if (!hostapd_hw_skip_mode(iface, mode) && + acs_surveys_are_sufficient_mode(mode)) + return 1; + } + + return 0; +} + + +static int acs_usable_chan(struct hostapd_channel_data *chan) +{ + return !dl_list_empty(&chan->survey_list) && + !(chan->flag & HOSTAPD_CHAN_DISABLED) && + acs_survey_list_is_sufficient(chan); +} + + +static int is_in_chanlist(struct hostapd_iface *iface, + struct hostapd_channel_data *chan) +{ + if (!iface->conf->acs_ch_list.num) + return 1; + + return freq_range_list_includes(&iface->conf->acs_ch_list, chan->chan); +} + + +static int is_in_freqlist(struct hostapd_iface *iface, + struct hostapd_channel_data *chan) +{ + if (!iface->conf->acs_freq_list.num) + return 1; + + return freq_range_list_includes(&iface->conf->acs_freq_list, + chan->freq); +} + + +static void acs_survey_mode_interference_factor( + struct hostapd_iface *iface, struct hostapd_hw_modes *mode) +{ + int i; + struct hostapd_channel_data *chan; + + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + + if (!acs_usable_chan(chan)) + continue; + + if ((chan->flag & HOSTAPD_CHAN_RADAR) && + iface->conf->acs_exclude_dfs) + continue; + + if (!is_in_chanlist(iface, chan)) + continue; + + if (!is_in_freqlist(iface, chan)) + continue; + + if (chan->max_tx_power < iface->conf->min_tx_power) + continue; + + if ((chan->flag & HOSTAPD_CHAN_INDOOR_ONLY) && + iface->conf->country[2] == 0x4f) + continue; + + wpa_printf(MSG_DEBUG, "ACS: Survey analysis for channel %d (%d MHz)", + chan->chan, chan->freq); + + acs_survey_chan_interference_factor(iface, chan); + + wpa_printf(MSG_DEBUG, "ACS: * interference factor average: %Lg", + chan->interference_factor); + } +} + + +static void acs_survey_all_chans_interference_factor( + struct hostapd_iface *iface) +{ + int i; + struct hostapd_hw_modes *mode; + + for (i = 0; i < iface->num_hw_features; i++) { + mode = &iface->hw_features[i]; + if (!hostapd_hw_skip_mode(iface, mode)) + acs_survey_mode_interference_factor(iface, mode); + } +} + + +static struct hostapd_channel_data * +acs_find_chan_mode(struct hostapd_hw_modes *mode, int freq) +{ + struct hostapd_channel_data *chan; + int i; + + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + + if (chan->flag & HOSTAPD_CHAN_DISABLED) + continue; + + if (chan->freq == freq) + return chan; + } + + return NULL; +} + + +static enum hostapd_hw_mode +acs_find_mode(struct hostapd_iface *iface, int freq) +{ + int i; + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *chan; + + for (i = 0; i < iface->num_hw_features; i++) { + mode = &iface->hw_features[i]; + if (!hostapd_hw_skip_mode(iface, mode)) { + chan = acs_find_chan_mode(mode, freq); + if (chan) + return mode->mode; + } + } + + return HOSTAPD_MODE_IEEE80211ANY; +} + + +static struct hostapd_channel_data * +acs_find_chan(struct hostapd_iface *iface, int freq) +{ + int i; + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *chan; + + for (i = 0; i < iface->num_hw_features; i++) { + mode = &iface->hw_features[i]; + if (!hostapd_hw_skip_mode(iface, mode)) { + chan = acs_find_chan_mode(mode, freq); + if (chan) + return chan; + } + } + + return NULL; +} + + +static int is_24ghz_mode(enum hostapd_hw_mode mode) +{ + return mode == HOSTAPD_MODE_IEEE80211B || + mode == HOSTAPD_MODE_IEEE80211G; +} + + +static int is_common_24ghz_chan(int chan) +{ + return chan == 1 || chan == 6 || chan == 11; +} + + +#ifndef ACS_ADJ_WEIGHT +#define ACS_ADJ_WEIGHT 0.85 +#endif /* ACS_ADJ_WEIGHT */ + +#ifndef ACS_NEXT_ADJ_WEIGHT +#define ACS_NEXT_ADJ_WEIGHT 0.55 +#endif /* ACS_NEXT_ADJ_WEIGHT */ + +#ifndef ACS_24GHZ_PREFER_1_6_11 +/* + * Select commonly used channels 1, 6, 11 by default even if a neighboring + * channel has a smaller interference factor as long as it is not better by more + * than this multiplier. + */ +#define ACS_24GHZ_PREFER_1_6_11 0.8 +#endif /* ACS_24GHZ_PREFER_1_6_11 */ + + +#ifdef CONFIG_IEEE80211BE +static void acs_update_puncturing_bitmap(struct hostapd_iface *iface, + struct hostapd_hw_modes *mode, u32 bw, + int n_chans, + struct hostapd_channel_data *chan, + long double factor, + int index_primary) +{ + struct hostapd_config *conf = iface->conf; + struct hostapd_channel_data *adj_chan = NULL, *first_chan = chan; + int i; + long double threshold; + + /* + * If threshold is 0 or user configured puncturing pattern is + * available then don't add additional puncturing. + */ + if (!conf->punct_acs_threshold || conf->punct_bitmap) + return; + + if (is_24ghz_mode(mode->mode) || bw < 80) + return; + + threshold = factor * conf->punct_acs_threshold / 100; + for (i = 0; i < n_chans; i++) { + int adj_freq; + + if (i == index_primary) + continue; /* Cannot puncture primary channel */ + + if (i > index_primary) + adj_freq = chan->freq + (i - index_primary) * 20; + else + adj_freq = chan->freq - (index_primary - i) * 20; + + adj_chan = acs_find_chan(iface, adj_freq); + if (!adj_chan) { + chan->punct_bitmap = 0; + return; + } + + if (i == 0) + first_chan = adj_chan; + + if (adj_chan->interference_factor > threshold) + chan->punct_bitmap |= BIT(i); + } + + if (!is_punct_bitmap_valid(bw, (chan->freq - first_chan->freq) / 20, + chan->punct_bitmap)) + chan->punct_bitmap = 0; +} +#endif /* CONFIG_IEEE80211BE */ + + +static bool +acs_usable_bw320_chan(struct hostapd_iface *iface, + struct hostapd_channel_data *chan, int *bw320_offset) +{ + const char *bw320_str[] = { "320 MHz", "320 MHz-1", "320 MHz-2" }; + int conf_bw320_offset = hostapd_get_bw320_offset(iface->conf); + + *bw320_offset = 0; + switch (conf_bw320_offset) { + case 1: + if (acs_usable_bw_chan(chan, ACS_BW320_1)) + *bw320_offset = 1; + break; + case 2: + if (acs_usable_bw_chan(chan, ACS_BW320_2)) + *bw320_offset = 2; + break; + case 0: + default: + conf_bw320_offset = 0; + if (acs_usable_bw_chan(chan, ACS_BW320_1)) + *bw320_offset = 1; + else if (acs_usable_bw_chan(chan, ACS_BW320_2)) + *bw320_offset = 2; + break; + } + + if (!*bw320_offset) + wpa_printf(MSG_DEBUG, + "ACS: Channel %d: not allowed as primary channel for %s bandwidth", + chan->chan, bw320_str[conf_bw320_offset]); + + return *bw320_offset != 0; +} + + +static void +acs_find_ideal_chan_mode(struct hostapd_iface *iface, + struct hostapd_hw_modes *mode, + int n_chans, u32 bw, + struct hostapd_channel_data **rand_chan, + struct hostapd_channel_data **ideal_chan, + long double *ideal_factor) +{ + struct hostapd_channel_data *chan, *adj_chan = NULL, *best; + long double factor; + int i, j; + int bw320_offset = 0, ideal_bw320_offset = 0; + unsigned int k; + int secondary_channel = 1, freq_offset; +#ifdef CONFIG_IEEE80211BE + int index_primary = 0; +#endif /* CONFIG_IEEE80211BE */ + + if (is_24ghz_mode(mode->mode)) + secondary_channel = iface->conf->secondary_channel; + + for (i = 0; i < mode->num_channels; i++) { + double total_weight = 0; + struct acs_bias *bias, tmp_bias; + + chan = &mode->channels[i]; + + /* Since in the current ACS implementation the first channel is + * always a primary channel, skip channels not available as + * primary until more sophisticated channel selection is + * implemented. + * + * If this implementation is changed to allow any channel in + * the bandwidth to be the primary one, the last parameter to + * acs_update_puncturing_bitmap() should be changed to the index + * of the primary channel + */ + if (!chan_pri_allowed(chan)) + continue; + + if ((chan->flag & HOSTAPD_CHAN_RADAR) && + iface->conf->acs_exclude_dfs) + continue; + + if (!is_in_chanlist(iface, chan)) + continue; + + if (!is_in_freqlist(iface, chan)) + continue; + + if (chan->max_tx_power < iface->conf->min_tx_power) + continue; + + if ((chan->flag & HOSTAPD_CHAN_INDOOR_ONLY) && + iface->conf->country[2] == 0x4f) + continue; + + if (!chan_bw_allowed(chan, bw, secondary_channel != -1, 1)) { + wpa_printf(MSG_DEBUG, + "ACS: Channel %d: BW %u is not supported", + chan->chan, bw); + continue; + } + + /* HT40 on 5 GHz has a limited set of primary channels as per + * 11n Annex J */ + if (mode->mode == HOSTAPD_MODE_IEEE80211A && + ((iface->conf->ieee80211n && + iface->conf->secondary_channel) || + is_6ghz_freq(chan->freq)) && + !acs_usable_bw_chan(chan, ACS_BW40)) { + wpa_printf(MSG_DEBUG, + "ACS: Channel %d: not allowed as primary channel for 40 MHz bandwidth", + chan->chan); + continue; + } + + if (mode->mode == HOSTAPD_MODE_IEEE80211A && + (iface->conf->ieee80211ac || iface->conf->ieee80211ax || + iface->conf->ieee80211be)) { + if (hostapd_get_oper_chwidth(iface->conf) == + CONF_OPER_CHWIDTH_80MHZ && + !acs_usable_bw_chan(chan, ACS_BW80)) { + wpa_printf(MSG_DEBUG, + "ACS: Channel %d: not allowed as primary channel for 80 MHz bandwidth", + chan->chan); + continue; + } + + if (hostapd_get_oper_chwidth(iface->conf) == + CONF_OPER_CHWIDTH_160MHZ && + !acs_usable_bw_chan(chan, ACS_BW160)) { + wpa_printf(MSG_DEBUG, + "ACS: Channel %d: not allowed as primary channel for 160 MHz bandwidth", + chan->chan); + continue; + } + } + + if (mode->mode == HOSTAPD_MODE_IEEE80211A && + iface->conf->ieee80211be) { + if (hostapd_get_oper_chwidth(iface->conf) == + CONF_OPER_CHWIDTH_320MHZ && + !acs_usable_bw320_chan(iface, chan, &bw320_offset)) + continue; + } + + factor = 0; + best = NULL; + if (acs_usable_chan(chan)) { + factor = chan->interference_factor; + total_weight = 1; + best = chan; + } + + for (j = 1; j < n_chans; j++) { + adj_chan = acs_find_chan(iface, chan->freq + + j * secondary_channel * 20); + if (!adj_chan) + break; + + if (!chan_bw_allowed(adj_chan, bw, 1, 0)) { + wpa_printf(MSG_DEBUG, + "ACS: PRI Channel %d: secondary channel %d BW %u is not supported", + chan->chan, adj_chan->chan, bw); + break; + } + + if (!acs_usable_chan(adj_chan)) + continue; + + factor += adj_chan->interference_factor; + total_weight += 1; + + /* find the best channel in this segment */ + if (!best || adj_chan->interference_factor < + best->interference_factor) + best = adj_chan; + } + + if (j != n_chans) { + wpa_printf(MSG_DEBUG, "ACS: Channel %d: not enough bandwidth", + chan->chan); + continue; + } + + /* If the AP is in the 5 GHz or 6 GHz band, lets prefer a less + * crowded primary channel if one was found in the segment */ + if (iface->current_mode && + iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A && + best && chan != best) { + wpa_printf(MSG_DEBUG, + "ACS: promoting channel %d over %d (less interference %Lg/%Lg)", + best->chan, chan->chan, + chan->interference_factor, + best->interference_factor); +#ifdef CONFIG_IEEE80211BE + index_primary = (chan->freq - best->freq) / 20; +#endif /* CONFIG_IEEE80211BE */ + chan = best; + } + + /* 2.4 GHz has overlapping 20 MHz channels. Include adjacent + * channel interference factor. */ + if (is_24ghz_mode(mode->mode)) { + for (j = 0; j < n_chans; j++) { + freq_offset = j * 20 * secondary_channel; + adj_chan = acs_find_chan(iface, chan->freq + + freq_offset - 5); + if (adj_chan && acs_usable_chan(adj_chan)) { + factor += ACS_ADJ_WEIGHT * + adj_chan->interference_factor; + total_weight += ACS_ADJ_WEIGHT; + } + + adj_chan = acs_find_chan(iface, chan->freq + + freq_offset - 10); + if (adj_chan && acs_usable_chan(adj_chan)) { + factor += ACS_NEXT_ADJ_WEIGHT * + adj_chan->interference_factor; + total_weight += ACS_NEXT_ADJ_WEIGHT; + } + + adj_chan = acs_find_chan(iface, chan->freq + + freq_offset + 5); + if (adj_chan && acs_usable_chan(adj_chan)) { + factor += ACS_ADJ_WEIGHT * + adj_chan->interference_factor; + total_weight += ACS_ADJ_WEIGHT; + } + + adj_chan = acs_find_chan(iface, chan->freq + + freq_offset + 10); + if (adj_chan && acs_usable_chan(adj_chan)) { + factor += ACS_NEXT_ADJ_WEIGHT * + adj_chan->interference_factor; + total_weight += ACS_NEXT_ADJ_WEIGHT; + } + } + } + + if (total_weight == 0) + continue; + + factor /= total_weight; + + bias = NULL; + if (iface->conf->acs_chan_bias) { + for (k = 0; k < iface->conf->num_acs_chan_bias; k++) { + bias = &iface->conf->acs_chan_bias[k]; + if (bias->channel == chan->chan) + break; + bias = NULL; + } + } else if (is_24ghz_mode(mode->mode) && + is_common_24ghz_chan(chan->chan)) { + tmp_bias.channel = chan->chan; + tmp_bias.bias = ACS_24GHZ_PREFER_1_6_11; + bias = &tmp_bias; + } + + if (bias) { + factor *= bias->bias; + wpa_printf(MSG_DEBUG, + "ACS: * channel %d: total interference = %Lg (%f bias)", + chan->chan, factor, bias->bias); + } else { + wpa_printf(MSG_DEBUG, + "ACS: * channel %d: total interference = %Lg", + chan->chan, factor); + } + + if (acs_usable_chan(chan) && + (!*ideal_chan || factor < *ideal_factor)) { + /* Reset puncturing bitmap for the previous ideal + * channel */ + if (*ideal_chan) + (*ideal_chan)->punct_bitmap = 0; + + *ideal_factor = factor; + *ideal_chan = chan; + ideal_bw320_offset = bw320_offset; + +#ifdef CONFIG_IEEE80211BE + if (iface->conf->ieee80211be) + acs_update_puncturing_bitmap(iface, mode, bw, + n_chans, chan, + factor, + index_primary); +#endif /* CONFIG_IEEE80211BE */ + } + + /* This channel would at least be usable */ + if (!(*rand_chan)) { + *rand_chan = chan; + ideal_bw320_offset = bw320_offset; + } + } + + hostapd_set_and_check_bw320_offset(iface->conf, ideal_bw320_offset); +} + + +/* + * At this point it's assumed chan->interference_factor has been computed. + * This function should be reusable regardless of interference computation + * option (survey, BSS, spectral, ...). chan->interference factor must be + * summable (i.e., must be always greater than zero). + */ +static struct hostapd_channel_data * +acs_find_ideal_chan(struct hostapd_iface *iface) +{ + struct hostapd_channel_data *ideal_chan = NULL, + *rand_chan = NULL; + long double ideal_factor = 0; + int i; + int n_chans = 1; + u32 bw; + struct hostapd_hw_modes *mode; + + if (is_6ghz_op_class(iface->conf->op_class)) { + bw = op_class_to_bandwidth(iface->conf->op_class); + n_chans = bw / 20; + goto bw_selected; + } + + if (iface->conf->ieee80211n && + iface->conf->secondary_channel) + n_chans = 2; + + if (iface->conf->ieee80211ac || iface->conf->ieee80211ax || + iface->conf->ieee80211be) { + switch (hostapd_get_oper_chwidth(iface->conf)) { + case CONF_OPER_CHWIDTH_80MHZ: + n_chans = 4; + break; + case CONF_OPER_CHWIDTH_160MHZ: + n_chans = 8; + break; + case CONF_OPER_CHWIDTH_320MHZ: + n_chans = 16; + break; + default: + break; + } + } + + bw = num_chan_to_bw(n_chans); + +bw_selected: + /* TODO: VHT/HE80+80. Update acs_adjust_center_freq() too. */ + + wpa_printf(MSG_DEBUG, + "ACS: Survey analysis for selected bandwidth %d MHz", bw); + + for (i = 0; i < iface->num_hw_features; i++) { + mode = &iface->hw_features[i]; + if (!hostapd_hw_skip_mode(iface, mode)) + acs_find_ideal_chan_mode(iface, mode, n_chans, bw, + &rand_chan, &ideal_chan, + &ideal_factor); + } + + if (ideal_chan) { + wpa_printf(MSG_DEBUG, "ACS: Ideal channel is %d (%d MHz) with total interference factor of %Lg", + ideal_chan->chan, ideal_chan->freq, ideal_factor); + +#ifdef CONFIG_IEEE80211BE + if (iface->conf->punct_acs_threshold) + wpa_printf(MSG_DEBUG, "ACS: RU puncturing bitmap 0x%x", + ideal_chan->punct_bitmap); +#endif /* CONFIG_IEEE80211BE */ + + return ideal_chan; + } + + return rand_chan; +} + + +static void acs_adjust_secondary(struct hostapd_iface *iface) +{ + unsigned int i; + + /* When working with bandwidth over 20 MHz on the 5 GHz or 6 GHz band, + * ACS can return a secondary channel which is not the first channel of + * the segment and we need to adjust. */ + if (!iface->conf->secondary_channel || + acs_find_mode(iface, iface->freq) != HOSTAPD_MODE_IEEE80211A) + return; + + wpa_printf(MSG_DEBUG, + "ACS: Adjusting HT/VHT/HE/EHT secondary frequency"); + + for (i = 0; bw_desc[ACS_BW40][i].first != -1; i++) { + if (iface->freq == bw_desc[ACS_BW40][i].first) + iface->conf->secondary_channel = 1; + else if (iface->freq == bw_desc[ACS_BW40][i].last) + iface->conf->secondary_channel = -1; + } +} + + +static void acs_adjust_center_freq(struct hostapd_iface *iface) +{ + int center; + + wpa_printf(MSG_DEBUG, "ACS: Adjusting center frequency"); + + switch (hostapd_get_oper_chwidth(iface->conf)) { + case CONF_OPER_CHWIDTH_USE_HT: + if (iface->conf->secondary_channel && + iface->freq >= 2400 && iface->freq < 2500) + center = iface->conf->channel + + 2 * iface->conf->secondary_channel; + else if (iface->conf->secondary_channel) + center = acs_get_bw_center_chan(iface->freq, ACS_BW40); + else + center = iface->conf->channel; + break; + case CONF_OPER_CHWIDTH_80MHZ: + center = acs_get_bw_center_chan(iface->freq, ACS_BW80); + break; + case CONF_OPER_CHWIDTH_160MHZ: + center = acs_get_bw_center_chan(iface->freq, ACS_BW160); + break; + case CONF_OPER_CHWIDTH_320MHZ: + switch (hostapd_get_bw320_offset(iface->conf)) { + case 1: + center = acs_get_bw_center_chan(iface->freq, + ACS_BW320_1); + break; + case 2: + center = acs_get_bw_center_chan(iface->freq, + ACS_BW320_2); + break; + default: + wpa_printf(MSG_INFO, + "ACS: BW320 offset is not selected"); + return; + } + + break; + default: + /* TODO: How can this be calculated? Adjust + * acs_find_ideal_chan() */ + wpa_printf(MSG_INFO, + "ACS: Only VHT20/40/80/160/320 is supported now"); + return; + } + + hostapd_set_oper_centr_freq_seg0_idx(iface->conf, center); +} + + +static int acs_study_survey_based(struct hostapd_iface *iface) +{ + wpa_printf(MSG_DEBUG, "ACS: Trying survey-based ACS"); + + if (!iface->chans_surveyed) { + wpa_printf(MSG_ERROR, "ACS: Unable to collect survey data"); + return -1; + } + + if (!acs_surveys_are_sufficient(iface)) { + wpa_printf(MSG_ERROR, "ACS: Surveys have insufficient data"); + return -1; + } + + acs_survey_all_chans_interference_factor(iface); + return 0; +} + + +static int acs_study_options(struct hostapd_iface *iface) +{ + if (acs_study_survey_based(iface) == 0) + return 0; + + /* TODO: If no surveys are available/sufficient this is a good + * place to fallback to BSS-based ACS */ + + return -1; +} + + +static void acs_study(struct hostapd_iface *iface) +{ + struct hostapd_channel_data *ideal_chan; + int err; + + err = acs_study_options(iface); + if (err < 0) { + wpa_printf(MSG_ERROR, "ACS: All study options have failed"); + goto fail; + } + + ideal_chan = acs_find_ideal_chan(iface); + if (!ideal_chan) { + wpa_printf(MSG_ERROR, "ACS: Failed to compute ideal channel"); + err = -1; + goto fail; + } + + iface->conf->channel = ideal_chan->chan; + iface->freq = ideal_chan->freq; +#ifdef CONFIG_IEEE80211BE + iface->conf->punct_bitmap = ideal_chan->punct_bitmap; +#endif /* CONFIG_IEEE80211BE */ + + if (iface->conf->ieee80211ac || iface->conf->ieee80211ax || + iface->conf->ieee80211be) { + acs_adjust_secondary(iface); + acs_adjust_center_freq(iface); + } + + err = hostapd_select_hw_mode(iface); + if (err) { + wpa_printf(MSG_ERROR, + "ACS: Could not (err: %d) select hw_mode for freq=%d channel=%d", + err, iface->freq, iface->conf->channel); + err = -1; + goto fail; + } + + err = 0; +fail: + /* + * hostapd_setup_interface_complete() will return -1 on failure, + * 0 on success and 0 is HOSTAPD_CHAN_VALID :) + */ + if (hostapd_acs_completed(iface, err) == HOSTAPD_CHAN_VALID) { + acs_cleanup(iface); + return; + } + + /* This can possibly happen if channel parameters (secondary + * channel, center frequencies) are misconfigured */ + wpa_printf(MSG_ERROR, "ACS: Possibly channel configuration is invalid, please report this along with your config file."); + acs_fail(iface); +} + + +static void acs_scan_complete(struct hostapd_iface *iface) +{ + int err; + + iface->scan_cb = NULL; + iface->acs_num_retries = 0; + + wpa_printf(MSG_DEBUG, "ACS: Using survey based algorithm (acs_num_scans=%d)", + iface->conf->acs_num_scans); + + err = hostapd_drv_get_survey(iface->bss[0], 0); + if (err) { + wpa_printf(MSG_ERROR, "ACS: Failed to get survey data"); + goto fail; + } + + if (++iface->acs_num_completed_scans < iface->conf->acs_num_scans) { + err = acs_request_scan(iface); + if (err && err != -EBUSY) { + wpa_printf(MSG_ERROR, "ACS: Failed to request scan"); + goto fail; + } + + return; + } + + acs_study(iface); + return; +fail: + hostapd_acs_completed(iface, 1); + acs_fail(iface); +} + + +static int * acs_request_scan_add_freqs(struct hostapd_iface *iface, + struct hostapd_hw_modes *mode, + int *freq) +{ + struct hostapd_channel_data *chan; + int i; + + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + if ((chan->flag & HOSTAPD_CHAN_DISABLED) || + ((chan->flag & HOSTAPD_CHAN_RADAR) && + iface->conf->acs_exclude_dfs)) + continue; + + if (!is_in_chanlist(iface, chan)) + continue; + + if (!is_in_freqlist(iface, chan)) + continue; + + if (chan->max_tx_power < iface->conf->min_tx_power) + continue; + + if ((chan->flag & HOSTAPD_CHAN_INDOOR_ONLY) && + iface->conf->country[2] == 0x4f) + continue; + + *freq++ = chan->freq; + } + + return freq; +} + + +static int acs_request_scan(struct hostapd_iface *iface) +{ + struct wpa_driver_scan_params params; + int i, *freq, ret; + int num_channels; + struct hostapd_hw_modes *mode; + + os_memset(¶ms, 0, sizeof(params)); + + num_channels = 0; + for (i = 0; i < iface->num_hw_features; i++) { + mode = &iface->hw_features[i]; + if (!hostapd_hw_skip_mode(iface, mode)) + num_channels += mode->num_channels; + } + + params.freqs = os_calloc(num_channels + 1, sizeof(params.freqs[0])); + if (params.freqs == NULL) + return -1; + + freq = params.freqs; + + for (i = 0; i < iface->num_hw_features; i++) { + mode = &iface->hw_features[i]; + if (!hostapd_hw_skip_mode(iface, mode)) + freq = acs_request_scan_add_freqs(iface, mode, freq); + } + + *freq = 0; + + if (params.freqs == freq) { + wpa_printf(MSG_ERROR, "ACS: No available channels found"); + os_free(params.freqs); + return -1; + } + + if (!iface->acs_num_retries) + wpa_printf(MSG_DEBUG, "ACS: Scanning %d / %d", + iface->acs_num_completed_scans + 1, + iface->conf->acs_num_scans); + else + wpa_printf(MSG_DEBUG, + "ACS: Re-try scanning attempt %d (%d / %d)", + iface->acs_num_retries, + iface->acs_num_completed_scans + 1, + iface->conf->acs_num_scans); + + ret = hostapd_driver_scan(iface->bss[0], ¶ms); + os_free(params.freqs); + + if (ret == -EBUSY) { + iface->acs_num_retries++; + if (iface->acs_num_retries >= ACS_SCAN_RETRY_MAX_COUNT) { + wpa_printf(MSG_ERROR, + "ACS: Failed to request initial scan (all re-attempts failed)"); + acs_fail(iface); + return -1; + } + + wpa_printf(MSG_INFO, + "Failed to request acs scan ret=%d (%s) - try to scan after %d seconds", + ret, strerror(-ret), ACS_SCAN_RETRY_INTERVAL); + eloop_cancel_timeout(acs_scan_retry, iface, NULL); + eloop_register_timeout(ACS_SCAN_RETRY_INTERVAL, 0, + acs_scan_retry, iface, NULL); + return 0; + } + + if (ret < 0) { + wpa_printf(MSG_ERROR, "ACS: Failed to request initial scan"); + acs_cleanup(iface); + return -1; + } + + iface->scan_cb = acs_scan_complete; + + return 0; +} + + +static void acs_scan_retry(void *eloop_data, void *user_data) +{ + struct hostapd_iface *iface = eloop_data; + + if (acs_request_scan(iface)) { + wpa_printf(MSG_ERROR, + "ACS: Failed to request re-try of initial scan"); + acs_fail(iface); + } +} + + +enum hostapd_chan_status acs_init(struct hostapd_iface *iface) +{ + int err; + + wpa_printf(MSG_INFO, "ACS: Automatic channel selection started, this may take a bit"); + + if (iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) { + wpa_printf(MSG_INFO, "ACS: Offloading to driver"); + + err = hostapd_drv_do_acs(iface->bss[0]); + if (err) { + if (err == 1) + return HOSTAPD_CHAN_INVALID_NO_IR; + return HOSTAPD_CHAN_INVALID; + } + + return HOSTAPD_CHAN_ACS; + } + + if (!iface->current_mode && + iface->conf->hw_mode != HOSTAPD_MODE_IEEE80211ANY) + return HOSTAPD_CHAN_INVALID; + + acs_cleanup(iface); + + if (acs_request_scan(iface) < 0) + return HOSTAPD_CHAN_INVALID; + + hostapd_set_state(iface, HAPD_IFACE_ACS); + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, ACS_EVENT_STARTED); + + return HOSTAPD_CHAN_ACS; +} diff --git a/src/ap/acs.h b/src/ap/acs.h new file mode 100644 index 0000000..8be3de5 --- /dev/null +++ b/src/ap/acs.h @@ -0,0 +1,35 @@ +/* + * ACS - Automatic Channel Selection module + * Copyright (c) 2011, Atheros Communications + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef ACS_H +#define ACS_H + +#ifdef CONFIG_ACS + +enum hostapd_chan_status acs_init(struct hostapd_iface *iface); +void acs_cleanup(struct hostapd_iface *iface); + +#define ACS_SCAN_RETRY_MAX_COUNT 15 +#define ACS_SCAN_RETRY_INTERVAL 5 + +#else /* CONFIG_ACS */ + +static inline enum hostapd_chan_status acs_init(struct hostapd_iface *iface) +{ + wpa_printf(MSG_ERROR, "ACS was disabled on your build, rebuild hostapd with CONFIG_ACS=y or set channel"); + return HOSTAPD_CHAN_INVALID; +} + +static inline void acs_cleanup(struct hostapd_iface *iface) +{ +} + +#endif /* CONFIG_ACS */ + +#endif /* ACS_H */ diff --git a/src/ap/airtime_policy.c b/src/ap/airtime_policy.c new file mode 100644 index 0000000..6844311 --- /dev/null +++ b/src/ap/airtime_policy.c @@ -0,0 +1,273 @@ +/* + * Airtime policy configuration + * Copyright (c) 2018-2019, Toke Høiland-Jørgensen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "hostapd.h" +#include "ap_drv_ops.h" +#include "sta_info.h" +#include "airtime_policy.h" + +/* Idea: + * Two modes of airtime enforcement: + * 1. Static weights: specify weights per MAC address with a per-BSS default + * 2. Per-BSS limits: Dynamically calculate weights of backlogged stations to + * enforce relative total shares between BSSes. + * + * - Periodic per-station callback to update queue status. + * + * Copy accounting_sta_update_stats() to get TXQ info and airtime weights and + * keep them updated in sta_info. + * + * - Separate periodic per-bss (or per-iface?) callback to update weights. + * + * Just need to loop through all interfaces, count sum the active stations (or + * should the per-STA callback just adjust that for the BSS?) and calculate new + * weights. + */ + +static int get_airtime_policy_update_timeout(struct hostapd_iface *iface, + unsigned int *sec, + unsigned int *usec) +{ + unsigned int update_int = iface->conf->airtime_update_interval; + + if (!update_int) { + wpa_printf(MSG_ERROR, + "Airtime policy: Invalid airtime policy update interval %u", + update_int); + return -1; + } + + *sec = update_int / 1000; + *usec = (update_int % 1000) * 1000; + + return 0; +} + + +static void set_new_backlog_time(struct hostapd_data *hapd, + struct sta_info *sta, + struct os_reltime *now) +{ + sta->backlogged_until = *now; + sta->backlogged_until.usec += hapd->iconf->airtime_update_interval * + AIRTIME_BACKLOG_EXPIRY_FACTOR; + while (sta->backlogged_until.usec >= 1000000) { + sta->backlogged_until.sec++; + sta->backlogged_until.usec -= 1000000; + } +} + + +static void count_backlogged_sta(struct hostapd_data *hapd) +{ + struct sta_info *sta; + struct hostap_sta_driver_data data = {}; + unsigned int num_backlogged = 0; + struct os_reltime now; + + os_get_reltime(&now); + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (hostapd_drv_read_sta_data(hapd, &data, sta->addr)) + continue; +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->force_backlog_bytes) + data.backlog_bytes = 1; +#endif /* CONFIG_TESTING_OPTIONS */ + + if (data.backlog_bytes > 0) + set_new_backlog_time(hapd, sta, &now); + if (os_reltime_before(&now, &sta->backlogged_until)) + num_backlogged++; + } + hapd->num_backlogged_sta = num_backlogged; +} + + +static int sta_set_airtime_weight(struct hostapd_data *hapd, + struct sta_info *sta, + unsigned int weight) +{ + int ret = 0; + + if (weight != sta->airtime_weight && + (ret = hostapd_sta_set_airtime_weight(hapd, sta->addr, weight))) + return ret; + + sta->airtime_weight = weight; + return ret; +} + + +static void set_sta_weights(struct hostapd_data *hapd, unsigned int weight) +{ + struct sta_info *sta; + + for (sta = hapd->sta_list; sta; sta = sta->next) + sta_set_airtime_weight(hapd, sta, weight); +} + + +static unsigned int get_airtime_quantum(unsigned int max_wt) +{ + unsigned int quantum = AIRTIME_QUANTUM_TARGET / max_wt; + + if (quantum < AIRTIME_QUANTUM_MIN) + quantum = AIRTIME_QUANTUM_MIN; + else if (quantum > AIRTIME_QUANTUM_MAX) + quantum = AIRTIME_QUANTUM_MAX; + + return quantum; +} + + +static void update_airtime_weights(void *eloop_data, void *user_data) +{ + struct hostapd_iface *iface = eloop_data; + struct hostapd_data *bss; + unsigned int sec, usec; + unsigned int num_sta_min = 0, num_sta_prod = 1, num_sta_sum = 0, + wt_sum = 0; + unsigned int quantum; + bool all_div_min = true; + bool apply_limit = iface->conf->airtime_mode == AIRTIME_MODE_DYNAMIC; + int wt, num_bss = 0, max_wt = 0; + size_t i; + + for (i = 0; i < iface->num_bss; i++) { + bss = iface->bss[i]; + if (!bss->started || !bss->conf->airtime_weight) + continue; + + count_backlogged_sta(bss); + if (!bss->num_backlogged_sta) + continue; + + if (!num_sta_min || bss->num_backlogged_sta < num_sta_min) + num_sta_min = bss->num_backlogged_sta; + + num_sta_prod *= bss->num_backlogged_sta; + num_sta_sum += bss->num_backlogged_sta; + wt_sum += bss->conf->airtime_weight; + num_bss++; + } + + if (num_sta_min) { + for (i = 0; i < iface->num_bss; i++) { + bss = iface->bss[i]; + if (!bss->started || !bss->conf->airtime_weight) + continue; + + /* Check if we can divide all sta numbers by the + * smallest number to keep weights as small as possible. + * This is a lazy way to avoid having to factor + * integers. */ + if (bss->num_backlogged_sta && + bss->num_backlogged_sta % num_sta_min > 0) + all_div_min = false; + + /* If we're in LIMIT mode, we only apply the weight + * scaling when the BSS(es) marked as limited would a + * larger share than the relative BSS weights indicates + * it should. */ + if (!apply_limit && bss->conf->airtime_limit) { + if (bss->num_backlogged_sta * wt_sum > + bss->conf->airtime_weight * num_sta_sum) + apply_limit = true; + } + } + if (all_div_min) + num_sta_prod /= num_sta_min; + } + + for (i = 0; i < iface->num_bss; i++) { + bss = iface->bss[i]; + if (!bss->started || !bss->conf->airtime_weight) + continue; + + /* We only set the calculated weight if the BSS has active + * stations and there are other active interfaces as well - + * otherwise we just set a unit weight. This ensures that + * the weights are set reasonably when stations transition from + * inactive to active. */ + if (apply_limit && bss->num_backlogged_sta && num_bss > 1) + wt = bss->conf->airtime_weight * num_sta_prod / + bss->num_backlogged_sta; + else + wt = 1; + + bss->airtime_weight = wt; + if (wt > max_wt) + max_wt = wt; + } + + quantum = get_airtime_quantum(max_wt); + + for (i = 0; i < iface->num_bss; i++) { + bss = iface->bss[i]; + if (!bss->started || !bss->conf->airtime_weight) + continue; + set_sta_weights(bss, bss->airtime_weight * quantum); + } + + if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0) + return; + + eloop_register_timeout(sec, usec, update_airtime_weights, iface, + NULL); +} + + +static int get_weight_for_sta(struct hostapd_data *hapd, const u8 *sta) +{ + struct airtime_sta_weight *wt; + + wt = hapd->conf->airtime_weight_list; + while (wt && !ether_addr_equal(wt->addr, sta)) + wt = wt->next; + + return wt ? wt->weight : hapd->conf->airtime_weight; +} + + +int airtime_policy_new_sta(struct hostapd_data *hapd, struct sta_info *sta) +{ + unsigned int weight; + + if (hapd->iconf->airtime_mode == AIRTIME_MODE_STATIC) { + weight = get_weight_for_sta(hapd, sta->addr); + if (weight) + return sta_set_airtime_weight(hapd, sta, weight); + } + return 0; +} + + +int airtime_policy_update_init(struct hostapd_iface *iface) +{ + unsigned int sec, usec; + + if (iface->conf->airtime_mode < AIRTIME_MODE_DYNAMIC) + return 0; + + if (get_airtime_policy_update_timeout(iface, &sec, &usec) < 0) + return -1; + + eloop_register_timeout(sec, usec, update_airtime_weights, iface, NULL); + return 0; +} + + +void airtime_policy_update_deinit(struct hostapd_iface *iface) +{ + eloop_cancel_timeout(update_airtime_weights, iface, NULL); +} diff --git a/src/ap/airtime_policy.h b/src/ap/airtime_policy.h new file mode 100644 index 0000000..c2a9b00 --- /dev/null +++ b/src/ap/airtime_policy.h @@ -0,0 +1,48 @@ +/* + * Airtime policy configuration + * Copyright (c) 2018-2019, Toke Høiland-Jørgensen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef AIRTIME_POLICY_H +#define AIRTIME_POLICY_H + +struct hostapd_iface; + +#ifdef CONFIG_AIRTIME_POLICY + +#define AIRTIME_DEFAULT_UPDATE_INTERVAL 200 /* ms */ +#define AIRTIME_BACKLOG_EXPIRY_FACTOR 2500 /* 2.5 intervals + convert to usec */ + +/* scale quantum so this becomes the effective quantum after applying the max + * weight, but never go below min or above max */ +#define AIRTIME_QUANTUM_MIN 8 /* usec */ +#define AIRTIME_QUANTUM_MAX 256 /* usec */ +#define AIRTIME_QUANTUM_TARGET 1024 /* usec */ + +int airtime_policy_new_sta(struct hostapd_data *hapd, struct sta_info *sta); +int airtime_policy_update_init(struct hostapd_iface *iface); +void airtime_policy_update_deinit(struct hostapd_iface *iface); + +#else /* CONFIG_AIRTIME_POLICY */ + +static inline int airtime_policy_new_sta(struct hostapd_data *hapd, + struct sta_info *sta) +{ + return -1; +} + +static inline int airtime_policy_update_init(struct hostapd_iface *iface) +{ + return -1; +} + +static inline void airtime_policy_update_deinit(struct hostapd_iface *iface) +{ +} + +#endif /* CONFIG_AIRTIME_POLICY */ + +#endif /* AIRTIME_POLICY_H */ diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c new file mode 100644 index 0000000..565b587 --- /dev/null +++ b/src/ap/ap_config.c @@ -0,0 +1,1789 @@ +/* + * hostapd / Configuration helper functions + * Copyright (c) 2003-2024, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "crypto/sha1.h" +#include "crypto/tls.h" +#include "radius/radius_client.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_1x_defs.h" +#include "common/eapol_common.h" +#include "common/dhcp.h" +#include "common/sae.h" +#include "eap_common/eap_wsc_common.h" +#include "eap_server/eap.h" +#include "wpa_auth.h" +#include "sta_info.h" +#include "airtime_policy.h" +#include "ap_config.h" + + +static void hostapd_config_free_vlan(struct hostapd_bss_config *bss) +{ + struct hostapd_vlan *vlan, *prev; + + vlan = bss->vlan; + prev = NULL; + while (vlan) { + prev = vlan; + vlan = vlan->next; + os_free(prev); + } + + bss->vlan = NULL; +} + + +#ifndef DEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES +#define DEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES 0 +#endif /* DEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES */ + +void hostapd_config_defaults_bss(struct hostapd_bss_config *bss) +{ + dl_list_init(&bss->anqp_elem); + + bss->logger_syslog_level = HOSTAPD_LEVEL_INFO; + bss->logger_stdout_level = HOSTAPD_LEVEL_INFO; + bss->logger_syslog = (unsigned int) -1; + bss->logger_stdout = (unsigned int) -1; + +#ifdef CONFIG_WEP + bss->auth_algs = WPA_AUTH_ALG_OPEN | WPA_AUTH_ALG_SHARED; + + bss->wep_rekeying_period = 300; + /* use key0 in individual key and key1 in broadcast key */ + bss->broadcast_key_idx_min = 1; + bss->broadcast_key_idx_max = 2; +#else /* CONFIG_WEP */ + bss->auth_algs = WPA_AUTH_ALG_OPEN; +#endif /* CONFIG_WEP */ + bss->eap_reauth_period = 3600; + + bss->wpa_group_rekey = 600; + bss->wpa_gmk_rekey = 86400; + bss->wpa_deny_ptk0_rekey = PTK0_REKEY_ALLOW_ALWAYS; + bss->wpa_group_update_count = 4; + bss->wpa_pairwise_update_count = 4; + bss->wpa_disable_eapol_key_retries = + DEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES; + bss->wpa_key_mgmt = WPA_KEY_MGMT_PSK; +#ifdef CONFIG_NO_TKIP + bss->wpa_pairwise = WPA_CIPHER_CCMP; + bss->wpa_group = WPA_CIPHER_CCMP; +#else /* CONFIG_NO_TKIP */ + bss->wpa_pairwise = WPA_CIPHER_TKIP; + bss->wpa_group = WPA_CIPHER_TKIP; +#endif /* CONFIG_NO_TKIP */ + bss->rsn_pairwise = 0; + + bss->max_num_sta = MAX_STA_COUNT; + + bss->dtim_period = 2; + + bss->radius_server_auth_port = 1812; + bss->eap_sim_db_timeout = 1; + bss->eap_sim_id = 3; + bss->eap_sim_aka_fast_reauth_limit = 1000; + bss->ap_max_inactivity = AP_MAX_INACTIVITY; + bss->bss_max_idle = 1; + bss->eapol_version = EAPOL_VERSION; + + bss->max_listen_interval = 65535; + + bss->pwd_group = 19; /* ECC: GF(p=256) */ + + bss->assoc_sa_query_max_timeout = 1000; + bss->assoc_sa_query_retry_timeout = 201; + bss->group_mgmt_cipher = WPA_CIPHER_AES_128_CMAC; +#ifdef EAP_SERVER_FAST + /* both anonymous and authenticated provisioning */ + bss->eap_fast_prov = 3; + bss->pac_key_lifetime = 7 * 24 * 60 * 60; + bss->pac_key_refresh_time = 1 * 24 * 60 * 60; +#endif /* EAP_SERVER_FAST */ + + /* Set to -1 as defaults depends on HT in setup */ + bss->wmm_enabled = -1; + +#ifdef CONFIG_IEEE80211R_AP + bss->ft_over_ds = 1; + bss->rkh_pos_timeout = 86400; + bss->rkh_neg_timeout = 60; + bss->rkh_pull_timeout = 1000; + bss->rkh_pull_retries = 4; + bss->r0_key_lifetime = 1209600; +#endif /* CONFIG_IEEE80211R_AP */ + + bss->radius_das_time_window = 300; + bss->radius_require_message_authenticator = 1; + + bss->anti_clogging_threshold = 5; + bss->sae_sync = 3; + + bss->gas_frag_limit = 1400; + +#ifdef CONFIG_FILS + dl_list_init(&bss->fils_realms); + bss->fils_hlp_wait_time = 30; + bss->dhcp_server_port = DHCP_SERVER_PORT; + bss->dhcp_relay_port = DHCP_SERVER_PORT; + bss->fils_discovery_min_int = 20; +#endif /* CONFIG_FILS */ + + bss->broadcast_deauth = 1; + +#ifdef CONFIG_MBO + bss->mbo_cell_data_conn_pref = -1; +#endif /* CONFIG_MBO */ + + /* Disable TLS v1.3 by default for now to avoid interoperability issue. + * This can be enabled by default once the implementation has been fully + * completed and tested with other implementations. */ + bss->tls_flags = TLS_CONN_DISABLE_TLSv1_3; + + bss->max_auth_rounds = 100; + bss->max_auth_rounds_short = 50; + + bss->send_probe_response = 1; + +#ifdef CONFIG_HS20 + bss->hs20_release = (HS20_VERSION >> 4) + 1; +#endif /* CONFIG_HS20 */ + +#ifdef CONFIG_MACSEC + bss->mka_priority = DEFAULT_PRIO_NOT_KEY_SERVER; + bss->macsec_port = 1; +#endif /* CONFIG_MACSEC */ + + /* Default to strict CRL checking. */ + bss->check_crl_strict = 1; + + bss->multi_ap_profile = MULTI_AP_PROFILE_2; + +#ifdef CONFIG_TESTING_OPTIONS + bss->sae_commit_status = -1; + bss->test_assoc_comeback_type = -1; +#endif /* CONFIG_TESTING_OPTIONS */ + +#ifdef CONFIG_PASN + /* comeback after 10 TUs */ + bss->pasn_comeback_after = 10; + bss->pasn_noauth = 1; +#endif /* CONFIG_PASN */ +} + + +struct hostapd_config * hostapd_config_defaults(void) +{ +#define ecw2cw(ecw) ((1 << (ecw)) - 1) + + struct hostapd_config *conf; + struct hostapd_bss_config *bss; + const int aCWmin = 4, aCWmax = 10; + const struct hostapd_wmm_ac_params ac_bk = + { aCWmin, aCWmax, 7, 0, 0 }; /* background traffic */ + const struct hostapd_wmm_ac_params ac_be = + { aCWmin, aCWmax, 3, 0, 0 }; /* best effort traffic */ + const struct hostapd_wmm_ac_params ac_vi = /* video traffic */ + { aCWmin - 1, aCWmin, 2, 3008 / 32, 0 }; + const struct hostapd_wmm_ac_params ac_vo = /* voice traffic */ + { aCWmin - 2, aCWmin - 1, 2, 1504 / 32, 0 }; + const struct hostapd_tx_queue_params txq_bk = + { 7, ecw2cw(aCWmin), ecw2cw(aCWmax), 0 }; + const struct hostapd_tx_queue_params txq_be = + { 3, ecw2cw(aCWmin), 4 * (ecw2cw(aCWmin) + 1) - 1, 0}; + const struct hostapd_tx_queue_params txq_vi = + { 1, (ecw2cw(aCWmin) + 1) / 2 - 1, ecw2cw(aCWmin), 30}; + const struct hostapd_tx_queue_params txq_vo = + { 1, (ecw2cw(aCWmin) + 1) / 4 - 1, + (ecw2cw(aCWmin) + 1) / 2 - 1, 15}; + +#undef ecw2cw + + conf = os_zalloc(sizeof(*conf)); + bss = os_zalloc(sizeof(*bss)); + if (conf == NULL || bss == NULL) { + wpa_printf(MSG_ERROR, "Failed to allocate memory for " + "configuration data."); + os_free(conf); + os_free(bss); + return NULL; + } + conf->bss = os_calloc(1, sizeof(struct hostapd_bss_config *)); + if (conf->bss == NULL) { + os_free(conf); + os_free(bss); + return NULL; + } + conf->bss[0] = bss; + + bss->radius = os_zalloc(sizeof(*bss->radius)); + if (bss->radius == NULL) { + os_free(conf->bss); + os_free(conf); + os_free(bss); + return NULL; + } + + hostapd_config_defaults_bss(bss); + + conf->num_bss = 1; + + conf->beacon_int = 100; + conf->rts_threshold = -2; /* use driver default: 2347 */ + conf->fragm_threshold = -2; /* user driver default: 2346 */ + /* Set to invalid value means do not add Power Constraint IE */ + conf->local_pwr_constraint = -1; + + conf->wmm_ac_params[0] = ac_be; + conf->wmm_ac_params[1] = ac_bk; + conf->wmm_ac_params[2] = ac_vi; + conf->wmm_ac_params[3] = ac_vo; + + conf->tx_queue[0] = txq_vo; + conf->tx_queue[1] = txq_vi; + conf->tx_queue[2] = txq_be; + conf->tx_queue[3] = txq_bk; + + conf->ht_capab = HT_CAP_INFO_SMPS_DISABLED; + + conf->ap_table_max_size = 255; + conf->ap_table_expiration_time = 60; + conf->track_sta_max_age = 180; + +#ifdef CONFIG_TESTING_OPTIONS + conf->ignore_probe_probability = 0.0; + conf->ignore_auth_probability = 0.0; + conf->ignore_assoc_probability = 0.0; + conf->ignore_reassoc_probability = 0.0; + conf->corrupt_gtk_rekey_mic_probability = 0.0; + conf->ecsa_ie_only = 0; +#endif /* CONFIG_TESTING_OPTIONS */ + + conf->acs = 0; + conf->acs_ch_list.num = 0; +#ifdef CONFIG_ACS + conf->acs_num_scans = 5; +#endif /* CONFIG_ACS */ + +#ifdef CONFIG_IEEE80211AX + conf->he_op.he_rts_threshold = HE_OPERATION_RTS_THRESHOLD_MASK >> + HE_OPERATION_RTS_THRESHOLD_OFFSET; + /* Set default basic MCS/NSS set to single stream MCS 0-7 */ + conf->he_op.he_basic_mcs_nss_set = 0xfffc; + conf->he_op.he_bss_color_disabled = 1; + conf->he_op.he_bss_color_partial = 0; + conf->he_op.he_bss_color = os_random() % 63 + 1; + conf->he_op.he_twt_responder = 1; + conf->he_6ghz_max_mpdu = 2; + conf->he_6ghz_max_ampdu_len_exp = 7; + conf->he_6ghz_rx_ant_pat = 1; + conf->he_6ghz_tx_ant_pat = 1; + conf->he_6ghz_reg_pwr_type = HE_REG_INFO_6GHZ_AP_TYPE_VLP; + conf->reg_def_cli_eirp_psd = -1; + conf->reg_sub_cli_eirp_psd = -1; + conf->reg_def_cli_eirp = -1; +#endif /* CONFIG_IEEE80211AX */ + + /* The third octet of the country string uses an ASCII space character + * by default to indicate that the regulations encompass all + * environments for the current frequency band in the country. */ + conf->country[2] = ' '; + + conf->rssi_reject_assoc_rssi = 0; + conf->rssi_reject_assoc_timeout = 30; + +#ifdef CONFIG_AIRTIME_POLICY + conf->airtime_update_interval = AIRTIME_DEFAULT_UPDATE_INTERVAL; +#endif /* CONFIG_AIRTIME_POLICY */ + + hostapd_set_and_check_bw320_offset(conf, 0); + + return conf; +} + + +int hostapd_mac_comp(const void *a, const void *b) +{ + return os_memcmp(a, b, sizeof(macaddr)); +} + + +static int hostapd_config_read_wpa_psk(const char *fname, + struct hostapd_ssid *ssid) +{ + FILE *f; + char buf[128], *pos; + const char *keyid; + char *context; + char *context2; + char *token; + char *name; + char *value; + int line = 0, ret = 0, len, ok; + u8 addr[ETH_ALEN]; + struct hostapd_wpa_psk *psk; + + if (!fname) + return 0; + + f = fopen(fname, "r"); + if (!f) { + wpa_printf(MSG_ERROR, "WPA PSK file '%s' not found.", fname); + return -1; + } + + while (fgets(buf, sizeof(buf), f)) { + int vlan_id = 0; + int wps = 0; + + line++; + + if (buf[0] == '#') + continue; + pos = buf; + while (*pos != '\0') { + if (*pos == '\n') { + *pos = '\0'; + break; + } + pos++; + } + if (buf[0] == '\0') + continue; + + context = NULL; + keyid = NULL; + while ((token = str_token(buf, " ", &context))) { + if (!os_strchr(token, '=')) + break; + context2 = NULL; + name = str_token(token, "=", &context2); + if (!name) + break; + value = str_token(token, "", &context2); + if (!value) + value = ""; + if (!os_strcmp(name, "keyid")) { + keyid = value; + } else if (!os_strcmp(name, "wps")) { + wps = atoi(value); + } else if (!os_strcmp(name, "vlanid")) { + vlan_id = atoi(value); + } else { + wpa_printf(MSG_ERROR, + "Unrecognized '%s=%s' on line %d in '%s'", + name, value, line, fname); + ret = -1; + break; + } + } + + if (ret == -1) + break; + + if (!token) + token = ""; + if (hwaddr_aton(token, addr)) { + wpa_printf(MSG_ERROR, + "Invalid MAC address '%s' on line %d in '%s'", + token, line, fname); + ret = -1; + break; + } + + psk = os_zalloc(sizeof(*psk)); + if (psk == NULL) { + wpa_printf(MSG_ERROR, "WPA PSK allocation failed"); + ret = -1; + break; + } + psk->vlan_id = vlan_id; + if (is_zero_ether_addr(addr)) + psk->group = 1; + else + os_memcpy(psk->addr, addr, ETH_ALEN); + + pos = str_token(buf, "", &context); + if (!pos) { + wpa_printf(MSG_ERROR, "No PSK on line %d in '%s'", + line, fname); + os_free(psk); + ret = -1; + break; + } + + ok = 0; + len = os_strlen(pos); + if (len == 2 * PMK_LEN && + hexstr2bin(pos, psk->psk, PMK_LEN) == 0) + ok = 1; + else if (len >= 8 && len < 64 && + pbkdf2_sha1(pos, ssid->ssid, ssid->ssid_len, + 4096, psk->psk, PMK_LEN) == 0) + ok = 1; + if (!ok) { + wpa_printf(MSG_ERROR, + "Invalid PSK '%s' on line %d in '%s'", + pos, line, fname); + os_free(psk); + ret = -1; + break; + } + + if (keyid) { + len = os_strlcpy(psk->keyid, keyid, sizeof(psk->keyid)); + if ((size_t) len >= sizeof(psk->keyid)) { + wpa_printf(MSG_ERROR, + "PSK keyid too long on line %d in '%s'", + line, fname); + os_free(psk); + ret = -1; + break; + } + } + + psk->wps = wps; + + psk->next = ssid->wpa_psk; + ssid->wpa_psk = psk; + } + + fclose(f); + + return ret; +} + + +static int hostapd_derive_psk(struct hostapd_ssid *ssid) +{ + ssid->wpa_psk = os_zalloc(sizeof(struct hostapd_wpa_psk)); + if (ssid->wpa_psk == NULL) { + wpa_printf(MSG_ERROR, "Unable to alloc space for PSK"); + return -1; + } + wpa_hexdump_ascii(MSG_DEBUG, "SSID", + (u8 *) ssid->ssid, ssid->ssid_len); + wpa_hexdump_ascii_key(MSG_DEBUG, "PSK (ASCII passphrase)", + (u8 *) ssid->wpa_passphrase, + os_strlen(ssid->wpa_passphrase)); + if (pbkdf2_sha1(ssid->wpa_passphrase, + ssid->ssid, ssid->ssid_len, + 4096, ssid->wpa_psk->psk, PMK_LEN) != 0) { + wpa_printf(MSG_ERROR, "Error in pbkdf2_sha1()"); + return -1; + } + wpa_hexdump_key(MSG_DEBUG, "PSK (from passphrase)", + ssid->wpa_psk->psk, PMK_LEN); + return 0; +} + + +int hostapd_setup_sae_pt(struct hostapd_bss_config *conf) +{ +#ifdef CONFIG_SAE + struct hostapd_ssid *ssid = &conf->ssid; + struct sae_password_entry *pw; + + if ((conf->sae_pwe == SAE_PWE_HUNT_AND_PECK && + !hostapd_sae_pw_id_in_use(conf) && + !wpa_key_mgmt_sae_ext_key(conf->wpa_key_mgmt) && + !hostapd_sae_pk_in_use(conf)) || + conf->sae_pwe == SAE_PWE_FORCE_HUNT_AND_PECK || + !wpa_key_mgmt_sae(conf->wpa_key_mgmt)) + return 0; /* PT not needed */ + + sae_deinit_pt(ssid->pt); + ssid->pt = NULL; + if (ssid->wpa_passphrase) { + ssid->pt = sae_derive_pt(conf->sae_groups, ssid->ssid, + ssid->ssid_len, + (const u8 *) ssid->wpa_passphrase, + os_strlen(ssid->wpa_passphrase), + NULL); + if (!ssid->pt) + return -1; + } + + for (pw = conf->sae_passwords; pw; pw = pw->next) { + sae_deinit_pt(pw->pt); + pw->pt = sae_derive_pt(conf->sae_groups, ssid->ssid, + ssid->ssid_len, + (const u8 *) pw->password, + os_strlen(pw->password), + pw->identifier); + if (!pw->pt) + return -1; + } +#endif /* CONFIG_SAE */ + + return 0; +} + + +int hostapd_setup_wpa_psk(struct hostapd_bss_config *conf) +{ + struct hostapd_ssid *ssid = &conf->ssid; + + if (hostapd_setup_sae_pt(conf) < 0) + return -1; + + if (ssid->wpa_passphrase != NULL) { + if (ssid->wpa_psk != NULL) { + wpa_printf(MSG_DEBUG, "Using pre-configured WPA PSK " + "instead of passphrase"); + } else { + wpa_printf(MSG_DEBUG, "Deriving WPA PSK based on " + "passphrase"); + if (hostapd_derive_psk(ssid) < 0) + return -1; + } + ssid->wpa_psk->group = 1; + } + + return hostapd_config_read_wpa_psk(ssid->wpa_psk_file, &conf->ssid); +} + + +static void hostapd_config_free_radius(struct hostapd_radius_server *servers, + int num_servers) +{ + int i; + + for (i = 0; i < num_servers; i++) { + os_free(servers[i].shared_secret); + os_free(servers[i].ca_cert); + os_free(servers[i].client_cert); + os_free(servers[i].private_key); + os_free(servers[i].private_key_passwd); + } + os_free(servers); +} + + +struct hostapd_radius_attr * +hostapd_config_get_radius_attr(struct hostapd_radius_attr *attr, u8 type) +{ + for (; attr; attr = attr->next) { + if (attr->type == type) + return attr; + } + return NULL; +} + + +struct hostapd_radius_attr * hostapd_parse_radius_attr(const char *value) +{ + const char *pos; + char syntax; + struct hostapd_radius_attr *attr; + size_t len; + + attr = os_zalloc(sizeof(*attr)); + if (!attr) + return NULL; + + attr->type = atoi(value); + + pos = os_strchr(value, ':'); + if (!pos) { + attr->val = wpabuf_alloc(1); + if (!attr->val) { + os_free(attr); + return NULL; + } + wpabuf_put_u8(attr->val, 0); + return attr; + } + + pos++; + if (pos[0] == '\0' || pos[1] != ':') { + os_free(attr); + return NULL; + } + syntax = *pos++; + pos++; + + switch (syntax) { + case 's': + attr->val = wpabuf_alloc_copy(pos, os_strlen(pos)); + break; + case 'x': + len = os_strlen(pos); + if (len & 1) + break; + len /= 2; + attr->val = wpabuf_alloc(len); + if (!attr->val) + break; + if (hexstr2bin(pos, wpabuf_put(attr->val, len), len) < 0) { + wpabuf_free(attr->val); + os_free(attr); + return NULL; + } + break; + case 'd': + attr->val = wpabuf_alloc(4); + if (attr->val) + wpabuf_put_be32(attr->val, atoi(pos)); + break; + default: + os_free(attr); + return NULL; + } + + if (!attr->val) { + os_free(attr); + return NULL; + } + + return attr; +} + + +void hostapd_config_free_radius_attr(struct hostapd_radius_attr *attr) +{ + struct hostapd_radius_attr *prev; + + while (attr) { + prev = attr; + attr = attr->next; + wpabuf_free(prev->val); + os_free(prev); + } +} + + +void hostapd_config_free_eap_user(struct hostapd_eap_user *user) +{ + hostapd_config_free_radius_attr(user->accept_attr); + os_free(user->identity); + bin_clear_free(user->password, user->password_len); + bin_clear_free(user->salt, user->salt_len); + os_free(user); +} + + +void hostapd_config_free_eap_users(struct hostapd_eap_user *user) +{ + struct hostapd_eap_user *prev_user; + + while (user) { + prev_user = user; + user = user->next; + hostapd_config_free_eap_user(prev_user); + } +} + + +#ifdef CONFIG_WEP +static void hostapd_config_free_wep(struct hostapd_wep_keys *keys) +{ + int i; + for (i = 0; i < NUM_WEP_KEYS; i++) { + bin_clear_free(keys->key[i], keys->len[i]); + keys->key[i] = NULL; + } +} +#endif /* CONFIG_WEP */ + + +void hostapd_config_clear_wpa_psk(struct hostapd_wpa_psk **l) +{ + struct hostapd_wpa_psk *psk, *tmp; + + for (psk = *l; psk;) { + tmp = psk; + psk = psk->next; + bin_clear_free(tmp, sizeof(*tmp)); + } + *l = NULL; +} + + +#ifdef CONFIG_IEEE80211R_AP + +void hostapd_config_clear_rxkhs(struct hostapd_bss_config *conf) +{ + struct ft_remote_r0kh *r0kh, *r0kh_prev; + struct ft_remote_r1kh *r1kh, *r1kh_prev; + + r0kh = conf->r0kh_list; + conf->r0kh_list = NULL; + while (r0kh) { + r0kh_prev = r0kh; + r0kh = r0kh->next; + os_free(r0kh_prev); + } + + r1kh = conf->r1kh_list; + conf->r1kh_list = NULL; + while (r1kh) { + r1kh_prev = r1kh; + r1kh = r1kh->next; + os_free(r1kh_prev); + } +} + +#endif /* CONFIG_IEEE80211R_AP */ + + +static void hostapd_config_free_anqp_elem(struct hostapd_bss_config *conf) +{ + struct anqp_element *elem; + + while ((elem = dl_list_first(&conf->anqp_elem, struct anqp_element, + list))) { + dl_list_del(&elem->list); + wpabuf_free(elem->payload); + os_free(elem); + } +} + + +static void hostapd_config_free_fils_realms(struct hostapd_bss_config *conf) +{ +#ifdef CONFIG_FILS + struct fils_realm *realm; + + while ((realm = dl_list_first(&conf->fils_realms, struct fils_realm, + list))) { + dl_list_del(&realm->list); + os_free(realm); + } +#endif /* CONFIG_FILS */ +} + + +static void hostapd_config_free_sae_passwords(struct hostapd_bss_config *conf) +{ + struct sae_password_entry *pw, *tmp; + + pw = conf->sae_passwords; + conf->sae_passwords = NULL; + while (pw) { + tmp = pw; + pw = pw->next; + str_clear_free(tmp->password); + os_free(tmp->identifier); +#ifdef CONFIG_SAE + sae_deinit_pt(tmp->pt); +#endif /* CONFIG_SAE */ +#ifdef CONFIG_SAE_PK + sae_deinit_pk(tmp->pk); +#endif /* CONFIG_SAE_PK */ + os_free(tmp); + } +} + + +#ifdef CONFIG_DPP2 +static void hostapd_dpp_controller_conf_free(struct dpp_controller_conf *conf) +{ + struct dpp_controller_conf *prev; + + while (conf) { + prev = conf; + conf = conf->next; + os_free(prev); + } +} +#endif /* CONFIG_DPP2 */ + + +void hostapd_config_free_bss(struct hostapd_bss_config *conf) +{ +#if defined(CONFIG_WPS) || defined(CONFIG_HS20) + size_t i; +#endif + + if (conf == NULL) + return; + + hostapd_config_clear_wpa_psk(&conf->ssid.wpa_psk); + + str_clear_free(conf->ssid.wpa_passphrase); + os_free(conf->ssid.wpa_psk_file); +#ifdef CONFIG_WEP + hostapd_config_free_wep(&conf->ssid.wep); +#endif /* CONFIG_WEP */ +#ifdef CONFIG_FULL_DYNAMIC_VLAN + os_free(conf->ssid.vlan_tagged_interface); +#endif /* CONFIG_FULL_DYNAMIC_VLAN */ +#ifdef CONFIG_SAE + sae_deinit_pt(conf->ssid.pt); +#endif /* CONFIG_SAE */ + + hostapd_config_free_eap_users(conf->eap_user); + os_free(conf->eap_user_sqlite); + + os_free(conf->eap_req_id_text); + os_free(conf->erp_domain); + os_free(conf->accept_mac); + os_free(conf->deny_mac); + os_free(conf->nas_identifier); + if (conf->radius) { + hostapd_config_free_radius(conf->radius->auth_servers, + conf->radius->num_auth_servers); + hostapd_config_free_radius(conf->radius->acct_servers, + conf->radius->num_acct_servers); + os_free(conf->radius->force_client_dev); + } + hostapd_config_free_radius_attr(conf->radius_auth_req_attr); + hostapd_config_free_radius_attr(conf->radius_acct_req_attr); + os_free(conf->radius_req_attr_sqlite); + os_free(conf->rsn_preauth_interfaces); + os_free(conf->ctrl_interface); + os_free(conf->config_id); + os_free(conf->ca_cert); + os_free(conf->server_cert); + os_free(conf->server_cert2); + os_free(conf->private_key); + os_free(conf->private_key2); + os_free(conf->private_key_passwd); + os_free(conf->private_key_passwd2); + os_free(conf->check_cert_subject); + os_free(conf->ocsp_stapling_response); + os_free(conf->ocsp_stapling_response_multi); + os_free(conf->dh_file); + os_free(conf->openssl_ciphers); + os_free(conf->openssl_ecdh_curves); + os_free(conf->pac_opaque_encr_key); + os_free(conf->eap_fast_a_id); + os_free(conf->eap_fast_a_id_info); + os_free(conf->eap_sim_db); + os_free(conf->imsi_privacy_key); + os_free(conf->radius_server_clients); + os_free(conf->radius); + os_free(conf->radius_das_shared_secret); + hostapd_config_free_vlan(conf); + os_free(conf->time_zone); + +#ifdef CONFIG_IEEE80211R_AP + hostapd_config_clear_rxkhs(conf); + os_free(conf->rxkh_file); + conf->rxkh_file = NULL; +#endif /* CONFIG_IEEE80211R_AP */ + +#ifdef CONFIG_WPS + os_free(conf->wps_pin_requests); + os_free(conf->device_name); + os_free(conf->manufacturer); + os_free(conf->model_name); + os_free(conf->model_number); + os_free(conf->serial_number); + os_free(conf->config_methods); + os_free(conf->ap_pin); + os_free(conf->extra_cred); + os_free(conf->ap_settings); + hostapd_config_clear_wpa_psk(&conf->multi_ap_backhaul_ssid.wpa_psk); + str_clear_free(conf->multi_ap_backhaul_ssid.wpa_passphrase); + os_free(conf->upnp_iface); + os_free(conf->friendly_name); + os_free(conf->manufacturer_url); + os_free(conf->model_description); + os_free(conf->model_url); + os_free(conf->upc); + for (i = 0; i < MAX_WPS_VENDOR_EXTENSIONS; i++) + wpabuf_free(conf->wps_vendor_ext[i]); + wpabuf_free(conf->wps_application_ext); + wpabuf_free(conf->wps_nfc_dh_pubkey); + wpabuf_free(conf->wps_nfc_dh_privkey); + wpabuf_free(conf->wps_nfc_dev_pw); +#endif /* CONFIG_WPS */ + + os_free(conf->roaming_consortium); + os_free(conf->venue_name); + os_free(conf->venue_url); + os_free(conf->nai_realm_data); + os_free(conf->network_auth_type); + os_free(conf->anqp_3gpp_cell_net); + os_free(conf->domain_name); + hostapd_config_free_anqp_elem(conf); + +#ifdef CONFIG_RADIUS_TEST + os_free(conf->dump_msk_file); +#endif /* CONFIG_RADIUS_TEST */ + +#ifdef CONFIG_HS20 + os_free(conf->hs20_oper_friendly_name); + os_free(conf->hs20_wan_metrics); + os_free(conf->hs20_connection_capability); + os_free(conf->hs20_operating_class); + os_free(conf->hs20_icons); + if (conf->hs20_osu_providers) { + for (i = 0; i < conf->hs20_osu_providers_count; i++) { + struct hs20_osu_provider *p; + size_t j; + p = &conf->hs20_osu_providers[i]; + os_free(p->friendly_name); + os_free(p->server_uri); + os_free(p->method_list); + for (j = 0; j < p->icons_count; j++) + os_free(p->icons[j]); + os_free(p->icons); + os_free(p->osu_nai); + os_free(p->osu_nai2); + os_free(p->service_desc); + } + os_free(conf->hs20_osu_providers); + } + if (conf->hs20_operator_icon) { + for (i = 0; i < conf->hs20_operator_icon_count; i++) + os_free(conf->hs20_operator_icon[i]); + os_free(conf->hs20_operator_icon); + } + os_free(conf->subscr_remediation_url); + os_free(conf->hs20_sim_provisioning_url); + os_free(conf->t_c_filename); + os_free(conf->t_c_server_url); +#endif /* CONFIG_HS20 */ + + wpabuf_free(conf->vendor_elements); + wpabuf_free(conf->assocresp_elements); + + os_free(conf->sae_groups); +#ifdef CONFIG_OWE + os_free(conf->owe_groups); +#endif /* CONFIG_OWE */ + + os_free(conf->wowlan_triggers); + + os_free(conf->server_id); + +#ifdef CONFIG_TESTING_OPTIONS + wpabuf_free(conf->own_ie_override); + wpabuf_free(conf->sae_commit_override); + wpabuf_free(conf->rsne_override_eapol); + wpabuf_free(conf->rsnxe_override_eapol); + wpabuf_free(conf->rsne_override_ft); + wpabuf_free(conf->rsnxe_override_ft); + wpabuf_free(conf->gtk_rsc_override); + wpabuf_free(conf->igtk_rsc_override); + wpabuf_free(conf->eapol_m1_elements); + wpabuf_free(conf->eapol_m3_elements); + wpabuf_free(conf->presp_elements); +#endif /* CONFIG_TESTING_OPTIONS */ + + os_free(conf->no_probe_resp_if_seen_on); + os_free(conf->no_auth_if_seen_on); + + hostapd_config_free_fils_realms(conf); + +#ifdef CONFIG_DPP + os_free(conf->dpp_name); + os_free(conf->dpp_mud_url); + os_free(conf->dpp_extra_conf_req_name); + os_free(conf->dpp_extra_conf_req_value); + os_free(conf->dpp_connector); + wpabuf_free(conf->dpp_netaccesskey); + wpabuf_free(conf->dpp_csign); +#ifdef CONFIG_DPP2 + hostapd_dpp_controller_conf_free(conf->dpp_controller); +#endif /* CONFIG_DPP2 */ +#endif /* CONFIG_DPP */ + + hostapd_config_free_sae_passwords(conf); + +#ifdef CONFIG_AIRTIME_POLICY + { + struct airtime_sta_weight *wt, *wt_prev; + + wt = conf->airtime_weight_list; + conf->airtime_weight_list = NULL; + while (wt) { + wt_prev = wt; + wt = wt->next; + os_free(wt_prev); + } + } +#endif /* CONFIG_AIRTIME_POLICY */ + +#ifdef CONFIG_PASN + os_free(conf->pasn_groups); +#endif /* CONFIG_PASN */ + + os_free(conf); +} + + +/** + * hostapd_config_free - Free hostapd configuration + * @conf: Configuration data from hostapd_config_read(). + */ +void hostapd_config_free(struct hostapd_config *conf) +{ + size_t i; + + if (conf == NULL) + return; + + for (i = 0; i < conf->num_bss; i++) + hostapd_config_free_bss(conf->bss[i]); + os_free(conf->bss); + os_free(conf->supported_rates); + os_free(conf->basic_rates); + os_free(conf->acs_ch_list.range); + os_free(conf->acs_freq_list.range); + os_free(conf->driver_params); +#ifdef CONFIG_ACS + os_free(conf->acs_chan_bias); +#endif /* CONFIG_ACS */ + wpabuf_free(conf->lci); + wpabuf_free(conf->civic); + + os_free(conf); +} + + +/** + * hostapd_maclist_found - Find a MAC address from a list + * @list: MAC address list + * @num_entries: Number of addresses in the list + * @addr: Address to search for + * @vlan_id: Buffer for returning VLAN ID or %NULL if not needed + * Returns: 1 if address is in the list or 0 if not. + * + * Perform a binary search for given MAC address from a pre-sorted list. + */ +int hostapd_maclist_found(struct mac_acl_entry *list, int num_entries, + const u8 *addr, struct vlan_description *vlan_id) +{ + int start, end, middle, res; + + start = 0; + end = num_entries - 1; + + while (start <= end) { + middle = (start + end) / 2; + res = os_memcmp(list[middle].addr, addr, ETH_ALEN); + if (res == 0) { + if (vlan_id) + *vlan_id = list[middle].vlan_id; + return 1; + } + if (res < 0) + start = middle + 1; + else + end = middle - 1; + } + + return 0; +} + + +int hostapd_rate_found(int *list, int rate) +{ + int i; + + if (list == NULL) + return 0; + + for (i = 0; list[i] >= 0; i++) + if (list[i] == rate) + return 1; + + return 0; +} + + +int hostapd_vlan_valid(struct hostapd_vlan *vlan, + struct vlan_description *vlan_desc) +{ + struct hostapd_vlan *v = vlan; + int i; + + if (!vlan_desc->notempty || vlan_desc->untagged < 0 || + vlan_desc->untagged > MAX_VLAN_ID) + return 0; + for (i = 0; i < MAX_NUM_TAGGED_VLAN; i++) { + if (vlan_desc->tagged[i] < 0 || + vlan_desc->tagged[i] > MAX_VLAN_ID) + return 0; + } + if (!vlan_desc->untagged && !vlan_desc->tagged[0]) + return 0; + + while (v) { + if (!vlan_compare(&v->vlan_desc, vlan_desc) || + v->vlan_id == VLAN_ID_WILDCARD) + return 1; + v = v->next; + } + return 0; +} + + +const char * hostapd_get_vlan_id_ifname(struct hostapd_vlan *vlan, int vlan_id) +{ + struct hostapd_vlan *v = vlan; + while (v) { + if (v->vlan_id == vlan_id) + return v->ifname; + v = v->next; + } + return NULL; +} + + +const u8 * hostapd_get_psk(const struct hostapd_bss_config *conf, + const u8 *addr, const u8 *p2p_dev_addr, + const u8 *prev_psk, int *vlan_id) +{ + struct hostapd_wpa_psk *psk; + int next_ok = prev_psk == NULL; + + if (vlan_id) + *vlan_id = 0; + + if (p2p_dev_addr && !is_zero_ether_addr(p2p_dev_addr)) { + wpa_printf(MSG_DEBUG, "Searching a PSK for " MACSTR + " p2p_dev_addr=" MACSTR " prev_psk=%p", + MAC2STR(addr), MAC2STR(p2p_dev_addr), prev_psk); + addr = NULL; /* Use P2P Device Address for matching */ + } else { + wpa_printf(MSG_DEBUG, "Searching a PSK for " MACSTR + " prev_psk=%p", + MAC2STR(addr), prev_psk); + } + + for (psk = conf->ssid.wpa_psk; psk != NULL; psk = psk->next) { + if (next_ok && + (psk->group || + (addr && ether_addr_equal(psk->addr, addr)) || + (!addr && p2p_dev_addr && + ether_addr_equal(psk->p2p_dev_addr, p2p_dev_addr)))) { + if (vlan_id) + *vlan_id = psk->vlan_id; + return psk->psk; + } + + if (psk->psk == prev_psk) + next_ok = 1; + } + + return NULL; +} + + +#ifdef CONFIG_SAE_PK +static bool hostapd_sae_pk_password_without_pk(struct hostapd_bss_config *bss) +{ + struct sae_password_entry *pw; + bool res = false; + + if (bss->ssid.wpa_passphrase && +#ifdef CONFIG_TESTING_OPTIONS + !bss->sae_pk_password_check_skip && +#endif /* CONFIG_TESTING_OPTIONS */ + sae_pk_valid_password(bss->ssid.wpa_passphrase)) + res = true; + + for (pw = bss->sae_passwords; pw; pw = pw->next) { + if (!pw->pk && +#ifdef CONFIG_TESTING_OPTIONS + !bss->sae_pk_password_check_skip && +#endif /* CONFIG_TESTING_OPTIONS */ + sae_pk_valid_password(pw->password)) + return true; + + if (bss->ssid.wpa_passphrase && res && pw->pk && + os_strcmp(bss->ssid.wpa_passphrase, pw->password) == 0) + res = false; + } + + return res; +} +#endif /* CONFIG_SAE_PK */ + + +static bool hostapd_config_check_bss_6g(struct hostapd_bss_config *bss) +{ + if (bss->wpa != WPA_PROTO_RSN) { + wpa_printf(MSG_ERROR, + "Pre-RSNA security methods are not allowed in 6 GHz"); + return false; + } + + if (bss->ieee80211w != MGMT_FRAME_PROTECTION_REQUIRED) { + wpa_printf(MSG_ERROR, + "Management frame protection is required in 6 GHz"); + return false; + } + + if (bss->wpa_key_mgmt & (WPA_KEY_MGMT_PSK | + WPA_KEY_MGMT_FT_PSK | + WPA_KEY_MGMT_PSK_SHA256)) { + wpa_printf(MSG_ERROR, "Invalid AKM suite for 6 GHz"); + return false; + } + + if (bss->rsn_pairwise & (WPA_CIPHER_WEP40 | + WPA_CIPHER_WEP104 | + WPA_CIPHER_TKIP)) { + wpa_printf(MSG_ERROR, + "Invalid pairwise cipher suite for 6 GHz"); + return false; + } + + if (bss->wpa_group & (WPA_CIPHER_WEP40 | + WPA_CIPHER_WEP104 | + WPA_CIPHER_TKIP)) { + wpa_printf(MSG_ERROR, "Invalid group cipher suite for 6 GHz"); + return false; + } + +#ifdef CONFIG_SAE + if (wpa_key_mgmt_sae(bss->wpa_key_mgmt) && + bss->sae_pwe == SAE_PWE_HUNT_AND_PECK) { + wpa_printf(MSG_INFO, "SAE: Enabling SAE H2E on 6 GHz"); + bss->sae_pwe = SAE_PWE_BOTH; + } +#endif /* CONFIG_SAE */ + + return true; +} + + +static int hostapd_config_check_bss(struct hostapd_bss_config *bss, + struct hostapd_config *conf, + int full_config) +{ + if (full_config && is_6ghz_op_class(conf->op_class) && + !hostapd_config_check_bss_6g(bss)) + return -1; + + if (full_config && bss->ieee802_1x && !bss->eap_server && + !bss->radius->auth_servers) { + wpa_printf(MSG_ERROR, "Invalid IEEE 802.1X configuration (no " + "EAP authenticator configured)."); + return -1; + } + +#ifdef CONFIG_WEP + if (bss->wpa) { + int wep, i; + + wep = bss->default_wep_key_len > 0 || + bss->individual_wep_key_len > 0; + for (i = 0; i < NUM_WEP_KEYS; i++) { + if (bss->ssid.wep.keys_set) { + wep = 1; + break; + } + } + + if (wep) { + wpa_printf(MSG_ERROR, "WEP configuration in a WPA network is not supported"); + return -1; + } + } +#endif /* CONFIG_WEP */ + + if (full_config && bss->wpa && + bss->wpa_psk_radius != PSK_RADIUS_IGNORED && + bss->wpa_psk_radius != PSK_RADIUS_DURING_4WAY_HS && + bss->macaddr_acl != USE_EXTERNAL_RADIUS_AUTH) { + wpa_printf(MSG_ERROR, "WPA-PSK using RADIUS enabled, but no " + "RADIUS checking (macaddr_acl=2) enabled."); + return -1; + } + + if (full_config && bss->wpa && + wpa_key_mgmt_wpa_psk_no_sae(bss->wpa_key_mgmt) && + bss->ssid.wpa_psk == NULL && bss->ssid.wpa_passphrase == NULL && + bss->ssid.wpa_psk_file == NULL && + bss->wpa_psk_radius != PSK_RADIUS_DURING_4WAY_HS && + (bss->wpa_psk_radius != PSK_RADIUS_REQUIRED || + bss->macaddr_acl != USE_EXTERNAL_RADIUS_AUTH)) { + wpa_printf(MSG_ERROR, "WPA-PSK enabled, but PSK or passphrase " + "is not configured."); + return -1; + } + + if (full_config && !is_zero_ether_addr(bss->bssid)) { + size_t i; + + for (i = 0; i < conf->num_bss; i++) { + if (conf->bss[i] != bss && + (hostapd_mac_comp(conf->bss[i]->bssid, + bss->bssid) == 0)) { + wpa_printf(MSG_ERROR, "Duplicate BSSID " MACSTR + " on interface '%s' and '%s'.", + MAC2STR(bss->bssid), + conf->bss[i]->iface, bss->iface); + return -1; + } + } + } + +#ifdef CONFIG_IEEE80211R_AP + if (full_config && wpa_key_mgmt_ft(bss->wpa_key_mgmt) && + (bss->nas_identifier == NULL || + os_strlen(bss->nas_identifier) < 1 || + os_strlen(bss->nas_identifier) > FT_R0KH_ID_MAX_LEN)) { + wpa_printf(MSG_ERROR, "FT (IEEE 802.11r) requires " + "nas_identifier to be configured as a 1..48 octet " + "string"); + return -1; + } +#endif /* CONFIG_IEEE80211R_AP */ + + if (full_config && conf->ieee80211n && + conf->hw_mode == HOSTAPD_MODE_IEEE80211B) { + bss->disable_11n = true; + wpa_printf(MSG_ERROR, "HT (IEEE 802.11n) in 11b mode is not " + "allowed, disabling HT capabilities"); + } + +#ifdef CONFIG_WEP + if (full_config && conf->ieee80211n && + bss->ssid.security_policy == SECURITY_STATIC_WEP) { + bss->disable_11n = true; + wpa_printf(MSG_ERROR, "HT (IEEE 802.11n) with WEP is not " + "allowed, disabling HT capabilities"); + } +#endif /* CONFIG_WEP */ + + if (full_config && conf->ieee80211n && bss->wpa && + !(bss->wpa_pairwise & WPA_CIPHER_CCMP) && + !(bss->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP | + WPA_CIPHER_CCMP_256 | WPA_CIPHER_GCMP_256))) + { + bss->disable_11n = true; + wpa_printf(MSG_ERROR, "HT (IEEE 802.11n) with WPA/WPA2 " + "requires CCMP/GCMP to be enabled, disabling HT " + "capabilities"); + } + +#ifdef CONFIG_IEEE80211AC +#ifdef CONFIG_WEP + if (full_config && conf->ieee80211ac && + bss->ssid.security_policy == SECURITY_STATIC_WEP) { + bss->disable_11ac = true; + wpa_printf(MSG_ERROR, + "VHT (IEEE 802.11ac) with WEP is not allowed, disabling VHT capabilities"); + } +#endif /* CONFIG_WEP */ + + if (full_config && conf->ieee80211ac && bss->wpa && + !(bss->wpa_pairwise & WPA_CIPHER_CCMP) && + !(bss->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP | + WPA_CIPHER_CCMP_256 | WPA_CIPHER_GCMP_256))) + { + bss->disable_11ac = true; + wpa_printf(MSG_ERROR, + "VHT (IEEE 802.11ac) with WPA/WPA2 requires CCMP/GCMP to be enabled, disabling VHT capabilities"); + } +#endif /* CONFIG_IEEE80211AC */ + +#ifdef CONFIG_IEEE80211AX +#ifdef CONFIG_WEP + if (full_config && conf->ieee80211ax && + bss->ssid.security_policy == SECURITY_STATIC_WEP) { + bss->disable_11ax = true; + wpa_printf(MSG_ERROR, + "HE (IEEE 802.11ax) with WEP is not allowed, disabling HE capabilities"); + } +#endif /* CONFIG_WEP */ + + if (full_config && conf->ieee80211ax && bss->wpa && + !(bss->wpa_pairwise & WPA_CIPHER_CCMP) && + !(bss->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP | + WPA_CIPHER_CCMP_256 | WPA_CIPHER_GCMP_256))) + { + bss->disable_11ax = true; + wpa_printf(MSG_ERROR, + "HE (IEEE 802.11ax) with WPA/WPA2 requires CCMP/GCMP to be enabled, disabling HE capabilities"); + } +#endif /* CONFIG_IEEE80211AX */ + +#ifdef CONFIG_WPS + if (full_config && bss->wps_state && bss->ignore_broadcast_ssid) { + wpa_printf(MSG_INFO, "WPS: ignore_broadcast_ssid " + "configuration forced WPS to be disabled"); + bss->wps_state = 0; + } + +#ifdef CONFIG_WEP + if (full_config && bss->wps_state && + bss->ssid.wep.keys_set && bss->wpa == 0) { + wpa_printf(MSG_INFO, "WPS: WEP configuration forced WPS to be " + "disabled"); + bss->wps_state = 0; + } +#endif /* CONFIG_WEP */ + + if (full_config && bss->wps_state && bss->wpa && + (!(bss->wpa & 2) || + !(bss->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP | + WPA_CIPHER_CCMP_256 | + WPA_CIPHER_GCMP_256)))) { + wpa_printf(MSG_INFO, "WPS: WPA/TKIP configuration without " + "WPA2/CCMP/GCMP forced WPS to be disabled"); + bss->wps_state = 0; + } +#endif /* CONFIG_WPS */ + +#ifdef CONFIG_HS20 + if (full_config && bss->hs20 && + (!(bss->wpa & 2) || + !(bss->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP | + WPA_CIPHER_CCMP_256 | + WPA_CIPHER_GCMP_256)))) { + wpa_printf(MSG_ERROR, "HS 2.0: WPA2-Enterprise/CCMP " + "configuration is required for Hotspot 2.0 " + "functionality"); + return -1; + } +#endif /* CONFIG_HS20 */ + +#ifdef CONFIG_MBO + if (full_config && bss->mbo_enabled && (bss->wpa & 2) && + bss->ieee80211w == NO_MGMT_FRAME_PROTECTION) { + wpa_printf(MSG_ERROR, + "MBO: PMF needs to be enabled whenever using WPA2 with MBO"); + return -1; + } +#endif /* CONFIG_MBO */ + +#ifdef CONFIG_OCV + if (full_config && bss->ieee80211w == NO_MGMT_FRAME_PROTECTION && + bss->ocv) { + wpa_printf(MSG_ERROR, + "OCV: PMF needs to be enabled whenever using OCV"); + return -1; + } +#endif /* CONFIG_OCV */ + +#ifdef CONFIG_SAE_PK + if (full_config && hostapd_sae_pk_in_use(bss) && + hostapd_sae_pk_password_without_pk(bss)) { + wpa_printf(MSG_ERROR, + "SAE-PK: SAE password uses SAE-PK style, but does not have PK configured"); + return -1; + } +#endif /* CONFIG_SAE_PK */ + +#ifdef CONFIG_FILS + if (full_config && bss->fils_discovery_max_int && + (!conf->ieee80211ax || bss->disable_11ax)) { + wpa_printf(MSG_ERROR, + "Currently IEEE 802.11ax support is mandatory to enable FILS discovery transmission."); + return -1; + } + + if (full_config && bss->fils_discovery_max_int && + bss->unsol_bcast_probe_resp_interval) { + wpa_printf(MSG_ERROR, + "Cannot enable both FILS discovery and unsolicited broadcast Probe Response at the same time"); + return -1; + } +#endif /* CONFIG_FILS */ + +#ifdef CONFIG_IEEE80211BE + if (full_config && !bss->disable_11be && bss->disable_11ax) { + bss->disable_11be = true; + wpa_printf(MSG_INFO, + "Disabling IEEE 802.11be as IEEE 802.11ax is disabled for this BSS"); + } +#endif /* CONFIG_IEEE80211BE */ + + if (full_config && bss->ignore_broadcast_ssid && conf->mbssid) { + wpa_printf(MSG_ERROR, + "Hidden SSID is not suppored when MBSSID is enabled"); + return -1; + } + + return 0; +} + + +static int hostapd_config_check_cw(struct hostapd_config *conf, int queue) +{ + int tx_cwmin = conf->tx_queue[queue].cwmin; + int tx_cwmax = conf->tx_queue[queue].cwmax; + int ac_cwmin = conf->wmm_ac_params[queue].cwmin; + int ac_cwmax = conf->wmm_ac_params[queue].cwmax; + + if (tx_cwmin > tx_cwmax) { + wpa_printf(MSG_ERROR, + "Invalid TX queue cwMin/cwMax values. cwMin(%d) greater than cwMax(%d)", + tx_cwmin, tx_cwmax); + return -1; + } + if (ac_cwmin > ac_cwmax) { + wpa_printf(MSG_ERROR, + "Invalid WMM AC cwMin/cwMax values. cwMin(%d) greater than cwMax(%d)", + ac_cwmin, ac_cwmax); + return -1; + } + return 0; +} + + +int hostapd_config_check(struct hostapd_config *conf, int full_config) +{ + size_t i; + + if (full_config && is_6ghz_op_class(conf->op_class) && + !conf->hw_mode_set) { + /* Use the appropriate hw_mode value automatically when the + * op_class parameter has been set, but hw_mode was not. */ + conf->hw_mode = HOSTAPD_MODE_IEEE80211A; + } + + if (full_config && conf->ieee80211d && + (!conf->country[0] || !conf->country[1])) { + wpa_printf(MSG_ERROR, "Cannot enable IEEE 802.11d without " + "setting the country_code"); + return -1; + } + + if (full_config && conf->ieee80211h && !conf->ieee80211d) { + wpa_printf(MSG_ERROR, "Cannot enable IEEE 802.11h without " + "IEEE 802.11d enabled"); + return -1; + } + + if (full_config && conf->local_pwr_constraint != -1 && + !conf->ieee80211d) { + wpa_printf(MSG_ERROR, "Cannot add Power Constraint element without Country element"); + return -1; + } + + if (full_config && conf->spectrum_mgmt_required && + conf->local_pwr_constraint == -1) { + wpa_printf(MSG_ERROR, "Cannot set Spectrum Management bit without Country and Power Constraint elements"); + return -1; + } + +#ifdef CONFIG_AIRTIME_POLICY + if (full_config && conf->airtime_mode > AIRTIME_MODE_STATIC && + !conf->airtime_update_interval) { + wpa_printf(MSG_ERROR, "Airtime update interval cannot be zero"); + return -1; + } +#endif /* CONFIG_AIRTIME_POLICY */ + for (i = 0; i < NUM_TX_QUEUES; i++) { + if (hostapd_config_check_cw(conf, i)) + return -1; + } + +#ifdef CONFIG_IEEE80211BE + if (full_config && conf->ieee80211be && !conf->ieee80211ax) { + wpa_printf(MSG_ERROR, + "Cannot set ieee80211be without ieee80211ax"); + return -1; + } + + if (full_config) + hostapd_set_and_check_bw320_offset(conf, + conf->eht_bw320_offset); +#endif /* CONFIG_IEEE80211BE */ + + if (full_config && conf->mbssid && !conf->ieee80211ax) { + wpa_printf(MSG_ERROR, + "Cannot enable multiple BSSID support without ieee80211ax"); + return -1; + } + + for (i = 0; i < conf->num_bss; i++) { + if (hostapd_config_check_bss(conf->bss[i], conf, full_config)) + return -1; + } + + return 0; +} + + +void hostapd_set_security_params(struct hostapd_bss_config *bss, + int full_config) +{ +#ifdef CONFIG_WEP + if (bss->individual_wep_key_len == 0) { + /* individual keys are not use; can use key idx0 for + * broadcast keys */ + bss->broadcast_key_idx_min = 0; + } +#endif /* CONFIG_WEP */ + + if ((bss->wpa & 2) && bss->rsn_pairwise == 0) + bss->rsn_pairwise = bss->wpa_pairwise; + if (bss->group_cipher) + bss->wpa_group = bss->group_cipher; + else + bss->wpa_group = wpa_select_ap_group_cipher(bss->wpa, + bss->wpa_pairwise, + bss->rsn_pairwise); + if (!bss->wpa_group_rekey_set) + bss->wpa_group_rekey = bss->wpa_group == WPA_CIPHER_TKIP ? + 600 : 86400; + + if (full_config) { + bss->radius->auth_server = bss->radius->auth_servers; + bss->radius->acct_server = bss->radius->acct_servers; + } + + if (bss->wpa && bss->ieee802_1x) { + bss->ssid.security_policy = SECURITY_WPA; + } else if (bss->wpa) { + bss->ssid.security_policy = SECURITY_WPA_PSK; + } else if (bss->ieee802_1x) { + int cipher = WPA_CIPHER_NONE; + bss->ssid.security_policy = SECURITY_IEEE_802_1X; +#ifdef CONFIG_WEP + bss->ssid.wep.default_len = bss->default_wep_key_len; + if (full_config && bss->default_wep_key_len) { + cipher = bss->default_wep_key_len >= 13 ? + WPA_CIPHER_WEP104 : WPA_CIPHER_WEP40; + } else if (full_config && bss->ssid.wep.keys_set) { + if (bss->ssid.wep.len[0] >= 13) + cipher = WPA_CIPHER_WEP104; + else + cipher = WPA_CIPHER_WEP40; + } +#endif /* CONFIG_WEP */ + bss->wpa_group = cipher; + bss->wpa_pairwise = cipher; + bss->rsn_pairwise = cipher; + if (full_config) + bss->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_NO_WPA; +#ifdef CONFIG_WEP + } else if (bss->ssid.wep.keys_set) { + int cipher = WPA_CIPHER_WEP40; + if (bss->ssid.wep.len[0] >= 13) + cipher = WPA_CIPHER_WEP104; + bss->ssid.security_policy = SECURITY_STATIC_WEP; + bss->wpa_group = cipher; + bss->wpa_pairwise = cipher; + bss->rsn_pairwise = cipher; + if (full_config) + bss->wpa_key_mgmt = WPA_KEY_MGMT_NONE; +#endif /* CONFIG_WEP */ + } else if (bss->osen) { + bss->ssid.security_policy = SECURITY_OSEN; + bss->wpa_group = WPA_CIPHER_CCMP; + bss->wpa_pairwise = 0; + bss->rsn_pairwise = WPA_CIPHER_CCMP; + } else { + bss->ssid.security_policy = SECURITY_PLAINTEXT; + if (full_config) { + bss->wpa_group = WPA_CIPHER_NONE; + bss->wpa_pairwise = WPA_CIPHER_NONE; + bss->rsn_pairwise = WPA_CIPHER_NONE; + bss->wpa_key_mgmt = WPA_KEY_MGMT_NONE; + } + } +} + + +int hostapd_sae_pw_id_in_use(struct hostapd_bss_config *conf) +{ + int with_id = 0, without_id = 0; + struct sae_password_entry *pw; + + if (conf->ssid.wpa_passphrase) + without_id = 1; + + for (pw = conf->sae_passwords; pw; pw = pw->next) { + if (pw->identifier) + with_id = 1; + else + without_id = 1; + if (with_id && without_id) + break; + } + + if (with_id && !without_id) + return 2; + return with_id; +} + + +bool hostapd_sae_pk_in_use(struct hostapd_bss_config *conf) +{ +#ifdef CONFIG_SAE_PK + struct sae_password_entry *pw; + + for (pw = conf->sae_passwords; pw; pw = pw->next) { + if (pw->pk) + return true; + } +#endif /* CONFIG_SAE_PK */ + + return false; +} + + +#ifdef CONFIG_SAE_PK +bool hostapd_sae_pk_exclusively(struct hostapd_bss_config *conf) +{ + bool with_pk = false; + struct sae_password_entry *pw; + + if (conf->ssid.wpa_passphrase) + return false; + + for (pw = conf->sae_passwords; pw; pw = pw->next) { + if (!pw->pk) + return false; + with_pk = true; + } + + return with_pk; +} +#endif /* CONFIG_SAE_PK */ + + +int hostapd_acl_comp(const void *a, const void *b) +{ + const struct mac_acl_entry *aa = a; + const struct mac_acl_entry *bb = b; + return os_memcmp(aa->addr, bb->addr, sizeof(macaddr)); +} + + +int hostapd_add_acl_maclist(struct mac_acl_entry **acl, int *num, + int vlan_id, const u8 *addr) +{ + struct mac_acl_entry *newacl; + + newacl = os_realloc_array(*acl, *num + 1, sizeof(**acl)); + if (!newacl) { + wpa_printf(MSG_ERROR, "MAC list reallocation failed"); + return -1; + } + + *acl = newacl; + os_memcpy((*acl)[*num].addr, addr, ETH_ALEN); + os_memset(&(*acl)[*num].vlan_id, 0, sizeof((*acl)[*num].vlan_id)); + (*acl)[*num].vlan_id.untagged = vlan_id; + (*acl)[*num].vlan_id.notempty = !!vlan_id; + (*num)++; + + return 0; +} + + +void hostapd_remove_acl_mac(struct mac_acl_entry **acl, int *num, + const u8 *addr) +{ + int i = 0; + + while (i < *num) { + if (ether_addr_equal((*acl)[i].addr, addr)) { + os_remove_in_array(*acl, *num, sizeof(**acl), i); + (*num)--; + } else { + i++; + } + } +} diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h new file mode 100644 index 0000000..ced2181 --- /dev/null +++ b/src/ap/ap_config.h @@ -0,0 +1,1401 @@ +/* + * hostapd / Configuration definitions and helpers functions + * Copyright (c) 2003-2024, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef HOSTAPD_CONFIG_H +#define HOSTAPD_CONFIG_H + +#include "common/defs.h" +#include "utils/list.h" +#include "ip_addr.h" +#include "common/wpa_common.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "crypto/sha256.h" +#include "wps/wps.h" +#include "fst/fst.h" +#include "vlan.h" + +enum macaddr_acl { + ACCEPT_UNLESS_DENIED = 0, + DENY_UNLESS_ACCEPTED = 1, + USE_EXTERNAL_RADIUS_AUTH = 2 +}; + +/** + * mesh_conf - local MBSS state and settings + */ +struct mesh_conf { + u8 meshid[32]; + u8 meshid_len; + /* Active Path Selection Protocol Identifier */ + u8 mesh_pp_id; + /* Active Path Selection Metric Identifier */ + u8 mesh_pm_id; + /* Congestion Control Mode Identifier */ + u8 mesh_cc_id; + /* Synchronization Protocol Identifier */ + u8 mesh_sp_id; + /* Authentication Protocol Identifier */ + u8 mesh_auth_id; + u8 *rsn_ie; + int rsn_ie_len; +#define MESH_CONF_SEC_NONE BIT(0) +#define MESH_CONF_SEC_AUTH BIT(1) +#define MESH_CONF_SEC_AMPE BIT(2) + unsigned int security; + enum mfp_options ieee80211w; + int ocv; + unsigned int pairwise_cipher; + unsigned int group_cipher; + unsigned int mgmt_group_cipher; + int dot11MeshMaxRetries; + int dot11MeshRetryTimeout; /* msec */ + int dot11MeshConfirmTimeout; /* msec */ + int dot11MeshHoldingTimeout; /* msec */ + int mesh_fwding; +}; + +#define MAX_STA_COUNT 2007 +#define MAX_VLAN_ID 4094 + +typedef u8 macaddr[ETH_ALEN]; + +struct mac_acl_entry { + macaddr addr; + struct vlan_description vlan_id; +}; + +struct hostapd_radius_servers; +struct ft_remote_r0kh; +struct ft_remote_r1kh; + +#ifdef CONFIG_WEP +#define NUM_WEP_KEYS 4 +struct hostapd_wep_keys { + u8 idx; + u8 *key[NUM_WEP_KEYS]; + size_t len[NUM_WEP_KEYS]; + int keys_set; + size_t default_len; /* key length used for dynamic key generation */ +}; +#endif /* CONFIG_WEP */ + +typedef enum hostap_security_policy { + SECURITY_PLAINTEXT = 0, +#ifdef CONFIG_WEP + SECURITY_STATIC_WEP = 1, +#endif /* CONFIG_WEP */ + SECURITY_IEEE_802_1X = 2, + SECURITY_WPA_PSK = 3, + SECURITY_WPA = 4, + SECURITY_OSEN = 5 +} secpolicy; + +struct hostapd_ssid { + u8 ssid[SSID_MAX_LEN]; + size_t ssid_len; + u32 short_ssid; + unsigned int ssid_set:1; + unsigned int utf8_ssid:1; + unsigned int wpa_passphrase_set:1; + unsigned int wpa_psk_set:1; + + char vlan[IFNAMSIZ + 1]; + secpolicy security_policy; + + struct hostapd_wpa_psk *wpa_psk; + char *wpa_passphrase; + char *wpa_psk_file; + struct sae_pt *pt; + +#ifdef CONFIG_WEP + struct hostapd_wep_keys wep; +#endif /* CONFIG_WEP */ + +#define DYNAMIC_VLAN_DISABLED 0 +#define DYNAMIC_VLAN_OPTIONAL 1 +#define DYNAMIC_VLAN_REQUIRED 2 + int dynamic_vlan; +#define DYNAMIC_VLAN_NAMING_WITHOUT_DEVICE 0 +#define DYNAMIC_VLAN_NAMING_WITH_DEVICE 1 +#define DYNAMIC_VLAN_NAMING_END 2 + int vlan_naming; + int per_sta_vif; +#ifdef CONFIG_FULL_DYNAMIC_VLAN + char *vlan_tagged_interface; +#endif /* CONFIG_FULL_DYNAMIC_VLAN */ +}; + + +#define VLAN_ID_WILDCARD -1 + +struct hostapd_vlan { + struct hostapd_vlan *next; + int vlan_id; /* VLAN ID or -1 (VLAN_ID_WILDCARD) for wildcard entry */ + struct vlan_description vlan_desc; + char ifname[IFNAMSIZ + 1]; + char bridge[IFNAMSIZ + 1]; + int configured; + int dynamic_vlan; +#ifdef CONFIG_FULL_DYNAMIC_VLAN + +#define DVLAN_CLEAN_WLAN_PORT 0x8 + int clean; +#endif /* CONFIG_FULL_DYNAMIC_VLAN */ +}; + +#define PMK_LEN 32 +#define KEYID_LEN 32 +#define MIN_PASSPHRASE_LEN 8 +#define MAX_PASSPHRASE_LEN 63 +struct hostapd_sta_wpa_psk_short { + struct hostapd_sta_wpa_psk_short *next; + unsigned int is_passphrase:1; + u8 psk[PMK_LEN]; + char passphrase[MAX_PASSPHRASE_LEN + 1]; + int ref; /* (number of references held) - 1 */ +}; + +struct hostapd_wpa_psk { + struct hostapd_wpa_psk *next; + int group; + char keyid[KEYID_LEN]; + int wps; + u8 psk[PMK_LEN]; + u8 addr[ETH_ALEN]; + u8 p2p_dev_addr[ETH_ALEN]; + int vlan_id; +}; + +struct hostapd_eap_user { + struct hostapd_eap_user *next; + u8 *identity; + size_t identity_len; + struct { + int vendor; + u32 method; + } methods[EAP_MAX_METHODS]; + u8 *password; + size_t password_len; + u8 *salt; + size_t salt_len; /* non-zero when password is salted */ + int phase2; + int force_version; + unsigned int wildcard_prefix:1; + unsigned int password_hash:1; /* whether password is hashed with + * nt_password_hash() */ + unsigned int remediation:1; + unsigned int macacl:1; + int ttls_auth; /* EAP_TTLS_AUTH_* bitfield */ + struct hostapd_radius_attr *accept_attr; + u32 t_c_timestamp; +}; + +struct hostapd_radius_attr { + u8 type; + struct wpabuf *val; + struct hostapd_radius_attr *next; +}; + + +#define NUM_TX_QUEUES 4 +#define MAX_ROAMING_CONSORTIUM_LEN 15 + +struct hostapd_roaming_consortium { + u8 len; + u8 oi[MAX_ROAMING_CONSORTIUM_LEN]; +}; + +struct hostapd_lang_string { + u8 lang[3]; + u8 name_len; + u8 name[252]; +}; + +struct hostapd_venue_url { + u8 venue_number; + u8 url_len; + u8 url[254]; +}; + +#define MAX_NAI_REALMS 10 +#define MAX_NAI_REALMLEN 255 +#define MAX_NAI_EAP_METHODS 5 +#define MAX_NAI_AUTH_TYPES 4 +struct hostapd_nai_realm_data { + u8 encoding; + char realm_buf[MAX_NAI_REALMLEN + 1]; + char *realm[MAX_NAI_REALMS]; + u8 eap_method_count; + struct hostapd_nai_realm_eap { + u8 eap_method; + u8 num_auths; + u8 auth_id[MAX_NAI_AUTH_TYPES]; + u8 auth_val[MAX_NAI_AUTH_TYPES]; + } eap_method[MAX_NAI_EAP_METHODS]; +}; + +struct anqp_element { + struct dl_list list; + u16 infoid; + struct wpabuf *payload; +}; + +struct fils_realm { + struct dl_list list; + u8 hash[2]; + char realm[]; +}; + +struct sae_password_entry { + struct sae_password_entry *next; + char *password; + char *identifier; + u8 peer_addr[ETH_ALEN]; + int vlan_id; + struct sae_pt *pt; + struct sae_pk *pk; +}; + +struct dpp_controller_conf { + struct dpp_controller_conf *next; + u8 pkhash[SHA256_MAC_LEN]; + struct hostapd_ip_addr ipaddr; +}; + +struct airtime_sta_weight { + struct airtime_sta_weight *next; + unsigned int weight; + u8 addr[ETH_ALEN]; +}; + +#define EXT_CAPA_MAX_LEN 15 + +/** + * struct hostapd_bss_config - Per-BSS configuration + */ +struct hostapd_bss_config { + char iface[IFNAMSIZ + 1]; + char bridge[IFNAMSIZ + 1]; + char vlan_bridge[IFNAMSIZ + 1]; + char wds_bridge[IFNAMSIZ + 1]; + int bridge_hairpin; /* hairpin_mode on bridge members */ + + enum hostapd_logger_level logger_syslog_level, logger_stdout_level; + + unsigned int logger_syslog; /* module bitfield */ + unsigned int logger_stdout; /* module bitfield */ + + int max_num_sta; /* maximum number of STAs in station table */ + + int dtim_period; + unsigned int bss_load_update_period; + unsigned int chan_util_avg_period; + + int ieee802_1x; /* use IEEE 802.1X */ + int eapol_version; + int eap_server; /* Use internal EAP server instead of external + * RADIUS server */ + struct hostapd_eap_user *eap_user; + char *eap_user_sqlite; + char *eap_sim_db; + unsigned int eap_sim_db_timeout; + int eap_server_erp; /* Whether ERP is enabled on internal EAP server */ + struct hostapd_ip_addr own_ip_addr; + char *nas_identifier; + struct hostapd_radius_servers *radius; + int radius_require_message_authenticator; + int acct_interim_interval; + int radius_request_cui; + struct hostapd_radius_attr *radius_auth_req_attr; + struct hostapd_radius_attr *radius_acct_req_attr; + char *radius_req_attr_sqlite; + int radius_das_port; + unsigned int radius_das_time_window; + int radius_das_require_event_timestamp; + int radius_das_require_message_authenticator; + struct hostapd_ip_addr radius_das_client_addr; + u8 *radius_das_shared_secret; + size_t radius_das_shared_secret_len; + + struct hostapd_ssid ssid; + + char *eap_req_id_text; /* optional displayable message sent with + * EAP Request-Identity */ + size_t eap_req_id_text_len; + int eapol_key_index_workaround; + +#ifdef CONFIG_WEP + size_t default_wep_key_len; + int individual_wep_key_len; + int wep_rekeying_period; + int broadcast_key_idx_min, broadcast_key_idx_max; +#endif /* CONFIG_WEP */ + int eap_reauth_period; + int erp_send_reauth_start; + char *erp_domain; +#ifdef CONFIG_TESTING_OPTIONS + bool eap_skip_prot_success; +#endif /* CONFIG_TESTING_OPTIONS */ + + enum macaddr_acl macaddr_acl; + struct mac_acl_entry *accept_mac; + int num_accept_mac; + struct mac_acl_entry *deny_mac; + int num_deny_mac; + int wds_sta; + int isolate; + int start_disabled; + + int auth_algs; /* bitfield of allowed IEEE 802.11 authentication + * algorithms, WPA_AUTH_ALG_{OPEN,SHARED,LEAP} */ + + int wpa; /* bitfield of WPA_PROTO_WPA, WPA_PROTO_RSN */ + int extended_key_id; + int wpa_key_mgmt; + enum mfp_options ieee80211w; + int group_mgmt_cipher; + int beacon_prot; + /* dot11AssociationSAQueryMaximumTimeout (in TUs) */ + unsigned int assoc_sa_query_max_timeout; + /* dot11AssociationSAQueryRetryTimeout (in TUs) */ + int assoc_sa_query_retry_timeout; +#ifdef CONFIG_OCV + int ocv; /* Operating Channel Validation */ +#endif /* CONFIG_OCV */ + enum { + PSK_RADIUS_IGNORED = 0, + PSK_RADIUS_ACCEPTED = 1, + PSK_RADIUS_REQUIRED = 2, + PSK_RADIUS_DURING_4WAY_HS = 3, + } wpa_psk_radius; + int wpa_pairwise; + int group_cipher; /* wpa_group value override from configuation */ + int wpa_group; + int wpa_group_rekey; + int wpa_group_rekey_set; + int wpa_strict_rekey; + int wpa_gmk_rekey; + int wpa_ptk_rekey; + enum ptk0_rekey_handling wpa_deny_ptk0_rekey; + u32 wpa_group_update_count; + u32 wpa_pairwise_update_count; + int wpa_disable_eapol_key_retries; + int rsn_pairwise; + int rsn_preauth; + char *rsn_preauth_interfaces; + +#ifdef CONFIG_IEEE80211R_AP + /* IEEE 802.11r - Fast BSS Transition */ + u8 mobility_domain[MOBILITY_DOMAIN_ID_LEN]; + u8 r1_key_holder[FT_R1KH_ID_LEN]; + u32 r0_key_lifetime; /* PMK-R0 lifetime seconds */ + int rkh_pos_timeout; + int rkh_neg_timeout; + int rkh_pull_timeout; /* ms */ + int rkh_pull_retries; + u32 reassociation_deadline; + struct ft_remote_r0kh *r0kh_list; + struct ft_remote_r1kh *r1kh_list; + int pmk_r1_push; + int ft_over_ds; + int ft_psk_generate_local; + int r1_max_key_lifetime; + char *rxkh_file; +#endif /* CONFIG_IEEE80211R_AP */ + + char *ctrl_interface; /* directory for UNIX domain sockets */ +#ifndef CONFIG_NATIVE_WINDOWS + gid_t ctrl_interface_gid; +#endif /* CONFIG_NATIVE_WINDOWS */ + int ctrl_interface_gid_set; + + char *ca_cert; + char *server_cert; + char *server_cert2; + char *private_key; + char *private_key2; + char *private_key_passwd; + char *private_key_passwd2; + char *check_cert_subject; + int check_crl; + int check_crl_strict; + unsigned int crl_reload_interval; + unsigned int tls_session_lifetime; + unsigned int tls_flags; + unsigned int max_auth_rounds; + unsigned int max_auth_rounds_short; + char *ocsp_stapling_response; + char *ocsp_stapling_response_multi; + char *dh_file; + char *openssl_ciphers; + char *openssl_ecdh_curves; + u8 *pac_opaque_encr_key; + u8 *eap_fast_a_id; + size_t eap_fast_a_id_len; + char *eap_fast_a_id_info; + int eap_fast_prov; + int pac_key_lifetime; + int pac_key_refresh_time; + int eap_teap_auth; + int eap_teap_pac_no_inner; + int eap_teap_separate_result; + int eap_teap_id; + int eap_teap_method_sequence; + int eap_sim_aka_result_ind; + int eap_sim_id; + char *imsi_privacy_key; + int eap_sim_aka_fast_reauth_limit; + int tnc; + int fragment_size; + u16 pwd_group; + + char *radius_server_clients; + int radius_server_auth_port; + int radius_server_acct_port; + int radius_server_ipv6; + + int use_pae_group_addr; /* Whether to send EAPOL frames to PAE group + * address instead of individual address + * (for driver_wired.c). + */ + + int ap_max_inactivity; + int bss_max_idle; + int max_acceptable_idle_period; + bool no_disconnect_on_group_keyerror; + int ignore_broadcast_ssid; + int no_probe_resp_if_max_sta; + + int wmm_enabled; + int wmm_uapsd; + + struct hostapd_vlan *vlan; + + macaddr bssid; + + /* + * Maximum listen interval that STAs can use when associating with this + * BSS. If a STA tries to use larger value, the association will be + * denied with status code 51. + */ + u16 max_listen_interval; + + int disable_pmksa_caching; + int okc; /* Opportunistic Key Caching */ + + int wps_state; +#ifdef CONFIG_WPS + int wps_independent; + int ap_setup_locked; + u8 uuid[16]; + char *wps_pin_requests; + char *device_name; + char *manufacturer; + char *model_name; + char *model_number; + char *serial_number; + u8 device_type[WPS_DEV_TYPE_LEN]; + char *config_methods; + u8 os_version[4]; + char *ap_pin; + int skip_cred_build; + u8 *extra_cred; + size_t extra_cred_len; + int wps_cred_processing; + int wps_cred_add_sae; + int force_per_enrollee_psk; + u8 *ap_settings; + size_t ap_settings_len; + struct hostapd_ssid multi_ap_backhaul_ssid; + char *upnp_iface; + char *friendly_name; + char *manufacturer_url; + char *model_description; + char *model_url; + char *upc; + struct wpabuf *wps_vendor_ext[MAX_WPS_VENDOR_EXTENSIONS]; + struct wpabuf *wps_application_ext; + int wps_nfc_pw_from_config; + int wps_nfc_dev_pw_id; + struct wpabuf *wps_nfc_dh_pubkey; + struct wpabuf *wps_nfc_dh_privkey; + struct wpabuf *wps_nfc_dev_pw; +#endif /* CONFIG_WPS */ + int pbc_in_m1; + char *server_id; + +#define P2P_ENABLED BIT(0) +#define P2P_GROUP_OWNER BIT(1) +#define P2P_GROUP_FORMATION BIT(2) +#define P2P_MANAGE BIT(3) +#define P2P_ALLOW_CROSS_CONNECTION BIT(4) + int p2p; +#ifdef CONFIG_P2P + u8 ip_addr_go[4]; + u8 ip_addr_mask[4]; + u8 ip_addr_start[4]; + u8 ip_addr_end[4]; +#endif /* CONFIG_P2P */ + + int disassoc_low_ack; + int skip_inactivity_poll; + +#define TDLS_PROHIBIT BIT(0) +#define TDLS_PROHIBIT_CHAN_SWITCH BIT(1) + int tdls; + bool disable_11n; + bool disable_11ac; + bool disable_11ax; + bool disable_11be; + + /* IEEE 802.11v */ + int time_advertisement; + char *time_zone; + int wnm_sleep_mode; + int wnm_sleep_mode_no_keys; + int bss_transition; + + /* IEEE 802.11u - Interworking */ + int interworking; + int access_network_type; + int internet; + int asra; + int esr; + int uesa; + int venue_info_set; + u8 venue_group; + u8 venue_type; + u8 hessid[ETH_ALEN]; + + /* IEEE 802.11u - Roaming Consortium list */ + unsigned int roaming_consortium_count; + struct hostapd_roaming_consortium *roaming_consortium; + + /* IEEE 802.11u - Venue Name duples */ + unsigned int venue_name_count; + struct hostapd_lang_string *venue_name; + + /* Venue URL duples */ + unsigned int venue_url_count; + struct hostapd_venue_url *venue_url; + + /* IEEE 802.11u - Network Authentication Type */ + u8 *network_auth_type; + size_t network_auth_type_len; + + /* IEEE 802.11u - IP Address Type Availability */ + u8 ipaddr_type_availability; + u8 ipaddr_type_configured; + + /* IEEE 802.11u - 3GPP Cellular Network */ + u8 *anqp_3gpp_cell_net; + size_t anqp_3gpp_cell_net_len; + + /* IEEE 802.11u - Domain Name */ + u8 *domain_name; + size_t domain_name_len; + + unsigned int nai_realm_count; + struct hostapd_nai_realm_data *nai_realm_data; + + struct dl_list anqp_elem; /* list of struct anqp_element */ + + u16 gas_comeback_delay; + size_t gas_frag_limit; + int gas_address3; + + u8 qos_map_set[16 + 2 * 21]; + unsigned int qos_map_set_len; + + int osen; + int proxy_arp; + int na_mcast_to_ucast; + +#ifdef CONFIG_HS20 + int hs20; + int hs20_release; + int disable_dgaf; + u16 anqp_domain_id; + unsigned int hs20_oper_friendly_name_count; + struct hostapd_lang_string *hs20_oper_friendly_name; + u8 *hs20_wan_metrics; + u8 *hs20_connection_capability; + size_t hs20_connection_capability_len; + u8 *hs20_operating_class; + u8 hs20_operating_class_len; + struct hs20_icon { + u16 width; + u16 height; + char language[3]; + char type[256]; + char name[256]; + char file[256]; + } *hs20_icons; + size_t hs20_icons_count; + u8 osu_ssid[SSID_MAX_LEN]; + size_t osu_ssid_len; + struct hs20_osu_provider { + unsigned int friendly_name_count; + struct hostapd_lang_string *friendly_name; + char *server_uri; + int *method_list; + char **icons; + size_t icons_count; + char *osu_nai; + char *osu_nai2; + unsigned int service_desc_count; + struct hostapd_lang_string *service_desc; + } *hs20_osu_providers, *last_osu; + size_t hs20_osu_providers_count; + size_t hs20_osu_providers_nai_count; + char **hs20_operator_icon; + size_t hs20_operator_icon_count; + unsigned int hs20_deauth_req_timeout; + char *subscr_remediation_url; + u8 subscr_remediation_method; + char *hs20_sim_provisioning_url; + char *t_c_filename; + u32 t_c_timestamp; + char *t_c_server_url; +#endif /* CONFIG_HS20 */ + + u8 wps_rf_bands; /* RF bands for WPS (WPS_RF_*) */ + +#ifdef CONFIG_RADIUS_TEST + char *dump_msk_file; +#endif /* CONFIG_RADIUS_TEST */ + + struct wpabuf *vendor_elements; + struct wpabuf *assocresp_elements; + + unsigned int anti_clogging_threshold; + unsigned int sae_sync; + int sae_require_mfp; + int sae_confirm_immediate; + enum sae_pwe sae_pwe; + int *sae_groups; + struct sae_password_entry *sae_passwords; + + char *wowlan_triggers; /* Wake-on-WLAN triggers */ + +#ifdef CONFIG_TESTING_OPTIONS + u8 bss_load_test[5]; + u8 bss_load_test_set; + struct wpabuf *own_ie_override; + int sae_reflection_attack; + int sae_commit_status; + int sae_pk_omit; + int sae_pk_password_check_skip; + struct wpabuf *sae_commit_override; + struct wpabuf *rsne_override_eapol; + struct wpabuf *rsnxe_override_eapol; + struct wpabuf *rsne_override_ft; + struct wpabuf *rsnxe_override_ft; + struct wpabuf *gtk_rsc_override; + struct wpabuf *igtk_rsc_override; + int no_beacon_rsnxe; + int skip_prune_assoc; + int ft_rsnxe_used; + unsigned int oci_freq_override_eapol_m3; + unsigned int oci_freq_override_eapol_g1; + unsigned int oci_freq_override_saquery_req; + unsigned int oci_freq_override_saquery_resp; + unsigned int oci_freq_override_ft_assoc; + unsigned int oci_freq_override_fils_assoc; + unsigned int oci_freq_override_wnm_sleep; + struct wpabuf *eapol_m1_elements; + struct wpabuf *eapol_m3_elements; + bool eapol_m3_no_encrypt; + int test_assoc_comeback_type; + struct wpabuf *presp_elements; + +#ifdef CONFIG_IEEE80211BE + u16 eht_oper_puncturing_override; +#endif /* CONFIG_IEEE80211BE */ +#endif /* CONFIG_TESTING_OPTIONS */ + +#define MESH_ENABLED BIT(0) + int mesh; + int mesh_fwding; + + u8 radio_measurements[RRM_CAPABILITIES_IE_LEN]; + + int vendor_vht; + int use_sta_nsts; + + char *no_probe_resp_if_seen_on; + char *no_auth_if_seen_on; + + int pbss; + +#ifdef CONFIG_MBO + int mbo_enabled; + /** + * oce - Enable OCE in AP and/or STA-CFON mode + * - BIT(0) is Reserved + * - Set BIT(1) to enable OCE in STA-CFON mode + * - Set BIT(2) to enable OCE in AP mode + */ + unsigned int oce; + int mbo_cell_data_conn_pref; +#endif /* CONFIG_MBO */ + + int ftm_responder; + int ftm_initiator; + +#ifdef CONFIG_FILS + u8 fils_cache_id[FILS_CACHE_ID_LEN]; + int fils_cache_id_set; + struct dl_list fils_realms; /* list of struct fils_realm */ + int fils_dh_group; + struct hostapd_ip_addr dhcp_server; + int dhcp_rapid_commit_proxy; + unsigned int fils_hlp_wait_time; + u16 dhcp_server_port; + u16 dhcp_relay_port; + u32 fils_discovery_min_int; + u32 fils_discovery_max_int; +#endif /* CONFIG_FILS */ + + int multicast_to_unicast; + int bridge_multicast_to_unicast; + + int broadcast_deauth; + + int notify_mgmt_frames; + +#ifdef CONFIG_DPP + char *dpp_name; + char *dpp_mud_url; + char *dpp_extra_conf_req_name; + char *dpp_extra_conf_req_value; + char *dpp_connector; + struct wpabuf *dpp_netaccesskey; + unsigned int dpp_netaccesskey_expiry; + struct wpabuf *dpp_csign; +#ifdef CONFIG_DPP2 + struct dpp_controller_conf *dpp_controller; + int dpp_relay_port; + int dpp_configurator_connectivity; + int dpp_pfs; +#endif /* CONFIG_DPP2 */ +#endif /* CONFIG_DPP */ + +#ifdef CONFIG_OWE + macaddr owe_transition_bssid; + u8 owe_transition_ssid[SSID_MAX_LEN]; + size_t owe_transition_ssid_len; + char owe_transition_ifname[IFNAMSIZ + 1]; + int *owe_groups; + int owe_ptk_workaround; +#endif /* CONFIG_OWE */ + + int coloc_intf_reporting; + + u8 send_probe_response; + + u8 transition_disable; + +#define BACKHAUL_BSS 1 +#define FRONTHAUL_BSS 2 + int multi_ap; /* bitmap of BACKHAUL_BSS, FRONTHAUL_BSS */ + int multi_ap_profile; + /* Multi-AP Profile-1 clients not allowed to connect */ +#define PROFILE1_CLIENT_ASSOC_DISALLOW BIT(0) + /* Multi-AP Profile-2 clients not allowed to connect */ +#define PROFILE2_CLIENT_ASSOC_DISALLOW BIT(1) + unsigned int multi_ap_client_disallow; + /* Primary VLAN ID to use in Multi-AP */ + int multi_ap_vlanid; + +#ifdef CONFIG_AIRTIME_POLICY + unsigned int airtime_weight; + int airtime_limit; + struct airtime_sta_weight *airtime_weight_list; +#endif /* CONFIG_AIRTIME_POLICY */ + +#ifdef CONFIG_MACSEC + /** + * macsec_policy - Determines the policy for MACsec secure session + * + * 0: MACsec not in use (default) + * 1: MACsec enabled - Should secure, accept key server's advice to + * determine whether to use a secure session or not. + */ + int macsec_policy; + + /** + * macsec_integ_only - Determines how MACsec are transmitted + * + * This setting applies only when MACsec is in use, i.e., + * - macsec_policy is enabled + * - the key server has decided to enable MACsec + * + * 0: Encrypt traffic (default) + * 1: Integrity only + */ + int macsec_integ_only; + + /** + * macsec_replay_protect - Enable MACsec replay protection + * + * This setting applies only when MACsec is in use, i.e., + * - macsec_policy is enabled + * - the key server has decided to enable MACsec + * + * 0: Replay protection disabled (default) + * 1: Replay protection enabled + */ + int macsec_replay_protect; + + /** + * macsec_replay_window - MACsec replay protection window + * + * A window in which replay is tolerated, to allow receipt of frames + * that have been misordered by the network. + * + * This setting applies only when MACsec replay protection active, i.e., + * - macsec_replay_protect is enabled + * - the key server has decided to enable MACsec + * + * 0: No replay window, strict check (default) + * 1..2^32-1: number of packets that could be misordered + */ + u32 macsec_replay_window; + + /** + * macsec_offload - Enable MACsec offload + * + * This setting applies only when MACsec is in use, i.e., + * - macsec_policy is enabled + * - the key server has decided to enable MACsec + * + * 0 = MACSEC_OFFLOAD_OFF (default) + * 1 = MACSEC_OFFLOAD_PHY + * 2 = MACSEC_OFFLOAD_MAC + */ + int macsec_offload; + + /** + * macsec_port - MACsec port (in SCI) + * + * Port component of the SCI. + * + * Range: 1-65534 (default: 1) + */ + int macsec_port; + + /** + * mka_priority - Priority of MKA Actor + * + * Range: 0-255 (default: 255) + */ + int mka_priority; + + /** + * macsec_csindex - Cipher suite index for MACsec + * + * Range: 0-1 (default: 0) + */ + int macsec_csindex; + + /** + * mka_ckn - MKA pre-shared CKN + */ +#define MACSEC_CKN_MAX_LEN 32 + size_t mka_ckn_len; + u8 mka_ckn[MACSEC_CKN_MAX_LEN]; + + /** + * mka_cak - MKA pre-shared CAK + */ +#define MACSEC_CAK_MAX_LEN 32 + size_t mka_cak_len; + u8 mka_cak[MACSEC_CAK_MAX_LEN]; + +#define MKA_PSK_SET_CKN BIT(0) +#define MKA_PSK_SET_CAK BIT(1) +#define MKA_PSK_SET (MKA_PSK_SET_CKN | MKA_PSK_SET_CAK) + /** + * mka_psk_set - Whether mka_ckn and mka_cak are set + */ + u8 mka_psk_set; +#endif /* CONFIG_MACSEC */ + +#ifdef CONFIG_PASN + /* Whether to allow PASN-UNAUTH */ + int pasn_noauth; + +#ifdef CONFIG_TESTING_OPTIONS + /* + * Normally, KDK should be derived if and only if both sides support + * secure LTF. Allow forcing KDK derivation for testing purposes. + */ + int force_kdk_derivation; + + /* If set, corrupt the MIC in the 2nd Authentication frame of PASN */ + int pasn_corrupt_mic; +#endif /* CONFIG_TESTING_OPTIONS */ + + int *pasn_groups; + + /* + * The time in TUs after which the non-AP STA is requested to retry the + * PASN authentication in case there are too many parallel operations. + */ + u16 pasn_comeback_after; +#endif /* CONFIG_PASN */ + + unsigned int unsol_bcast_probe_resp_interval; + + u8 ext_capa_mask[EXT_CAPA_MAX_LEN]; + u8 ext_capa[EXT_CAPA_MAX_LEN]; + + u8 rnr; + char *config_id; + bool xrates_supported; + + bool ssid_protection; + +#ifdef CONFIG_IEEE80211BE + /* The AP is part of an AP MLD */ + u8 mld_ap; + + /* The MLD ID to which the AP MLD is affiliated with */ + u8 mld_id; + + /* The AP's MLD MAC address within the AP MLD */ + u8 mld_addr[ETH_ALEN]; + +#ifdef CONFIG_TESTING_OPTIONS + /* + * If set indicate the AP as disabled in the RNR element included in the + * other APs in the AP MLD. + */ + bool mld_indicate_disabled; +#endif /* CONFIG_TESTING_OPTIONS */ +#endif /* CONFIG_IEEE80211BE */ +}; + +/** + * struct he_phy_capabilities_info - HE PHY capabilities + */ +struct he_phy_capabilities_info { + bool he_su_beamformer; + bool he_su_beamformee; + bool he_mu_beamformer; +}; + +/** + * struct he_operation - HE operation + */ +struct he_operation { + u8 he_bss_color; + u8 he_bss_color_disabled; + u8 he_bss_color_partial; + u8 he_default_pe_duration; + u8 he_twt_required; + u8 he_twt_responder; + u16 he_rts_threshold; + u8 he_er_su_disable; + u16 he_basic_mcs_nss_set; +}; + +/** + * struct spatial_reuse - Spatial reuse + */ +struct spatial_reuse { + u8 sr_control; + u8 non_srg_obss_pd_max_offset; + u8 srg_obss_pd_min_offset; + u8 srg_obss_pd_max_offset; + u8 srg_bss_color_bitmap[8]; + u8 srg_partial_bssid_bitmap[8]; +}; + +/** + * struct eht_phy_capabilities_info - EHT PHY capabilities + */ +struct eht_phy_capabilities_info { + bool su_beamformer; + bool su_beamformee; + bool mu_beamformer; +}; + +/** + * struct hostapd_config - Per-radio interface configuration + */ +struct hostapd_config { + struct hostapd_bss_config **bss, *last_bss; + size_t num_bss; + + u16 beacon_int; + int rts_threshold; + int fragm_threshold; + u8 op_class; + u8 channel; + int enable_edmg; + u8 edmg_channel; + u8 acs; + struct wpa_freq_range_list acs_ch_list; + struct wpa_freq_range_list acs_freq_list; + u8 acs_freq_list_present; + int acs_exclude_dfs; + u8 min_tx_power; + enum hostapd_hw_mode hw_mode; /* HOSTAPD_MODE_IEEE80211A, .. */ + bool hw_mode_set; + int acs_exclude_6ghz_non_psc; + int enable_background_radar; + enum { + LONG_PREAMBLE = 0, + SHORT_PREAMBLE = 1 + } preamble; + + int *supported_rates; + int *basic_rates; + unsigned int beacon_rate; + enum beacon_rate_type rate_type; + + const struct wpa_driver_ops *driver; + char *driver_params; + + int ap_table_max_size; + int ap_table_expiration_time; + + unsigned int track_sta_max_num; + unsigned int track_sta_max_age; + + char country[3]; /* first two octets: country code as described in + * ISO/IEC 3166-1. Third octet: + * ' ' (ascii 32): all environments + * 'O': Outdoor environemnt only + * 'I': Indoor environment only + * 'X': Used with noncountry entity ("XXX") + * 0x00..0x31: identifying IEEE 802.11 standard + * Annex E table (0x04 = global table) + */ + + int ieee80211d; + + int ieee80211h; /* DFS */ + + /* + * Local power constraint is an octet encoded as an unsigned integer in + * units of decibels. Invalid value -1 indicates that Power Constraint + * element will not be added. + */ + int local_pwr_constraint; + + /* Control Spectrum Management bit */ + int spectrum_mgmt_required; + + struct hostapd_tx_queue_params tx_queue[NUM_TX_QUEUES]; + + /* + * WMM AC parameters, in same order as 802.1D, i.e. + * 0 = BE (best effort) + * 1 = BK (background) + * 2 = VI (video) + * 3 = VO (voice) + */ + struct hostapd_wmm_ac_params wmm_ac_params[4]; + + int ht_op_mode_fixed; + u16 ht_capab; + int ieee80211n; + int secondary_channel; + int no_pri_sec_switch; + int require_ht; + int obss_interval; + u32 vht_capab; + int ieee80211ac; + int require_vht; + enum oper_chan_width vht_oper_chwidth; + u8 vht_oper_centr_freq_seg0_idx; + u8 vht_oper_centr_freq_seg1_idx; + u8 ht40_plus_minus_allowed; + + /* Use driver-generated interface addresses when adding multiple BSSs */ + u8 use_driver_iface_addr; + +#ifdef CONFIG_FST + struct fst_iface_cfg fst_cfg; +#endif /* CONFIG_FST */ + +#ifdef CONFIG_P2P + u8 p2p_go_ctwindow; +#endif /* CONFIG_P2P */ + +#ifdef CONFIG_TESTING_OPTIONS + double ignore_probe_probability; + double ignore_auth_probability; + double ignore_assoc_probability; + double ignore_reassoc_probability; + double corrupt_gtk_rekey_mic_probability; + int ecsa_ie_only; + bool delay_eapol_tx; +#endif /* CONFIG_TESTING_OPTIONS */ + +#ifdef CONFIG_ACS + unsigned int acs_num_scans; + struct acs_bias { + int channel; + double bias; + } *acs_chan_bias; + unsigned int num_acs_chan_bias; +#endif /* CONFIG_ACS */ + + struct wpabuf *lci; + struct wpabuf *civic; + int stationary_ap; + + int ieee80211ax; +#ifdef CONFIG_IEEE80211AX + struct he_phy_capabilities_info he_phy_capab; + struct he_operation he_op; + struct ieee80211_he_mu_edca_parameter_set he_mu_edca; + struct spatial_reuse spr; + enum oper_chan_width he_oper_chwidth; + u8 he_oper_centr_freq_seg0_idx; + u8 he_oper_centr_freq_seg1_idx; + u8 he_6ghz_max_mpdu; + u8 he_6ghz_max_ampdu_len_exp; + u8 he_6ghz_rx_ant_pat; + u8 he_6ghz_tx_ant_pat; + u8 he_6ghz_reg_pwr_type; + + int reg_def_cli_eirp_psd; + int reg_sub_cli_eirp_psd; + + /* + * This value should be used when regulatory client EIRP PSD values + * advertised by an AP that is an SP AP or an indoor SP AP are + * insufficient to ensure that regulatory client limits on total EIRP + * are always met for all transmission bandwidths within the bandwidth + * of the AP’s BSS. + */ + int reg_def_cli_eirp; + + bool require_he; +#endif /* CONFIG_IEEE80211AX */ + + /* VHT enable/disable config from CHAN_SWITCH */ +#define CH_SWITCH_VHT_ENABLED BIT(0) +#define CH_SWITCH_VHT_DISABLED BIT(1) + unsigned int ch_switch_vht_config; + + /* HE enable/disable config from CHAN_SWITCH */ +#define CH_SWITCH_HE_ENABLED BIT(0) +#define CH_SWITCH_HE_DISABLED BIT(1) + unsigned int ch_switch_he_config; + + int rssi_reject_assoc_rssi; + int rssi_reject_assoc_timeout; + int rssi_ignore_probe_request; + +#ifdef CONFIG_AIRTIME_POLICY + enum { + AIRTIME_MODE_OFF = 0, + AIRTIME_MODE_STATIC = 1, + AIRTIME_MODE_DYNAMIC = 2, + AIRTIME_MODE_LIMIT = 3, + __AIRTIME_MODE_MAX, + } airtime_mode; + unsigned int airtime_update_interval; +#define AIRTIME_MODE_MAX (__AIRTIME_MODE_MAX - 1) +#endif /* CONFIG_AIRTIME_POLICY */ + + int ieee80211be; +#ifdef CONFIG_IEEE80211BE + enum oper_chan_width eht_oper_chwidth; + u8 eht_oper_centr_freq_seg0_idx; + struct eht_phy_capabilities_info eht_phy_capab; + u16 punct_bitmap; /* a bitmap of disabled 20 MHz channels */ + u8 punct_acs_threshold; + u8 eht_default_pe_duration; + u8 eht_bw320_offset; +#endif /* CONFIG_IEEE80211BE */ + + /* EHT enable/disable config from CHAN_SWITCH */ +#define CH_SWITCH_EHT_ENABLED BIT(0) +#define CH_SWITCH_EHT_DISABLED BIT(1) + unsigned int ch_switch_eht_config; + + enum mbssid { + MBSSID_DISABLED = 0, + MBSSID_ENABLED = 1, + ENHANCED_MBSSID_ENABLED = 2, + } mbssid; + + /* Whether to enable TWT responder in HT and VHT modes */ + bool ht_vht_twt_responder; +}; + + +static inline enum oper_chan_width +hostapd_get_oper_chwidth(struct hostapd_config *conf) +{ +#ifdef CONFIG_IEEE80211BE + if (conf->ieee80211be) + return conf->eht_oper_chwidth; +#endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211AX + if (conf->ieee80211ax) + return conf->he_oper_chwidth; +#endif /* CONFIG_IEEE80211AX */ + return conf->vht_oper_chwidth; +} + +static inline void +hostapd_set_oper_chwidth(struct hostapd_config *conf, + enum oper_chan_width oper_chwidth) +{ +#ifdef CONFIG_IEEE80211BE + if (conf->ieee80211be) + conf->eht_oper_chwidth = oper_chwidth; + if (oper_chwidth == CONF_OPER_CHWIDTH_320MHZ) + oper_chwidth = CONF_OPER_CHWIDTH_160MHZ; +#endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211AX + if (conf->ieee80211ax) + conf->he_oper_chwidth = oper_chwidth; +#endif /* CONFIG_IEEE80211AX */ + conf->vht_oper_chwidth = oper_chwidth; +} + +static inline u8 +hostapd_get_oper_centr_freq_seg0_idx(struct hostapd_config *conf) +{ +#ifdef CONFIG_IEEE80211BE + if (conf->ieee80211be) + return conf->eht_oper_centr_freq_seg0_idx; +#endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211AX + if (conf->ieee80211ax) + return conf->he_oper_centr_freq_seg0_idx; +#endif /* CONFIG_IEEE80211AX */ + return conf->vht_oper_centr_freq_seg0_idx; +} + +static inline void +hostapd_set_oper_centr_freq_seg0_idx(struct hostapd_config *conf, + u8 oper_centr_freq_seg0_idx) +{ +#ifdef CONFIG_IEEE80211BE + if (conf->ieee80211be) + conf->eht_oper_centr_freq_seg0_idx = oper_centr_freq_seg0_idx; + if (is_6ghz_op_class(conf->op_class) && + center_idx_to_bw_6ghz(oper_centr_freq_seg0_idx) == 4) + oper_centr_freq_seg0_idx += + conf->channel > oper_centr_freq_seg0_idx ? 16 : -16; +#endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_IEEE80211AX + if (conf->ieee80211ax) + conf->he_oper_centr_freq_seg0_idx = oper_centr_freq_seg0_idx; +#endif /* CONFIG_IEEE80211AX */ + conf->vht_oper_centr_freq_seg0_idx = oper_centr_freq_seg0_idx; +} + +static inline u8 +hostapd_get_oper_centr_freq_seg1_idx(struct hostapd_config *conf) +{ +#ifdef CONFIG_IEEE80211AX + if (conf->ieee80211ax) + return conf->he_oper_centr_freq_seg1_idx; +#endif /* CONFIG_IEEE80211AX */ + return conf->vht_oper_centr_freq_seg1_idx; +} + +static inline void +hostapd_set_oper_centr_freq_seg1_idx(struct hostapd_config *conf, + u8 oper_centr_freq_seg1_idx) +{ +#ifdef CONFIG_IEEE80211AX + if (conf->ieee80211ax) + conf->he_oper_centr_freq_seg1_idx = oper_centr_freq_seg1_idx; +#endif /* CONFIG_IEEE80211AX */ + conf->vht_oper_centr_freq_seg1_idx = oper_centr_freq_seg1_idx; +} + +static inline u8 +hostapd_get_bw320_offset(struct hostapd_config *conf) +{ +#ifdef CONFIG_IEEE80211BE + if (conf->ieee80211be && is_6ghz_op_class(conf->op_class) && + hostapd_get_oper_chwidth(conf) == CONF_OPER_CHWIDTH_320MHZ) + return conf->eht_bw320_offset; +#endif /* CONFIG_IEEE80211BE */ + return 0; +} + +static inline void +hostapd_set_and_check_bw320_offset(struct hostapd_config *conf, + u8 bw320_offset) +{ +#ifdef CONFIG_IEEE80211BE + if (conf->ieee80211be && is_6ghz_op_class(conf->op_class) && + op_class_to_ch_width(conf->op_class) == CONF_OPER_CHWIDTH_320MHZ) { + if (conf->channel) { + /* If the channel is set, then calculate bw320_offset + * by center frequency segment 0. + */ + u8 seg0 = hostapd_get_oper_centr_freq_seg0_idx(conf); + + conf->eht_bw320_offset = (seg0 - 31) % 64 ? 2 : 1; + } else { + /* If the channel is not set, bw320_offset indicates + * preferred offset of 320 MHz. + */ + conf->eht_bw320_offset = bw320_offset; + } + } else { + conf->eht_bw320_offset = 0; + } +#endif /* CONFIG_IEEE80211BE */ +} + + +int hostapd_mac_comp(const void *a, const void *b); +struct hostapd_config * hostapd_config_defaults(void); +void hostapd_config_defaults_bss(struct hostapd_bss_config *bss); +void hostapd_config_free_radius_attr(struct hostapd_radius_attr *attr); +void hostapd_config_free_eap_user(struct hostapd_eap_user *user); +void hostapd_config_free_eap_users(struct hostapd_eap_user *user); +void hostapd_config_clear_wpa_psk(struct hostapd_wpa_psk **p); +void hostapd_config_clear_rxkhs(struct hostapd_bss_config *conf); +void hostapd_config_free_bss(struct hostapd_bss_config *conf); +void hostapd_config_free(struct hostapd_config *conf); +int hostapd_maclist_found(struct mac_acl_entry *list, int num_entries, + const u8 *addr, struct vlan_description *vlan_id); +int hostapd_rate_found(int *list, int rate); +const u8 * hostapd_get_psk(const struct hostapd_bss_config *conf, + const u8 *addr, const u8 *p2p_dev_addr, + const u8 *prev_psk, int *vlan_id); +int hostapd_setup_wpa_psk(struct hostapd_bss_config *conf); +int hostapd_vlan_valid(struct hostapd_vlan *vlan, + struct vlan_description *vlan_desc); +const char * hostapd_get_vlan_id_ifname(struct hostapd_vlan *vlan, + int vlan_id); +struct hostapd_radius_attr * +hostapd_config_get_radius_attr(struct hostapd_radius_attr *attr, u8 type); +struct hostapd_radius_attr * hostapd_parse_radius_attr(const char *value); +int hostapd_config_check(struct hostapd_config *conf, int full_config); +void hostapd_set_security_params(struct hostapd_bss_config *bss, + int full_config); +int hostapd_sae_pw_id_in_use(struct hostapd_bss_config *conf); +bool hostapd_sae_pk_in_use(struct hostapd_bss_config *conf); +bool hostapd_sae_pk_exclusively(struct hostapd_bss_config *conf); +int hostapd_setup_sae_pt(struct hostapd_bss_config *conf); +int hostapd_acl_comp(const void *a, const void *b); +int hostapd_add_acl_maclist(struct mac_acl_entry **acl, int *num, + int vlan_id, const u8 *addr); +void hostapd_remove_acl_mac(struct mac_acl_entry **acl, int *num, + const u8 *addr); + +#endif /* HOSTAPD_CONFIG_H */ diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c new file mode 100644 index 0000000..c473491 --- /dev/null +++ b/src/ap/ap_drv_ops.c @@ -0,0 +1,1252 @@ +/* + * hostapd - Driver operations + * Copyright (c) 2009-2010, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "common/hw_features_common.h" +#include "wps/wps.h" +#include "p2p/p2p.h" +#include "hostapd.h" +#include "ieee802_11.h" +#include "sta_info.h" +#include "ap_config.h" +#include "p2p_hostapd.h" +#include "hs20.h" +#include "wpa_auth.h" +#include "ap_drv_ops.h" + + +u32 hostapd_sta_flags_to_drv(u32 flags) +{ + int res = 0; + if (flags & WLAN_STA_AUTHORIZED) + res |= WPA_STA_AUTHORIZED; + if (flags & WLAN_STA_WMM) + res |= WPA_STA_WMM; + if (flags & WLAN_STA_SHORT_PREAMBLE) + res |= WPA_STA_SHORT_PREAMBLE; + if (flags & WLAN_STA_MFP) + res |= WPA_STA_MFP; + if (flags & WLAN_STA_AUTH) + res |= WPA_STA_AUTHENTICATED; + if (flags & WLAN_STA_ASSOC) + res |= WPA_STA_ASSOCIATED; + return res; +} + + +static int add_buf(struct wpabuf **dst, const struct wpabuf *src) +{ + if (!src) + return 0; + if (wpabuf_resize(dst, wpabuf_len(src)) != 0) + return -1; + wpabuf_put_buf(*dst, src); + return 0; +} + + +static int add_buf_data(struct wpabuf **dst, const u8 *data, size_t len) +{ + if (!data || !len) + return 0; + if (wpabuf_resize(dst, len) != 0) + return -1; + wpabuf_put_data(*dst, data, len); + return 0; +} + + +int hostapd_build_ap_extra_ies(struct hostapd_data *hapd, + struct wpabuf **beacon_ret, + struct wpabuf **proberesp_ret, + struct wpabuf **assocresp_ret) +{ + struct wpabuf *beacon = NULL, *proberesp = NULL, *assocresp = NULL; + u8 buf[200], *pos; + + *beacon_ret = *proberesp_ret = *assocresp_ret = NULL; + +#ifdef NEED_AP_MLME + pos = buf; + pos = hostapd_eid_rm_enabled_capab(hapd, pos, sizeof(buf)); + if (add_buf_data(&assocresp, buf, pos - buf) < 0 || + add_buf_data(&proberesp, buf, pos - buf) < 0) + goto fail; +#endif /* NEED_AP_MLME */ + + pos = buf; + pos = hostapd_eid_time_adv(hapd, pos); + if (add_buf_data(&beacon, buf, pos - buf) < 0) + goto fail; + pos = hostapd_eid_time_zone(hapd, pos); + if (add_buf_data(&proberesp, buf, pos - buf) < 0) + goto fail; + + pos = buf; + pos = hostapd_eid_ext_capab(hapd, pos, false); + if (add_buf_data(&assocresp, buf, pos - buf) < 0) + goto fail; + pos = hostapd_eid_interworking(hapd, pos); + pos = hostapd_eid_adv_proto(hapd, pos); + pos = hostapd_eid_roaming_consortium(hapd, pos); + if (add_buf_data(&beacon, buf, pos - buf) < 0 || + add_buf_data(&proberesp, buf, pos - buf) < 0) + goto fail; + +#ifdef CONFIG_FST + if (add_buf(&beacon, hapd->iface->fst_ies) < 0 || + add_buf(&proberesp, hapd->iface->fst_ies) < 0 || + add_buf(&assocresp, hapd->iface->fst_ies) < 0) + goto fail; +#endif /* CONFIG_FST */ + +#ifdef CONFIG_FILS + pos = hostapd_eid_fils_indic(hapd, buf, 0); + if (add_buf_data(&beacon, buf, pos - buf) < 0 || + add_buf_data(&proberesp, buf, pos - buf) < 0) + goto fail; +#endif /* CONFIG_FILS */ + + pos = hostapd_eid_rsnxe(hapd, buf, sizeof(buf)); + if (add_buf_data(&assocresp, buf, pos - buf) < 0) + goto fail; + + if (add_buf(&beacon, hapd->wps_beacon_ie) < 0 || + add_buf(&proberesp, hapd->wps_probe_resp_ie) < 0) + goto fail; + +#ifdef CONFIG_P2P + if (add_buf(&beacon, hapd->p2p_beacon_ie) < 0 || + add_buf(&proberesp, hapd->p2p_probe_resp_ie) < 0) + goto fail; +#endif /* CONFIG_P2P */ + +#ifdef CONFIG_P2P_MANAGER + if (hapd->conf->p2p & P2P_MANAGE) { + if (wpabuf_resize(&beacon, 100) == 0) { + u8 *start, *p; + start = wpabuf_put(beacon, 0); + p = hostapd_eid_p2p_manage(hapd, start); + wpabuf_put(beacon, p - start); + } + + if (wpabuf_resize(&proberesp, 100) == 0) { + u8 *start, *p; + start = wpabuf_put(proberesp, 0); + p = hostapd_eid_p2p_manage(hapd, start); + wpabuf_put(proberesp, p - start); + } + } +#endif /* CONFIG_P2P_MANAGER */ + +#ifdef CONFIG_WPS + if (hapd->conf->wps_state) { + struct wpabuf *a = wps_build_assoc_resp_ie(); + add_buf(&assocresp, a); + wpabuf_free(a); + } +#endif /* CONFIG_WPS */ + +#ifdef CONFIG_P2P_MANAGER + if (hapd->conf->p2p & P2P_MANAGE) { + if (wpabuf_resize(&assocresp, 100) == 0) { + u8 *start, *p; + start = wpabuf_put(assocresp, 0); + p = hostapd_eid_p2p_manage(hapd, start); + wpabuf_put(assocresp, p - start); + } + } +#endif /* CONFIG_P2P_MANAGER */ + +#ifdef CONFIG_WIFI_DISPLAY + if (hapd->p2p_group) { + struct wpabuf *a; + a = p2p_group_assoc_resp_ie(hapd->p2p_group, P2P_SC_SUCCESS); + add_buf(&assocresp, a); + wpabuf_free(a); + } +#endif /* CONFIG_WIFI_DISPLAY */ + +#ifdef CONFIG_HS20 + pos = hostapd_eid_hs20_indication(hapd, buf); + if (add_buf_data(&beacon, buf, pos - buf) < 0 || + add_buf_data(&proberesp, buf, pos - buf) < 0) + goto fail; + + pos = hostapd_eid_osen(hapd, buf); + if (add_buf_data(&beacon, buf, pos - buf) < 0 || + add_buf_data(&proberesp, buf, pos - buf) < 0) + goto fail; +#endif /* CONFIG_HS20 */ + +#ifdef CONFIG_MBO + if (hapd->conf->mbo_enabled || + OCE_STA_CFON_ENABLED(hapd) || OCE_AP_ENABLED(hapd)) { + pos = hostapd_eid_mbo(hapd, buf, sizeof(buf)); + if (add_buf_data(&beacon, buf, pos - buf) < 0 || + add_buf_data(&proberesp, buf, pos - buf) < 0 || + add_buf_data(&assocresp, buf, pos - buf) < 0) + goto fail; + } +#endif /* CONFIG_MBO */ + +#ifdef CONFIG_OWE + pos = hostapd_eid_owe_trans(hapd, buf, sizeof(buf)); + if (add_buf_data(&beacon, buf, pos - buf) < 0 || + add_buf_data(&proberesp, buf, pos - buf) < 0) + goto fail; +#endif /* CONFIG_OWE */ + + add_buf(&beacon, hapd->conf->vendor_elements); + add_buf(&proberesp, hapd->conf->vendor_elements); +#ifdef CONFIG_TESTING_OPTIONS + add_buf(&proberesp, hapd->conf->presp_elements); +#endif /* CONFIG_TESTING_OPTIONS */ + add_buf(&assocresp, hapd->conf->assocresp_elements); + + *beacon_ret = beacon; + *proberesp_ret = proberesp; + *assocresp_ret = assocresp; + + return 0; + +fail: + wpabuf_free(beacon); + wpabuf_free(proberesp); + wpabuf_free(assocresp); + return -1; +} + + +void hostapd_free_ap_extra_ies(struct hostapd_data *hapd, + struct wpabuf *beacon, + struct wpabuf *proberesp, + struct wpabuf *assocresp) +{ + wpabuf_free(beacon); + wpabuf_free(proberesp); + wpabuf_free(assocresp); +} + + +int hostapd_reset_ap_wps_ie(struct hostapd_data *hapd) +{ + if (hapd->driver == NULL || hapd->driver->set_ap_wps_ie == NULL) + return 0; + + return hapd->driver->set_ap_wps_ie(hapd->drv_priv, NULL, NULL, NULL); +} + + +int hostapd_set_ap_wps_ie(struct hostapd_data *hapd) +{ + struct wpabuf *beacon, *proberesp, *assocresp; + int ret; + + if (hapd->driver == NULL || hapd->driver->set_ap_wps_ie == NULL) + return 0; + + if (hostapd_build_ap_extra_ies(hapd, &beacon, &proberesp, &assocresp) < + 0) + return -1; + + ret = hapd->driver->set_ap_wps_ie(hapd->drv_priv, beacon, proberesp, + assocresp); + + hostapd_free_ap_extra_ies(hapd, beacon, proberesp, assocresp); + + return ret; +} + + +bool hostapd_sta_is_link_sta(struct hostapd_data *hapd, + struct sta_info *sta) +{ +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta) && + sta->mld_assoc_link_id != hapd->mld_link_id) + return true; +#endif /* CONFIG_IEEE80211BE */ + + return false; +} + + +int hostapd_set_authorized(struct hostapd_data *hapd, + struct sta_info *sta, int authorized) +{ + /* + * The WPA_STA_AUTHORIZED flag is relevant only for the MLD station and + * not to the link stations (as the authorization is done between the + * MLD peers). Thus, do not propagate the change to the driver for the + * link stations. + */ + if (hostapd_sta_is_link_sta(hapd, sta)) { + wpa_printf(MSG_DEBUG, + "%s: Do not update link station flags (" MACSTR ")", + __func__, MAC2STR(sta->addr)); + return 0; + } + + if (authorized) { + return hostapd_sta_set_flags(hapd, sta->addr, + hostapd_sta_flags_to_drv( + sta->flags), + WPA_STA_AUTHORIZED, ~0); + } + + return hostapd_sta_set_flags(hapd, sta->addr, + hostapd_sta_flags_to_drv(sta->flags), + 0, ~WPA_STA_AUTHORIZED); +} + + +int hostapd_set_sta_flags(struct hostapd_data *hapd, struct sta_info *sta) +{ + int set_flags, total_flags, flags_and, flags_or; + total_flags = hostapd_sta_flags_to_drv(sta->flags); + set_flags = WPA_STA_SHORT_PREAMBLE | WPA_STA_WMM | WPA_STA_MFP | + WPA_STA_AUTHORIZED; + + /* + * All the station flags other than WPA_STA_SHORT_PREAMBLE are relevant + * only for the MLD station and not to the link stations (as these flags + * are related to the MLD state and not the link state). As for the + * WPA_STA_SHORT_PREAMBLE, since the station is an EHT station, it must + * support short preamble. Thus, do not propagate the change to the + * driver for the link stations. + */ + if (hostapd_sta_is_link_sta(hapd, sta)) { + wpa_printf(MSG_DEBUG, + "%s: Do not update link station flags (" MACSTR ")", + __func__, MAC2STR(sta->addr)); + return 0; + } + + flags_or = total_flags & set_flags; + flags_and = total_flags | ~set_flags; + return hostapd_sta_set_flags(hapd, sta->addr, total_flags, + flags_or, flags_and); +} + + +int hostapd_set_drv_ieee8021x(struct hostapd_data *hapd, const char *ifname, + int enabled) +{ + struct wpa_bss_params params; + os_memset(¶ms, 0, sizeof(params)); + params.ifname = ifname; + params.enabled = enabled; + if (enabled) { + params.wpa = hapd->conf->wpa; + params.ieee802_1x = hapd->conf->ieee802_1x; + params.wpa_group = hapd->conf->wpa_group; + if ((hapd->conf->wpa & (WPA_PROTO_WPA | WPA_PROTO_RSN)) == + (WPA_PROTO_WPA | WPA_PROTO_RSN)) + params.wpa_pairwise = hapd->conf->wpa_pairwise | + hapd->conf->rsn_pairwise; + else if (hapd->conf->wpa & WPA_PROTO_RSN) + params.wpa_pairwise = hapd->conf->rsn_pairwise; + else if (hapd->conf->wpa & WPA_PROTO_WPA) + params.wpa_pairwise = hapd->conf->wpa_pairwise; + params.wpa_key_mgmt = hapd->conf->wpa_key_mgmt; + params.rsn_preauth = hapd->conf->rsn_preauth; + params.ieee80211w = hapd->conf->ieee80211w; + } + return hostapd_set_ieee8021x(hapd, ¶ms); +} + + +int hostapd_vlan_if_add(struct hostapd_data *hapd, const char *ifname) +{ + char force_ifname[IFNAMSIZ]; + u8 if_addr[ETH_ALEN]; + return hostapd_if_add(hapd, WPA_IF_AP_VLAN, ifname, hapd->own_addr, + NULL, NULL, force_ifname, if_addr, NULL, 0); +} + + +int hostapd_vlan_if_remove(struct hostapd_data *hapd, const char *ifname) +{ + return hostapd_if_remove(hapd, WPA_IF_AP_VLAN, ifname); +} + + +int hostapd_set_wds_sta(struct hostapd_data *hapd, char *ifname_wds, + const u8 *addr, int aid, int val) +{ + const char *bridge = NULL; + + if (hapd->driver == NULL || hapd->driver->set_wds_sta == NULL) + return -1; + if (hapd->conf->wds_bridge[0]) + bridge = hapd->conf->wds_bridge; + else if (hapd->conf->bridge[0]) + bridge = hapd->conf->bridge; + return hapd->driver->set_wds_sta(hapd->drv_priv, addr, aid, val, + bridge, ifname_wds); +} + + +int hostapd_add_sta_node(struct hostapd_data *hapd, const u8 *addr, + u16 auth_alg) +{ + if (hapd->driver == NULL || hapd->driver->add_sta_node == NULL) + return -EOPNOTSUPP; + return hapd->driver->add_sta_node(hapd->drv_priv, addr, auth_alg); +} + + +int hostapd_sta_auth(struct hostapd_data *hapd, const u8 *addr, + u16 seq, u16 status, const u8 *ie, size_t len) +{ + struct wpa_driver_sta_auth_params params; +#ifdef CONFIG_FILS + struct sta_info *sta; +#endif /* CONFIG_FILS */ + + if (hapd->driver == NULL || hapd->driver->sta_auth == NULL) + return 0; + + os_memset(¶ms, 0, sizeof(params)); + +#ifdef CONFIG_FILS + sta = ap_get_sta(hapd, addr); + if (!sta) { + wpa_printf(MSG_DEBUG, "Station " MACSTR + " not found for sta_auth processing", + MAC2STR(addr)); + return 0; + } + + if (sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK) { + params.fils_auth = 1; + wpa_auth_get_fils_aead_params(sta->wpa_sm, params.fils_anonce, + params.fils_snonce, + params.fils_kek, + ¶ms.fils_kek_len); + } +#endif /* CONFIG_FILS */ + + params.own_addr = hapd->own_addr; + params.addr = addr; + params.seq = seq; + params.status = status; + params.ie = ie; + params.len = len; + + return hapd->driver->sta_auth(hapd->drv_priv, ¶ms); +} + + +int hostapd_sta_assoc(struct hostapd_data *hapd, const u8 *addr, + int reassoc, u16 status, const u8 *ie, size_t len) +{ + if (hapd->driver == NULL || hapd->driver->sta_assoc == NULL) + return 0; + return hapd->driver->sta_assoc(hapd->drv_priv, hapd->own_addr, addr, + reassoc, status, ie, len); +} + + +int hostapd_sta_add(struct hostapd_data *hapd, + const u8 *addr, u16 aid, u16 capability, + const u8 *supp_rates, size_t supp_rates_len, + u16 listen_interval, + const struct ieee80211_ht_capabilities *ht_capab, + const struct ieee80211_vht_capabilities *vht_capab, + const struct ieee80211_he_capabilities *he_capab, + size_t he_capab_len, + const struct ieee80211_eht_capabilities *eht_capab, + size_t eht_capab_len, + const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab, + u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps, + int set, const u8 *link_addr, bool mld_link_sta) +{ + struct hostapd_sta_add_params params; + + if (hapd->driver == NULL) + return 0; + if (hapd->driver->sta_add == NULL) + return 0; + + os_memset(¶ms, 0, sizeof(params)); + params.addr = addr; + params.aid = aid; + params.capability = capability; + params.supp_rates = supp_rates; + params.supp_rates_len = supp_rates_len; + params.listen_interval = listen_interval; + params.ht_capabilities = ht_capab; + params.vht_capabilities = vht_capab; + params.he_capab = he_capab; + params.he_capab_len = he_capab_len; + params.eht_capab = eht_capab; + params.eht_capab_len = eht_capab_len; + params.he_6ghz_capab = he_6ghz_capab; + params.vht_opmode_enabled = !!(flags & WLAN_STA_VHT_OPMODE_ENABLED); + params.vht_opmode = vht_opmode; + params.flags = hostapd_sta_flags_to_drv(flags); + params.qosinfo = qosinfo; + params.support_p2p_ps = supp_p2p_ps; + params.set = set; + params.mld_link_id = -1; + +#ifdef CONFIG_IEEE80211BE + /* + * An AP MLD needs to always specify to what link the station needs + * to be added. + */ + if (hapd->conf->mld_ap) { + params.mld_link_id = hapd->mld_link_id; + params.mld_link_addr = link_addr; + params.mld_link_sta = mld_link_sta; + } +#endif /* CONFIG_IEEE80211BE */ + + return hapd->driver->sta_add(hapd->drv_priv, ¶ms); +} + + +int hostapd_add_tspec(struct hostapd_data *hapd, const u8 *addr, + u8 *tspec_ie, size_t tspec_ielen) +{ + if (hapd->driver == NULL || hapd->driver->add_tspec == NULL) + return 0; + return hapd->driver->add_tspec(hapd->drv_priv, addr, tspec_ie, + tspec_ielen); +} + + +int hostapd_set_privacy(struct hostapd_data *hapd, int enabled) +{ + if (hapd->driver == NULL || hapd->driver->set_privacy == NULL) + return 0; + return hapd->driver->set_privacy(hapd->drv_priv, enabled); +} + + +int hostapd_set_generic_elem(struct hostapd_data *hapd, const u8 *elem, + size_t elem_len) +{ + if (hapd->driver == NULL || hapd->driver->set_generic_elem == NULL) + return 0; + return hapd->driver->set_generic_elem(hapd->drv_priv, elem, elem_len); +} + + +int hostapd_get_ssid(struct hostapd_data *hapd, u8 *buf, size_t len) +{ + if (hapd->driver == NULL || hapd->driver->hapd_get_ssid == NULL) + return 0; + return hapd->driver->hapd_get_ssid(hapd->drv_priv, buf, len); +} + + +int hostapd_set_ssid(struct hostapd_data *hapd, const u8 *buf, size_t len) +{ + if (hapd->driver == NULL || hapd->driver->hapd_set_ssid == NULL) + return 0; + return hapd->driver->hapd_set_ssid(hapd->drv_priv, buf, len); +} + + +int hostapd_if_add(struct hostapd_data *hapd, enum wpa_driver_if_type type, + const char *ifname, const u8 *addr, void *bss_ctx, + void **drv_priv, char *force_ifname, u8 *if_addr, + const char *bridge, int use_existing) +{ + if (hapd->driver == NULL || hapd->driver->if_add == NULL) + return -1; + return hapd->driver->if_add(hapd->drv_priv, type, ifname, addr, + bss_ctx, drv_priv, force_ifname, if_addr, + bridge, use_existing, 1); +} + + +#ifdef CONFIG_IEEE80211BE +int hostapd_if_link_remove(struct hostapd_data *hapd, + enum wpa_driver_if_type type, + const char *ifname, u8 link_id) +{ + if (!hapd->driver || !hapd->drv_priv || !hapd->driver->link_remove) + return -1; + + return hapd->driver->link_remove(hapd->drv_priv, type, ifname, + hapd->mld_link_id); +} +#endif /* CONFIG_IEEE80211BE */ + + +int hostapd_if_remove(struct hostapd_data *hapd, enum wpa_driver_if_type type, + const char *ifname) +{ + if (hapd->driver == NULL || hapd->drv_priv == NULL || + hapd->driver->if_remove == NULL) + return -1; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) + return hostapd_if_link_remove(hapd, type, ifname, + hapd->mld_link_id); +#endif /* CONFIG_IEEE80211BE */ + + return hapd->driver->if_remove(hapd->drv_priv, type, ifname); +} + + +int hostapd_set_ieee8021x(struct hostapd_data *hapd, + struct wpa_bss_params *params) +{ + if (hapd->driver == NULL || hapd->driver->set_ieee8021x == NULL) + return 0; + return hapd->driver->set_ieee8021x(hapd->drv_priv, params); +} + + +int hostapd_get_seqnum(const char *ifname, struct hostapd_data *hapd, + const u8 *addr, int idx, int link_id, u8 *seq) +{ + if (hapd->driver == NULL || hapd->driver->get_seqnum == NULL) + return 0; + return hapd->driver->get_seqnum(ifname, hapd->drv_priv, addr, idx, + link_id, seq); +} + + +int hostapd_flush(struct hostapd_data *hapd) +{ + int link_id = -1; + + if (hapd->driver == NULL || hapd->driver->flush == NULL) + return 0; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf && hapd->conf->mld_ap) + link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + + return hapd->driver->flush(hapd->drv_priv, link_id); +} + + +int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode, + int freq, int channel, int edmg, u8 edmg_channel, + int ht_enabled, int vht_enabled, + int he_enabled, bool eht_enabled, + int sec_channel_offset, int oper_chwidth, + int center_segment0, int center_segment1) +{ + struct hostapd_freq_params data; + struct hostapd_hw_modes *cmode = hapd->iface->current_mode; + + if (hostapd_set_freq_params(&data, mode, freq, channel, edmg, + edmg_channel, ht_enabled, + vht_enabled, he_enabled, eht_enabled, + sec_channel_offset, oper_chwidth, + center_segment0, center_segment1, + cmode ? cmode->vht_capab : 0, + cmode ? + &cmode->he_capab[IEEE80211_MODE_AP] : NULL, + cmode ? + &cmode->eht_capab[IEEE80211_MODE_AP] : + NULL, hostapd_get_punct_bitmap(hapd))) + return -1; + + if (hapd->driver == NULL) + return 0; + if (hapd->driver->set_freq == NULL) + return 0; + + data.link_id = -1; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) { + data.link_id = hapd->mld_link_id; + wpa_printf(MSG_DEBUG, + "hostapd_set_freq: link_id=%d", data.link_id); + } +#endif /* CONFIG_IEEE80211BE */ + + return hapd->driver->set_freq(hapd->drv_priv, &data); +} + +int hostapd_set_rts(struct hostapd_data *hapd, int rts) +{ + if (hapd->driver == NULL || hapd->driver->set_rts == NULL) + return 0; + return hapd->driver->set_rts(hapd->drv_priv, rts); +} + + +int hostapd_set_frag(struct hostapd_data *hapd, int frag) +{ + if (hapd->driver == NULL || hapd->driver->set_frag == NULL) + return 0; + return hapd->driver->set_frag(hapd->drv_priv, frag); +} + + +int hostapd_sta_set_flags(struct hostapd_data *hapd, u8 *addr, + int total_flags, int flags_or, int flags_and) +{ + if (!hapd->driver || !hapd->drv_priv || !hapd->driver->sta_set_flags) + return 0; + return hapd->driver->sta_set_flags(hapd->drv_priv, addr, total_flags, + flags_or, flags_and); +} + + +int hostapd_sta_set_airtime_weight(struct hostapd_data *hapd, const u8 *addr, + unsigned int weight) +{ + if (!hapd->driver || !hapd->driver->sta_set_airtime_weight) + return 0; + return hapd->driver->sta_set_airtime_weight(hapd->drv_priv, addr, + weight); +} + + +int hostapd_set_country(struct hostapd_data *hapd, const char *country) +{ + if (hapd->driver == NULL || + hapd->driver->set_country == NULL) + return 0; + return hapd->driver->set_country(hapd->drv_priv, country); +} + + +int hostapd_set_tx_queue_params(struct hostapd_data *hapd, int queue, int aifs, + int cw_min, int cw_max, int burst_time) +{ + int link_id = -1; + + if (hapd->driver == NULL || hapd->driver->set_tx_queue_params == NULL) + return 0; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) + link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + + return hapd->driver->set_tx_queue_params(hapd->drv_priv, queue, aifs, + cw_min, cw_max, burst_time, + link_id); +} + + +struct hostapd_hw_modes * +hostapd_get_hw_feature_data(struct hostapd_data *hapd, u16 *num_modes, + u16 *flags, u8 *dfs_domain) +{ + if (!hapd->driver || !hapd->driver->get_hw_feature_data || + !hapd->drv_priv) + return NULL; + return hapd->driver->get_hw_feature_data(hapd->drv_priv, num_modes, + flags, dfs_domain); +} + + +int hostapd_driver_commit(struct hostapd_data *hapd) +{ + if (hapd->driver == NULL || hapd->driver->commit == NULL) + return 0; + return hapd->driver->commit(hapd->drv_priv); +} + + +int hostapd_drv_none(struct hostapd_data *hapd) +{ + return hapd->driver && os_strcmp(hapd->driver->name, "none") == 0; +} + + +bool hostapd_drv_nl80211(struct hostapd_data *hapd) +{ + return hapd->driver && os_strcmp(hapd->driver->name, "nl80211") == 0; +} + + +int hostapd_driver_scan(struct hostapd_data *hapd, + struct wpa_driver_scan_params *params) +{ + if (hapd->driver && hapd->driver->scan2) + return hapd->driver->scan2(hapd->drv_priv, params); + return -1; +} + + +struct wpa_scan_results * hostapd_driver_get_scan_results( + struct hostapd_data *hapd) +{ + if (hapd->driver && hapd->driver->get_scan_results) + return hapd->driver->get_scan_results(hapd->drv_priv, NULL); + if (hapd->driver && hapd->driver->get_scan_results2) + return hapd->driver->get_scan_results2(hapd->drv_priv); + return NULL; +} + + +int hostapd_driver_set_noa(struct hostapd_data *hapd, u8 count, int start, + int duration) +{ + if (hapd->driver && hapd->driver->set_noa) + return hapd->driver->set_noa(hapd->drv_priv, count, start, + duration); + return -1; +} + + +int hostapd_drv_set_key(const char *ifname, struct hostapd_data *hapd, + enum wpa_alg alg, const u8 *addr, + int key_idx, int vlan_id, int set_tx, + const u8 *seq, size_t seq_len, + const u8 *key, size_t key_len, enum key_flag key_flag) +{ + struct wpa_driver_set_key_params params; + + if (hapd->driver == NULL || hapd->driver->set_key == NULL) + return 0; + + os_memset(¶ms, 0, sizeof(params)); + params.ifname = ifname; + params.alg = alg; + params.addr = addr; + params.key_idx = key_idx; + params.set_tx = set_tx; + params.seq = seq; + params.seq_len = seq_len; + params.key = key; + params.key_len = key_len; + params.vlan_id = vlan_id; + params.key_flag = key_flag; + params.link_id = -1; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap && !(key_flag & KEY_FLAG_PAIRWISE)) + params.link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + + return hapd->driver->set_key(hapd->drv_priv, ¶ms); +} + + +int hostapd_drv_send_mlme(struct hostapd_data *hapd, + const void *msg, size_t len, int noack, + const u16 *csa_offs, size_t csa_offs_len, + int no_encrypt) +{ + int link_id = -1; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) + link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + + if (!hapd->driver || !hapd->driver->send_mlme || !hapd->drv_priv) + return 0; + return hapd->driver->send_mlme(hapd->drv_priv, msg, len, noack, 0, + csa_offs, csa_offs_len, no_encrypt, 0, + link_id); +} + + +int hostapd_drv_sta_deauth(struct hostapd_data *hapd, + const u8 *addr, int reason) +{ + int link_id = -1; + const u8 *own_addr = hapd->own_addr; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) { + struct sta_info *sta = ap_get_sta(hapd, addr); + + link_id = hapd->mld_link_id; + if (ap_sta_is_mld(hapd, sta)) + own_addr = hapd->mld->mld_addr; + } +#endif /* CONFIG_IEEE80211BE */ + + if (!hapd->driver || !hapd->driver->sta_deauth || !hapd->drv_priv) + return 0; + return hapd->driver->sta_deauth(hapd->drv_priv, own_addr, addr, + reason, link_id); +} + + +int hostapd_drv_sta_disassoc(struct hostapd_data *hapd, + const u8 *addr, int reason) +{ + const u8 *own_addr = hapd->own_addr; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) { + struct sta_info *sta = ap_get_sta(hapd, addr); + + if (ap_sta_is_mld(hapd, sta)) + own_addr = hapd->mld->mld_addr; + } +#endif /* CONFIG_IEEE80211BE */ + + if (!hapd->driver || !hapd->driver->sta_disassoc || !hapd->drv_priv) + return 0; + return hapd->driver->sta_disassoc(hapd->drv_priv, own_addr, addr, + reason); +} + + +int hostapd_drv_wnm_oper(struct hostapd_data *hapd, enum wnm_oper oper, + const u8 *peer, u8 *buf, u16 *buf_len) +{ + if (hapd->driver == NULL || hapd->driver->wnm_oper == NULL) + return -1; + return hapd->driver->wnm_oper(hapd->drv_priv, oper, peer, buf, + buf_len); +} + + +static int hapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq, + unsigned int wait, const u8 *dst, + const u8 *data, size_t len, bool addr3_ap) +{ + const u8 *own_addr = hapd->own_addr; + const u8 *bssid; + const u8 wildcard_bssid[ETH_ALEN] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + struct sta_info *sta; + + if (!hapd->driver || !hapd->driver->send_action || !hapd->drv_priv) + return 0; + bssid = hapd->own_addr; + if (!addr3_ap && !is_multicast_ether_addr(dst) && + len > 0 && data[0] == WLAN_ACTION_PUBLIC) { + /* + * Public Action frames to a STA that is not a member of the BSS + * shall use wildcard BSSID value. + */ + sta = ap_get_sta(hapd, dst); + if (!sta || !(sta->flags & WLAN_STA_ASSOC)) + bssid = wildcard_bssid; + } else if (!addr3_ap && is_broadcast_ether_addr(dst) && + len > 0 && data[0] == WLAN_ACTION_PUBLIC) { + /* + * The only current use case of Public Action frames with + * broadcast destination address is DPP PKEX. That case is + * directing all devices and not just the STAs within the BSS, + * so have to use the wildcard BSSID value. + */ + bssid = wildcard_bssid; +#ifdef CONFIG_IEEE80211BE + } else if (hapd->conf->mld_ap) { + sta = ap_get_sta(hapd, dst); + + if (ap_sta_is_mld(hapd, sta)) { + own_addr = hapd->mld->mld_addr; + bssid = own_addr; + } +#endif /* CONFIG_IEEE80211BE */ + } + + return hapd->driver->send_action(hapd->drv_priv, freq, wait, dst, + own_addr, bssid, data, len, 0); +} + + +int hostapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq, + unsigned int wait, const u8 *dst, const u8 *data, + size_t len) +{ + return hapd_drv_send_action(hapd, freq, wait, dst, data, len, false); +} + + +int hostapd_drv_send_action_addr3_ap(struct hostapd_data *hapd, + unsigned int freq, + unsigned int wait, const u8 *dst, + const u8 *data, size_t len) +{ + return hapd_drv_send_action(hapd, freq, wait, dst, data, len, true); +} + + +int hostapd_start_dfs_cac(struct hostapd_iface *iface, + enum hostapd_hw_mode mode, int freq, + int channel, int ht_enabled, int vht_enabled, + int he_enabled, bool eht_enabled, + int sec_channel_offset, int oper_chwidth, + int center_segment0, int center_segment1, + bool radar_background) +{ + struct hostapd_data *hapd = iface->bss[0]; + struct hostapd_freq_params data; + int res; + struct hostapd_hw_modes *cmode = iface->current_mode; + + if (!hapd->driver || !hapd->driver->start_dfs_cac || !cmode) + return 0; + + if (!iface->conf->ieee80211h) { + wpa_printf(MSG_ERROR, "Can't start DFS CAC, DFS functionality " + "is not enabled"); + return -1; + } + + if (hostapd_set_freq_params(&data, mode, freq, channel, 0, 0, + ht_enabled, + vht_enabled, he_enabled, eht_enabled, + sec_channel_offset, + oper_chwidth, center_segment0, + center_segment1, + cmode->vht_capab, + &cmode->he_capab[IEEE80211_MODE_AP], + &cmode->eht_capab[IEEE80211_MODE_AP], + hostapd_get_punct_bitmap(hapd))) { + wpa_printf(MSG_ERROR, "Can't set freq params"); + return -1; + } + data.radar_background = radar_background; + + res = hapd->driver->start_dfs_cac(hapd->drv_priv, &data); + if (!res) { + if (radar_background) + iface->radar_background.cac_started = 1; + else + iface->cac_started = 1; + os_get_reltime(&iface->dfs_cac_start); + } + + return res; +} + + +int hostapd_drv_set_qos_map(struct hostapd_data *hapd, + const u8 *qos_map_set, u8 qos_map_set_len) +{ + if (!hapd->driver || !hapd->driver->set_qos_map || !hapd->drv_priv || + !(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_QOS_MAPPING)) + return 0; + return hapd->driver->set_qos_map(hapd->drv_priv, qos_map_set, + qos_map_set_len); +} + + +void hostapd_get_hw_mode_any_channels(struct hostapd_data *hapd, + struct hostapd_hw_modes *mode, + int acs_ch_list_all, bool allow_disabled, + int **freq_list) +{ + int i; + bool is_no_ir = false; + + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + + if (!acs_ch_list_all && + (hapd->iface->conf->acs_freq_list.num && + !freq_range_list_includes( + &hapd->iface->conf->acs_freq_list, + chan->freq))) + continue; + if (!acs_ch_list_all && + (!hapd->iface->conf->acs_freq_list_present && + hapd->iface->conf->acs_ch_list.num && + !freq_range_list_includes( + &hapd->iface->conf->acs_ch_list, + chan->chan))) + continue; + if (is_6ghz_freq(chan->freq) && + ((hapd->iface->conf->acs_exclude_6ghz_non_psc && + !is_6ghz_psc_frequency(chan->freq)) || + (!hapd->iface->conf->ieee80211ax && + !hapd->iface->conf->ieee80211be))) + continue; + if ((!(chan->flag & HOSTAPD_CHAN_DISABLED) || allow_disabled) && + !(hapd->iface->conf->acs_exclude_dfs && + (chan->flag & HOSTAPD_CHAN_RADAR)) && + !(chan->max_tx_power < hapd->iface->conf->min_tx_power)) + int_array_add_unique(freq_list, chan->freq); + else if ((chan->flag & HOSTAPD_CHAN_NO_IR) && + is_6ghz_freq(chan->freq)) + is_no_ir = true; + } + + hapd->iface->is_no_ir = is_no_ir; +} + + +void hostapd_get_ext_capa(struct hostapd_iface *iface) +{ + struct hostapd_data *hapd = iface->bss[0]; + + if (!hapd->driver || !hapd->driver->get_ext_capab) + return; + + hapd->driver->get_ext_capab(hapd->drv_priv, WPA_IF_AP_BSS, + &iface->extended_capa, + &iface->extended_capa_mask, + &iface->extended_capa_len); +} + + +void hostapd_get_mld_capa(struct hostapd_iface *iface) +{ + struct hostapd_data *hapd = iface->bss[0]; + + if (!hapd->driver || !hapd->driver->get_mld_capab) + return; + + hapd->driver->get_mld_capab(hapd->drv_priv, WPA_IF_AP_BSS, + &iface->mld_eml_capa, + &iface->mld_mld_capa); +} + + +/** + * hostapd_drv_do_acs - Start automatic channel selection + * @hapd: BSS data for the device initiating ACS + * Returns: 0 on success, -1 on failure, 1 on failure due to NO_IR (AFC) + */ +int hostapd_drv_do_acs(struct hostapd_data *hapd) +{ + struct drv_acs_params params; + int ret, i, acs_ch_list_all = 0; + struct hostapd_hw_modes *mode; + int *freq_list = NULL; + enum hostapd_hw_mode selected_mode; + + if (hapd->driver == NULL || hapd->driver->do_acs == NULL) + return 0; + + os_memset(¶ms, 0, sizeof(params)); + params.hw_mode = hapd->iface->conf->hw_mode; + params.link_id = -1; +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap && hapd->iconf->ieee80211be && + !hapd->conf->disable_11be) + params.link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + + /* + * If no chanlist config parameter is provided, include all enabled + * channels of the selected hw_mode. + */ + if (hapd->iface->conf->acs_freq_list_present) + acs_ch_list_all = !hapd->iface->conf->acs_freq_list.num; + else + acs_ch_list_all = !hapd->iface->conf->acs_ch_list.num; + + if (hapd->iface->current_mode) + selected_mode = hapd->iface->current_mode->mode; + else + selected_mode = HOSTAPD_MODE_IEEE80211ANY; + + for (i = 0; i < hapd->iface->num_hw_features; i++) { + mode = &hapd->iface->hw_features[i]; + if (selected_mode != HOSTAPD_MODE_IEEE80211ANY && + selected_mode != mode->mode) + continue; + hostapd_get_hw_mode_any_channels(hapd, mode, acs_ch_list_all, + false, &freq_list); + } + + if (!freq_list && hapd->iface->is_no_ir) { + wpa_printf(MSG_ERROR, + "NO_IR: Interface freq_list is empty. Failing do_acs."); + return 1; + } + + params.freq_list = freq_list; + params.edmg_enabled = hapd->iface->conf->enable_edmg; + + params.ht_enabled = !!(hapd->iface->conf->ieee80211n); + params.ht40_enabled = !!(hapd->iface->conf->ht_capab & + HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET); + params.vht_enabled = !!(hapd->iface->conf->ieee80211ac); + params.eht_enabled = !!(hapd->iface->conf->ieee80211be); + params.ch_width = 20; + if (hapd->iface->conf->ieee80211n && params.ht40_enabled) + params.ch_width = 40; + + /* Note: VHT20 is defined by combination of ht_capab & oper_chwidth + */ + if ((hapd->iface->conf->ieee80211be || + hapd->iface->conf->ieee80211ax || + hapd->iface->conf->ieee80211ac) && + params.ht40_enabled) { + enum oper_chan_width oper_chwidth; + + oper_chwidth = hostapd_get_oper_chwidth(hapd->iface->conf); + if (oper_chwidth == CONF_OPER_CHWIDTH_80MHZ) + params.ch_width = 80; + else if (oper_chwidth == CONF_OPER_CHWIDTH_160MHZ || + oper_chwidth == CONF_OPER_CHWIDTH_80P80MHZ) + params.ch_width = 160; + else if (oper_chwidth == CONF_OPER_CHWIDTH_320MHZ) + params.ch_width = 320; + } + + if (hapd->iface->conf->op_class) + params.ch_width = op_class_to_bandwidth( + hapd->iface->conf->op_class); + ret = hapd->driver->do_acs(hapd->drv_priv, ¶ms); + os_free(freq_list); + + return ret; +} + + +int hostapd_drv_update_dh_ie(struct hostapd_data *hapd, const u8 *peer, + u16 reason_code, const u8 *ie, size_t ielen) +{ + if (!hapd->driver || !hapd->driver->update_dh_ie || !hapd->drv_priv) + return 0; + return hapd->driver->update_dh_ie(hapd->drv_priv, peer, reason_code, + ie, ielen); +} + + +int hostapd_drv_dpp_listen(struct hostapd_data *hapd, bool enable) +{ + if (!hapd->driver || !hapd->driver->dpp_listen || !hapd->drv_priv) + return 0; + return hapd->driver->dpp_listen(hapd->drv_priv, enable); +} + + +#ifdef CONFIG_PASN +int hostapd_drv_set_secure_ranging_ctx(struct hostapd_data *hapd, + const u8 *own_addr, const u8 *peer_addr, + u32 cipher, u8 tk_len, const u8 *tk, + u8 ltf_keyseed_len, + const u8 *ltf_keyseed, u32 action) +{ + struct secure_ranging_params params; + + if (!hapd->driver || !hapd->driver->set_secure_ranging_ctx) + return 0; + + os_memset(¶ms, 0, sizeof(params)); + params.own_addr = own_addr; + params.peer_addr = peer_addr; + params.cipher = cipher; + params.tk_len = tk_len; + params.tk = tk; + params.ltf_keyseed_len = ltf_keyseed_len; + params.ltf_keyseed = ltf_keyseed; + params.action = action; + + return hapd->driver->set_secure_ranging_ctx(hapd->drv_priv, ¶ms); +} +#endif /* CONFIG_PASN */ diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h new file mode 100644 index 0000000..d7e79c8 --- /dev/null +++ b/src/ap/ap_drv_ops.h @@ -0,0 +1,481 @@ +/* + * hostapd - Driver operations + * Copyright (c) 2009-2014, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef AP_DRV_OPS +#define AP_DRV_OPS + +enum wpa_driver_if_type; +struct wpa_bss_params; +struct wpa_driver_scan_params; +struct ieee80211_ht_capabilities; +struct ieee80211_vht_capabilities; +struct hostapd_freq_params; + +u32 hostapd_sta_flags_to_drv(u32 flags); +int hostapd_build_ap_extra_ies(struct hostapd_data *hapd, + struct wpabuf **beacon, + struct wpabuf **proberesp, + struct wpabuf **assocresp); +void hostapd_free_ap_extra_ies(struct hostapd_data *hapd, struct wpabuf *beacon, + struct wpabuf *proberesp, + struct wpabuf *assocresp); +int hostapd_reset_ap_wps_ie(struct hostapd_data *hapd); +int hostapd_set_ap_wps_ie(struct hostapd_data *hapd); +bool hostapd_sta_is_link_sta(struct hostapd_data *hapd, + struct sta_info *sta); +int hostapd_set_authorized(struct hostapd_data *hapd, + struct sta_info *sta, int authorized); +int hostapd_set_sta_flags(struct hostapd_data *hapd, struct sta_info *sta); +int hostapd_set_drv_ieee8021x(struct hostapd_data *hapd, const char *ifname, + int enabled); +int hostapd_vlan_if_add(struct hostapd_data *hapd, const char *ifname); +int hostapd_vlan_if_remove(struct hostapd_data *hapd, const char *ifname); +int hostapd_set_wds_sta(struct hostapd_data *hapd, char *ifname_wds, + const u8 *addr, int aid, int val); +int hostapd_sta_add(struct hostapd_data *hapd, + const u8 *addr, u16 aid, u16 capability, + const u8 *supp_rates, size_t supp_rates_len, + u16 listen_interval, + const struct ieee80211_ht_capabilities *ht_capab, + const struct ieee80211_vht_capabilities *vht_capab, + const struct ieee80211_he_capabilities *he_capab, + size_t he_capab_len, + const struct ieee80211_eht_capabilities *eht_capab, + size_t eht_capab_len, + const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab, + u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps, + int set, const u8 *link_addr, bool mld_link_sta); +int hostapd_set_privacy(struct hostapd_data *hapd, int enabled); +int hostapd_set_generic_elem(struct hostapd_data *hapd, const u8 *elem, + size_t elem_len); +int hostapd_get_ssid(struct hostapd_data *hapd, u8 *buf, size_t len); +int hostapd_set_ssid(struct hostapd_data *hapd, const u8 *buf, size_t len); +int hostapd_if_add(struct hostapd_data *hapd, enum wpa_driver_if_type type, + const char *ifname, const u8 *addr, void *bss_ctx, + void **drv_priv, char *force_ifname, u8 *if_addr, + const char *bridge, int use_existing); +int hostapd_if_remove(struct hostapd_data *hapd, enum wpa_driver_if_type type, + const char *ifname); +int hostapd_if_link_remove(struct hostapd_data *hapd, + enum wpa_driver_if_type type, + const char *ifname, u8 link_id); +int hostapd_set_ieee8021x(struct hostapd_data *hapd, + struct wpa_bss_params *params); +int hostapd_get_seqnum(const char *ifname, struct hostapd_data *hapd, + const u8 *addr, int idx, int link_id, u8 *seq); +int hostapd_flush(struct hostapd_data *hapd); +int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode, + int freq, int channel, int edmg, u8 edmg_channel, + int ht_enabled, int vht_enabled, int he_enabled, + bool eht_enabled, int sec_channel_offset, int oper_chwidth, + int center_segment0, int center_segment1); +int hostapd_set_rts(struct hostapd_data *hapd, int rts); +int hostapd_set_frag(struct hostapd_data *hapd, int frag); +int hostapd_sta_set_flags(struct hostapd_data *hapd, u8 *addr, + int total_flags, int flags_or, int flags_and); +int hostapd_sta_set_airtime_weight(struct hostapd_data *hapd, const u8 *addr, + unsigned int weight); +int hostapd_set_country(struct hostapd_data *hapd, const char *country); +int hostapd_set_tx_queue_params(struct hostapd_data *hapd, int queue, int aifs, + int cw_min, int cw_max, int burst_time); +struct hostapd_hw_modes * +hostapd_get_hw_feature_data(struct hostapd_data *hapd, u16 *num_modes, + u16 *flags, u8 *dfs_domain); +int hostapd_driver_commit(struct hostapd_data *hapd); +int hostapd_drv_none(struct hostapd_data *hapd); +bool hostapd_drv_nl80211(struct hostapd_data *hapd); +int hostapd_driver_scan(struct hostapd_data *hapd, + struct wpa_driver_scan_params *params); +struct wpa_scan_results * hostapd_driver_get_scan_results( + struct hostapd_data *hapd); +int hostapd_driver_set_noa(struct hostapd_data *hapd, u8 count, int start, + int duration); +int hostapd_drv_set_key(const char *ifname, + struct hostapd_data *hapd, + enum wpa_alg alg, const u8 *addr, + int key_idx, int vlan_id, int set_tx, + const u8 *seq, size_t seq_len, + const u8 *key, size_t key_len, enum key_flag key_flag); +int hostapd_drv_send_mlme(struct hostapd_data *hapd, + const void *msg, size_t len, int noack, + const u16 *csa_offs, size_t csa_offs_len, + int no_encrypt); +int hostapd_drv_sta_deauth(struct hostapd_data *hapd, + const u8 *addr, int reason); +int hostapd_drv_sta_disassoc(struct hostapd_data *hapd, + const u8 *addr, int reason); +int hostapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq, + unsigned int wait, const u8 *dst, const u8 *data, + size_t len); +int hostapd_drv_send_action_addr3_ap(struct hostapd_data *hapd, + unsigned int freq, + unsigned int wait, const u8 *dst, + const u8 *data, size_t len); +static inline void +hostapd_drv_send_action_cancel_wait(struct hostapd_data *hapd) +{ + if (!hapd->driver || !hapd->driver->send_action_cancel_wait || + !hapd->drv_priv) + return; + hapd->driver->send_action_cancel_wait(hapd->drv_priv); +} +int hostapd_add_sta_node(struct hostapd_data *hapd, const u8 *addr, + u16 auth_alg); +int hostapd_sta_auth(struct hostapd_data *hapd, const u8 *addr, + u16 seq, u16 status, const u8 *ie, size_t len); +int hostapd_sta_assoc(struct hostapd_data *hapd, const u8 *addr, + int reassoc, u16 status, const u8 *ie, size_t len); +int hostapd_add_tspec(struct hostapd_data *hapd, const u8 *addr, + u8 *tspec_ie, size_t tspec_ielen); +int hostapd_start_dfs_cac(struct hostapd_iface *iface, + enum hostapd_hw_mode mode, int freq, + int channel, int ht_enabled, int vht_enabled, + int he_enabled, bool eht_enabled, + int sec_channel_offset, int oper_chwidth, + int center_segment0, int center_segment1, + bool radar_background); +int hostapd_drv_do_acs(struct hostapd_data *hapd); +int hostapd_drv_update_dh_ie(struct hostapd_data *hapd, const u8 *peer, + u16 reason_code, const u8 *ie, size_t ielen); +int hostapd_drv_dpp_listen(struct hostapd_data *hapd, bool enable); +int hostapd_drv_set_secure_ranging_ctx(struct hostapd_data *hapd, + const u8 *own_addr, const u8 *addr, + u32 cipher, u8 key_len, const u8 *key, + u8 ltf_keyseed_len, + const u8 *ltf_keyseed, u32 action); + + +#include "drivers/driver.h" + +int hostapd_drv_wnm_oper(struct hostapd_data *hapd, + enum wnm_oper oper, const u8 *peer, + u8 *buf, u16 *buf_len); + +int hostapd_drv_set_qos_map(struct hostapd_data *hapd, const u8 *qos_map_set, + u8 qos_map_set_len); + +void hostapd_get_ext_capa(struct hostapd_iface *iface); +void hostapd_get_mld_capa(struct hostapd_iface *iface); + +void hostapd_get_hw_mode_any_channels(struct hostapd_data *hapd, + struct hostapd_hw_modes *mode, + int acs_ch_list_all, bool allow_disabled, + int **freq_list); + +static inline int hostapd_drv_set_countermeasures(struct hostapd_data *hapd, + int enabled) +{ + if (hapd->driver == NULL || + hapd->driver->hapd_set_countermeasures == NULL) + return 0; + return hapd->driver->hapd_set_countermeasures(hapd->drv_priv, enabled); +} + +static inline int hostapd_drv_set_sta_vlan(const char *ifname, + struct hostapd_data *hapd, + const u8 *addr, int vlan_id, + int link_id) +{ + if (hapd->driver == NULL || hapd->driver->set_sta_vlan == NULL) + return 0; + return hapd->driver->set_sta_vlan(hapd->drv_priv, addr, ifname, + vlan_id, link_id); +} + +static inline int hostapd_drv_get_inact_sec(struct hostapd_data *hapd, + const u8 *addr) +{ + if (hapd->driver == NULL || hapd->driver->get_inact_sec == NULL) + return 0; + return hapd->driver->get_inact_sec(hapd->drv_priv, addr); +} + +static inline int hostapd_drv_sta_remove(struct hostapd_data *hapd, + const u8 *addr) +{ + if (!hapd->driver || !hapd->driver->sta_remove || !hapd->drv_priv) + return 0; + return hapd->driver->sta_remove(hapd->drv_priv, addr); +} + +static inline int hostapd_drv_hapd_send_eapol(struct hostapd_data *hapd, + const u8 *addr, const u8 *data, + size_t data_len, int encrypt, + u32 flags, int link_id) +{ + if (hapd->driver == NULL || hapd->driver->hapd_send_eapol == NULL) + return 0; + return hapd->driver->hapd_send_eapol(hapd->drv_priv, addr, data, + data_len, encrypt, + hapd->own_addr, flags, link_id); +} + +static inline int hostapd_drv_read_sta_data( + struct hostapd_data *hapd, struct hostap_sta_driver_data *data, + const u8 *addr) +{ + if (hapd->driver == NULL || hapd->driver->read_sta_data == NULL) + return -1; + return hapd->driver->read_sta_data(hapd->drv_priv, data, addr); +} + +static inline int hostapd_drv_sta_clear_stats(struct hostapd_data *hapd, + const u8 *addr) +{ + if (hapd->driver == NULL || hapd->driver->sta_clear_stats == NULL) + return 0; + return hapd->driver->sta_clear_stats(hapd->drv_priv, addr); +} + +static inline int hostapd_drv_set_acl(struct hostapd_data *hapd, + struct hostapd_acl_params *params) +{ + if (hapd->driver == NULL || hapd->driver->set_acl == NULL) + return 0; + return hapd->driver->set_acl(hapd->drv_priv, params); +} + +static inline int hostapd_drv_set_ap(struct hostapd_data *hapd, + struct wpa_driver_ap_params *params) +{ + if (hapd->driver == NULL || hapd->driver->set_ap == NULL) + return 0; + return hapd->driver->set_ap(hapd->drv_priv, params); +} + +static inline int hostapd_drv_set_radius_acl_auth(struct hostapd_data *hapd, + const u8 *mac, int accepted, + u32 session_timeout) +{ + if (hapd->driver == NULL || hapd->driver->set_radius_acl_auth == NULL) + return 0; + return hapd->driver->set_radius_acl_auth(hapd->drv_priv, mac, accepted, + session_timeout); +} + +static inline int hostapd_drv_set_radius_acl_expire(struct hostapd_data *hapd, + const u8 *mac) +{ + if (hapd->driver == NULL || + hapd->driver->set_radius_acl_expire == NULL) + return 0; + return hapd->driver->set_radius_acl_expire(hapd->drv_priv, mac); +} + +static inline int hostapd_drv_set_authmode(struct hostapd_data *hapd, + int auth_algs) +{ + if (hapd->driver == NULL || hapd->driver->set_authmode == NULL) + return 0; + return hapd->driver->set_authmode(hapd->drv_priv, auth_algs); +} + +static inline void hostapd_drv_poll_client(struct hostapd_data *hapd, + const u8 *own_addr, const u8 *addr, + int qos) +{ + if (hapd->driver == NULL || hapd->driver->poll_client == NULL) + return; + hapd->driver->poll_client(hapd->drv_priv, own_addr, addr, qos); +} + +static inline int hostapd_drv_get_survey(struct hostapd_data *hapd, + unsigned int freq) +{ + if (hapd->driver == NULL) + return -1; + if (!hapd->driver->get_survey) + return -1; + return hapd->driver->get_survey(hapd->drv_priv, freq); +} + +static inline int hostapd_get_country(struct hostapd_data *hapd, char *alpha2) +{ + if (hapd->driver == NULL || hapd->driver->get_country == NULL) + return -1; + return hapd->driver->get_country(hapd->drv_priv, alpha2); +} + +static inline const char * hostapd_drv_get_radio_name(struct hostapd_data *hapd) +{ + if (hapd->driver == NULL || hapd->drv_priv == NULL || + hapd->driver->get_radio_name == NULL) + return NULL; + return hapd->driver->get_radio_name(hapd->drv_priv); +} + +static inline int hostapd_drv_switch_channel(struct hostapd_data *hapd, + struct csa_settings *settings) +{ + if (hapd->driver == NULL || hapd->driver->switch_channel == NULL || + hapd->drv_priv == NULL) + return -1; + + return hapd->driver->switch_channel(hapd->drv_priv, settings); +} + +#ifdef CONFIG_IEEE80211AX +static inline int hostapd_drv_switch_color(struct hostapd_data *hapd, + struct cca_settings *settings) +{ + if (!hapd->driver || !hapd->driver->switch_color || !hapd->drv_priv) + return -1; + + return hapd->driver->switch_color(hapd->drv_priv, settings); +} +#endif /* CONFIG_IEEE80211AX */ + +static inline int hostapd_drv_status(struct hostapd_data *hapd, char *buf, + size_t buflen) +{ + if (!hapd->driver || !hapd->driver->status || !hapd->drv_priv) + return -1; + return hapd->driver->status(hapd->drv_priv, buf, buflen); +} + +static inline int hostapd_drv_br_add_ip_neigh(struct hostapd_data *hapd, + int version, const u8 *ipaddr, + int prefixlen, const u8 *addr) +{ + if (hapd->driver == NULL || hapd->drv_priv == NULL || + hapd->driver->br_add_ip_neigh == NULL) + return -1; + return hapd->driver->br_add_ip_neigh(hapd->drv_priv, version, ipaddr, + prefixlen, addr); +} + +static inline int hostapd_drv_br_delete_ip_neigh(struct hostapd_data *hapd, + u8 version, const u8 *ipaddr) +{ + if (hapd->driver == NULL || hapd->drv_priv == NULL || + hapd->driver->br_delete_ip_neigh == NULL) + return -1; + return hapd->driver->br_delete_ip_neigh(hapd->drv_priv, version, + ipaddr); +} + +static inline int hostapd_drv_br_port_set_attr(struct hostapd_data *hapd, + enum drv_br_port_attr attr, + unsigned int val) +{ + if (hapd->driver == NULL || hapd->drv_priv == NULL || + hapd->driver->br_port_set_attr == NULL) + return -1; + return hapd->driver->br_port_set_attr(hapd->drv_priv, attr, val); +} + +static inline int hostapd_drv_br_set_net_param(struct hostapd_data *hapd, + enum drv_br_net_param param, + unsigned int val) +{ + if (hapd->driver == NULL || hapd->drv_priv == NULL || + hapd->driver->br_set_net_param == NULL) + return -1; + return hapd->driver->br_set_net_param(hapd->drv_priv, param, val); +} + +static inline int hostapd_drv_vendor_cmd(struct hostapd_data *hapd, + int vendor_id, int subcmd, + const u8 *data, size_t data_len, + enum nested_attr nested_attr_flag, + struct wpabuf *buf) +{ + if (hapd->driver == NULL || hapd->driver->vendor_cmd == NULL) + return -1; + return hapd->driver->vendor_cmd(hapd->drv_priv, vendor_id, subcmd, data, + data_len, nested_attr_flag, buf); +} + +static inline int hostapd_drv_stop_ap(struct hostapd_data *hapd) +{ + int link_id = -1; + + if (!hapd->driver || !hapd->driver->stop_ap || !hapd->drv_priv) + return 0; +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) + link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + return hapd->driver->stop_ap(hapd->drv_priv, link_id); +} + +static inline int hostapd_drv_channel_info(struct hostapd_data *hapd, + struct wpa_channel_info *ci) +{ + if (!hapd->driver || !hapd->driver->channel_info) + return -1; + return hapd->driver->channel_info(hapd->drv_priv, ci); +} + +static inline int +hostapd_drv_send_external_auth_status(struct hostapd_data *hapd, + struct external_auth *params) +{ + if (!hapd->driver || !hapd->drv_priv || + !hapd->driver->send_external_auth_status) + return -1; + return hapd->driver->send_external_auth_status(hapd->drv_priv, params); +} + +static inline int +hostapd_drv_set_band(struct hostapd_data *hapd, u32 band_mask) +{ + if (!hapd->driver || !hapd->drv_priv || !hapd->driver->set_band) + return -1; + return hapd->driver->set_band(hapd->drv_priv, band_mask); +} + +#ifdef ANDROID +static inline int hostapd_drv_driver_cmd(struct hostapd_data *hapd, + char *cmd, char *buf, size_t buf_len) +{ + if (!hapd->driver->driver_cmd) + return -1; + return hapd->driver->driver_cmd(hapd->drv_priv, cmd, buf, buf_len); +} +#endif /* ANDROID */ + +#ifdef CONFIG_TESTING_OPTIONS +static inline int +hostapd_drv_register_frame(struct hostapd_data *hapd, u16 type, + const u8 *match, size_t match_len, + bool multicast) +{ + if (!hapd->driver || !hapd->drv_priv || !hapd->driver->register_frame) + return -1; + return hapd->driver->register_frame(hapd->drv_priv, type, match, + match_len, multicast); +} +#endif /* CONFIG_TESTING_OPTIONS */ + +#ifdef CONFIG_IEEE80211BE + +static inline int hostapd_drv_link_add(struct hostapd_data *hapd, + u8 link_id, const u8 *addr) +{ + if (!hapd->driver || !hapd->drv_priv || !hapd->driver->link_add) + return -1; + + return hapd->driver->link_add(hapd->drv_priv, link_id, addr, hapd); + +} + +static inline int hostapd_drv_link_sta_remove(struct hostapd_data *hapd, + const u8 *addr) +{ + if (!hapd->conf->mld_ap || !hapd->driver || !hapd->drv_priv || + !hapd->driver->link_sta_remove) + return -1; + + return hapd->driver->link_sta_remove(hapd->drv_priv, hapd->mld_link_id, + addr); +} + +#endif /* CONFIG_IEEE80211BE */ + +#endif /* AP_DRV_OPS */ diff --git a/src/ap/ap_list.c b/src/ap/ap_list.c new file mode 100644 index 0000000..13facab --- /dev/null +++ b/src/ap/ap_list.c @@ -0,0 +1,308 @@ +/* + * hostapd / AP table + * Copyright (c) 2002-2009, Jouni Malinen + * Copyright (c) 2003-2004, Instant802 Networks, Inc. + * Copyright (c) 2006, Devicescape Software, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "hostapd.h" +#include "ap_config.h" +#include "ieee802_11.h" +#include "sta_info.h" +#include "beacon.h" +#include "ap_list.h" + + +/* AP list is a double linked list with head->prev pointing to the end of the + * list and tail->next = NULL. Entries are moved to the head of the list + * whenever a beacon has been received from the AP in question. The tail entry + * in this link will thus be the least recently used entry. */ + + +static int ap_list_beacon_olbc(struct hostapd_iface *iface, struct ap_info *ap) +{ + int i; + + if (iface->current_mode == NULL || + iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G || + iface->conf->channel != ap->channel) + return 0; + + if (ap->erp != -1 && (ap->erp & ERP_INFO_NON_ERP_PRESENT)) + return 1; + + for (i = 0; i < WLAN_SUPP_RATES_MAX; i++) { + int rate = (ap->supported_rates[i] & 0x7f) * 5; + if (rate == 60 || rate == 90 || rate > 110) + return 0; + } + + return 1; +} + + +static struct ap_info * ap_get_ap(struct hostapd_iface *iface, const u8 *ap) +{ + struct ap_info *s; + + s = iface->ap_hash[STA_HASH(ap)]; + while (s != NULL && !ether_addr_equal(s->addr, ap)) + s = s->hnext; + return s; +} + + +static void ap_ap_list_add(struct hostapd_iface *iface, struct ap_info *ap) +{ + if (iface->ap_list) { + ap->prev = iface->ap_list->prev; + iface->ap_list->prev = ap; + } else + ap->prev = ap; + ap->next = iface->ap_list; + iface->ap_list = ap; +} + + +static void ap_ap_list_del(struct hostapd_iface *iface, struct ap_info *ap) +{ + if (iface->ap_list == ap) + iface->ap_list = ap->next; + else + ap->prev->next = ap->next; + + if (ap->next) + ap->next->prev = ap->prev; + else if (iface->ap_list) + iface->ap_list->prev = ap->prev; +} + + +static void ap_ap_hash_add(struct hostapd_iface *iface, struct ap_info *ap) +{ + ap->hnext = iface->ap_hash[STA_HASH(ap->addr)]; + iface->ap_hash[STA_HASH(ap->addr)] = ap; +} + + +static void ap_ap_hash_del(struct hostapd_iface *iface, struct ap_info *ap) +{ + struct ap_info *s; + + s = iface->ap_hash[STA_HASH(ap->addr)]; + if (s == NULL) return; + if (ether_addr_equal(s->addr, ap->addr)) { + iface->ap_hash[STA_HASH(ap->addr)] = s->hnext; + return; + } + + while (s->hnext != NULL && + !ether_addr_equal(s->hnext->addr, ap->addr)) + s = s->hnext; + if (s->hnext != NULL) + s->hnext = s->hnext->hnext; + else + wpa_printf(MSG_INFO, "AP: could not remove AP " MACSTR + " from hash table", MAC2STR(ap->addr)); +} + + +static void ap_free_ap(struct hostapd_iface *iface, struct ap_info *ap) +{ + ap_ap_hash_del(iface, ap); + ap_ap_list_del(iface, ap); + + iface->num_ap--; + os_free(ap); +} + + +static void hostapd_free_aps(struct hostapd_iface *iface) +{ + struct ap_info *ap, *prev; + + ap = iface->ap_list; + + while (ap) { + prev = ap; + ap = ap->next; + ap_free_ap(iface, prev); + } + + iface->ap_list = NULL; +} + + +static struct ap_info * ap_ap_add(struct hostapd_iface *iface, const u8 *addr) +{ + struct ap_info *ap; + + ap = os_zalloc(sizeof(struct ap_info)); + if (ap == NULL) + return NULL; + + /* initialize AP info data */ + os_memcpy(ap->addr, addr, ETH_ALEN); + ap_ap_list_add(iface, ap); + iface->num_ap++; + ap_ap_hash_add(iface, ap); + + if (iface->num_ap > iface->conf->ap_table_max_size && ap != ap->prev) { + wpa_printf(MSG_DEBUG, "Removing the least recently used AP " + MACSTR " from AP table", MAC2STR(ap->prev->addr)); + ap_free_ap(iface, ap->prev); + } + + return ap; +} + + +void ap_list_process_beacon(struct hostapd_iface *iface, + const struct ieee80211_mgmt *mgmt, + struct ieee802_11_elems *elems, + struct hostapd_frame_info *fi) +{ + struct ap_info *ap; + int new_ap = 0; + int set_beacon = 0; + + if (iface->conf->ap_table_max_size < 1) + return; + + ap = ap_get_ap(iface, mgmt->bssid); + if (!ap) { + ap = ap_ap_add(iface, mgmt->bssid); + if (!ap) { + wpa_printf(MSG_INFO, + "Failed to allocate AP information entry"); + return; + } + new_ap = 1; + } + + merge_byte_arrays(ap->supported_rates, WLAN_SUPP_RATES_MAX, + elems->supp_rates, elems->supp_rates_len, + elems->ext_supp_rates, elems->ext_supp_rates_len); + + if (elems->erp_info) + ap->erp = elems->erp_info[0]; + else + ap->erp = -1; + + if (elems->ds_params) + ap->channel = elems->ds_params[0]; + else if (elems->ht_operation) + ap->channel = elems->ht_operation[0]; + else if (fi) + ap->channel = fi->channel; + + if (elems->ht_capabilities) + ap->ht_support = 1; + else + ap->ht_support = 0; + + os_get_reltime(&ap->last_beacon); + + if (!new_ap && ap != iface->ap_list) { + /* move AP entry into the beginning of the list so that the + * oldest entry is always in the end of the list */ + ap_ap_list_del(iface, ap); + ap_ap_list_add(iface, ap); + } + + if (!iface->olbc && + ap_list_beacon_olbc(iface, ap)) { + iface->olbc = 1; + wpa_printf(MSG_DEBUG, "OLBC AP detected: " MACSTR + " (channel %d) - enable protection", + MAC2STR(ap->addr), ap->channel); + set_beacon++; + } + + if (!iface->olbc_ht && !ap->ht_support && + (ap->channel == 0 || + ap->channel == iface->conf->channel || + ap->channel == iface->conf->channel + + iface->conf->secondary_channel * 4)) { + iface->olbc_ht = 1; + hostapd_ht_operation_update(iface); + wpa_printf(MSG_DEBUG, "OLBC HT AP detected: " MACSTR + " (channel %d) - enable protection", + MAC2STR(ap->addr), ap->channel); + set_beacon++; + } + + if (set_beacon) + ieee802_11_update_beacons(iface); +} + + +void ap_list_timer(struct hostapd_iface *iface) +{ + struct os_reltime now; + struct ap_info *ap; + int set_beacon = 0; + + if (!iface->ap_list) + return; + + os_get_reltime(&now); + + while (iface->ap_list) { + ap = iface->ap_list->prev; + if (!os_reltime_expired(&now, &ap->last_beacon, + iface->conf->ap_table_expiration_time)) + break; + + ap_free_ap(iface, ap); + } + + if (iface->olbc || iface->olbc_ht) { + int olbc = 0; + int olbc_ht = 0; + + ap = iface->ap_list; + while (ap && (olbc == 0 || olbc_ht == 0)) { + if (ap_list_beacon_olbc(iface, ap)) + olbc = 1; + if (!ap->ht_support) + olbc_ht = 1; + ap = ap->next; + } + if (!olbc && iface->olbc) { + wpa_printf(MSG_DEBUG, "OLBC not detected anymore"); + iface->olbc = 0; + set_beacon++; + } + if (!olbc_ht && iface->olbc_ht) { + wpa_printf(MSG_DEBUG, "OLBC HT not detected anymore"); + iface->olbc_ht = 0; + hostapd_ht_operation_update(iface); + set_beacon++; + } + } + + if (set_beacon) + ieee802_11_update_beacons(iface); +} + + +int ap_list_init(struct hostapd_iface *iface) +{ + return 0; +} + + +void ap_list_deinit(struct hostapd_iface *iface) +{ + hostapd_free_aps(iface); +} diff --git a/src/ap/ap_list.h b/src/ap/ap_list.h new file mode 100644 index 0000000..9e0353c --- /dev/null +++ b/src/ap/ap_list.h @@ -0,0 +1,58 @@ +/* + * hostapd / AP table + * Copyright (c) 2002-2003, Jouni Malinen + * Copyright (c) 2003-2004, Instant802 Networks, Inc. + * Copyright (c) 2006, Devicescape Software, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef AP_LIST_H +#define AP_LIST_H + +struct ap_info { + /* Note: next/prev pointers are updated whenever a new beacon is + * received because these are used to find the least recently used + * entries. */ + struct ap_info *next; /* next entry in AP list */ + struct ap_info *prev; /* previous entry in AP list */ + struct ap_info *hnext; /* next entry in hash table list */ + u8 addr[6]; + u8 supported_rates[WLAN_SUPP_RATES_MAX]; + int erp; /* ERP Info or -1 if ERP info element not present */ + + int channel; + + int ht_support; + + struct os_reltime last_beacon; +}; + +struct ieee802_11_elems; +struct hostapd_frame_info; + +void ap_list_process_beacon(struct hostapd_iface *iface, + const struct ieee80211_mgmt *mgmt, + struct ieee802_11_elems *elems, + struct hostapd_frame_info *fi); +#ifdef NEED_AP_MLME +int ap_list_init(struct hostapd_iface *iface); +void ap_list_deinit(struct hostapd_iface *iface); +void ap_list_timer(struct hostapd_iface *iface); +#else /* NEED_AP_MLME */ +static inline int ap_list_init(struct hostapd_iface *iface) +{ + return 0; +} + +static inline void ap_list_deinit(struct hostapd_iface *iface) +{ +} + +static inline void ap_list_timer(struct hostapd_iface *iface) +{ +} +#endif /* NEED_AP_MLME */ + +#endif /* AP_LIST_H */ diff --git a/src/ap/ap_mlme.c b/src/ap/ap_mlme.c new file mode 100644 index 0000000..309e69a --- /dev/null +++ b/src/ap/ap_mlme.c @@ -0,0 +1,191 @@ +/* + * hostapd / IEEE 802.11 MLME + * Copyright 2003-2006, Jouni Malinen + * Copyright 2003-2004, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "ieee802_11.h" +#include "wpa_auth.h" +#include "sta_info.h" +#include "ap_mlme.h" +#include "hostapd.h" + + +#ifndef CONFIG_NO_HOSTAPD_LOGGER +static const char * mlme_auth_alg_str(int alg) +{ + switch (alg) { + case WLAN_AUTH_OPEN: + return "OPEN_SYSTEM"; + case WLAN_AUTH_SHARED_KEY: + return "SHARED_KEY"; + case WLAN_AUTH_FT: + return "FT"; + default: + return "unknown"; + } +} +#endif /* CONFIG_NO_HOSTAPD_LOGGER */ + + +/** + * mlme_authenticate_indication - Report the establishment of an authentication + * relationship with a specific peer MAC entity + * @hapd: BSS data + * @sta: peer STA data + * + * MLME calls this function as a result of the establishment of an + * authentication relationship with a specific peer MAC entity that + * resulted from an authentication procedure that was initiated by + * that specific peer MAC entity. + * + * PeerSTAAddress = sta->addr + * AuthenticationType = sta->auth_alg (WLAN_AUTH_OPEN / WLAN_AUTH_SHARED_KEY) + */ +void mlme_authenticate_indication(struct hostapd_data *hapd, + struct sta_info *sta) +{ + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_MLME, + HOSTAPD_LEVEL_DEBUG, + "MLME-AUTHENTICATE.indication(" MACSTR ", %s)", + MAC2STR(sta->addr), mlme_auth_alg_str(sta->auth_alg)); + if (sta->auth_alg != WLAN_AUTH_FT && + sta->auth_alg != WLAN_AUTH_FILS_SK && + sta->auth_alg != WLAN_AUTH_FILS_SK_PFS && + sta->auth_alg != WLAN_AUTH_FILS_PK && + !(sta->flags & WLAN_STA_MFP)) + mlme_deletekeys_request(hapd, sta); + ap_sta_clear_disconnect_timeouts(hapd, sta); +} + + +/** + * mlme_deauthenticate_indication - Report the invalidation of an + * authentication relationship with a specific peer MAC entity + * @hapd: BSS data + * @sta: Peer STA data + * @reason_code: ReasonCode from Deauthentication frame + * + * MLME calls this function as a result of the invalidation of an + * authentication relationship with a specific peer MAC entity. + * + * PeerSTAAddress = sta->addr + */ +void mlme_deauthenticate_indication(struct hostapd_data *hapd, + struct sta_info *sta, u16 reason_code) +{ + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_MLME, + HOSTAPD_LEVEL_DEBUG, + "MLME-DEAUTHENTICATE.indication(" MACSTR ", %d)", + MAC2STR(sta->addr), reason_code); + if (!hapd->iface->driver_ap_teardown) + mlme_deletekeys_request(hapd, sta); +} + + +/** + * mlme_associate_indication - Report the establishment of an association with + * a specific peer MAC entity + * @hapd: BSS data + * @sta: peer STA data + * + * MLME calls this function as a result of the establishment of an + * association with a specific peer MAC entity that resulted from an + * association procedure that was initiated by that specific peer MAC entity. + * + * PeerSTAAddress = sta->addr + */ +void mlme_associate_indication(struct hostapd_data *hapd, struct sta_info *sta) +{ + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_MLME, + HOSTAPD_LEVEL_DEBUG, + "MLME-ASSOCIATE.indication(" MACSTR ")", + MAC2STR(sta->addr)); + if (sta->auth_alg != WLAN_AUTH_FT && + sta->auth_alg != WLAN_AUTH_FILS_SK && + sta->auth_alg != WLAN_AUTH_FILS_SK_PFS && + sta->auth_alg != WLAN_AUTH_FILS_PK) + mlme_deletekeys_request(hapd, sta); + ap_sta_clear_disconnect_timeouts(hapd, sta); +} + + +/** + * mlme_reassociate_indication - Report the establishment of an reassociation + * with a specific peer MAC entity + * @hapd: BSS data + * @sta: peer STA data + * + * MLME calls this function as a result of the establishment of an + * reassociation with a specific peer MAC entity that resulted from a + * reassociation procedure that was initiated by that specific peer MAC entity. + * + * PeerSTAAddress = sta->addr + */ +void mlme_reassociate_indication(struct hostapd_data *hapd, + struct sta_info *sta) +{ + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_MLME, + HOSTAPD_LEVEL_DEBUG, + "MLME-REASSOCIATE.indication(" MACSTR ")", + MAC2STR(sta->addr)); + if (sta->auth_alg != WLAN_AUTH_FT && + sta->auth_alg != WLAN_AUTH_FILS_SK && + sta->auth_alg != WLAN_AUTH_FILS_SK_PFS && + sta->auth_alg != WLAN_AUTH_FILS_PK) + mlme_deletekeys_request(hapd, sta); + ap_sta_clear_disconnect_timeouts(hapd, sta); +} + + +/** + * mlme_disassociate_indication - Report disassociation with a specific peer + * MAC entity + * @hapd: BSS data + * @sta: Peer STA data + * @reason_code: ReasonCode from Disassociation frame + * + * MLME calls this function as a result of the invalidation of an association + * relationship with a specific peer MAC entity. + * + * PeerSTAAddress = sta->addr + */ +void mlme_disassociate_indication(struct hostapd_data *hapd, + struct sta_info *sta, u16 reason_code) +{ + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_MLME, + HOSTAPD_LEVEL_DEBUG, + "MLME-DISASSOCIATE.indication(" MACSTR ", %d)", + MAC2STR(sta->addr), reason_code); + mlme_deletekeys_request(hapd, sta); +} + + +void mlme_michaelmicfailure_indication(struct hostapd_data *hapd, + const u8 *addr) +{ + hostapd_logger(hapd, addr, HOSTAPD_MODULE_MLME, + HOSTAPD_LEVEL_DEBUG, + "MLME-MichaelMICFailure.indication(" MACSTR ")", + MAC2STR(addr)); +} + + +void mlme_deletekeys_request(struct hostapd_data *hapd, struct sta_info *sta) +{ + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_MLME, + HOSTAPD_LEVEL_DEBUG, + "MLME-DELETEKEYS.request(" MACSTR ")", + MAC2STR(sta->addr)); + + if (sta->wpa_sm) + wpa_remove_ptk(sta->wpa_sm); +} diff --git a/src/ap/ap_mlme.h b/src/ap/ap_mlme.h new file mode 100644 index 0000000..e7fd69d --- /dev/null +++ b/src/ap/ap_mlme.h @@ -0,0 +1,34 @@ +/* + * hostapd / IEEE 802.11 MLME + * Copyright 2003, Jouni Malinen + * Copyright 2003-2004, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef MLME_H +#define MLME_H + +void mlme_authenticate_indication(struct hostapd_data *hapd, + struct sta_info *sta); + +void mlme_deauthenticate_indication(struct hostapd_data *hapd, + struct sta_info *sta, u16 reason_code); + +void mlme_associate_indication(struct hostapd_data *hapd, + struct sta_info *sta); + +void mlme_reassociate_indication(struct hostapd_data *hapd, + struct sta_info *sta); + +void mlme_disassociate_indication(struct hostapd_data *hapd, + struct sta_info *sta, u16 reason_code); + +void mlme_michaelmicfailure_indication(struct hostapd_data *hapd, + const u8 *addr); + +void mlme_deletekeys_request(struct hostapd_data *hapd, struct sta_info *sta); + +#endif /* MLME_H */ diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c new file mode 100644 index 0000000..837b690 --- /dev/null +++ b/src/ap/authsrv.c @@ -0,0 +1,445 @@ +/* + * Authentication server setup + * Copyright (c) 2002-2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "crypto/crypto.h" +#include "crypto/tls.h" +#include "eap_server/eap.h" +#include "eap_server/eap_sim_db.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "radius/radius_server.h" +#include "hostapd.h" +#include "ap_config.h" +#include "sta_info.h" +#include "authsrv.h" + + +#if defined(EAP_SERVER_SIM) || defined(EAP_SERVER_AKA) +#define EAP_SIM_DB +#endif /* EAP_SERVER_SIM || EAP_SERVER_AKA */ + + +#ifdef EAP_SIM_DB +static int hostapd_sim_db_cb_sta(struct hostapd_data *hapd, + struct sta_info *sta, void *ctx) +{ + if (eapol_auth_eap_pending_cb(sta->eapol_sm, ctx) == 0) + return 1; + return 0; +} + + +static void hostapd_sim_db_cb(void *ctx, void *session_ctx) +{ + struct hostapd_data *hapd = ctx; + if (ap_for_each_sta(hapd, hostapd_sim_db_cb_sta, session_ctx) == 0) { +#ifdef RADIUS_SERVER + radius_server_eap_pending_cb(hapd->radius_srv, session_ctx); +#endif /* RADIUS_SERVER */ + } +} +#endif /* EAP_SIM_DB */ + + +#ifdef RADIUS_SERVER + +static int hostapd_radius_get_eap_user(void *ctx, const u8 *identity, + size_t identity_len, int phase2, + struct eap_user *user) +{ + const struct hostapd_eap_user *eap_user; + int i; + int rv = -1; + + eap_user = hostapd_get_eap_user(ctx, identity, identity_len, phase2); + if (eap_user == NULL) + goto out; + + if (user == NULL) + return 0; + + os_memset(user, 0, sizeof(*user)); + for (i = 0; i < EAP_MAX_METHODS; i++) { + user->methods[i].vendor = eap_user->methods[i].vendor; + user->methods[i].method = eap_user->methods[i].method; + } + + if (eap_user->password) { + user->password = os_memdup(eap_user->password, + eap_user->password_len); + if (user->password == NULL) + goto out; + user->password_len = eap_user->password_len; + user->password_hash = eap_user->password_hash; + if (eap_user->salt && eap_user->salt_len) { + user->salt = os_memdup(eap_user->salt, + eap_user->salt_len); + if (!user->salt) + goto out; + user->salt_len = eap_user->salt_len; + } + } + user->force_version = eap_user->force_version; + user->macacl = eap_user->macacl; + user->ttls_auth = eap_user->ttls_auth; + user->remediation = eap_user->remediation; + user->accept_attr = eap_user->accept_attr; + user->t_c_timestamp = eap_user->t_c_timestamp; + rv = 0; + +out: + if (rv) + wpa_printf(MSG_DEBUG, "%s: Failed to find user", __func__); + + return rv; +} + + +static int hostapd_setup_radius_srv(struct hostapd_data *hapd) +{ + struct radius_server_conf srv; + struct hostapd_bss_config *conf = hapd->conf; + +#ifdef CONFIG_IEEE80211BE + if (!hostapd_mld_is_first_bss(hapd)) { + struct hostapd_data *first; + + wpa_printf(MSG_DEBUG, + "MLD: Using RADIUS server of the first BSS"); + + first = hostapd_mld_get_first_bss(hapd); + if (!first) + return -1; + hapd->radius_srv = first->radius_srv; + return 0; + } +#endif /* CONFIG_IEEE80211BE */ + + os_memset(&srv, 0, sizeof(srv)); + srv.client_file = conf->radius_server_clients; + srv.auth_port = conf->radius_server_auth_port; + srv.acct_port = conf->radius_server_acct_port; + srv.conf_ctx = hapd; + srv.ipv6 = conf->radius_server_ipv6; + srv.get_eap_user = hostapd_radius_get_eap_user; + srv.eap_req_id_text = conf->eap_req_id_text; + srv.eap_req_id_text_len = conf->eap_req_id_text_len; + srv.sqlite_file = conf->eap_user_sqlite; +#ifdef CONFIG_RADIUS_TEST + srv.dump_msk_file = conf->dump_msk_file; +#endif /* CONFIG_RADIUS_TEST */ +#ifdef CONFIG_HS20 + srv.subscr_remediation_url = conf->subscr_remediation_url; + srv.subscr_remediation_method = conf->subscr_remediation_method; + srv.hs20_sim_provisioning_url = conf->hs20_sim_provisioning_url; + srv.t_c_server_url = conf->t_c_server_url; +#endif /* CONFIG_HS20 */ + srv.erp_domain = conf->erp_domain; + srv.eap_cfg = hapd->eap_cfg; + + hapd->radius_srv = radius_server_init(&srv); + if (hapd->radius_srv == NULL) { + wpa_printf(MSG_ERROR, "RADIUS server initialization failed."); + return -1; + } + + return 0; +} + +#endif /* RADIUS_SERVER */ + + +#ifdef EAP_TLS_FUNCS +static void authsrv_tls_event(void *ctx, enum tls_event ev, + union tls_event_data *data) +{ + switch (ev) { + case TLS_CERT_CHAIN_SUCCESS: + wpa_printf(MSG_DEBUG, "authsrv: remote certificate verification success"); + break; + case TLS_CERT_CHAIN_FAILURE: + wpa_printf(MSG_INFO, "authsrv: certificate chain failure: reason=%d depth=%d subject='%s' err='%s'", + data->cert_fail.reason, + data->cert_fail.depth, + data->cert_fail.subject, + data->cert_fail.reason_txt); + break; + case TLS_PEER_CERTIFICATE: + wpa_printf(MSG_DEBUG, "authsrv: peer certificate: depth=%d serial_num=%s subject=%s", + data->peer_cert.depth, + data->peer_cert.serial_num ? data->peer_cert.serial_num : "N/A", + data->peer_cert.subject); + break; + case TLS_ALERT: + if (data->alert.is_local) + wpa_printf(MSG_DEBUG, "authsrv: local TLS alert: %s", + data->alert.description); + else + wpa_printf(MSG_DEBUG, "authsrv: remote TLS alert: %s", + data->alert.description); + break; + case TLS_UNSAFE_RENEGOTIATION_DISABLED: + /* Not applicable to TLS server */ + break; + } +} +#endif /* EAP_TLS_FUNCS */ + + +static struct eap_config * authsrv_eap_config(struct hostapd_data *hapd) +{ + struct eap_config *cfg; + + cfg = os_zalloc(sizeof(*cfg)); + if (!cfg) + return NULL; + + cfg->eap_server = hapd->conf->eap_server; + cfg->ssl_ctx = hapd->ssl_ctx; + cfg->msg_ctx = hapd->msg_ctx; + cfg->eap_sim_db_priv = hapd->eap_sim_db_priv; + cfg->tls_session_lifetime = hapd->conf->tls_session_lifetime; + cfg->tls_flags = hapd->conf->tls_flags; + cfg->max_auth_rounds = hapd->conf->max_auth_rounds; + cfg->max_auth_rounds_short = hapd->conf->max_auth_rounds_short; + if (hapd->conf->pac_opaque_encr_key) + cfg->pac_opaque_encr_key = + os_memdup(hapd->conf->pac_opaque_encr_key, 16); + if (hapd->conf->eap_fast_a_id) { + cfg->eap_fast_a_id = os_memdup(hapd->conf->eap_fast_a_id, + hapd->conf->eap_fast_a_id_len); + cfg->eap_fast_a_id_len = hapd->conf->eap_fast_a_id_len; + } + if (hapd->conf->eap_fast_a_id_info) + cfg->eap_fast_a_id_info = + os_strdup(hapd->conf->eap_fast_a_id_info); + cfg->eap_fast_prov = hapd->conf->eap_fast_prov; + cfg->pac_key_lifetime = hapd->conf->pac_key_lifetime; + cfg->pac_key_refresh_time = hapd->conf->pac_key_refresh_time; + cfg->eap_teap_auth = hapd->conf->eap_teap_auth; + cfg->eap_teap_pac_no_inner = hapd->conf->eap_teap_pac_no_inner; + cfg->eap_teap_separate_result = hapd->conf->eap_teap_separate_result; + cfg->eap_teap_id = hapd->conf->eap_teap_id; + cfg->eap_teap_method_sequence = hapd->conf->eap_teap_method_sequence; + cfg->eap_sim_aka_result_ind = hapd->conf->eap_sim_aka_result_ind; + cfg->eap_sim_id = hapd->conf->eap_sim_id; + cfg->imsi_privacy_key = hapd->imsi_privacy_key; + cfg->eap_sim_aka_fast_reauth_limit = + hapd->conf->eap_sim_aka_fast_reauth_limit; + cfg->tnc = hapd->conf->tnc; + cfg->wps = hapd->wps; + cfg->fragment_size = hapd->conf->fragment_size; + cfg->pwd_group = hapd->conf->pwd_group; + cfg->pbc_in_m1 = hapd->conf->pbc_in_m1; + if (hapd->conf->server_id) { + cfg->server_id = (u8 *) os_strdup(hapd->conf->server_id); + cfg->server_id_len = os_strlen(hapd->conf->server_id); + } else { + cfg->server_id = (u8 *) os_strdup("hostapd"); + cfg->server_id_len = 7; + } + cfg->erp = hapd->conf->eap_server_erp; +#ifdef CONFIG_TESTING_OPTIONS + cfg->skip_prot_success = hapd->conf->eap_skip_prot_success; +#endif /* CONFIG_TESTING_OPTIONS */ + + return cfg; +} + + +int authsrv_init(struct hostapd_data *hapd) +{ +#ifdef CONFIG_IEEE80211BE + if (!hostapd_mld_is_first_bss(hapd)) { + struct hostapd_data *first; + + first = hostapd_mld_get_first_bss(hapd); + if (!first) + return -1; + + if (!first->eap_cfg) { + wpa_printf(MSG_DEBUG, + "MLD: First BSS auth_serv does not exist. Init on its behalf"); + + if (authsrv_init(first)) + return -1; + } + + wpa_printf(MSG_DEBUG, "MLD: Using auth_serv of the first BSS"); + +#ifdef EAP_TLS_FUNCS + hapd->ssl_ctx = first->ssl_ctx; +#endif /* EAP_TLS_FUNCS */ + hapd->eap_cfg = first->eap_cfg; +#ifdef EAP_SIM_DB + hapd->eap_sim_db_priv = first->eap_sim_db_priv; +#endif /* EAP_SIM_DB */ + return 0; + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef EAP_TLS_FUNCS + if (hapd->conf->eap_server && + (hapd->conf->ca_cert || hapd->conf->server_cert || + hapd->conf->private_key || hapd->conf->dh_file || + hapd->conf->server_cert2 || hapd->conf->private_key2)) { + struct tls_config conf; + struct tls_connection_params params; + + os_memset(&conf, 0, sizeof(conf)); + conf.tls_session_lifetime = hapd->conf->tls_session_lifetime; + if (hapd->conf->crl_reload_interval > 0 && + hapd->conf->check_crl <= 0) { + wpa_printf(MSG_INFO, + "Cannot enable CRL reload functionality - it depends on check_crl being set"); + } else if (hapd->conf->crl_reload_interval > 0) { + conf.crl_reload_interval = + hapd->conf->crl_reload_interval; + wpa_printf(MSG_INFO, + "Enabled CRL reload functionality"); + } + conf.tls_flags = hapd->conf->tls_flags; + conf.event_cb = authsrv_tls_event; + conf.cb_ctx = hapd; + hapd->ssl_ctx = tls_init(&conf); + if (hapd->ssl_ctx == NULL) { + wpa_printf(MSG_ERROR, "Failed to initialize TLS"); + authsrv_deinit(hapd); + return -1; + } + + os_memset(¶ms, 0, sizeof(params)); + params.ca_cert = hapd->conf->ca_cert; + params.client_cert = hapd->conf->server_cert; + params.client_cert2 = hapd->conf->server_cert2; + params.private_key = hapd->conf->private_key; + params.private_key2 = hapd->conf->private_key2; + params.private_key_passwd = hapd->conf->private_key_passwd; + params.private_key_passwd2 = hapd->conf->private_key_passwd2; + params.dh_file = hapd->conf->dh_file; + params.openssl_ciphers = hapd->conf->openssl_ciphers; + params.openssl_ecdh_curves = hapd->conf->openssl_ecdh_curves; + params.ocsp_stapling_response = + hapd->conf->ocsp_stapling_response; + params.ocsp_stapling_response_multi = + hapd->conf->ocsp_stapling_response_multi; + params.check_cert_subject = hapd->conf->check_cert_subject; + + if (tls_global_set_params(hapd->ssl_ctx, ¶ms)) { + wpa_printf(MSG_ERROR, "Failed to set TLS parameters"); + authsrv_deinit(hapd); + return -1; + } + + if (tls_global_set_verify(hapd->ssl_ctx, + hapd->conf->check_crl, + hapd->conf->check_crl_strict)) { + wpa_printf(MSG_ERROR, "Failed to enable check_crl"); + authsrv_deinit(hapd); + return -1; + } + } +#endif /* EAP_TLS_FUNCS */ + +#ifdef CRYPTO_RSA_OAEP_SHA256 + crypto_rsa_key_free(hapd->imsi_privacy_key); + hapd->imsi_privacy_key = NULL; + if (hapd->conf->imsi_privacy_key) { + hapd->imsi_privacy_key = crypto_rsa_key_read( + hapd->conf->imsi_privacy_key, true); + if (!hapd->imsi_privacy_key) { + wpa_printf(MSG_ERROR, + "Failed to read/parse IMSI privacy key %s", + hapd->conf->imsi_privacy_key); + authsrv_deinit(hapd); + return -1; + } + } +#endif /* CRYPTO_RSA_OAEP_SHA256 */ + +#ifdef EAP_SIM_DB + if (hapd->conf->eap_sim_db) { + hapd->eap_sim_db_priv = + eap_sim_db_init(hapd->conf->eap_sim_db, + hapd->conf->eap_sim_db_timeout, + hostapd_sim_db_cb, hapd); + if (hapd->eap_sim_db_priv == NULL) { + wpa_printf(MSG_ERROR, "Failed to initialize EAP-SIM " + "database interface"); + authsrv_deinit(hapd); + return -1; + } + } +#endif /* EAP_SIM_DB */ + + hapd->eap_cfg = authsrv_eap_config(hapd); + if (!hapd->eap_cfg) { + wpa_printf(MSG_ERROR, + "Failed to build EAP server configuration"); + authsrv_deinit(hapd); + return -1; + } + +#ifdef RADIUS_SERVER + if (hapd->conf->radius_server_clients && + hostapd_setup_radius_srv(hapd)) + return -1; +#endif /* RADIUS_SERVER */ + + return 0; +} + + +void authsrv_deinit(struct hostapd_data *hapd) +{ +#ifdef CONFIG_IEEE80211BE + if (!hostapd_mld_is_first_bss(hapd)) { + wpa_printf(MSG_DEBUG, + "MLD: Deinit auth_serv of a non-first BSS"); + + hapd->radius_srv = NULL; + hapd->eap_cfg = NULL; +#ifdef EAP_SIM_DB + hapd->eap_sim_db_priv = NULL; +#endif /* EAP_SIM_DB */ +#ifdef EAP_TLS_FUNCS + hapd->ssl_ctx = NULL; +#endif /* EAP_TLS_FUNCS */ + return; + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef RADIUS_SERVER + radius_server_deinit(hapd->radius_srv); + hapd->radius_srv = NULL; +#endif /* RADIUS_SERVER */ + +#ifdef CRYPTO_RSA_OAEP_SHA256 + crypto_rsa_key_free(hapd->imsi_privacy_key); + hapd->imsi_privacy_key = NULL; +#endif /* CRYPTO_RSA_OAEP_SHA256 */ + +#ifdef EAP_TLS_FUNCS + if (hapd->ssl_ctx) { + tls_deinit(hapd->ssl_ctx); + hapd->ssl_ctx = NULL; + } +#endif /* EAP_TLS_FUNCS */ + +#ifdef EAP_SIM_DB + if (hapd->eap_sim_db_priv) { + eap_sim_db_deinit(hapd->eap_sim_db_priv); + hapd->eap_sim_db_priv = NULL; + } +#endif /* EAP_SIM_DB */ + + eap_server_config_free(hapd->eap_cfg); + hapd->eap_cfg = NULL; +} diff --git a/src/ap/authsrv.h b/src/ap/authsrv.h new file mode 100644 index 0000000..2f4ed34 --- /dev/null +++ b/src/ap/authsrv.h @@ -0,0 +1,15 @@ +/* + * Authentication server setup + * Copyright (c) 2002-2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef AUTHSRV_H +#define AUTHSRV_H + +int authsrv_init(struct hostapd_data *hapd); +void authsrv_deinit(struct hostapd_data *hapd); + +#endif /* AUTHSRV_H */ diff --git a/src/ap/beacon.c b/src/ap/beacon.c new file mode 100644 index 0000000..cec0c98 --- /dev/null +++ b/src/ap/beacon.c @@ -0,0 +1,2762 @@ +/* + * hostapd / IEEE 802.11 Management: Beacon and Probe Request/Response + * Copyright (c) 2002-2004, Instant802 Networks, Inc. + * Copyright (c) 2005-2006, Devicescape Software, Inc. + * Copyright (c) 2008-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#ifndef CONFIG_NATIVE_WINDOWS + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "common/hw_features_common.h" +#include "common/wpa_ctrl.h" +#include "crypto/sha1.h" +#include "wps/wps_defs.h" +#include "p2p/p2p.h" +#include "hostapd.h" +#include "ieee802_11.h" +#include "wpa_auth.h" +#include "wmm.h" +#include "ap_config.h" +#include "sta_info.h" +#include "p2p_hostapd.h" +#include "ap_drv_ops.h" +#include "beacon.h" +#include "hs20.h" +#include "dfs.h" +#include "taxonomy.h" +#include "ieee802_11_auth.h" + + +#ifdef NEED_AP_MLME + +static u8 * hostapd_eid_bss_load(struct hostapd_data *hapd, u8 *eid, size_t len) +{ + if (len < 2 + 5) + return eid; + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->bss_load_test_set) { + *eid++ = WLAN_EID_BSS_LOAD; + *eid++ = 5; + os_memcpy(eid, hapd->conf->bss_load_test, 5); + eid += 5; + return eid; + } +#endif /* CONFIG_TESTING_OPTIONS */ + if (hapd->conf->bss_load_update_period) { + *eid++ = WLAN_EID_BSS_LOAD; + *eid++ = 5; + WPA_PUT_LE16(eid, hapd->num_sta); + eid += 2; + *eid++ = hapd->iface->channel_utilization; + WPA_PUT_LE16(eid, 0); /* no available admission capabity */ + eid += 2; + } + return eid; +} + + +static u8 ieee802_11_erp_info(struct hostapd_data *hapd) +{ + u8 erp = 0; + + if (hapd->iface->current_mode == NULL || + hapd->iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G) + return 0; + + if (hapd->iface->olbc) + erp |= ERP_INFO_USE_PROTECTION; + if (hapd->iface->num_sta_non_erp > 0) { + erp |= ERP_INFO_NON_ERP_PRESENT | + ERP_INFO_USE_PROTECTION; + } + if (hapd->iface->num_sta_no_short_preamble > 0 || + hapd->iconf->preamble == LONG_PREAMBLE) + erp |= ERP_INFO_BARKER_PREAMBLE_MODE; + + return erp; +} + + +static u8 * hostapd_eid_ds_params(struct hostapd_data *hapd, u8 *eid) +{ + enum hostapd_hw_mode hw_mode = hapd->iconf->hw_mode; + + if (hw_mode != HOSTAPD_MODE_IEEE80211G && + hw_mode != HOSTAPD_MODE_IEEE80211B) + return eid; + + *eid++ = WLAN_EID_DS_PARAMS; + *eid++ = 1; + *eid++ = hapd->iconf->channel; + return eid; +} + + +static u8 * hostapd_eid_erp_info(struct hostapd_data *hapd, u8 *eid) +{ + if (hapd->iface->current_mode == NULL || + hapd->iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G) + return eid; + + /* Set NonERP_present and use_protection bits if there + * are any associated NonERP stations. */ + /* TODO: use_protection bit can be set to zero even if + * there are NonERP stations present. This optimization + * might be useful if NonERP stations are "quiet". + * See 802.11g/D6 E-1 for recommended practice. + * In addition, Non ERP present might be set, if AP detects Non ERP + * operation on other APs. */ + + /* Add ERP Information element */ + *eid++ = WLAN_EID_ERP_INFO; + *eid++ = 1; + *eid++ = ieee802_11_erp_info(hapd); + + return eid; +} + + +static u8 * hostapd_eid_pwr_constraint(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; + u8 local_pwr_constraint = 0; + int dfs; + + if (hapd->iface->current_mode == NULL || + hapd->iface->current_mode->mode != HOSTAPD_MODE_IEEE80211A) + return eid; + + /* Let host drivers add this IE if DFS support is offloaded */ + if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) + return eid; + + /* + * There is no DFS support and power constraint was not directly + * requested by config option. + */ + if (!hapd->iconf->ieee80211h && + hapd->iconf->local_pwr_constraint == -1) + return eid; + + /* Check if DFS is required by regulatory. */ + dfs = hostapd_is_dfs_required(hapd->iface); + if (dfs < 0) { + wpa_printf(MSG_WARNING, "Failed to check if DFS is required; ret=%d", + dfs); + dfs = 0; + } + + if (dfs == 0 && hapd->iconf->local_pwr_constraint == -1) + return eid; + + /* + * ieee80211h (DFS) is enabled so Power Constraint element shall + * be added when running on DFS channel whenever local_pwr_constraint + * is configured or not. In order to meet regulations when TPC is not + * implemented using a transmit power that is below the legal maximum + * (including any mitigation factor) should help. In this case, + * indicate 3 dB below maximum allowed transmit power. + */ + if (hapd->iconf->local_pwr_constraint == -1) + local_pwr_constraint = 3; + + /* + * A STA that is not an AP shall use a transmit power less than or + * equal to the local maximum transmit power level for the channel. + * The local maximum transmit power can be calculated from the formula: + * local max TX pwr = max TX pwr - local pwr constraint + * Where max TX pwr is maximum transmit power level specified for + * channel in Country element and local pwr constraint is specified + * for channel in this Power Constraint element. + */ + + /* Element ID */ + *pos++ = WLAN_EID_PWR_CONSTRAINT; + /* Length */ + *pos++ = 1; + /* Local Power Constraint */ + if (local_pwr_constraint) + *pos++ = local_pwr_constraint; + else + *pos++ = hapd->iconf->local_pwr_constraint; + + return pos; +} + + +static u8 * hostapd_eid_country_add(struct hostapd_data *hapd, u8 *pos, + u8 *end, int chan_spacing, + struct hostapd_channel_data *start, + struct hostapd_channel_data *prev) +{ + if (end - pos < 3) + return pos; + + /* first channel number */ + *pos++ = start->chan; + /* number of channels */ + *pos++ = (prev->chan - start->chan) / chan_spacing + 1; + /* maximum transmit power level */ + if (!is_6ghz_op_class(hapd->iconf->op_class)) + *pos++ = start->max_tx_power; + else + *pos++ = 0; /* Reserved when operating on the 6 GHz band */ + + return pos; +} + + +static u8 * hostapd_fill_subband_triplets(struct hostapd_data *hapd, u8 *pos, + u8 *end) +{ + int i; + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *start, *prev; + int chan_spacing = 1; + + mode = hapd->iface->current_mode; + if (mode->mode == HOSTAPD_MODE_IEEE80211A) + chan_spacing = 4; + + start = prev = NULL; + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + if (chan->flag & HOSTAPD_CHAN_DISABLED) + continue; + if (start && prev && + prev->chan + chan_spacing == chan->chan && + start->max_tx_power == chan->max_tx_power) { + prev = chan; + continue; /* can use same entry */ + } + + if (start && prev) + pos = hostapd_eid_country_add(hapd, pos, end, + chan_spacing, + start, prev); + + /* Start new group */ + start = prev = chan; + } + + if (start) { + pos = hostapd_eid_country_add(hapd, pos, end, chan_spacing, + start, prev); + } + + return pos; +} + + +static u8 * hostapd_eid_country(struct hostapd_data *hapd, u8 *eid, + int max_len) +{ + u8 *pos = eid; + u8 *end = eid + max_len; + + if (!hapd->iconf->ieee80211d || max_len < 6 || + hapd->iface->current_mode == NULL) + return eid; + + *pos++ = WLAN_EID_COUNTRY; + pos++; /* length will be set later */ + os_memcpy(pos, hapd->iconf->country, 3); /* e.g., 'US ' */ + pos += 3; + + if (is_6ghz_op_class(hapd->iconf->op_class)) { + /* Force the third octet of the country string to indicate + * Global Operating Class (Table E-4) */ + eid[4] = 0x04; + + /* Operating Triplet field */ + /* Operating Extension Identifier (>= 201 to indicate this is + * not a Subband Triplet field) */ + *pos++ = 201; + /* Operating Class */ + *pos++ = hapd->iconf->op_class; + /* Coverage Class */ + *pos++ = 0; + /* Subband Triplets are required only for the 20 MHz case */ + if (hapd->iconf->op_class == 131 || + hapd->iconf->op_class == 136) + pos = hostapd_fill_subband_triplets(hapd, pos, end); + } else { + pos = hostapd_fill_subband_triplets(hapd, pos, end); + } + + if ((pos - eid) & 1) { + if (end - pos < 1) + return eid; + *pos++ = 0; /* pad for 16-bit alignment */ + } + + eid[1] = (pos - eid) - 2; + + return pos; +} + + +const u8 * hostapd_wpa_ie(struct hostapd_data *hapd, u8 eid) +{ + const u8 *ies; + size_t ies_len; + + ies = wpa_auth_get_wpa_ie(hapd->wpa_auth, &ies_len); + if (!ies) + return NULL; + + return get_ie(ies, ies_len, eid); +} + + +static const u8 * hostapd_vendor_wpa_ie(struct hostapd_data *hapd, + u32 vendor_type) +{ + const u8 *ies; + size_t ies_len; + + ies = wpa_auth_get_wpa_ie(hapd->wpa_auth, &ies_len); + if (!ies) + return NULL; + + return get_vendor_ie(ies, ies_len, vendor_type); +} + + +static u8 * hostapd_get_rsne(struct hostapd_data *hapd, u8 *pos, size_t len) +{ + const u8 *ie; + + ie = hostapd_wpa_ie(hapd, WLAN_EID_RSN); + if (!ie || 2U + ie[1] > len) + return pos; + + os_memcpy(pos, ie, 2 + ie[1]); + return pos + 2 + ie[1]; +} + + +static u8 * hostapd_get_mde(struct hostapd_data *hapd, u8 *pos, size_t len) +{ + const u8 *ie; + + ie = hostapd_wpa_ie(hapd, WLAN_EID_MOBILITY_DOMAIN); + if (!ie || 2U + ie[1] > len) + return pos; + + os_memcpy(pos, ie, 2 + ie[1]); + return pos + 2 + ie[1]; +} + + +static u8 * hostapd_get_rsnxe(struct hostapd_data *hapd, u8 *pos, size_t len) +{ + const u8 *ie; + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->no_beacon_rsnxe) { + wpa_printf(MSG_INFO, "TESTING: Do not add RSNXE into Beacon"); + return pos; + } +#endif /* CONFIG_TESTING_OPTIONS */ + ie = hostapd_wpa_ie(hapd, WLAN_EID_RSNX); + if (!ie || 2U + ie[1] > len) + return pos; + + os_memcpy(pos, ie, 2 + ie[1]); + return pos + 2 + ie[1]; +} + + +static u8 * hostapd_get_wpa_ie(struct hostapd_data *hapd, u8 *pos, size_t len) +{ + const u8 *ie; + + ie = hostapd_vendor_wpa_ie(hapd, WPA_IE_VENDOR_TYPE); + if (!ie || 2U + ie[1] > len) + return pos; + + os_memcpy(pos, ie, 2 + ie[1]); + return pos + 2 + ie[1]; +} + + +static u8 * hostapd_get_osen_ie(struct hostapd_data *hapd, u8 *pos, size_t len) +{ + const u8 *ie; + + ie = hostapd_vendor_wpa_ie(hapd, OSEN_IE_VENDOR_TYPE); + if (!ie || 2U + ie[1] > len) + return pos; + + os_memcpy(pos, ie, 2 + ie[1]); + return pos + 2 + ie[1]; +} + + +static u8 * hostapd_eid_csa(struct hostapd_data *hapd, u8 *eid) +{ +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->iface->cs_oper_class && hapd->iconf->ecsa_ie_only) + return eid; +#endif /* CONFIG_TESTING_OPTIONS */ + + if (!hapd->cs_freq_params.channel) + return eid; + + *eid++ = WLAN_EID_CHANNEL_SWITCH; + *eid++ = 3; + *eid++ = hapd->cs_block_tx; + *eid++ = hapd->cs_freq_params.channel; + *eid++ = hapd->cs_count; + + return eid; +} + + +static u8 * hostapd_eid_ecsa(struct hostapd_data *hapd, u8 *eid) +{ + if (!hapd->cs_freq_params.channel || !hapd->iface->cs_oper_class) + return eid; + + *eid++ = WLAN_EID_EXT_CHANSWITCH_ANN; + *eid++ = 4; + *eid++ = hapd->cs_block_tx; + *eid++ = hapd->iface->cs_oper_class; + *eid++ = hapd->cs_freq_params.channel; + *eid++ = hapd->cs_count; + + return eid; +} + + +static u8 * hostapd_eid_supported_op_classes(struct hostapd_data *hapd, u8 *eid) +{ + u8 op_class, channel; + + if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_CSA) || + !hapd->iface->freq) + return eid; + + if (ieee80211_freq_to_channel_ext(hapd->iface->freq, + hapd->iconf->secondary_channel, + hostapd_get_oper_chwidth(hapd->iconf), + &op_class, &channel) == + NUM_HOSTAPD_MODES) + return eid; + + *eid++ = WLAN_EID_SUPPORTED_OPERATING_CLASSES; + *eid++ = 2; + + /* Current Operating Class */ + *eid++ = op_class; + + /* TODO: Advertise all the supported operating classes */ + *eid++ = 0; + + return eid; +} + + +static int +ieee802_11_build_ap_params_mbssid(struct hostapd_data *hapd, + struct wpa_driver_ap_params *params) +{ + struct hostapd_iface *iface = hapd->iface; + struct hostapd_data *tx_bss; + size_t len, rnr_len = 0; + u8 elem_count = 0, *elem = NULL, **elem_offset = NULL, *end; + u8 rnr_elem_count = 0, *rnr_elem = NULL, **rnr_elem_offset = NULL; + size_t i; + + if (!iface->mbssid_max_interfaces || + iface->num_bss > iface->mbssid_max_interfaces || + (iface->conf->mbssid == ENHANCED_MBSSID_ENABLED && + !iface->ema_max_periodicity)) + goto fail; + + /* Make sure bss->xrates_supported is set for all BSSs to know whether + * it need to be non-inherited. */ + for (i = 0; i < iface->num_bss; i++) { + u8 buf[100]; + + hostapd_eid_ext_supp_rates(iface->bss[i], buf); + } + + tx_bss = hostapd_mbssid_get_tx_bss(hapd); + len = hostapd_eid_mbssid_len(tx_bss, WLAN_FC_STYPE_BEACON, &elem_count, + NULL, 0, &rnr_len); + if (!len || (iface->conf->mbssid == ENHANCED_MBSSID_ENABLED && + elem_count > iface->ema_max_periodicity)) + goto fail; + + elem = os_zalloc(len); + if (!elem) + goto fail; + + elem_offset = os_zalloc(elem_count * sizeof(u8 *)); + if (!elem_offset) + goto fail; + + if (rnr_len) { + rnr_elem = os_zalloc(rnr_len); + if (!rnr_elem) + goto fail; + + rnr_elem_offset = os_calloc(elem_count + 1, sizeof(u8 *)); + if (!rnr_elem_offset) + goto fail; + } + + end = hostapd_eid_mbssid(tx_bss, elem, elem + len, WLAN_FC_STYPE_BEACON, + elem_count, elem_offset, NULL, 0, rnr_elem, + &rnr_elem_count, rnr_elem_offset, rnr_len); + + params->mbssid_tx_iface = tx_bss->conf->iface; + params->mbssid_index = hostapd_mbssid_get_bss_index(hapd); + params->mbssid_elem = elem; + params->mbssid_elem_len = end - elem; + params->mbssid_elem_count = elem_count; + params->mbssid_elem_offset = elem_offset; + params->rnr_elem = rnr_elem; + params->rnr_elem_len = rnr_len; + params->rnr_elem_count = rnr_elem_count; + params->rnr_elem_offset = rnr_elem_offset; + if (iface->conf->mbssid == ENHANCED_MBSSID_ENABLED) + params->ema = true; + + return 0; + +fail: + os_free(rnr_elem); + os_free(rnr_elem_offset); + os_free(elem_offset); + os_free(elem); + wpa_printf(MSG_ERROR, "MBSSID: Configuration failed"); + return -1; +} + + +static u8 * hostapd_eid_mbssid_config(struct hostapd_data *hapd, u8 *eid, + u8 mbssid_elem_count) +{ + struct hostapd_iface *iface = hapd->iface; + + if (iface->conf->mbssid == ENHANCED_MBSSID_ENABLED) { + *eid++ = WLAN_EID_EXTENSION; + *eid++ = 3; + *eid++ = WLAN_EID_EXT_MULTIPLE_BSSID_CONFIGURATION; + *eid++ = iface->num_bss; + *eid++ = mbssid_elem_count; + } + + return eid; +} + + +static size_t he_elem_len(struct hostapd_data *hapd) +{ + size_t len = 0; + +#ifdef CONFIG_IEEE80211AX + if (!hapd->iconf->ieee80211ax || hapd->conf->disable_11ax) + return len; + + len += 3 + sizeof(struct ieee80211_he_capabilities) + + 3 + sizeof(struct ieee80211_he_operation) + + 3 + sizeof(struct ieee80211_he_mu_edca_parameter_set) + + 3 + sizeof(struct ieee80211_spatial_reuse); + if (is_6ghz_op_class(hapd->iconf->op_class)) { + len += sizeof(struct ieee80211_he_6ghz_oper_info) + + 3 + sizeof(struct ieee80211_he_6ghz_band_cap); + /* An additional Transmit Power Envelope element for + * subordinate client */ + if (he_reg_is_indoor(hapd->iconf->he_6ghz_reg_pwr_type)) + len += 4; + + /* An additional Transmit Power Envelope element for + * default client with unit interpretation of regulatory + * client EIRP */ + if (hapd->iconf->reg_def_cli_eirp != -1 && + he_reg_is_sp(hapd->iconf->he_6ghz_reg_pwr_type)) + len += 4; + } +#endif /* CONFIG_IEEE80211AX */ + + return len; +} + + +struct probe_resp_params { + const struct ieee80211_mgmt *req; + bool is_p2p; + + /* Generated IEs will be included inside an ML element */ + bool is_ml_sta_info; + struct hostapd_data *mld_ap; + struct mld_info *mld_info; + + struct ieee80211_mgmt *resp; + size_t resp_len; + u8 *csa_pos; + u8 *ecsa_pos; + const u8 *known_bss; + u8 known_bss_len; + +#ifdef CONFIG_IEEE80211AX + u8 *cca_pos; +#endif /* CONFIG_IEEE80211AX */ +}; + + +static void hostapd_free_probe_resp_params(struct probe_resp_params *params) +{ +#ifdef CONFIG_IEEE80211BE + if (!params) + return; + ap_sta_free_sta_profile(params->mld_info); + os_free(params->mld_info); + params->mld_info = NULL; +#endif /* CONFIG_IEEE80211BE */ +} + + +static size_t hostapd_probe_resp_elems_len(struct hostapd_data *hapd, + struct probe_resp_params *params) +{ + size_t buflen = 0; + +#ifdef CONFIG_WPS + if (hapd->wps_probe_resp_ie) + buflen += wpabuf_len(hapd->wps_probe_resp_ie); +#endif /* CONFIG_WPS */ +#ifdef CONFIG_P2P + if (hapd->p2p_probe_resp_ie) + buflen += wpabuf_len(hapd->p2p_probe_resp_ie); +#endif /* CONFIG_P2P */ +#ifdef CONFIG_FST + if (hapd->iface->fst_ies) + buflen += wpabuf_len(hapd->iface->fst_ies); +#endif /* CONFIG_FST */ + if (hapd->conf->vendor_elements) + buflen += wpabuf_len(hapd->conf->vendor_elements); +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->presp_elements) + buflen += wpabuf_len(hapd->conf->presp_elements); +#endif /* CONFIG_TESTING_OPTIONS */ + if (hapd->conf->vendor_vht) { + buflen += 5 + 2 + sizeof(struct ieee80211_vht_capabilities) + + 2 + sizeof(struct ieee80211_vht_operation); + } + + buflen += he_elem_len(hapd); + +#ifdef CONFIG_IEEE80211BE + if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { + buflen += hostapd_eid_eht_capab_len(hapd, IEEE80211_MODE_AP); + buflen += 3 + sizeof(struct ieee80211_eht_operation); + if (hapd->iconf->punct_bitmap) + buflen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE; + + if (!params->is_ml_sta_info && hapd->conf->mld_ap) { + struct hostapd_data *ml_elem_ap = + params->mld_ap ? params->mld_ap : hapd; + + buflen += hostapd_eid_eht_ml_beacon_len( + ml_elem_ap, params->mld_info, !!params->mld_ap); + } + } +#endif /* CONFIG_IEEE80211BE */ + + buflen += hostapd_eid_mbssid_len(hapd, WLAN_FC_STYPE_PROBE_RESP, NULL, + params->known_bss, + params->known_bss_len, NULL); + if (!params->is_ml_sta_info) + buflen += hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_PROBE_RESP, + true); + buflen += hostapd_mbo_ie_len(hapd); + buflen += hostapd_eid_owe_trans_len(hapd); + buflen += hostapd_eid_dpp_cc_len(hapd); + + return buflen; +} + + +static u8 * hostapd_probe_resp_fill_elems(struct hostapd_data *hapd, + struct probe_resp_params *params, + u8 *pos, size_t len) +{ + u8 *csa_pos; + u8 *epos; + + epos = pos + len; + + if (!params->is_ml_sta_info) { + *pos++ = WLAN_EID_SSID; + *pos++ = hapd->conf->ssid.ssid_len; + os_memcpy(pos, hapd->conf->ssid.ssid, + hapd->conf->ssid.ssid_len); + pos += hapd->conf->ssid.ssid_len; + } + + /* Supported rates */ + pos = hostapd_eid_supp_rates(hapd, pos); + + /* DS Params */ + pos = hostapd_eid_ds_params(hapd, pos); + + pos = hostapd_eid_country(hapd, pos, epos - pos); + + /* Power Constraint element */ + pos = hostapd_eid_pwr_constraint(hapd, pos); + + /* + * CSA IE + * TODO: This should be included inside the ML sta profile + */ + if (!params->is_ml_sta_info) { + csa_pos = hostapd_eid_csa(hapd, pos); + if (csa_pos != pos) + params->csa_pos = csa_pos - 1; + else + params->csa_pos = NULL; + pos = csa_pos; + } + + /* ERP Information element */ + pos = hostapd_eid_erp_info(hapd, pos); + + /* Extended supported rates */ + pos = hostapd_eid_ext_supp_rates(hapd, pos); + + pos = hostapd_get_rsne(hapd, pos, epos - pos); + pos = hostapd_eid_bss_load(hapd, pos, epos - pos); + pos = hostapd_eid_mbssid(hapd, pos, epos, WLAN_FC_STYPE_PROBE_RESP, 0, + NULL, params->known_bss, params->known_bss_len, + NULL, NULL, NULL, 0); + pos = hostapd_eid_rm_enabled_capab(hapd, pos, epos - pos); + pos = hostapd_get_mde(hapd, pos, epos - pos); + + /* + * eCSA IE + * TODO: This should be included inside the ML sta profile + */ + if (!params->is_ml_sta_info) { + csa_pos = hostapd_eid_ecsa(hapd, pos); + if (csa_pos != pos) + params->ecsa_pos = csa_pos - 1; + else + params->ecsa_pos = NULL; + pos = csa_pos; + } + + pos = hostapd_eid_supported_op_classes(hapd, pos); + pos = hostapd_eid_ht_capabilities(hapd, pos); + pos = hostapd_eid_ht_operation(hapd, pos); + + /* Probe Response frames always include all non-TX profiles except + * when a list of known BSSes is included in the Probe Request frame. */ + pos = hostapd_eid_ext_capab(hapd, pos, + hapd->iconf->mbssid >= MBSSID_ENABLED && + !params->known_bss_len); + + pos = hostapd_eid_time_adv(hapd, pos); + pos = hostapd_eid_time_zone(hapd, pos); + + pos = hostapd_eid_interworking(hapd, pos); + pos = hostapd_eid_adv_proto(hapd, pos); + pos = hostapd_eid_roaming_consortium(hapd, pos); + +#ifdef CONFIG_FST + if (hapd->iface->fst_ies) { + os_memcpy(pos, wpabuf_head(hapd->iface->fst_ies), + wpabuf_len(hapd->iface->fst_ies)); + pos += wpabuf_len(hapd->iface->fst_ies); + } +#endif /* CONFIG_FST */ + +#ifdef CONFIG_IEEE80211AC + if (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac && + !is_6ghz_op_class(hapd->iconf->op_class)) { + pos = hostapd_eid_vht_capabilities(hapd, pos, 0); + pos = hostapd_eid_vht_operation(hapd, pos); + pos = hostapd_eid_txpower_envelope(hapd, pos); + } +#endif /* CONFIG_IEEE80211AC */ + +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax && + is_6ghz_op_class(hapd->iconf->op_class)) + pos = hostapd_eid_txpower_envelope(hapd, pos); +#endif /* CONFIG_IEEE80211AX */ + + pos = hostapd_eid_wb_chsw_wrapper(hapd, pos); + + if (!params->is_ml_sta_info) + pos = hostapd_eid_rnr(hapd, pos, WLAN_FC_STYPE_PROBE_RESP, + true); + pos = hostapd_eid_fils_indic(hapd, pos, 0); + pos = hostapd_get_rsnxe(hapd, pos, epos - pos); + +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) { + u8 *cca_pos; + + pos = hostapd_eid_he_capab(hapd, pos, IEEE80211_MODE_AP); + pos = hostapd_eid_he_operation(hapd, pos); + + /* BSS Color Change Announcement element */ + cca_pos = hostapd_eid_cca(hapd, pos); + if (cca_pos != pos) + params->cca_pos = cca_pos - 2; + else + params->cca_pos = NULL; + pos = cca_pos; + + pos = hostapd_eid_spatial_reuse(hapd, pos); + pos = hostapd_eid_he_mu_edca_parameter_set(hapd, pos); + pos = hostapd_eid_he_6ghz_band_cap(hapd, pos); + } +#endif /* CONFIG_IEEE80211AX */ + +#ifdef CONFIG_IEEE80211BE + if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { + struct hostapd_data *ml_elem_ap = + params->mld_ap ? params->mld_ap : hapd; + + if (ml_elem_ap->conf->mld_ap) + pos = hostapd_eid_eht_ml_beacon( + ml_elem_ap, params->mld_info, + pos, !!params->mld_ap); + + pos = hostapd_eid_eht_capab(hapd, pos, IEEE80211_MODE_AP); + pos = hostapd_eid_eht_operation(hapd, pos); + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_IEEE80211AC + if (hapd->conf->vendor_vht) + pos = hostapd_eid_vendor_vht(hapd, pos); +#endif /* CONFIG_IEEE80211AC */ + + /* WPA / OSEN */ + pos = hostapd_get_wpa_ie(hapd, pos, epos - pos); + pos = hostapd_get_osen_ie(hapd, pos, epos - pos); + + /* Wi-Fi Alliance WMM */ + pos = hostapd_eid_wmm(hapd, pos); + +#ifdef CONFIG_WPS + if (hapd->conf->wps_state && hapd->wps_probe_resp_ie) { + os_memcpy(pos, wpabuf_head(hapd->wps_probe_resp_ie), + wpabuf_len(hapd->wps_probe_resp_ie)); + pos += wpabuf_len(hapd->wps_probe_resp_ie); + } +#endif /* CONFIG_WPS */ + +#ifdef CONFIG_P2P + if ((hapd->conf->p2p & P2P_ENABLED) && params->is_p2p && + hapd->p2p_probe_resp_ie) { + os_memcpy(pos, wpabuf_head(hapd->p2p_probe_resp_ie), + wpabuf_len(hapd->p2p_probe_resp_ie)); + pos += wpabuf_len(hapd->p2p_probe_resp_ie); + } +#endif /* CONFIG_P2P */ +#ifdef CONFIG_P2P_MANAGER + if ((hapd->conf->p2p & (P2P_MANAGE | P2P_ENABLED | P2P_GROUP_OWNER)) == + P2P_MANAGE) + pos = hostapd_eid_p2p_manage(hapd, pos); +#endif /* CONFIG_P2P_MANAGER */ + +#ifdef CONFIG_HS20 + pos = hostapd_eid_hs20_indication(hapd, pos); +#endif /* CONFIG_HS20 */ + + pos = hostapd_eid_mbo(hapd, pos, epos - pos); + pos = hostapd_eid_owe_trans(hapd, pos, epos - pos); + pos = hostapd_eid_dpp_cc(hapd, pos, epos - pos); + + if (hapd->conf->vendor_elements) { + os_memcpy(pos, wpabuf_head(hapd->conf->vendor_elements), + wpabuf_len(hapd->conf->vendor_elements)); + pos += wpabuf_len(hapd->conf->vendor_elements); + } + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->presp_elements) { + os_memcpy(pos, wpabuf_head(hapd->conf->presp_elements), + wpabuf_len(hapd->conf->presp_elements)); + pos += wpabuf_len(hapd->conf->presp_elements); + } +#endif /* CONFIG_TESTING_OPTIONS */ + + return pos; +} + + +static void hostapd_gen_probe_resp(struct hostapd_data *hapd, + struct probe_resp_params *params) +{ + u8 *pos; + size_t buflen; + + hapd = hostapd_mbssid_get_tx_bss(hapd); + +#define MAX_PROBERESP_LEN 768 + buflen = MAX_PROBERESP_LEN; + buflen += hostapd_probe_resp_elems_len(hapd, params); + params->resp = os_zalloc(buflen); + if (!params->resp) { + params->resp_len = 0; + return; + } + + params->resp->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_PROBE_RESP); + /* Unicast the response to all requests on bands other than 6 GHz. For + * the 6 GHz, unicast is used only if the actual SSID is not included in + * the Beacon frames. Otherwise, broadcast response is used per IEEE + * Std 802.11ax-2021, 26.17.2.3.2. Broadcast address is also used for + * the Probe Response frame template for the unsolicited (i.e., not as + * a response to a specific request) case. */ + if (params->req && (!is_6ghz_op_class(hapd->iconf->op_class) || + hapd->conf->ignore_broadcast_ssid)) + os_memcpy(params->resp->da, params->req->sa, ETH_ALEN); + else + os_memset(params->resp->da, 0xff, ETH_ALEN); + os_memcpy(params->resp->sa, hapd->own_addr, ETH_ALEN); + + os_memcpy(params->resp->bssid, hapd->own_addr, ETH_ALEN); + params->resp->u.probe_resp.beacon_int = + host_to_le16(hapd->iconf->beacon_int); + + /* hardware or low-level driver will setup seq_ctrl and timestamp */ + params->resp->u.probe_resp.capab_info = + host_to_le16(hostapd_own_capab_info(hapd)); + + pos = hostapd_probe_resp_fill_elems(hapd, params, + params->resp->u.probe_resp.variable, + buflen); + + params->resp_len = pos - (u8 *) params->resp; +} + + +#ifdef CONFIG_IEEE80211BE +static void hostapd_fill_probe_resp_ml_params(struct hostapd_data *hapd, + struct probe_resp_params *params, + const struct ieee80211_mgmt *mgmt, + int mld_id, u16 links) +{ + struct probe_resp_params sta_info_params; + struct hostapd_data *link; + + params->mld_ap = NULL; + params->mld_info = os_zalloc(sizeof(*params->mld_info)); + if (!params->mld_info) + return; + + wpa_printf(MSG_DEBUG, + "MLD: Got ML probe request with AP MLD ID %d for links %04x", + mld_id, links); + + for_each_mld_link(link, hapd) { + struct mld_link_info *link_info; + size_t buflen; + u8 mld_link_id = link->mld_link_id; + u8 *epos; + u8 buf[EHT_ML_MAX_STA_PROF_LEN]; + + /* + * Set mld_ap iff the ML probe request explicitly + * requested a specific MLD ID. In that case, the targeted + * AP may have been a nontransmitted BSSID on the same + * interface. + */ + if (mld_id != -1 && link->iface == hapd->iface) + params->mld_ap = link; + + /* Never duplicate main Probe Response frame body */ + if (link == hapd) + continue; + + /* Only include requested links */ + if (!(BIT(mld_link_id) & links)) + continue; + + link_info = ¶ms->mld_info->links[mld_link_id]; + + sta_info_params.req = params->req; + sta_info_params.is_p2p = false; + sta_info_params.is_ml_sta_info = true; + sta_info_params.mld_ap = NULL; + sta_info_params.mld_info = NULL; + + buflen = MAX_PROBERESP_LEN; + buflen += hostapd_probe_resp_elems_len(link, &sta_info_params); + + if (buflen > EHT_ML_MAX_STA_PROF_LEN) { + wpa_printf(MSG_DEBUG, + "MLD: Not including link %d in ML probe response (%zu bytes is too long)", + mld_link_id, buflen); + goto fail; + } + + /* + * NOTE: This does not properly handle inheritance and + * various other things. + */ + link_info->valid = true; + epos = buf; + + /* Capabilities is the only fixed parameter */ + WPA_PUT_LE16(epos, hostapd_own_capab_info(hapd)); + epos += 2; + + epos = hostapd_probe_resp_fill_elems( + link, &sta_info_params, epos, + EHT_ML_MAX_STA_PROF_LEN - 2); + link_info->resp_sta_profile_len = epos - buf; + os_free(link_info->resp_sta_profile); + link_info->resp_sta_profile = os_memdup( + buf, link_info->resp_sta_profile_len); + if (!link_info->resp_sta_profile) + link_info->resp_sta_profile_len = 0; + os_memcpy(link_info->local_addr, link->own_addr, ETH_ALEN); + + wpa_printf(MSG_DEBUG, + "MLD: ML probe response includes link sta info for %d: %u bytes (estimate %zu)", + mld_link_id, link_info->resp_sta_profile_len, + buflen); + } + + if (mld_id != -1 && !params->mld_ap) { + wpa_printf(MSG_DEBUG, + "MLD: No nontransmitted BSSID for MLD ID %d", + mld_id); + goto fail; + } + + return; + +fail: + hostapd_free_probe_resp_params(params); + params->mld_ap = NULL; + params->mld_info = NULL; +} +#endif /* CONFIG_IEEE80211BE */ + + +enum ssid_match_result { + NO_SSID_MATCH, + EXACT_SSID_MATCH, + WILDCARD_SSID_MATCH, + CO_LOCATED_SSID_MATCH, +}; + +static enum ssid_match_result ssid_match(struct hostapd_data *hapd, + const u8 *ssid, size_t ssid_len, + const u8 *ssid_list, + size_t ssid_list_len, + const u8 *short_ssid_list, + size_t short_ssid_list_len) +{ + const u8 *pos, *end; + struct hostapd_iface *iface = hapd->iface; + int wildcard = 0; + size_t i, j; + + if (ssid_len == 0) + wildcard = 1; + if (ssid_len == hapd->conf->ssid.ssid_len && + os_memcmp(ssid, hapd->conf->ssid.ssid, ssid_len) == 0) + return EXACT_SSID_MATCH; + + if (ssid_list) { + pos = ssid_list; + end = ssid_list + ssid_list_len; + while (end - pos >= 2) { + if (2 + pos[1] > end - pos) + break; + if (pos[1] == 0) + wildcard = 1; + if (pos[1] == hapd->conf->ssid.ssid_len && + os_memcmp(pos + 2, hapd->conf->ssid.ssid, + pos[1]) == 0) + return EXACT_SSID_MATCH; + pos += 2 + pos[1]; + } + } + + if (short_ssid_list) { + pos = short_ssid_list; + end = short_ssid_list + short_ssid_list_len; + while (end - pos >= 4) { + if (hapd->conf->ssid.short_ssid == WPA_GET_LE32(pos)) + return EXACT_SSID_MATCH; + pos += 4; + } + } + + if (wildcard) + return WILDCARD_SSID_MATCH; + + if (!iface->interfaces || iface->interfaces->count <= 1 || + is_6ghz_op_class(hapd->iconf->op_class)) + return NO_SSID_MATCH; + + for (i = 0; i < iface->interfaces->count; i++) { + struct hostapd_iface *colocated; + + colocated = iface->interfaces->iface[i]; + + if (colocated == iface || + !is_6ghz_op_class(colocated->conf->op_class)) + continue; + + for (j = 0; j < colocated->num_bss; j++) { + struct hostapd_bss_config *conf; + + conf = colocated->bss[j]->conf; + if (ssid_len == conf->ssid.ssid_len && + os_memcmp(ssid, conf->ssid.ssid, ssid_len) == 0) + return CO_LOCATED_SSID_MATCH; + } + } + + return NO_SSID_MATCH; +} + + +void sta_track_expire(struct hostapd_iface *iface, int force) +{ + struct os_reltime now; + struct hostapd_sta_info *info; + + if (!iface->num_sta_seen) + return; + + os_get_reltime(&now); + while ((info = dl_list_first(&iface->sta_seen, struct hostapd_sta_info, + list))) { + if (!force && + !os_reltime_expired(&now, &info->last_seen, + iface->conf->track_sta_max_age)) + break; + force = 0; + + wpa_printf(MSG_MSGDUMP, "%s: Expire STA tracking entry for " + MACSTR, iface->bss[0]->conf->iface, + MAC2STR(info->addr)); + dl_list_del(&info->list); + iface->num_sta_seen--; + sta_track_del(info); + } +} + + +static struct hostapd_sta_info * sta_track_get(struct hostapd_iface *iface, + const u8 *addr) +{ + struct hostapd_sta_info *info; + + dl_list_for_each(info, &iface->sta_seen, struct hostapd_sta_info, list) + if (ether_addr_equal(addr, info->addr)) + return info; + + return NULL; +} + + +void sta_track_add(struct hostapd_iface *iface, const u8 *addr, int ssi_signal) +{ + struct hostapd_sta_info *info; + + info = sta_track_get(iface, addr); + if (info) { + /* Move the most recent entry to the end of the list */ + dl_list_del(&info->list); + dl_list_add_tail(&iface->sta_seen, &info->list); + os_get_reltime(&info->last_seen); + info->ssi_signal = ssi_signal; + return; + } + + /* Add a new entry */ + info = os_zalloc(sizeof(*info)); + if (info == NULL) + return; + os_memcpy(info->addr, addr, ETH_ALEN); + os_get_reltime(&info->last_seen); + info->ssi_signal = ssi_signal; + + if (iface->num_sta_seen >= iface->conf->track_sta_max_num) { + /* Expire oldest entry to make room for a new one */ + sta_track_expire(iface, 1); + } + + wpa_printf(MSG_MSGDUMP, "%s: Add STA tracking entry for " + MACSTR, iface->bss[0]->conf->iface, MAC2STR(addr)); + dl_list_add_tail(&iface->sta_seen, &info->list); + iface->num_sta_seen++; +} + + +struct hostapd_data * +sta_track_seen_on(struct hostapd_iface *iface, const u8 *addr, + const char *ifname) +{ + struct hapd_interfaces *interfaces = iface->interfaces; + size_t i, j; + + for (i = 0; i < interfaces->count; i++) { + struct hostapd_data *hapd = NULL; + + iface = interfaces->iface[i]; + for (j = 0; j < iface->num_bss; j++) { + hapd = iface->bss[j]; + if (os_strcmp(ifname, hapd->conf->iface) == 0) + break; + hapd = NULL; + } + + if (hapd && sta_track_get(iface, addr)) + return hapd; + } + + return NULL; +} + + +#ifdef CONFIG_TAXONOMY +void sta_track_claim_taxonomy_info(struct hostapd_iface *iface, const u8 *addr, + struct wpabuf **probe_ie_taxonomy) +{ + struct hostapd_sta_info *info; + + info = sta_track_get(iface, addr); + if (!info) + return; + + wpabuf_free(*probe_ie_taxonomy); + *probe_ie_taxonomy = info->probe_ie_taxonomy; + info->probe_ie_taxonomy = NULL; +} +#endif /* CONFIG_TAXONOMY */ + + +#ifdef CONFIG_IEEE80211BE +static bool parse_ml_probe_req(const struct ieee80211_eht_ml *ml, size_t ml_len, + int *mld_id, u16 *links) +{ + u16 ml_control; + const struct element *sub; + const u8 *pos; + size_t len; + + *mld_id = -1; + *links = 0xffff; + + if (ml_len < sizeof(struct ieee80211_eht_ml)) + return false; + + ml_control = le_to_host16(ml->ml_control); + if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) != + MULTI_LINK_CONTROL_TYPE_PROBE_REQ) { + wpa_printf(MSG_DEBUG, "MLD: Not an ML probe req"); + return false; + } + + if (sizeof(struct ieee80211_eht_ml) + 1 > ml_len) { + wpa_printf(MSG_DEBUG, "MLD: ML probe req too short"); + return false; + } + + pos = ml->variable; + len = pos[0]; + if (len < 1 || sizeof(struct ieee80211_eht_ml) + len > ml_len) { + wpa_printf(MSG_DEBUG, + "MLD: ML probe request with invalid length"); + return false; + } + + if (ml_control & EHT_ML_PRES_BM_PROBE_REQ_AP_MLD_ID) { + if (len < 2) { + wpa_printf(MSG_DEBUG, + "MLD: ML probe req too short for MLD ID"); + return false; + } + + *mld_id = pos[1]; + } + pos += len; + + /* Parse subelements (if there are any) */ + len = ml_len - len - sizeof(struct ieee80211_eht_ml); + for_each_element_id(sub, 0, pos, len) { + const struct ieee80211_eht_per_sta_profile *sta; + u16 sta_control; + + if (*links == 0xffff) + *links = 0; + + if (sub->datalen < + sizeof(struct ieee80211_eht_per_sta_profile)) { + wpa_printf(MSG_DEBUG, + "MLD: ML probe req %d too short for sta profile", + sub->datalen); + return false; + } + + sta = (struct ieee80211_eht_per_sta_profile *) sub->data; + + /* + * Extract the link ID, do not return whether a complete or + * partial profile was requested. + */ + sta_control = le_to_host16(sta->sta_control); + *links |= BIT(sta_control & EHT_PER_STA_CTRL_LINK_ID_MSK); + } + + if (!for_each_element_completed(sub, pos, len)) { + wpa_printf(MSG_DEBUG, + "MLD: ML probe req sub-elements parsing error"); + return false; + } + + return true; +} +#endif /* CONFIG_IEEE80211BE */ + + +void handle_probe_req(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len, + int ssi_signal) +{ + struct ieee802_11_elems elems; + const u8 *ie; + size_t ie_len; + size_t i; + int noack; + enum ssid_match_result res; + int ret; + u16 csa_offs[2]; + size_t csa_offs_len; + struct radius_sta rad_info; + struct probe_resp_params params; +#ifdef CONFIG_IEEE80211BE + int mld_id; + u16 links; +#endif /* CONFIG_IEEE80211BE */ + + if (hapd->iconf->rssi_ignore_probe_request && ssi_signal && + ssi_signal < hapd->iconf->rssi_ignore_probe_request) + return; + + if (len < IEEE80211_HDRLEN) + return; + ie = ((const u8 *) mgmt) + IEEE80211_HDRLEN; + if (hapd->iconf->track_sta_max_num) + sta_track_add(hapd->iface, mgmt->sa, ssi_signal); + ie_len = len - IEEE80211_HDRLEN; + + ret = hostapd_allowed_address(hapd, mgmt->sa, (const u8 *) mgmt, len, + &rad_info, 1); + if (ret == HOSTAPD_ACL_REJECT) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "Ignore Probe Request frame from " MACSTR + " due to ACL reject ", MAC2STR(mgmt->sa)); + return; + } + + for (i = 0; hapd->probereq_cb && i < hapd->num_probereq_cb; i++) + if (hapd->probereq_cb[i].cb(hapd->probereq_cb[i].ctx, + mgmt->sa, mgmt->da, mgmt->bssid, + ie, ie_len, ssi_signal) > 0) + return; + + if (!hapd->conf->send_probe_response) + return; + + if (ieee802_11_parse_elems(ie, ie_len, &elems, 0) == ParseFailed) { + wpa_printf(MSG_DEBUG, "Could not parse ProbeReq from " MACSTR, + MAC2STR(mgmt->sa)); + return; + } + + if ((!elems.ssid || !elems.supp_rates)) { + wpa_printf(MSG_DEBUG, "STA " MACSTR " sent probe request " + "without SSID or supported rates element", + MAC2STR(mgmt->sa)); + return; + } + + /* + * No need to reply if the Probe Request frame was sent on an adjacent + * channel. IEEE Std 802.11-2012 describes this as a requirement for an + * AP with dot11RadioMeasurementActivated set to true, but strictly + * speaking does not allow such ignoring of Probe Request frames if + * dot11RadioMeasurementActivated is false. Anyway, this can help reduce + * number of unnecessary Probe Response frames for cases where the STA + * is less likely to see them (Probe Request frame sent on a + * neighboring, but partially overlapping, channel). + */ + if (elems.ds_params && + hapd->iface->current_mode && + (hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G || + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211B) && + hapd->iconf->channel != elems.ds_params[0]) { + wpa_printf(MSG_DEBUG, + "Ignore Probe Request due to DS Params mismatch: chan=%u != ds.chan=%u", + hapd->iconf->channel, elems.ds_params[0]); + return; + } + +#ifdef CONFIG_P2P + if (hapd->p2p && hapd->p2p_group && elems.wps_ie) { + struct wpabuf *wps; + wps = ieee802_11_vendor_ie_concat(ie, ie_len, WPS_DEV_OUI_WFA); + if (wps && !p2p_group_match_dev_type(hapd->p2p_group, wps)) { + wpa_printf(MSG_MSGDUMP, "P2P: Ignore Probe Request " + "due to mismatch with Requested Device " + "Type"); + wpabuf_free(wps); + return; + } + wpabuf_free(wps); + } + + if (hapd->p2p && hapd->p2p_group && elems.p2p) { + struct wpabuf *p2p; + p2p = ieee802_11_vendor_ie_concat(ie, ie_len, P2P_IE_VENDOR_TYPE); + if (p2p && !p2p_group_match_dev_id(hapd->p2p_group, p2p)) { + wpa_printf(MSG_MSGDUMP, "P2P: Ignore Probe Request " + "due to mismatch with Device ID"); + wpabuf_free(p2p); + return; + } + wpabuf_free(p2p); + } +#endif /* CONFIG_P2P */ + + if (hapd->conf->ignore_broadcast_ssid && elems.ssid_len == 0 && + elems.ssid_list_len == 0 && elems.short_ssid_list_len == 0) { + wpa_printf(MSG_MSGDUMP, "Probe Request from " MACSTR " for " + "broadcast SSID ignored", MAC2STR(mgmt->sa)); + return; + } + +#ifdef CONFIG_P2P + if ((hapd->conf->p2p & P2P_GROUP_OWNER) && + elems.ssid_len == P2P_WILDCARD_SSID_LEN && + os_memcmp(elems.ssid, P2P_WILDCARD_SSID, + P2P_WILDCARD_SSID_LEN) == 0) { + /* Process P2P Wildcard SSID like Wildcard SSID */ + elems.ssid_len = 0; + } +#endif /* CONFIG_P2P */ + +#ifdef CONFIG_TAXONOMY + { + struct sta_info *sta; + struct hostapd_sta_info *info; + + if ((sta = ap_get_sta(hapd, mgmt->sa)) != NULL) { + taxonomy_sta_info_probe_req(hapd, sta, ie, ie_len); + } else if ((info = sta_track_get(hapd->iface, + mgmt->sa)) != NULL) { + taxonomy_hostapd_sta_info_probe_req(hapd, info, + ie, ie_len); + } + } +#endif /* CONFIG_TAXONOMY */ + + res = ssid_match(hapd, elems.ssid, elems.ssid_len, + elems.ssid_list, elems.ssid_list_len, + elems.short_ssid_list, elems.short_ssid_list_len); + if (res == NO_SSID_MATCH) { + if (!(mgmt->da[0] & 0x01)) { + wpa_printf(MSG_MSGDUMP, "Probe Request from " MACSTR + " for foreign SSID '%s' (DA " MACSTR ")%s", + MAC2STR(mgmt->sa), + wpa_ssid_txt(elems.ssid, elems.ssid_len), + MAC2STR(mgmt->da), + elems.ssid_list ? " (SSID list)" : ""); + } + return; + } + + if (hapd->conf->ignore_broadcast_ssid && res == WILDCARD_SSID_MATCH) { + wpa_printf(MSG_MSGDUMP, "Probe Request from " MACSTR " for " + "broadcast SSID ignored", MAC2STR(mgmt->sa)); + return; + } + +#ifdef CONFIG_INTERWORKING + if (hapd->conf->interworking && + elems.interworking && elems.interworking_len >= 1) { + u8 ant = elems.interworking[0] & 0x0f; + if (ant != INTERWORKING_ANT_WILDCARD && + ant != hapd->conf->access_network_type) { + wpa_printf(MSG_MSGDUMP, "Probe Request from " MACSTR + " for mismatching ANT %u ignored", + MAC2STR(mgmt->sa), ant); + return; + } + } + + if (hapd->conf->interworking && elems.interworking && + (elems.interworking_len == 7 || elems.interworking_len == 9)) { + const u8 *hessid; + if (elems.interworking_len == 7) + hessid = elems.interworking + 1; + else + hessid = elems.interworking + 1 + 2; + if (!is_broadcast_ether_addr(hessid) && + !ether_addr_equal(hessid, hapd->conf->hessid)) { + wpa_printf(MSG_MSGDUMP, "Probe Request from " MACSTR + " for mismatching HESSID " MACSTR + " ignored", + MAC2STR(mgmt->sa), MAC2STR(hessid)); + return; + } + } +#endif /* CONFIG_INTERWORKING */ + +#ifdef CONFIG_P2P + if ((hapd->conf->p2p & P2P_GROUP_OWNER) && + supp_rates_11b_only(&elems)) { + /* Indicates support for 11b rates only */ + wpa_printf(MSG_EXCESSIVE, "P2P: Ignore Probe Request from " + MACSTR " with only 802.11b rates", + MAC2STR(mgmt->sa)); + return; + } +#endif /* CONFIG_P2P */ + + /* TODO: verify that supp_rates contains at least one matching rate + * with AP configuration */ + + if (hapd->conf->no_probe_resp_if_seen_on && + is_multicast_ether_addr(mgmt->da) && + is_multicast_ether_addr(mgmt->bssid) && + sta_track_seen_on(hapd->iface, mgmt->sa, + hapd->conf->no_probe_resp_if_seen_on)) { + wpa_printf(MSG_MSGDUMP, "%s: Ignore Probe Request from " MACSTR + " since STA has been seen on %s", + hapd->conf->iface, MAC2STR(mgmt->sa), + hapd->conf->no_probe_resp_if_seen_on); + return; + } + + if (hapd->conf->no_probe_resp_if_max_sta && + is_multicast_ether_addr(mgmt->da) && + is_multicast_ether_addr(mgmt->bssid) && + hapd->num_sta >= hapd->conf->max_num_sta && + !ap_get_sta(hapd, mgmt->sa)) { + wpa_printf(MSG_MSGDUMP, "%s: Ignore Probe Request from " MACSTR + " since no room for additional STA", + hapd->conf->iface, MAC2STR(mgmt->sa)); + return; + } + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->iconf->ignore_probe_probability > 0.0 && + drand48() < hapd->iconf->ignore_probe_probability) { + wpa_printf(MSG_INFO, + "TESTING: ignoring probe request from " MACSTR, + MAC2STR(mgmt->sa)); + return; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + /* Do not send Probe Response frame from a non-transmitting multiple + * BSSID profile unless the Probe Request frame is directed at that + * particular BSS. */ + if (hapd != hostapd_mbssid_get_tx_bss(hapd) && res != EXACT_SSID_MATCH) + return; + + wpa_msg_ctrl(hapd->msg_ctx, MSG_INFO, RX_PROBE_REQUEST "sa=" MACSTR + " signal=%d", MAC2STR(mgmt->sa), ssi_signal); + + os_memset(¶ms, 0, sizeof(params)); + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap && elems.probe_req_mle && + parse_ml_probe_req((struct ieee80211_eht_ml *) elems.probe_req_mle, + elems.probe_req_mle_len, &mld_id, &links)) { + hostapd_fill_probe_resp_ml_params(hapd, ¶ms, mgmt, + mld_id, links); + } +#endif /* CONFIG_IEEE80211BE */ + + params.req = mgmt; + params.is_p2p = !!elems.p2p; + params.known_bss = elems.mbssid_known_bss; + params.known_bss_len = elems.mbssid_known_bss_len; + params.is_ml_sta_info = false; + + hostapd_gen_probe_resp(hapd, ¶ms); + + hostapd_free_probe_resp_params(¶ms); + + if (!params.resp) + return; + + /* + * If this is a broadcast probe request, apply no ack policy to avoid + * excessive retries. + */ + noack = !!(res == WILDCARD_SSID_MATCH && + is_broadcast_ether_addr(mgmt->da)); + + csa_offs_len = 0; + if (hapd->csa_in_progress) { + if (params.csa_pos) + csa_offs[csa_offs_len++] = + params.csa_pos - (u8 *) params.resp; + + if (params.ecsa_pos) + csa_offs[csa_offs_len++] = + params.ecsa_pos - (u8 *) params.resp; + } + + ret = hostapd_drv_send_mlme(hapd, params.resp, params.resp_len, noack, + csa_offs_len ? csa_offs : NULL, + csa_offs_len, 0); + + if (ret < 0) + wpa_printf(MSG_INFO, "handle_probe_req: send failed"); + + os_free(params.resp); + + wpa_printf(MSG_EXCESSIVE, "STA " MACSTR " sent probe request for %s " + "SSID", MAC2STR(mgmt->sa), + elems.ssid_len == 0 ? "broadcast" : "our"); +} + + +static u8 * hostapd_probe_resp_offloads(struct hostapd_data *hapd, + size_t *resp_len) +{ + struct probe_resp_params params; + + /* check probe response offloading caps and print warnings */ + if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_PROBE_RESP_OFFLOAD)) + return NULL; + +#ifdef CONFIG_WPS + if (hapd->conf->wps_state && hapd->wps_probe_resp_ie && + (!(hapd->iface->probe_resp_offloads & + (WPA_DRIVER_PROBE_RESP_OFFLOAD_WPS | + WPA_DRIVER_PROBE_RESP_OFFLOAD_WPS2)))) + wpa_printf(MSG_WARNING, "Device is trying to offload WPS " + "Probe Response while not supporting this"); +#endif /* CONFIG_WPS */ + +#ifdef CONFIG_P2P + if ((hapd->conf->p2p & P2P_ENABLED) && hapd->p2p_probe_resp_ie && + !(hapd->iface->probe_resp_offloads & + WPA_DRIVER_PROBE_RESP_OFFLOAD_P2P)) + wpa_printf(MSG_WARNING, "Device is trying to offload P2P " + "Probe Response while not supporting this"); +#endif /* CONFIG_P2P */ + + if (hapd->conf->interworking && + !(hapd->iface->probe_resp_offloads & + WPA_DRIVER_PROBE_RESP_OFFLOAD_INTERWORKING)) + wpa_printf(MSG_WARNING, "Device is trying to offload " + "Interworking Probe Response while not supporting " + "this"); + + /* Generate a Probe Response template for the non-P2P case */ + os_memset(¶ms, 0, sizeof(params)); + params.req = NULL; + params.is_p2p = false; + params.known_bss = NULL; + params.known_bss_len = 0; + params.is_ml_sta_info = false; + params.mld_ap = NULL; + params.mld_info = NULL; + + hostapd_gen_probe_resp(hapd, ¶ms); + *resp_len = params.resp_len; + if (!params.resp) + return NULL; + + /* TODO: Avoid passing these through struct hostapd_data */ + if (params.csa_pos) + hapd->cs_c_off_proberesp = params.csa_pos - (u8 *) params.resp; + if (params.ecsa_pos) + hapd->cs_c_off_ecsa_proberesp = params.ecsa_pos - + (u8 *) params.resp; +#ifdef CONFIG_IEEE80211AX + if (params.cca_pos) + hapd->cca_c_off_proberesp = params.cca_pos - (u8 *) params.resp; +#endif /* CONFIG_IEEE80211AX */ + + return (u8 *) params.resp; +} + +#endif /* NEED_AP_MLME */ + + +#ifdef CONFIG_IEEE80211AX +/* Unsolicited broadcast Probe Response transmission, 6 GHz only */ +u8 * hostapd_unsol_bcast_probe_resp(struct hostapd_data *hapd, + struct unsol_bcast_probe_resp *ubpr) +{ + struct probe_resp_params probe_params; + + if (!is_6ghz_op_class(hapd->iconf->op_class)) + return NULL; + + ubpr->unsol_bcast_probe_resp_interval = + hapd->conf->unsol_bcast_probe_resp_interval; + + os_memset(&probe_params, 0, sizeof(probe_params)); + probe_params.req = NULL; + probe_params.is_p2p = false; + probe_params.known_bss = NULL; + probe_params.known_bss_len = 0; + probe_params.is_ml_sta_info = false; + probe_params.mld_ap = NULL; + probe_params.mld_info = NULL; + + hostapd_gen_probe_resp(hapd, &probe_params); + ubpr->unsol_bcast_probe_resp_tmpl_len = probe_params.resp_len; + return (u8 *) probe_params.resp; +} +#endif /* CONFIG_IEEE80211AX */ + + +void sta_track_del(struct hostapd_sta_info *info) +{ +#ifdef CONFIG_TAXONOMY + wpabuf_free(info->probe_ie_taxonomy); + info->probe_ie_taxonomy = NULL; +#endif /* CONFIG_TAXONOMY */ + os_free(info); +} + + +#ifdef CONFIG_FILS + +static u16 hostapd_gen_fils_discovery_phy_index(struct hostapd_data *hapd) +{ +#ifdef CONFIG_IEEE80211BE + if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) + return FD_CAP_PHY_INDEX_EHT; +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) + return FD_CAP_PHY_INDEX_HE; +#endif /* CONFIG_IEEE80211AX */ + +#ifdef CONFIG_IEEE80211AC + if (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac) + return FD_CAP_PHY_INDEX_VHT; +#endif /* CONFIG_IEEE80211AC */ + + if (hapd->iconf->ieee80211n && !hapd->conf->disable_11n) + return FD_CAP_PHY_INDEX_HT; + + return 0; +} + + +static u16 hostapd_gen_fils_discovery_nss(struct hostapd_hw_modes *mode, + u16 phy_index, u8 he_mcs_nss_size) +{ + u16 nss = 0; + + if (!mode) + return 0; + + if (phy_index == FD_CAP_PHY_INDEX_HE) { + const u8 *he_mcs = mode->he_capab[IEEE80211_MODE_AP].mcs; + int i; + u16 mcs[6]; + + os_memset(mcs, 0xff, 6 * sizeof(u16)); + + if (he_mcs_nss_size == 4) { + mcs[0] = WPA_GET_LE16(&he_mcs[0]); + mcs[1] = WPA_GET_LE16(&he_mcs[2]); + } + + if (he_mcs_nss_size == 8) { + mcs[2] = WPA_GET_LE16(&he_mcs[4]); + mcs[3] = WPA_GET_LE16(&he_mcs[6]); + } + + if (he_mcs_nss_size == 12) { + mcs[4] = WPA_GET_LE16(&he_mcs[8]); + mcs[5] = WPA_GET_LE16(&he_mcs[10]); + } + + for (i = 0; i < HE_NSS_MAX_STREAMS; i++) { + u16 nss_mask = 0x3 << (i * 2); + + /* + * If Tx and/or Rx indicate support for a given NSS, + * count it towards the maximum NSS. + */ + if (he_mcs_nss_size == 4 && + (((mcs[0] & nss_mask) != nss_mask) || + ((mcs[1] & nss_mask) != nss_mask))) { + nss++; + continue; + } + + if (he_mcs_nss_size == 8 && + (((mcs[2] & nss_mask) != nss_mask) || + ((mcs[3] & nss_mask) != nss_mask))) { + nss++; + continue; + } + + if (he_mcs_nss_size == 12 && + (((mcs[4] & nss_mask) != nss_mask) || + ((mcs[5] & nss_mask) != nss_mask))) { + nss++; + continue; + } + } + } else if (phy_index == FD_CAP_PHY_INDEX_EHT) { + u8 rx_nss, tx_nss, max_nss = 0, i; + u8 *mcs = mode->eht_capab[IEEE80211_MODE_AP].mcs; + + /* + * The Supported EHT-MCS And NSS Set field for the AP contains + * one to three EHT-MCS Map fields based on the supported + * bandwidth. Check the first byte (max NSS for Rx/Tx that + * supports EHT-MCS 0-9) for each bandwidth (<= 80, + * 160, 320) to find the maximum NSS. This assumes that + * the lowest MCS rates support the largest number of spatial + * streams. If values are different between Tx, Rx or the + * bandwidths, choose the highest value. + */ + for (i = 0; i < 3; i++) { + rx_nss = mcs[3 * i] & 0x0F; + if (rx_nss > max_nss) + max_nss = rx_nss; + + tx_nss = (mcs[3 * i] & 0xF0) >> 4; + if (tx_nss > max_nss) + max_nss = tx_nss; + } + + nss = max_nss; + } + + if (nss > 4) + return FD_CAP_NSS_5_8 << FD_CAP_NSS_SHIFT; + if (nss) + return (nss - 1) << FD_CAP_NSS_SHIFT; + + return 0; +} + + +static u16 hostapd_fils_discovery_cap(struct hostapd_data *hapd) +{ + u16 cap_info, phy_index; + u8 chwidth = FD_CAP_BSS_CHWIDTH_20, he_mcs_nss_size = 4; + struct hostapd_hw_modes *mode = hapd->iface->current_mode; + + cap_info = FD_CAP_ESS; + if (hapd->conf->wpa) + cap_info |= FD_CAP_PRIVACY; + + if (is_6ghz_op_class(hapd->iconf->op_class)) { + switch (hapd->iconf->op_class) { + case 137: + chwidth = FD_CAP_BSS_CHWIDTH_320; + break; + case 135: + he_mcs_nss_size += 4; + /* fallthrough */ + case 134: + he_mcs_nss_size += 4; + chwidth = FD_CAP_BSS_CHWIDTH_160_80_80; + break; + case 133: + chwidth = FD_CAP_BSS_CHWIDTH_80; + break; + case 132: + chwidth = FD_CAP_BSS_CHWIDTH_40; + break; + } + } else { + switch (hostapd_get_oper_chwidth(hapd->iconf)) { + case CONF_OPER_CHWIDTH_80P80MHZ: + he_mcs_nss_size += 4; + /* fallthrough */ + case CONF_OPER_CHWIDTH_160MHZ: + he_mcs_nss_size += 4; + chwidth = FD_CAP_BSS_CHWIDTH_160_80_80; + break; + case CONF_OPER_CHWIDTH_80MHZ: + chwidth = FD_CAP_BSS_CHWIDTH_80; + break; + case CONF_OPER_CHWIDTH_USE_HT: + if (hapd->iconf->secondary_channel) + chwidth = FD_CAP_BSS_CHWIDTH_40; + else + chwidth = FD_CAP_BSS_CHWIDTH_20; + break; + default: + break; + } + } + + phy_index = hostapd_gen_fils_discovery_phy_index(hapd); + cap_info |= phy_index << FD_CAP_PHY_INDEX_SHIFT; + cap_info |= chwidth << FD_CAP_BSS_CHWIDTH_SHIFT; + cap_info |= hostapd_gen_fils_discovery_nss(mode, phy_index, + he_mcs_nss_size); + return cap_info; +} + + +static u8 * hostapd_gen_fils_discovery(struct hostapd_data *hapd, size_t *len) +{ + struct ieee80211_mgmt *head; + const u8 *mobility_domain; + u8 *pos, *length_pos, buf[200]; + u16 ctl = 0; + u8 fd_rsn_info[5]; + size_t total_len, buf_len; + + total_len = 24 + 2 + 12; + + /* FILS Discovery Frame Control */ + ctl = (sizeof(hapd->conf->ssid.short_ssid) - 1) | + FD_FRAME_CTL_SHORT_SSID_PRESENT | + FD_FRAME_CTL_LENGTH_PRESENT | + FD_FRAME_CTL_CAP_PRESENT; + total_len += 4 + 1 + 2; + + /* Fill primary channel information for 6 GHz channels with over 20 MHz + * bandwidth, if the primary channel is not a PSC */ + if (is_6ghz_op_class(hapd->iconf->op_class) && + !is_6ghz_psc_frequency(ieee80211_chan_to_freq( + NULL, hapd->iconf->op_class, + hapd->iconf->channel)) && + op_class_to_bandwidth(hapd->iconf->op_class) > 20) { + ctl |= FD_FRAME_CTL_PRI_CHAN_PRESENT; + total_len += 2; + } + + /* Check for optional subfields and calculate length */ + if (wpa_auth_write_fd_rsn_info(hapd->wpa_auth, fd_rsn_info)) { + ctl |= FD_FRAME_CTL_RSN_INFO_PRESENT; + total_len += sizeof(fd_rsn_info); + } + + mobility_domain = hostapd_wpa_ie(hapd, WLAN_EID_MOBILITY_DOMAIN); + if (mobility_domain) { + ctl |= FD_FRAME_CTL_MD_PRESENT; + total_len += 3; + } + + total_len += hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_ACTION, true); + + pos = hostapd_eid_fils_indic(hapd, buf, 0); + buf_len = pos - buf; + total_len += buf_len; + + /* he_elem_len() may return too large a value for FD frame, but that is + * fine here since this is used as the maximum length of the buffer. */ + total_len += he_elem_len(hapd); + + head = os_zalloc(total_len); + if (!head) + return NULL; + + head->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memset(head->da, 0xff, ETH_ALEN); + os_memcpy(head->sa, hapd->own_addr, ETH_ALEN); + os_memcpy(head->bssid, hapd->own_addr, ETH_ALEN); + + head->u.action.category = WLAN_ACTION_PUBLIC; + head->u.action.u.public_action.action = WLAN_PA_FILS_DISCOVERY; + + pos = &head->u.action.u.public_action.variable[0]; + + /* FILS Discovery Information field */ + + /* FILS Discovery Frame Control */ + WPA_PUT_LE16(pos, ctl); + pos += 2; + + /* Hardware or low-level driver will fill in the Timestamp value */ + pos += 8; + + /* Beacon Interval */ + WPA_PUT_LE16(pos, hapd->iconf->beacon_int); + pos += 2; + + /* Short SSID */ + WPA_PUT_LE32(pos, hapd->conf->ssid.short_ssid); + pos += sizeof(hapd->conf->ssid.short_ssid); + + /* Store position of FILS discovery information element Length field */ + length_pos = pos++; + + /* FD Capability */ + WPA_PUT_LE16(pos, hostapd_fils_discovery_cap(hapd)); + pos += 2; + + /* Operating Class and Primary Channel - if a 6 GHz chan is non PSC */ + if (ctl & FD_FRAME_CTL_PRI_CHAN_PRESENT) { + *pos++ = hapd->iconf->op_class; + *pos++ = hapd->iconf->channel; + } + + /* AP Configuration Sequence Number - not present */ + + /* Access Network Options - not present */ + + /* FD RSN Information */ + if (ctl & FD_FRAME_CTL_RSN_INFO_PRESENT) { + os_memcpy(pos, fd_rsn_info, sizeof(fd_rsn_info)); + pos += sizeof(fd_rsn_info); + } + + /* Channel Center Frequency Segment 1 - not present */ + + /* Mobility Domain */ + if (ctl & FD_FRAME_CTL_MD_PRESENT) { + os_memcpy(pos, &mobility_domain[2], 3); + pos += 3; + } + + /* Fill in the Length field value */ + *length_pos = pos - (length_pos + 1); + + pos = hostapd_eid_rnr(hapd, pos, WLAN_FC_STYPE_ACTION, true); + + /* FILS Indication element */ + if (buf_len) { + os_memcpy(pos, buf, buf_len); + pos += buf_len; + } + + if (is_6ghz_op_class(hapd->iconf->op_class)) + pos = hostapd_eid_txpower_envelope(hapd, pos); + + *len = pos - (u8 *) head; + wpa_hexdump(MSG_DEBUG, "FILS Discovery frame template", + head, pos - (u8 *) head); + return (u8 *) head; +} + + +/* Configure FILS Discovery frame transmission parameters */ +static u8 * hostapd_fils_discovery(struct hostapd_data *hapd, + struct wpa_driver_ap_params *params) +{ + params->fd_max_int = hapd->conf->fils_discovery_max_int; + if (is_6ghz_op_class(hapd->iconf->op_class) && + params->fd_max_int > FD_MAX_INTERVAL_6GHZ) + params->fd_max_int = FD_MAX_INTERVAL_6GHZ; + + params->fd_min_int = hapd->conf->fils_discovery_min_int; + if (params->fd_min_int > params->fd_max_int) + params->fd_min_int = params->fd_max_int; + + if (params->fd_max_int) + return hostapd_gen_fils_discovery(hapd, + ¶ms->fd_frame_tmpl_len); + + return NULL; +} + +#endif /* CONFIG_FILS */ + + +int ieee802_11_build_ap_params(struct hostapd_data *hapd, + struct wpa_driver_ap_params *params) +{ + struct ieee80211_mgmt *head = NULL; + u8 *tail = NULL; + size_t head_len = 0, tail_len = 0; + u8 *resp = NULL; + size_t resp_len = 0; +#ifdef NEED_AP_MLME + u16 capab_info; + u8 *pos, *tailpos, *tailend, *csa_pos; + bool complete = false; +#endif /* NEED_AP_MLME */ + + os_memset(params, 0, sizeof(*params)); + +#ifdef NEED_AP_MLME +#define BEACON_HEAD_BUF_SIZE 256 +#define BEACON_TAIL_BUF_SIZE 512 + head = os_zalloc(BEACON_HEAD_BUF_SIZE); + tail_len = BEACON_TAIL_BUF_SIZE; +#ifdef CONFIG_WPS + if (hapd->conf->wps_state && hapd->wps_beacon_ie) + tail_len += wpabuf_len(hapd->wps_beacon_ie); +#endif /* CONFIG_WPS */ +#ifdef CONFIG_P2P + if (hapd->p2p_beacon_ie) + tail_len += wpabuf_len(hapd->p2p_beacon_ie); +#endif /* CONFIG_P2P */ +#ifdef CONFIG_FST + if (hapd->iface->fst_ies) + tail_len += wpabuf_len(hapd->iface->fst_ies); +#endif /* CONFIG_FST */ + if (hapd->conf->vendor_elements) + tail_len += wpabuf_len(hapd->conf->vendor_elements); + +#ifdef CONFIG_IEEE80211AC + if (hapd->conf->vendor_vht) { + tail_len += 5 + 2 + sizeof(struct ieee80211_vht_capabilities) + + 2 + sizeof(struct ieee80211_vht_operation); + } +#endif /* CONFIG_IEEE80211AC */ + + tail_len += he_elem_len(hapd); + +#ifdef CONFIG_IEEE80211BE + if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { + tail_len += hostapd_eid_eht_capab_len(hapd, IEEE80211_MODE_AP); + tail_len += 3 + sizeof(struct ieee80211_eht_operation); + if (hapd->iconf->punct_bitmap) + tail_len += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE; + + /* + * TODO: Multi-Link element has variable length and can be + * long based on the common info and number of per + * station profiles. For now use 256. + */ + if (hapd->conf->mld_ap) + tail_len += 256; + } +#endif /* CONFIG_IEEE80211BE */ + + if (hapd->iconf->mbssid == ENHANCED_MBSSID_ENABLED && + hapd == hostapd_mbssid_get_tx_bss(hapd)) + tail_len += 5; /* Multiple BSSID Configuration element */ + tail_len += hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_BEACON, true); + tail_len += hostapd_mbo_ie_len(hapd); + tail_len += hostapd_eid_owe_trans_len(hapd); + tail_len += hostapd_eid_dpp_cc_len(hapd); + + tailpos = tail = os_malloc(tail_len); + if (head == NULL || tail == NULL) { + wpa_printf(MSG_ERROR, "Failed to set beacon data"); + os_free(head); + os_free(tail); + return -1; + } + tailend = tail + tail_len; + + head->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_BEACON); + head->duration = host_to_le16(0); + os_memset(head->da, 0xff, ETH_ALEN); + + os_memcpy(head->sa, hapd->own_addr, ETH_ALEN); + os_memcpy(head->bssid, hapd->own_addr, ETH_ALEN); + head->u.beacon.beacon_int = + host_to_le16(hapd->iconf->beacon_int); + + /* hardware or low-level driver will setup seq_ctrl and timestamp */ + capab_info = hostapd_own_capab_info(hapd); + head->u.beacon.capab_info = host_to_le16(capab_info); + pos = &head->u.beacon.variable[0]; + + /* SSID */ + *pos++ = WLAN_EID_SSID; + if (hapd->conf->ignore_broadcast_ssid == 2) { + /* clear the data, but keep the correct length of the SSID */ + *pos++ = hapd->conf->ssid.ssid_len; + os_memset(pos, 0, hapd->conf->ssid.ssid_len); + pos += hapd->conf->ssid.ssid_len; + } else if (hapd->conf->ignore_broadcast_ssid) { + *pos++ = 0; /* empty SSID */ + } else { + *pos++ = hapd->conf->ssid.ssid_len; + os_memcpy(pos, hapd->conf->ssid.ssid, + hapd->conf->ssid.ssid_len); + pos += hapd->conf->ssid.ssid_len; + } + + /* Supported rates */ + pos = hostapd_eid_supp_rates(hapd, pos); + + /* DS Params */ + pos = hostapd_eid_ds_params(hapd, pos); + + head_len = pos - (u8 *) head; + + tailpos = hostapd_eid_country(hapd, tailpos, tailend - tailpos); + + /* Power Constraint element */ + tailpos = hostapd_eid_pwr_constraint(hapd, tailpos); + + /* CSA IE */ + csa_pos = hostapd_eid_csa(hapd, tailpos); + if (csa_pos != tailpos) + hapd->cs_c_off_beacon = csa_pos - tail - 1; + tailpos = csa_pos; + + /* ERP Information element */ + tailpos = hostapd_eid_erp_info(hapd, tailpos); + + /* Extended supported rates */ + tailpos = hostapd_eid_ext_supp_rates(hapd, tailpos); + + tailpos = hostapd_get_rsne(hapd, tailpos, tailend - tailpos); + tailpos = hostapd_eid_bss_load(hapd, tailpos, tailend - tailpos); + tailpos = hostapd_eid_rm_enabled_capab(hapd, tailpos, + tailend - tailpos); + tailpos = hostapd_get_mde(hapd, tailpos, tailend - tailpos); + + /* eCSA IE */ + csa_pos = hostapd_eid_ecsa(hapd, tailpos); + if (csa_pos != tailpos) + hapd->cs_c_off_ecsa_beacon = csa_pos - tail - 1; + tailpos = csa_pos; + + tailpos = hostapd_eid_supported_op_classes(hapd, tailpos); + tailpos = hostapd_eid_ht_capabilities(hapd, tailpos); + tailpos = hostapd_eid_ht_operation(hapd, tailpos); + + if (hapd->iconf->mbssid && hapd->iconf->num_bss > 1) { + if (ieee802_11_build_ap_params_mbssid(hapd, params)) { + os_free(head); + os_free(tail); + wpa_printf(MSG_ERROR, + "MBSSID: Failed to set beacon data"); + return -1; + } + complete = hapd->iconf->mbssid == MBSSID_ENABLED || + (hapd->iconf->mbssid == ENHANCED_MBSSID_ENABLED && + params->mbssid_elem_count == 1); + } + + tailpos = hostapd_eid_ext_capab(hapd, tailpos, complete); + + /* + * TODO: Time Advertisement element should only be included in some + * DTIM Beacon frames. + */ + tailpos = hostapd_eid_time_adv(hapd, tailpos); + + tailpos = hostapd_eid_interworking(hapd, tailpos); + tailpos = hostapd_eid_adv_proto(hapd, tailpos); + tailpos = hostapd_eid_roaming_consortium(hapd, tailpos); + +#ifdef CONFIG_FST + if (hapd->iface->fst_ies) { + os_memcpy(tailpos, wpabuf_head(hapd->iface->fst_ies), + wpabuf_len(hapd->iface->fst_ies)); + tailpos += wpabuf_len(hapd->iface->fst_ies); + } +#endif /* CONFIG_FST */ + +#ifdef CONFIG_IEEE80211AC + if (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac && + !is_6ghz_op_class(hapd->iconf->op_class)) { + tailpos = hostapd_eid_vht_capabilities(hapd, tailpos, 0); + tailpos = hostapd_eid_vht_operation(hapd, tailpos); + tailpos = hostapd_eid_txpower_envelope(hapd, tailpos); + } +#endif /* CONFIG_IEEE80211AC */ + +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax && + is_6ghz_op_class(hapd->iconf->op_class)) + tailpos = hostapd_eid_txpower_envelope(hapd, tailpos); +#endif /* CONFIG_IEEE80211AX */ + + tailpos = hostapd_eid_wb_chsw_wrapper(hapd, tailpos); + + tailpos = hostapd_eid_rnr(hapd, tailpos, WLAN_FC_STYPE_BEACON, true); + tailpos = hostapd_eid_fils_indic(hapd, tailpos, 0); + tailpos = hostapd_get_rsnxe(hapd, tailpos, tailend - tailpos); + tailpos = hostapd_eid_mbssid_config(hapd, tailpos, + params->mbssid_elem_count); + +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) { + u8 *cca_pos; + + tailpos = hostapd_eid_he_capab(hapd, tailpos, + IEEE80211_MODE_AP); + tailpos = hostapd_eid_he_operation(hapd, tailpos); + + /* BSS Color Change Announcement element */ + cca_pos = hostapd_eid_cca(hapd, tailpos); + if (cca_pos != tailpos) + hapd->cca_c_off_beacon = cca_pos - tail - 2; + tailpos = cca_pos; + + tailpos = hostapd_eid_spatial_reuse(hapd, tailpos); + tailpos = hostapd_eid_he_mu_edca_parameter_set(hapd, tailpos); + tailpos = hostapd_eid_he_6ghz_band_cap(hapd, tailpos); + } +#endif /* CONFIG_IEEE80211AX */ + +#ifdef CONFIG_IEEE80211BE + if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { + if (hapd->conf->mld_ap) + tailpos = hostapd_eid_eht_ml_beacon(hapd, NULL, + tailpos, false); + tailpos = hostapd_eid_eht_capab(hapd, tailpos, + IEEE80211_MODE_AP); + tailpos = hostapd_eid_eht_operation(hapd, tailpos); + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_IEEE80211AC + if (hapd->conf->vendor_vht) + tailpos = hostapd_eid_vendor_vht(hapd, tailpos); +#endif /* CONFIG_IEEE80211AC */ + + /* WPA / OSEN */ + tailpos = hostapd_get_wpa_ie(hapd, tailpos, tailend - tailpos); + tailpos = hostapd_get_osen_ie(hapd, tailpos, tailend - tailpos); + + /* Wi-Fi Alliance WMM */ + tailpos = hostapd_eid_wmm(hapd, tailpos); + +#ifdef CONFIG_WPS + if (hapd->conf->wps_state && hapd->wps_beacon_ie) { + os_memcpy(tailpos, wpabuf_head(hapd->wps_beacon_ie), + wpabuf_len(hapd->wps_beacon_ie)); + tailpos += wpabuf_len(hapd->wps_beacon_ie); + } +#endif /* CONFIG_WPS */ + +#ifdef CONFIG_P2P + if ((hapd->conf->p2p & P2P_ENABLED) && hapd->p2p_beacon_ie) { + os_memcpy(tailpos, wpabuf_head(hapd->p2p_beacon_ie), + wpabuf_len(hapd->p2p_beacon_ie)); + tailpos += wpabuf_len(hapd->p2p_beacon_ie); + } +#endif /* CONFIG_P2P */ +#ifdef CONFIG_P2P_MANAGER + if ((hapd->conf->p2p & (P2P_MANAGE | P2P_ENABLED | P2P_GROUP_OWNER)) == + P2P_MANAGE) + tailpos = hostapd_eid_p2p_manage(hapd, tailpos); +#endif /* CONFIG_P2P_MANAGER */ + +#ifdef CONFIG_HS20 + tailpos = hostapd_eid_hs20_indication(hapd, tailpos); +#endif /* CONFIG_HS20 */ + + tailpos = hostapd_eid_mbo(hapd, tailpos, tail + tail_len - tailpos); + tailpos = hostapd_eid_owe_trans(hapd, tailpos, + tail + tail_len - tailpos); + tailpos = hostapd_eid_dpp_cc(hapd, tailpos, tail + tail_len - tailpos); + + if (hapd->conf->vendor_elements) { + os_memcpy(tailpos, wpabuf_head(hapd->conf->vendor_elements), + wpabuf_len(hapd->conf->vendor_elements)); + tailpos += wpabuf_len(hapd->conf->vendor_elements); + } + + tail_len = tailpos > tail ? tailpos - tail : 0; + + resp = hostapd_probe_resp_offloads(hapd, &resp_len); +#endif /* NEED_AP_MLME */ + + /* If key management offload is enabled, configure PSK to the driver. */ + if (wpa_key_mgmt_wpa_psk_no_sae(hapd->conf->wpa_key_mgmt) && + (hapd->iface->drv_flags2 & + WPA_DRIVER_FLAGS2_4WAY_HANDSHAKE_AP_PSK)) { + if (hapd->conf->ssid.wpa_psk && hapd->conf->ssid.wpa_psk_set) { + os_memcpy(params->psk, hapd->conf->ssid.wpa_psk->psk, + PMK_LEN); + params->psk_len = PMK_LEN; + } else if (hapd->conf->ssid.wpa_passphrase && + pbkdf2_sha1(hapd->conf->ssid.wpa_passphrase, + hapd->conf->ssid.ssid, + hapd->conf->ssid.ssid_len, 4096, + params->psk, PMK_LEN) == 0) { + params->psk_len = PMK_LEN; + } + } + +#ifdef CONFIG_SAE + /* If SAE offload is enabled, provide password to lower layer for + * SAE authentication and PMK generation. + */ + if (wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) && + (hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP)) { + if (hostapd_sae_pk_in_use(hapd->conf)) { + wpa_printf(MSG_ERROR, + "SAE PK not supported with SAE offload"); + return -1; + } + + if (hostapd_sae_pw_id_in_use(hapd->conf)) { + wpa_printf(MSG_ERROR, + "SAE Password Identifiers not supported with SAE offload"); + return -1; + } + + params->sae_password = sae_get_password(hapd, NULL, NULL, NULL, + NULL, NULL); + if (!params->sae_password) { + wpa_printf(MSG_ERROR, "SAE password not configured for offload"); + return -1; + } + } +#endif /* CONFIG_SAE */ + + params->head = (u8 *) head; + params->head_len = head_len; + params->tail = tail; + params->tail_len = tail_len; + params->proberesp = resp; + params->proberesp_len = resp_len; + params->dtim_period = hapd->conf->dtim_period; + params->beacon_int = hapd->iconf->beacon_int; + params->basic_rates = hapd->iface->basic_rates; + params->beacon_rate = hapd->iconf->beacon_rate; + params->rate_type = hapd->iconf->rate_type; + params->ssid = hapd->conf->ssid.ssid; + params->ssid_len = hapd->conf->ssid.ssid_len; + if ((hapd->conf->wpa & (WPA_PROTO_WPA | WPA_PROTO_RSN)) == + (WPA_PROTO_WPA | WPA_PROTO_RSN)) + params->pairwise_ciphers = hapd->conf->wpa_pairwise | + hapd->conf->rsn_pairwise; + else if (hapd->conf->wpa & WPA_PROTO_RSN) + params->pairwise_ciphers = hapd->conf->rsn_pairwise; + else if (hapd->conf->wpa & WPA_PROTO_WPA) + params->pairwise_ciphers = hapd->conf->wpa_pairwise; + params->group_cipher = hapd->conf->wpa_group; + params->key_mgmt_suites = hapd->conf->wpa_key_mgmt; + params->auth_algs = hapd->conf->auth_algs; + params->wpa_version = hapd->conf->wpa; + params->privacy = hapd->conf->wpa; +#ifdef CONFIG_WEP + params->privacy |= hapd->conf->ssid.wep.keys_set || + (hapd->conf->ieee802_1x && + (hapd->conf->default_wep_key_len || + hapd->conf->individual_wep_key_len)); +#endif /* CONFIG_WEP */ + switch (hapd->conf->ignore_broadcast_ssid) { + case 0: + params->hide_ssid = NO_SSID_HIDING; + break; + case 1: + params->hide_ssid = HIDDEN_SSID_ZERO_LEN; + break; + case 2: + params->hide_ssid = HIDDEN_SSID_ZERO_CONTENTS; + break; + } + params->isolate = hapd->conf->isolate; +#ifdef NEED_AP_MLME + params->cts_protect = !!(ieee802_11_erp_info(hapd) & + ERP_INFO_USE_PROTECTION); + params->preamble = hapd->iface->num_sta_no_short_preamble == 0 && + hapd->iconf->preamble == SHORT_PREAMBLE; + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G) + params->short_slot_time = + hapd->iface->num_sta_no_short_slot_time > 0 ? 0 : 1; + else + params->short_slot_time = -1; + if (!hapd->iconf->ieee80211n || hapd->conf->disable_11n) + params->ht_opmode = -1; + else + params->ht_opmode = hapd->iface->ht_op_mode; +#endif /* NEED_AP_MLME */ + params->interworking = hapd->conf->interworking; + if (hapd->conf->interworking && + !is_zero_ether_addr(hapd->conf->hessid)) + params->hessid = hapd->conf->hessid; + params->access_network_type = hapd->conf->access_network_type; + params->ap_max_inactivity = hapd->conf->ap_max_inactivity; +#ifdef CONFIG_P2P + params->p2p_go_ctwindow = hapd->iconf->p2p_go_ctwindow; +#endif /* CONFIG_P2P */ +#ifdef CONFIG_HS20 + params->disable_dgaf = hapd->conf->disable_dgaf; + if (hapd->conf->osen) { + params->privacy = 1; + params->osen = 1; + } +#endif /* CONFIG_HS20 */ + params->multicast_to_unicast = hapd->conf->multicast_to_unicast; + params->pbss = hapd->conf->pbss; + + if (hapd->conf->ftm_responder) { + if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_FTM_RESPONDER) { + params->ftm_responder = 1; + params->lci = hapd->iface->conf->lci; + params->civic = hapd->iface->conf->civic; + } else { + wpa_printf(MSG_WARNING, + "Not configuring FTM responder as the driver doesn't advertise support for it"); + } + } + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap && hapd->iconf->ieee80211be && + !hapd->conf->disable_11be) { + params->mld_ap = true; + params->mld_link_id = hapd->mld_link_id; + } +#endif /* CONFIG_IEEE80211BE */ + + return 0; +} + + +void ieee802_11_free_ap_params(struct wpa_driver_ap_params *params) +{ + os_free(params->tail); + params->tail = NULL; + os_free(params->head); + params->head = NULL; + os_free(params->proberesp); + params->proberesp = NULL; + os_free(params->mbssid_elem); + params->mbssid_elem = NULL; + os_free(params->mbssid_elem_offset); + params->mbssid_elem_offset = NULL; + os_free(params->rnr_elem); + params->rnr_elem = NULL; + os_free(params->rnr_elem_offset); + params->rnr_elem_offset = NULL; +#ifdef CONFIG_FILS + os_free(params->fd_frame_tmpl); + params->fd_frame_tmpl = NULL; +#endif /* CONFIG_FILS */ +#ifdef CONFIG_IEEE80211AX + os_free(params->ubpr.unsol_bcast_probe_resp_tmpl); + params->ubpr.unsol_bcast_probe_resp_tmpl = NULL; +#endif /* CONFIG_IEEE80211AX */ + os_free(params->allowed_freqs); + params->allowed_freqs = NULL; +} + + +static int __ieee802_11_set_beacon(struct hostapd_data *hapd) +{ + struct wpa_driver_ap_params params; + struct hostapd_freq_params freq; + struct hostapd_iface *iface = hapd->iface; + struct hostapd_config *iconf = iface->conf; + struct hostapd_hw_modes *cmode = iface->current_mode; + struct wpabuf *beacon, *proberesp, *assocresp; + bool twt_he_responder = false; + int res, ret = -1, i; + struct hostapd_hw_modes *mode; + + if (!hapd->drv_priv) { + wpa_printf(MSG_ERROR, "Interface is disabled"); + return -1; + } + + if (hapd->csa_in_progress) { + wpa_printf(MSG_ERROR, "Cannot set beacons during CSA period"); + return -1; + } + + hapd->beacon_set_done = 1; + + if (ieee802_11_build_ap_params(hapd, ¶ms) < 0) + return -1; + + if (hostapd_build_ap_extra_ies(hapd, &beacon, &proberesp, &assocresp) < + 0) + goto fail; + + params.beacon_ies = beacon; + params.proberesp_ies = proberesp; + params.assocresp_ies = assocresp; + params.reenable = hapd->reenable_beacon; +#ifdef CONFIG_IEEE80211AX + params.he_spr_ctrl = hapd->iface->conf->spr.sr_control; + params.he_spr_non_srg_obss_pd_max_offset = + hapd->iface->conf->spr.non_srg_obss_pd_max_offset; + params.he_spr_srg_obss_pd_min_offset = + hapd->iface->conf->spr.srg_obss_pd_min_offset; + params.he_spr_srg_obss_pd_max_offset = + hapd->iface->conf->spr.srg_obss_pd_max_offset; + os_memcpy(params.he_spr_bss_color_bitmap, + hapd->iface->conf->spr.srg_bss_color_bitmap, 8); + os_memcpy(params.he_spr_partial_bssid_bitmap, + hapd->iface->conf->spr.srg_partial_bssid_bitmap, 8); + params.he_bss_color_disabled = + hapd->iface->conf->he_op.he_bss_color_disabled; + params.he_bss_color_partial = + hapd->iface->conf->he_op.he_bss_color_partial; + params.he_bss_color = hapd->iface->conf->he_op.he_bss_color; + twt_he_responder = hostapd_get_he_twt_responder(hapd, + IEEE80211_MODE_AP); + params.ubpr.unsol_bcast_probe_resp_tmpl = + hostapd_unsol_bcast_probe_resp(hapd, ¶ms.ubpr); +#endif /* CONFIG_IEEE80211AX */ + params.twt_responder = + twt_he_responder || hostapd_get_ht_vht_twt_responder(hapd); + hapd->reenable_beacon = 0; +#ifdef CONFIG_SAE + params.sae_pwe = hapd->conf->sae_pwe; +#endif /* CONFIG_SAE */ + +#ifdef CONFIG_FILS + params.fd_frame_tmpl = hostapd_fils_discovery(hapd, ¶ms); +#endif /* CONFIG_FILS */ + +#ifdef CONFIG_IEEE80211BE + params.punct_bitmap = iconf->punct_bitmap; +#endif /* CONFIG_IEEE80211BE */ + + if (cmode && + hostapd_set_freq_params(&freq, iconf->hw_mode, iface->freq, + iconf->channel, iconf->enable_edmg, + iconf->edmg_channel, iconf->ieee80211n, + iconf->ieee80211ac, iconf->ieee80211ax, + iconf->ieee80211be, + iconf->secondary_channel, + hostapd_get_oper_chwidth(iconf), + hostapd_get_oper_centr_freq_seg0_idx(iconf), + hostapd_get_oper_centr_freq_seg1_idx(iconf), + cmode->vht_capab, + &cmode->he_capab[IEEE80211_MODE_AP], + &cmode->eht_capab[IEEE80211_MODE_AP], + hostapd_get_punct_bitmap(hapd)) == 0) { + freq.link_id = -1; +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) + freq.link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + params.freq = &freq; + } + + for (i = 0; i < hapd->iface->num_hw_features; i++) { + mode = &hapd->iface->hw_features[i]; + + if (iconf->hw_mode != HOSTAPD_MODE_IEEE80211ANY && + iconf->hw_mode != mode->mode) + continue; + + hostapd_get_hw_mode_any_channels(hapd, mode, + !(iconf->acs_freq_list.num || + iconf->acs_ch_list.num), + true, ¶ms.allowed_freqs); + } + + res = hostapd_drv_set_ap(hapd, ¶ms); + hostapd_free_ap_extra_ies(hapd, beacon, proberesp, assocresp); + if (res) + wpa_printf(MSG_ERROR, "Failed to set beacon parameters"); + else + ret = 0; +fail: + ieee802_11_free_ap_params(¶ms); + return ret; +} + + +void ieee802_11_set_beacon_per_bss_only(struct hostapd_data *hapd) +{ + __ieee802_11_set_beacon(hapd); +} + + +int ieee802_11_set_beacon(struct hostapd_data *hapd) +{ + struct hostapd_iface *iface = hapd->iface; + int ret; + size_t i, j; + bool is_6g, hapd_mld = false; + + ret = __ieee802_11_set_beacon(hapd); + if (ret != 0) + return ret; + + if (!iface->interfaces || iface->interfaces->count <= 1) + return 0; + +#ifdef CONFIG_IEEE80211BE + hapd_mld = hapd->conf->mld_ap; +#endif /* CONFIG_IEEE80211BE */ + + /* Update Beacon frames in case of 6 GHz colocation or AP MLD */ + is_6g = is_6ghz_op_class(iface->conf->op_class); + for (j = 0; j < iface->interfaces->count; j++) { + struct hostapd_iface *other; + bool other_iface_6g; + + other = iface->interfaces->iface[j]; + if (other == iface || !other || !other->conf) + continue; + + other_iface_6g = is_6ghz_op_class(other->conf->op_class); + + if (is_6g == other_iface_6g && !hapd_mld) + continue; + + for (i = 0; i < other->num_bss; i++) { +#ifdef CONFIG_IEEE80211BE + if (is_6g == other_iface_6g && + !(hapd_mld && other->bss[i]->conf->mld_ap && + hostapd_is_ml_partner(hapd, other->bss[i]))) + continue; +#endif /* CONFIG_IEEE80211BE */ + + if (other->bss[i] && other->bss[i]->started) + __ieee802_11_set_beacon(other->bss[i]); + } + } + + return 0; +} + + +int ieee802_11_set_beacons(struct hostapd_iface *iface) +{ + size_t i; + int ret = 0; + + for (i = 0; i < iface->num_bss; i++) { + if (iface->bss[i]->started && + ieee802_11_set_beacon(iface->bss[i]) < 0) + ret = -1; + } + + return ret; +} + + +/* only update beacons if started */ +int ieee802_11_update_beacons(struct hostapd_iface *iface) +{ + size_t i; + int ret = 0; + + for (i = 0; i < iface->num_bss; i++) { + if (iface->bss[i]->beacon_set_done && iface->bss[i]->started && + ieee802_11_set_beacon(iface->bss[i]) < 0) + ret = -1; + } + + return ret; +} + +#endif /* CONFIG_NATIVE_WINDOWS */ diff --git a/src/ap/beacon.h b/src/ap/beacon.h new file mode 100644 index 0000000..e381542 --- /dev/null +++ b/src/ap/beacon.h @@ -0,0 +1,39 @@ +/* + * hostapd / IEEE 802.11 Management: Beacon and Probe Request/Response + * Copyright (c) 2002-2004, Instant802 Networks, Inc. + * Copyright (c) 2005-2006, Devicescape Software, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef BEACON_H +#define BEACON_H + +struct ieee80211_mgmt; + +void handle_probe_req(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len, + int ssi_signal); +void ieee802_11_set_beacon_per_bss_only(struct hostapd_data *hapd); +int ieee802_11_set_beacon(struct hostapd_data *hapd); +int ieee802_11_set_beacons(struct hostapd_iface *iface); +int ieee802_11_update_beacons(struct hostapd_iface *iface); +int ieee802_11_build_ap_params(struct hostapd_data *hapd, + struct wpa_driver_ap_params *params); +void ieee802_11_free_ap_params(struct wpa_driver_ap_params *params); +void sta_track_add(struct hostapd_iface *iface, const u8 *addr, int ssi_signal); +void sta_track_del(struct hostapd_sta_info *info); +void sta_track_expire(struct hostapd_iface *iface, int force); +struct hostapd_data * +sta_track_seen_on(struct hostapd_iface *iface, const u8 *addr, + const char *ifname); +void sta_track_claim_taxonomy_info(struct hostapd_iface *iface, const u8 *addr, + struct wpabuf **probe_ie_taxonomy); + +const u8 * hostapd_wpa_ie(struct hostapd_data *hapd, u8 eid); + +u8 * hostapd_unsol_bcast_probe_resp(struct hostapd_data *hapd, + struct unsol_bcast_probe_resp *ubpr); + +#endif /* BEACON_H */ diff --git a/src/ap/bss_load.c b/src/ap/bss_load.c new file mode 100644 index 0000000..e9baafc --- /dev/null +++ b/src/ap/bss_load.c @@ -0,0 +1,99 @@ +/* + * BSS Load Element / Channel Utilization + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "hostapd.h" +#include "bss_load.h" +#include "ap_drv_ops.h" +#include "beacon.h" + + +static int get_bss_load_update_timeout(struct hostapd_data *hapd, + unsigned int *sec, unsigned int *usec) +{ + unsigned int update_period = hapd->conf->bss_load_update_period; + unsigned int beacon_int = hapd->iconf->beacon_int; + unsigned int update_timeout; + + if (!update_period || !beacon_int) { + wpa_printf(MSG_ERROR, + "BSS Load: Invalid BSS load update configuration (period=%u beacon_int=%u)", + update_period, beacon_int); + return -1; + } + + update_timeout = update_period * beacon_int; + + *sec = ((update_timeout / 1000) * 1024) / 1000; + *usec = (update_timeout % 1000) * 1024; + + return 0; +} + + +static void update_channel_utilization(void *eloop_data, void *user_data) +{ + struct hostapd_data *hapd = eloop_data; + unsigned int sec, usec; + int err; + struct hostapd_iface *iface = hapd->iface; + + if (!(hapd->beacon_set_done && hapd->started)) + return; + + err = hostapd_drv_get_survey(hapd, hapd->iface->freq); + if (err) { + wpa_printf(MSG_ERROR, "BSS Load: Failed to get survey data"); + return; + } + + ieee802_11_set_beacon_per_bss_only(hapd); + + if (get_bss_load_update_timeout(hapd, &sec, &usec) < 0) + return; + + if (hapd->conf->chan_util_avg_period) { + iface->chan_util_samples_sum += iface->channel_utilization; + iface->chan_util_num_sample_periods += + hapd->conf->bss_load_update_period; + if (iface->chan_util_num_sample_periods >= + hapd->conf->chan_util_avg_period) { + iface->chan_util_average = + iface->chan_util_samples_sum / + (iface->chan_util_num_sample_periods / + hapd->conf->bss_load_update_period); + iface->chan_util_samples_sum = 0; + iface->chan_util_num_sample_periods = 0; + } + } + + eloop_register_timeout(sec, usec, update_channel_utilization, hapd, + NULL); +} + + +int bss_load_update_init(struct hostapd_data *hapd) +{ + unsigned int sec, usec; + + if (get_bss_load_update_timeout(hapd, &sec, &usec) < 0) + return -1; + + eloop_register_timeout(sec, usec, update_channel_utilization, hapd, + NULL); + return 0; +} + + +void bss_load_update_deinit(struct hostapd_data *hapd) +{ + eloop_cancel_timeout(update_channel_utilization, hapd, NULL); +} diff --git a/src/ap/bss_load.h b/src/ap/bss_load.h new file mode 100644 index 0000000..ac3c793 --- /dev/null +++ b/src/ap/bss_load.h @@ -0,0 +1,17 @@ +/* + * BSS load update + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef BSS_LOAD_UPDATE_H +#define BSS_LOAD_UPDATE_H + + +int bss_load_update_init(struct hostapd_data *hapd); +void bss_load_update_deinit(struct hostapd_data *hapd); + + +#endif /* BSS_LOAD_UPDATE_H */ diff --git a/src/ap/comeback_token.c b/src/ap/comeback_token.c new file mode 100644 index 0000000..8d9f21b --- /dev/null +++ b/src/ap/comeback_token.c @@ -0,0 +1,139 @@ +/* + * hostapd / Comeback token mechanism for SAE + * Copyright (c) 2002-2017, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "hostapd.h" +#include "crypto/sha256.h" +#include "crypto/random.h" +#include "common/ieee802_11_defs.h" +#include "comeback_token.h" + + +#if defined(CONFIG_SAE) || defined(CONFIG_PASN) + +static int comeback_token_hash(const u8 *comeback_key, const u8 *addr, u8 *idx) +{ + u8 hash[SHA256_MAC_LEN]; + + if (hmac_sha256(comeback_key, COMEBACK_KEY_SIZE, + addr, ETH_ALEN, hash) < 0) + return -1; + *idx = hash[0]; + return 0; +} + + +int check_comeback_token(const u8 *comeback_key, + u16 *comeback_pending_idx, const u8 *addr, + const u8 *token, size_t token_len) +{ + u8 mac[SHA256_MAC_LEN]; + const u8 *addrs[2]; + size_t len[2]; + u16 token_idx; + u8 idx; + + if (token_len != SHA256_MAC_LEN || + comeback_token_hash(comeback_key, addr, &idx) < 0) + return -1; + token_idx = comeback_pending_idx[idx]; + if (token_idx == 0 || token_idx != WPA_GET_BE16(token)) { + wpa_printf(MSG_DEBUG, + "Comeback: Invalid anti-clogging token from " + MACSTR " - token_idx 0x%04x, expected 0x%04x", + MAC2STR(addr), WPA_GET_BE16(token), token_idx); + return -1; + } + + addrs[0] = addr; + len[0] = ETH_ALEN; + addrs[1] = token; + len[1] = 2; + if (hmac_sha256_vector(comeback_key, COMEBACK_KEY_SIZE, + 2, addrs, len, mac) < 0 || + os_memcmp_const(token + 2, &mac[2], SHA256_MAC_LEN - 2) != 0) + return -1; + + comeback_pending_idx[idx] = 0; /* invalidate used token */ + + return 0; +} + + +struct wpabuf * +auth_build_token_req(struct os_reltime *last_comeback_key_update, + u8 *comeback_key, u16 comeback_idx, + u16 *comeback_pending_idx, size_t idx_len, + int group, const u8 *addr, int h2e) +{ + struct wpabuf *buf; + u8 *token; + struct os_reltime now; + u8 idx[2]; + const u8 *addrs[2]; + size_t len[2]; + u8 p_idx; + u16 token_idx; + + os_get_reltime(&now); + if (!os_reltime_initialized(last_comeback_key_update) || + os_reltime_expired(&now, last_comeback_key_update, 60) || + comeback_idx == 0xffff) { + if (random_get_bytes(comeback_key, COMEBACK_KEY_SIZE) < 0) + return NULL; + wpa_hexdump(MSG_DEBUG, "Comeback: Updated token key", + comeback_key, COMEBACK_KEY_SIZE); + *last_comeback_key_update = now; + comeback_idx = 0; + os_memset(comeback_pending_idx, 0, idx_len); + } + + buf = wpabuf_alloc(sizeof(le16) + 3 + SHA256_MAC_LEN); + if (buf == NULL) + return NULL; + + if (group) + wpabuf_put_le16(buf, group); /* Finite Cyclic Group */ + + if (h2e) { + /* Encapsulate Anti-clogging Token field in a container IE */ + wpabuf_put_u8(buf, WLAN_EID_EXTENSION); + wpabuf_put_u8(buf, 1 + SHA256_MAC_LEN); + wpabuf_put_u8(buf, WLAN_EID_EXT_ANTI_CLOGGING_TOKEN); + } + + if (comeback_token_hash(comeback_key, addr, &p_idx) < 0) { + wpabuf_free(buf); + return NULL; + } + + token_idx = comeback_pending_idx[p_idx]; + if (!token_idx) { + comeback_idx++; + token_idx = comeback_idx; + comeback_pending_idx[p_idx] = token_idx; + } + WPA_PUT_BE16(idx, token_idx); + token = wpabuf_put(buf, SHA256_MAC_LEN); + addrs[0] = addr; + len[0] = ETH_ALEN; + addrs[1] = idx; + len[1] = sizeof(idx); + if (hmac_sha256_vector(comeback_key, COMEBACK_KEY_SIZE, + 2, addrs, len, token) < 0) { + wpabuf_free(buf); + return NULL; + } + WPA_PUT_BE16(token, token_idx); + + return buf; +} + +#endif /* defined(CONFIG_SAE) || defined(CONFIG_PASN) */ diff --git a/src/ap/comeback_token.h b/src/ap/comeback_token.h new file mode 100644 index 0000000..d5de9e6 --- /dev/null +++ b/src/ap/comeback_token.h @@ -0,0 +1,21 @@ +/* + * hostapd / Comeback token mechanism for SAE + * Copyright (c) 2002-2017, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef COMEBACK_TOKEN_H +#define COMEBACK_TOKEN_H + +int check_comeback_token(const u8 *comeback_key, + u16 *comeback_pending_idx, const u8 *addr, + const u8 *token, size_t token_len); +struct wpabuf * +auth_build_token_req(struct os_reltime *last_comeback_key_update, + u8 *comeback_key, u16 comeback_idx, + u16 *comeback_pending_idx, size_t idx_len, + int group, const u8 *addr, int h2e); + +#endif /* COMEBACK_TOKEN_H */ diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c new file mode 100644 index 0000000..d4d73de --- /dev/null +++ b/src/ap/ctrl_iface_ap.c @@ -0,0 +1,1622 @@ +/* + * Control interface for shared AP commands + * Copyright (c) 2004-2019, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "common/sae.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "fst/fst_ctrl_iface.h" +#include "hostapd.h" +#include "ieee802_1x.h" +#include "wpa_auth.h" +#include "ieee802_11.h" +#include "sta_info.h" +#include "wps_hostapd.h" +#include "p2p_hostapd.h" +#include "ctrl_iface_ap.h" +#include "ap_drv_ops.h" +#include "mbo_ap.h" +#include "taxonomy.h" +#include "wnm_ap.h" + + +static size_t hostapd_write_ht_mcs_bitmask(char *buf, size_t buflen, + size_t curr_len, const u8 *mcs_set) +{ + int ret; + size_t len = curr_len; + + ret = os_snprintf(buf + len, buflen - len, + "ht_mcs_bitmask="); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* 77 first bits (+ 3 reserved bits) */ + len += wpa_snprintf_hex(buf + len, buflen - len, mcs_set, 10); + + ret = os_snprintf(buf + len, buflen - len, "\n"); + if (os_snprintf_error(buflen - len, ret)) + return curr_len; + len += ret; + + return len; +} + + +static int hostapd_get_sta_conn_time(struct sta_info *sta, + struct hostap_sta_driver_data *data, + char *buf, size_t buflen) +{ + struct os_reltime age; + unsigned long secs; + int ret; + + if (sta->connected_time.sec) { + /* Locally maintained time in AP mode */ + os_reltime_age(&sta->connected_time, &age); + secs = (unsigned long) age.sec; + } else if (data->flags & STA_DRV_DATA_CONN_TIME) { + /* Time from the driver in mesh mode */ + secs = data->connected_sec; + } else { + return 0; + } + + ret = os_snprintf(buf, buflen, "connected_time=%lu\n", secs); + if (os_snprintf_error(buflen, ret)) + return 0; + return ret; +} + + +static int hostapd_get_sta_info(struct hostapd_data *hapd, + struct sta_info *sta, + char *buf, size_t buflen) +{ + struct hostap_sta_driver_data data; + int ret; + int len = 0; + + if (hostapd_drv_read_sta_data(hapd, &data, sta->addr) < 0) + return 0; + + ret = os_snprintf(buf, buflen, "rx_packets=%lu\ntx_packets=%lu\n" + "rx_bytes=%llu\ntx_bytes=%llu\ninactive_msec=%lu\n" + "signal=%d\n", + data.rx_packets, data.tx_packets, + data.rx_bytes, data.tx_bytes, data.inactive_msec, + data.signal); + if (os_snprintf_error(buflen, ret)) + return 0; + len += ret; + + ret = os_snprintf(buf + len, buflen - len, "rx_rate_info=%lu", + data.current_rx_rate / 100); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + if (data.flags & STA_DRV_DATA_RX_MCS) { + ret = os_snprintf(buf + len, buflen - len, " mcs %u", + data.rx_mcs); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + if (data.flags & STA_DRV_DATA_RX_VHT_MCS) { + ret = os_snprintf(buf + len, buflen - len, " vhtmcs %u", + data.rx_vhtmcs); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + if (data.flags & STA_DRV_DATA_RX_VHT_NSS) { + ret = os_snprintf(buf + len, buflen - len, " vhtnss %u", + data.rx_vht_nss); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + if (data.flags & STA_DRV_DATA_RX_SHORT_GI) { + ret = os_snprintf(buf + len, buflen - len, " shortGI"); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + ret = os_snprintf(buf + len, buflen - len, "\n"); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + + ret = os_snprintf(buf + len, buflen - len, "tx_rate_info=%lu", + data.current_tx_rate / 100); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + if (data.flags & STA_DRV_DATA_TX_MCS) { + ret = os_snprintf(buf + len, buflen - len, " mcs %u", + data.tx_mcs); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + if (data.flags & STA_DRV_DATA_TX_VHT_MCS) { + ret = os_snprintf(buf + len, buflen - len, " vhtmcs %u", + data.tx_vhtmcs); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + if (data.flags & STA_DRV_DATA_TX_VHT_NSS) { + ret = os_snprintf(buf + len, buflen - len, " vhtnss %u", + data.tx_vht_nss); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + if (data.flags & STA_DRV_DATA_TX_SHORT_GI) { + ret = os_snprintf(buf + len, buflen - len, " shortGI"); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + ret = os_snprintf(buf + len, buflen - len, "\n"); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + + if ((sta->flags & WLAN_STA_VHT) && sta->vht_capabilities) { + ret = os_snprintf(buf + len, buflen - len, + "rx_vht_mcs_map=%04x\n" + "tx_vht_mcs_map=%04x\n", + le_to_host16(sta->vht_capabilities-> + vht_supported_mcs_set.rx_map), + le_to_host16(sta->vht_capabilities-> + vht_supported_mcs_set.tx_map)); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + + if ((sta->flags & WLAN_STA_HT) && sta->ht_capabilities) { + len = hostapd_write_ht_mcs_bitmask(buf, buflen, len, + sta->ht_capabilities-> + supported_mcs_set); + } + + if (data.flags & STA_DRV_DATA_LAST_ACK_RSSI) { + ret = os_snprintf(buf + len, buflen - len, + "last_ack_signal=%d\n", data.last_ack_rssi); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + + len += hostapd_get_sta_conn_time(sta, &data, buf + len, buflen - len); + + return len; +} + + +static const char * timeout_next_str(int val) +{ + switch (val) { + case STA_NULLFUNC: + return "NULLFUNC POLL"; + case STA_DISASSOC: + return "DISASSOC"; + case STA_DEAUTH: + return "DEAUTH"; + case STA_REMOVE: + return "REMOVE"; + case STA_DISASSOC_FROM_CLI: + return "DISASSOC_FROM_CLI"; + default: + return "?"; + } +} + + +static const char * hw_mode_str(enum hostapd_hw_mode mode) +{ + switch (mode) { + case HOSTAPD_MODE_IEEE80211B: + return "b"; + case HOSTAPD_MODE_IEEE80211G: + return "g"; + case HOSTAPD_MODE_IEEE80211A: + return "a"; + case HOSTAPD_MODE_IEEE80211AD: + return "ad"; + case HOSTAPD_MODE_IEEE80211ANY: + return "any"; + case NUM_HOSTAPD_MODES: + return "invalid"; + } + return "unknown"; +} + + +static int hostapd_ctrl_iface_sta_mib(struct hostapd_data *hapd, + struct sta_info *sta, + char *buf, size_t buflen) +{ + int len, res, ret, i; + const char *keyid; + const u8 *dpp_pkhash; + + if (!sta) + return 0; + + len = 0; + ret = os_snprintf(buf + len, buflen - len, MACSTR "\nflags=", + MAC2STR(sta->addr)); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + ret = ap_sta_flags_txt(sta->flags, buf + len, buflen - len); + if (ret < 0) + return len; + len += ret; + + ret = os_snprintf(buf + len, buflen - len, "\naid=%d\ncapability=0x%x\n" + "listen_interval=%d\nsupported_rates=", + sta->aid, sta->capability, sta->listen_interval); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + for (i = 0; i < sta->supported_rates_len; i++) { + ret = os_snprintf(buf + len, buflen - len, "%02x%s", + sta->supported_rates[i], + i + 1 < sta->supported_rates_len ? " " : ""); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + ret = os_snprintf(buf + len, buflen - len, "\ntimeout_next=%s\n", + timeout_next_str(sta->timeout_next)); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + if (sta->max_idle_period) { + ret = os_snprintf(buf + len, buflen - len, + "max_idle_period=%d\n", sta->max_idle_period); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + res = ieee802_11_get_mib_sta(hapd, sta, buf + len, buflen - len); + if (res >= 0) + len += res; + res = wpa_get_mib_sta(sta->wpa_sm, buf + len, buflen - len); + if (res >= 0) + len += res; + res = ieee802_1x_get_mib_sta(hapd, sta, buf + len, buflen - len); + if (res >= 0) + len += res; + res = hostapd_wps_get_mib_sta(hapd, sta->addr, buf + len, + buflen - len); + if (res >= 0) + len += res; + res = hostapd_p2p_get_mib_sta(hapd, sta, buf + len, buflen - len); + if (res >= 0) + len += res; + + len += hostapd_get_sta_info(hapd, sta, buf + len, buflen - len); + +#ifdef CONFIG_SAE + if (sta->sae && sta->sae->state == SAE_ACCEPTED) { + res = os_snprintf(buf + len, buflen - len, "sae_group=%d\n", + sta->sae->group); + if (!os_snprintf_error(buflen - len, res)) + len += res; + } + + if (sta->sae && sta->sae->tmp) { + const u8 *pos; + unsigned int j, count; + struct wpabuf *groups = sta->sae->tmp->peer_rejected_groups; + + res = os_snprintf(buf + len, buflen - len, + "sae_rejected_groups="); + if (!os_snprintf_error(buflen - len, res)) + len += res; + + if (groups) { + pos = wpabuf_head(groups); + count = wpabuf_len(groups) / 2; + } else { + pos = NULL; + count = 0; + } + for (j = 0; pos && j < count; j++) { + res = os_snprintf(buf + len, buflen - len, "%s%d", + j == 0 ? "" : " ", WPA_GET_LE16(pos)); + if (!os_snprintf_error(buflen - len, res)) + len += res; + pos += 2; + } + + res = os_snprintf(buf + len, buflen - len, "\n"); + if (!os_snprintf_error(buflen - len, res)) + len += res; + } +#endif /* CONFIG_SAE */ + + if (sta->vlan_id > 0) { + res = os_snprintf(buf + len, buflen - len, "vlan_id=%d\n", + sta->vlan_id); + if (!os_snprintf_error(buflen - len, res)) + len += res; + } + + res = mbo_ap_get_info(sta, buf + len, buflen - len); + if (res >= 0) + len += res; + + if (sta->supp_op_classes && + buflen - len > (unsigned) (17 + 2 * sta->supp_op_classes[0])) { + res = os_snprintf(buf + len, buflen - len, "supp_op_classes="); + if (!os_snprintf_error(buflen - len, res)) + len += res; + len += wpa_snprintf_hex(buf + len, buflen - len, + sta->supp_op_classes + 1, + sta->supp_op_classes[0]); + res = os_snprintf(buf + len, buflen - len, "\n"); + if (!os_snprintf_error(buflen - len, res)) + len += res; + } + + if (sta->power_capab) { + ret = os_snprintf(buf + len, buflen - len, + "min_txpower=%d\n" + "max_txpower=%d\n", + sta->min_tx_power, sta->max_tx_power); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + +#ifdef CONFIG_IEEE80211AX + if ((sta->flags & WLAN_STA_HE) && sta->he_capab) { + res = os_snprintf(buf + len, buflen - len, "he_capab="); + if (!os_snprintf_error(buflen - len, res)) + len += res; + len += wpa_snprintf_hex(buf + len, buflen - len, + (const u8 *) sta->he_capab, + sta->he_capab_len); + res = os_snprintf(buf + len, buflen - len, "\n"); + if (!os_snprintf_error(buflen - len, res)) + len += res; + } +#endif /* CONFIG_IEEE80211AX */ + +#ifdef CONFIG_IEEE80211BE + if ((sta->flags & WLAN_STA_EHT) && sta->eht_capab) { + res = os_snprintf(buf + len, buflen - len, "eht_capab="); + if (!os_snprintf_error(buflen - len, res)) + len += res; + len += wpa_snprintf_hex(buf + len, buflen - len, + (const u8 *) sta->eht_capab, + sta->eht_capab_len); + res = os_snprintf(buf + len, buflen - len, "\n"); + if (!os_snprintf_error(buflen - len, res)) + len += res; + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_IEEE80211AC + if ((sta->flags & WLAN_STA_VHT) && sta->vht_capabilities) { + res = os_snprintf(buf + len, buflen - len, + "vht_caps_info=0x%08x\n", + le_to_host32(sta->vht_capabilities-> + vht_capabilities_info)); + if (!os_snprintf_error(buflen - len, res)) + len += res; + + res = os_snprintf(buf + len, buflen - len, "vht_capab="); + if (!os_snprintf_error(buflen - len, res)) + len += res; + len += wpa_snprintf_hex(buf + len, buflen - len, + (const u8 *) sta->vht_capabilities, + sizeof(*sta->vht_capabilities)); + res = os_snprintf(buf + len, buflen - len, "\n"); + if (!os_snprintf_error(buflen - len, res)) + len += res; + } +#endif /* CONFIG_IEEE80211AC */ + + if ((sta->flags & WLAN_STA_HT) && sta->ht_capabilities) { + res = os_snprintf(buf + len, buflen - len, + "ht_caps_info=0x%04x\n", + le_to_host16(sta->ht_capabilities-> + ht_capabilities_info)); + if (!os_snprintf_error(buflen - len, res)) + len += res; + } + + if (sta->ext_capability && + buflen - len > (unsigned) (11 + 2 * sta->ext_capability[0])) { + res = os_snprintf(buf + len, buflen - len, "ext_capab="); + if (!os_snprintf_error(buflen - len, res)) + len += res; + len += wpa_snprintf_hex(buf + len, buflen - len, + sta->ext_capability + 1, + sta->ext_capability[0]); + res = os_snprintf(buf + len, buflen - len, "\n"); + if (!os_snprintf_error(buflen - len, res)) + len += res; + } + + if (sta->flags & WLAN_STA_WDS && sta->ifname_wds) { + ret = os_snprintf(buf + len, buflen - len, + "wds_sta_ifname=%s\n", sta->ifname_wds); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + + keyid = ap_sta_wpa_get_keyid(hapd, sta); + if (keyid) { + ret = os_snprintf(buf + len, buflen - len, "keyid=%s\n", keyid); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + + dpp_pkhash = ap_sta_wpa_get_dpp_pkhash(hapd, sta); + if (dpp_pkhash) { + ret = os_snprintf(buf + len, buflen - len, "dpp_pkhash="); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + len += wpa_snprintf_hex(buf + len, buflen - len, dpp_pkhash, + SHA256_MAC_LEN); + ret = os_snprintf(buf + len, buflen - len, "\n"); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + +#ifdef CONFIG_IEEE80211BE + if (sta->mld_info.mld_sta) { + for (i = 0; i < MAX_NUM_MLD_LINKS; ++i) { + if (!sta->mld_info.links[i].valid) + continue; + ret = os_snprintf( + buf + len, buflen - len, + "peer_addr[%d]=" MACSTR "\n", + i, MAC2STR(sta->mld_info.links[i].peer_addr)); + if (!os_snprintf_error(buflen - len, ret)) + len += ret; + } + } +#endif /* CONFIG_IEEE80211BE */ + + return len; +} + + +int hostapd_ctrl_iface_sta_first(struct hostapd_data *hapd, + char *buf, size_t buflen) +{ + return hostapd_ctrl_iface_sta_mib(hapd, hapd->sta_list, buf, buflen); +} + + +int hostapd_ctrl_iface_sta(struct hostapd_data *hapd, const char *txtaddr, + char *buf, size_t buflen) +{ + u8 addr[ETH_ALEN]; + int ret; + const char *pos; + struct sta_info *sta; + + if (hwaddr_aton(txtaddr, addr)) { + ret = os_snprintf(buf, buflen, "FAIL\n"); + if (os_snprintf_error(buflen, ret)) + return 0; + return ret; + } + + sta = ap_get_sta(hapd, addr); + if (sta == NULL) + return -1; + + pos = os_strchr(txtaddr, ' '); + if (pos) { + pos++; + +#ifdef HOSTAPD_DUMP_STATE + if (os_strcmp(pos, "eapol") == 0) { + if (sta->eapol_sm == NULL) + return -1; + return eapol_auth_dump_state(sta->eapol_sm, buf, + buflen); + } +#endif /* HOSTAPD_DUMP_STATE */ + + return -1; + } + + ret = hostapd_ctrl_iface_sta_mib(hapd, sta, buf, buflen); + ret += fst_ctrl_iface_mb_info(addr, buf + ret, buflen - ret); + + return ret; +} + + +int hostapd_ctrl_iface_sta_next(struct hostapd_data *hapd, const char *txtaddr, + char *buf, size_t buflen) +{ + u8 addr[ETH_ALEN]; + struct sta_info *sta; + int ret; + + if (hwaddr_aton(txtaddr, addr) || + (sta = ap_get_sta(hapd, addr)) == NULL) { + ret = os_snprintf(buf, buflen, "FAIL\n"); + if (os_snprintf_error(buflen, ret)) + return 0; + return ret; + } + + if (!sta->next) + return 0; + + return hostapd_ctrl_iface_sta_mib(hapd, sta->next, buf, buflen); +} + + +#ifdef CONFIG_P2P_MANAGER +static int p2p_manager_disconnect(struct hostapd_data *hapd, u16 stype, + u8 minor_reason_code, const u8 *addr) +{ + struct ieee80211_mgmt *mgmt; + int ret; + u8 *pos; + + mgmt = os_zalloc(sizeof(*mgmt) + 100); + if (mgmt == NULL) + return -1; + + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, stype); + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "P2P: Disconnect STA " MACSTR + " with minor reason code %u (stype=%u (%s))", + MAC2STR(addr), minor_reason_code, stype, + fc2str(le_to_host16(mgmt->frame_control))); + + os_memcpy(mgmt->da, addr, ETH_ALEN); + os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN); + if (stype == WLAN_FC_STYPE_DEAUTH) { + mgmt->u.deauth.reason_code = + host_to_le16(WLAN_REASON_PREV_AUTH_NOT_VALID); + pos = mgmt->u.deauth.variable; + } else { + mgmt->u.disassoc.reason_code = + host_to_le16(WLAN_REASON_PREV_AUTH_NOT_VALID); + pos = mgmt->u.disassoc.variable; + } + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = 4 + 3 + 1; + WPA_PUT_BE32(pos, P2P_IE_VENDOR_TYPE); + pos += 4; + + *pos++ = P2P_ATTR_MINOR_REASON_CODE; + WPA_PUT_LE16(pos, 1); + pos += 2; + *pos++ = minor_reason_code; + + ret = hostapd_drv_send_mlme(hapd, mgmt, pos - (u8 *) mgmt, 0, NULL, 0, + 0); + os_free(mgmt); + + return ret < 0 ? -1 : 0; +} +#endif /* CONFIG_P2P_MANAGER */ + + +int hostapd_ctrl_iface_deauthenticate(struct hostapd_data *hapd, + const char *txtaddr) +{ + u8 addr[ETH_ALEN]; + struct sta_info *sta; + const char *pos; + u16 reason = WLAN_REASON_PREV_AUTH_NOT_VALID; + + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "CTRL_IFACE DEAUTHENTICATE %s", + txtaddr); + + if (hwaddr_aton(txtaddr, addr)) + return -1; + + pos = os_strstr(txtaddr, " reason="); + if (pos) + reason = atoi(pos + 8); + + pos = os_strstr(txtaddr, " test="); + if (pos) { + struct ieee80211_mgmt mgmt; + int encrypt; + + pos += 6; + encrypt = atoi(pos); + os_memset(&mgmt, 0, sizeof(mgmt)); + mgmt.frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_DEAUTH); + os_memcpy(mgmt.da, addr, ETH_ALEN); + os_memcpy(mgmt.sa, hapd->own_addr, ETH_ALEN); + os_memcpy(mgmt.bssid, hapd->own_addr, ETH_ALEN); + mgmt.u.deauth.reason_code = host_to_le16(reason); + if (hostapd_drv_send_mlme(hapd, (u8 *) &mgmt, + IEEE80211_HDRLEN + + sizeof(mgmt.u.deauth), + 0, NULL, 0, !encrypt) < 0) + return -1; + return 0; + } + +#ifdef CONFIG_P2P_MANAGER + pos = os_strstr(txtaddr, " p2p="); + if (pos) { + return p2p_manager_disconnect(hapd, WLAN_FC_STYPE_DEAUTH, + atoi(pos + 5), addr); + } +#endif /* CONFIG_P2P_MANAGER */ + + if (os_strstr(txtaddr, " tx=0")) + hostapd_drv_sta_remove(hapd, addr); + else + hostapd_drv_sta_deauth(hapd, addr, reason); + sta = ap_get_sta(hapd, addr); + if (sta) + ap_sta_deauthenticate(hapd, sta, reason); + else if (addr[0] == 0xff) + hostapd_free_stas(hapd); + + return 0; +} + + +int hostapd_ctrl_iface_disassociate(struct hostapd_data *hapd, + const char *txtaddr) +{ + u8 addr[ETH_ALEN]; + struct sta_info *sta; + const char *pos; + u16 reason = WLAN_REASON_PREV_AUTH_NOT_VALID; + + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "CTRL_IFACE DISASSOCIATE %s", + txtaddr); + + if (hwaddr_aton(txtaddr, addr)) + return -1; + + pos = os_strstr(txtaddr, " reason="); + if (pos) + reason = atoi(pos + 8); + + pos = os_strstr(txtaddr, " test="); + if (pos) { + struct ieee80211_mgmt mgmt; + int encrypt; + + pos += 6; + encrypt = atoi(pos); + os_memset(&mgmt, 0, sizeof(mgmt)); + mgmt.frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_DISASSOC); + os_memcpy(mgmt.da, addr, ETH_ALEN); + os_memcpy(mgmt.sa, hapd->own_addr, ETH_ALEN); + os_memcpy(mgmt.bssid, hapd->own_addr, ETH_ALEN); + mgmt.u.disassoc.reason_code = host_to_le16(reason); + if (hostapd_drv_send_mlme(hapd, (u8 *) &mgmt, + IEEE80211_HDRLEN + + sizeof(mgmt.u.deauth), + 0, NULL, 0, !encrypt) < 0) + return -1; + return 0; + } + +#ifdef CONFIG_P2P_MANAGER + pos = os_strstr(txtaddr, " p2p="); + if (pos) { + return p2p_manager_disconnect(hapd, WLAN_FC_STYPE_DISASSOC, + atoi(pos + 5), addr); + } +#endif /* CONFIG_P2P_MANAGER */ + + if (os_strstr(txtaddr, " tx=0")) + hostapd_drv_sta_remove(hapd, addr); + else + hostapd_drv_sta_disassoc(hapd, addr, reason); + sta = ap_get_sta(hapd, addr); + if (sta) + ap_sta_disassociate(hapd, sta, reason); + else if (addr[0] == 0xff) + hostapd_free_stas(hapd); + + return 0; +} + + +#ifdef CONFIG_TAXONOMY +int hostapd_ctrl_iface_signature(struct hostapd_data *hapd, + const char *txtaddr, + char *buf, size_t buflen) +{ + u8 addr[ETH_ALEN]; + struct sta_info *sta; + + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "CTRL_IFACE SIGNATURE %s", txtaddr); + + if (hwaddr_aton(txtaddr, addr)) + return -1; + + sta = ap_get_sta(hapd, addr); + if (!sta) + return -1; + + return retrieve_sta_taxonomy(hapd, sta, buf, buflen); +} +#endif /* CONFIG_TAXONOMY */ + + +int hostapd_ctrl_iface_poll_sta(struct hostapd_data *hapd, + const char *txtaddr) +{ + u8 addr[ETH_ALEN]; + struct sta_info *sta; + + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "CTRL_IFACE POLL_STA %s", txtaddr); + + if (hwaddr_aton(txtaddr, addr)) + return -1; + + sta = ap_get_sta(hapd, addr); + if (!sta) + return -1; + + hostapd_drv_poll_client(hapd, hapd->own_addr, addr, + sta->flags & WLAN_STA_WMM); + return 0; +} + + +int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf, + size_t buflen) +{ + struct hostapd_iface *iface = hapd->iface; + struct hostapd_hw_modes *mode = iface->current_mode; + struct hostapd_config *iconf = hapd->iconf; + int len = 0, ret, j; + size_t i; + + ret = os_snprintf(buf + len, buflen - len, + "state=%s\n" + "phy=%s\n" + "freq=%d\n" + "num_sta_non_erp=%d\n" + "num_sta_no_short_slot_time=%d\n" + "num_sta_no_short_preamble=%d\n" + "olbc=%d\n" + "num_sta_ht_no_gf=%d\n" + "num_sta_no_ht=%d\n" + "num_sta_ht_20_mhz=%d\n" + "num_sta_ht40_intolerant=%d\n" + "olbc_ht=%d\n" + "ht_op_mode=0x%x\n", + hostapd_state_text(iface->state), + iface->phy, + iface->freq, + iface->num_sta_non_erp, + iface->num_sta_no_short_slot_time, + iface->num_sta_no_short_preamble, + iface->olbc, + iface->num_sta_ht_no_gf, + iface->num_sta_no_ht, + iface->num_sta_ht_20mhz, + iface->num_sta_ht40_intolerant, + iface->olbc_ht, + iface->ht_op_mode); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + if (mode) { + ret = os_snprintf(buf + len, buflen - len, "hw_mode=%s\n", + hw_mode_str(mode->mode)); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + if (iconf->country[0] && iconf->country[1]) { + ret = os_snprintf(buf + len, buflen - len, + "country_code=%c%c\ncountry3=0x%X\n", + iconf->country[0], iconf->country[1], + iconf->country[2]); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + if (!iface->cac_started || !iface->dfs_cac_ms) { + ret = os_snprintf(buf + len, buflen - len, + "cac_time_seconds=%d\n" + "cac_time_left_seconds=N/A\n", + iface->dfs_cac_ms / 1000); + } else { + /* CAC started and CAC time set - calculate remaining time */ + struct os_reltime now; + long left_time; + + os_reltime_age(&iface->dfs_cac_start, &now); + left_time = (long) iface->dfs_cac_ms / 1000 - now.sec; + ret = os_snprintf(buf + len, buflen - len, + "cac_time_seconds=%u\n" + "cac_time_left_seconds=%lu\n", + iface->dfs_cac_ms / 1000, + left_time > 0 ? left_time : 0); + } + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + ret = os_snprintf(buf + len, buflen - len, + "channel=%u\n" + "edmg_enable=%d\n" + "edmg_channel=%d\n" + "secondary_channel=%d\n" + "ieee80211n=%d\n" + "ieee80211ac=%d\n" + "ieee80211ax=%d\n" + "ieee80211be=%d\n" + "beacon_int=%u\n" + "dtim_period=%d\n", + iface->conf->channel, + iface->conf->enable_edmg, + iface->conf->edmg_channel, + iface->conf->ieee80211n && !hapd->conf->disable_11n ? + iface->conf->secondary_channel : 0, + iface->conf->ieee80211n && !hapd->conf->disable_11n, + iface->conf->ieee80211ac && + !hapd->conf->disable_11ac, + iface->conf->ieee80211ax && + !hapd->conf->disable_11ax, + iface->conf->ieee80211be && + !hapd->conf->disable_11be, + iface->conf->beacon_int, + hapd->conf->dtim_period); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + +#ifdef CONFIG_IEEE80211BE + if (iface->conf->ieee80211be && !hapd->conf->disable_11be) { + ret = os_snprintf(buf + len, buflen - len, + "eht_oper_chwidth=%d\n" + "eht_oper_centr_freq_seg0_idx=%d\n", + iface->conf->eht_oper_chwidth, + iface->conf->eht_oper_centr_freq_seg0_idx); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + if (is_6ghz_op_class(iface->conf->op_class) && + hostapd_get_oper_chwidth(iface->conf) == + CONF_OPER_CHWIDTH_320MHZ) { + ret = os_snprintf(buf + len, buflen - len, + "eht_bw320_offset=%d\n", + iface->conf->eht_bw320_offset); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + if (hapd->conf->mld_ap) { + struct hostapd_data *link_bss; + + ret = os_snprintf(buf + len, buflen - len, + "num_links=%d\n", + hapd->mld->num_links); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* Self BSS */ + ret = os_snprintf(buf + len, buflen - len, + "link_id=%d\n" + "link_addr=" MACSTR "\n", + hapd->mld_link_id, + MAC2STR(hapd->own_addr)); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* Partner BSSs */ + for_each_mld_link(link_bss, hapd) { + if (link_bss == hapd) + continue; + + ret = os_snprintf(buf + len, buflen - len, + "partner_link[%d]=" MACSTR + "\n", + link_bss->mld_link_id, + MAC2STR(link_bss->own_addr)); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + } + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_IEEE80211AX + if (iface->conf->ieee80211ax && !hapd->conf->disable_11ax) { + ret = os_snprintf(buf + len, buflen - len, + "he_oper_chwidth=%d\n" + "he_oper_centr_freq_seg0_idx=%d\n" + "he_oper_centr_freq_seg1_idx=%d\n", + iface->conf->he_oper_chwidth, + iface->conf->he_oper_centr_freq_seg0_idx, + iface->conf->he_oper_centr_freq_seg1_idx); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + if (!iconf->he_op.he_bss_color_disabled && + iconf->he_op.he_bss_color) { + ret = os_snprintf(buf + len, buflen - len, + "he_bss_color=%d\n", + iconf->he_op.he_bss_color); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + } +#endif /* CONFIG_IEEE80211AX */ + + if (iface->conf->ieee80211ac && !hapd->conf->disable_11ac) { + ret = os_snprintf(buf + len, buflen - len, + "vht_oper_chwidth=%d\n" + "vht_oper_centr_freq_seg0_idx=%d\n" + "vht_oper_centr_freq_seg1_idx=%d\n" + "vht_caps_info=%08x\n", + iface->conf->vht_oper_chwidth, + iface->conf->vht_oper_centr_freq_seg0_idx, + iface->conf->vht_oper_centr_freq_seg1_idx, + iface->conf->vht_capab); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + if (iface->conf->ieee80211ac && !hapd->conf->disable_11ac && mode) { + u16 rxmap = WPA_GET_LE16(&mode->vht_mcs_set[0]); + u16 txmap = WPA_GET_LE16(&mode->vht_mcs_set[4]); + + ret = os_snprintf(buf + len, buflen - len, + "rx_vht_mcs_map=%04x\n" + "tx_vht_mcs_map=%04x\n", + rxmap, txmap); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + if (iface->conf->ieee80211n && !hapd->conf->disable_11n) { + ret = os_snprintf(buf + len, buflen - len, + "ht_caps_info=%04x\n", + hapd->iconf->ht_capab); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + if (iface->conf->ieee80211n && !hapd->conf->disable_11n && mode) { + len = hostapd_write_ht_mcs_bitmask(buf, buflen, len, + mode->mcs_set); + } + + if (iface->current_rates && iface->num_rates) { + ret = os_snprintf(buf + len, buflen - len, "supported_rates="); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + for (j = 0; j < iface->num_rates; j++) { + ret = os_snprintf(buf + len, buflen - len, "%s%02x", + j > 0 ? " " : "", + iface->current_rates[j].rate / 5); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + ret = os_snprintf(buf + len, buflen - len, "\n"); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + for (j = 0; mode && j < mode->num_channels; j++) { + if (mode->channels[j].freq == iface->freq) { + ret = os_snprintf(buf + len, buflen - len, + "max_txpower=%u\n", + mode->channels[j].max_tx_power); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + break; + } + } + + for (i = 0; i < iface->num_bss; i++) { + struct hostapd_data *bss = iface->bss[i]; + ret = os_snprintf(buf + len, buflen - len, + "bss[%d]=%s\n" + "bssid[%d]=" MACSTR "\n" + "ssid[%d]=%s\n" + "num_sta[%d]=%d\n", + (int) i, bss->conf->iface, + (int) i, MAC2STR(bss->own_addr), + (int) i, + wpa_ssid_txt(bss->conf->ssid.ssid, + bss->conf->ssid.ssid_len), + (int) i, bss->num_sta); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + +#ifdef CONFIG_IEEE80211BE + if (bss->conf->mld_ap) { + ret = os_snprintf(buf + len, buflen - len, + "mld_addr[%d]=" MACSTR "\n" + "mld_id[%d]=%d\n" + "mld_link_id[%d]=%d\n", + (int) i, MAC2STR(bss->mld->mld_addr), + (int) i, hostapd_get_mld_id(bss), + (int) i, bss->mld_link_id); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } +#endif /* CONFIG_IEEE80211BE */ + } + + if (hapd->conf->chan_util_avg_period) { + ret = os_snprintf(buf + len, buflen - len, + "chan_util_avg=%u\n", + iface->chan_util_average); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + return len; +} + + +int hostapd_parse_csa_settings(const char *pos, + struct csa_settings *settings) +{ + char *end; + + os_memset(settings, 0, sizeof(*settings)); + settings->cs_count = strtol(pos, &end, 10); + if (pos == end) { + wpa_printf(MSG_ERROR, "chanswitch: invalid cs_count provided"); + return -1; + } + + settings->freq_params.freq = atoi(end); + if (settings->freq_params.freq == 0) { + wpa_printf(MSG_ERROR, "chanswitch: invalid freq provided"); + return -1; + } + +#define SET_CSA_SETTING(str) \ + do { \ + const char *pos2 = os_strstr(pos, " " #str "="); \ + if (pos2) { \ + pos2 += sizeof(" " #str "=") - 1; \ + settings->freq_params.str = atoi(pos2); \ + } \ + } while (0) + +#define SET_CSA_SETTING_EXT(str) \ + do { \ + const char *pos2 = os_strstr(pos, " " #str "="); \ + if (pos2) { \ + pos2 += sizeof(" " #str "=") - 1; \ + settings->str = atoi(pos2); \ + } \ + } while (0) + + SET_CSA_SETTING(center_freq1); + SET_CSA_SETTING(center_freq2); + SET_CSA_SETTING(bandwidth); + SET_CSA_SETTING(sec_channel_offset); + SET_CSA_SETTING_EXT(punct_bitmap); + settings->freq_params.ht_enabled = !!os_strstr(pos, " ht"); + settings->freq_params.vht_enabled = !!os_strstr(pos, " vht"); + settings->freq_params.he_enabled = !!os_strstr(pos, " he"); + settings->freq_params.eht_enabled = !!os_strstr(pos, " eht"); + settings->block_tx = !!os_strstr(pos, " blocktx"); +#undef SET_CSA_SETTING +#undef SET_CSA_SETTING_EXT + + return 0; +} + + +int hostapd_ctrl_iface_stop_ap(struct hostapd_data *hapd) +{ + return hostapd_drv_stop_ap(hapd); +} + + +int hostapd_ctrl_iface_pmksa_list(struct hostapd_data *hapd, char *buf, + size_t len) +{ + return wpa_auth_pmksa_list(hapd->wpa_auth, buf, len); +} + + +void hostapd_ctrl_iface_pmksa_flush(struct hostapd_data *hapd) +{ + wpa_auth_pmksa_flush(hapd->wpa_auth); +} + + +int hostapd_ctrl_iface_pmksa_add(struct hostapd_data *hapd, char *cmd) +{ + u8 spa[ETH_ALEN]; + u8 pmkid[PMKID_LEN]; + u8 pmk[PMK_LEN_MAX]; + size_t pmk_len; + char *pos, *pos2; + int akmp = 0, expiration = 0; + + /* + * Entry format: + * + */ + + if (hwaddr_aton(cmd, spa)) + return -1; + + pos = os_strchr(cmd, ' '); + if (!pos) + return -1; + pos++; + + if (hexstr2bin(pos, pmkid, PMKID_LEN) < 0) + return -1; + + pos = os_strchr(pos, ' '); + if (!pos) + return -1; + pos++; + + pos2 = os_strchr(pos, ' '); + if (!pos2) + return -1; + pmk_len = (pos2 - pos) / 2; + if (pmk_len < PMK_LEN || pmk_len > PMK_LEN_MAX || + hexstr2bin(pos, pmk, pmk_len) < 0) + return -1; + + pos = pos2 + 1; + + if (sscanf(pos, "%d %d", &expiration, &akmp) != 2) + return -1; + + return wpa_auth_pmksa_add2(hapd->wpa_auth, spa, pmk, pmk_len, + pmkid, expiration, akmp, NULL); +} + + +#ifdef CONFIG_PMKSA_CACHE_EXTERNAL +#ifdef CONFIG_MESH + +int hostapd_ctrl_iface_pmksa_list_mesh(struct hostapd_data *hapd, + const u8 *addr, char *buf, size_t len) +{ + return wpa_auth_pmksa_list_mesh(hapd->wpa_auth, addr, buf, len); +} + + +void * hostapd_ctrl_iface_pmksa_create_entry(const u8 *aa, char *cmd) +{ + u8 spa[ETH_ALEN]; + u8 pmkid[PMKID_LEN]; + u8 pmk[PMK_LEN_MAX]; + char *pos; + int expiration; + + /* + * Entry format: + * + */ + + if (hwaddr_aton(cmd, spa)) + return NULL; + + pos = os_strchr(cmd, ' '); + if (!pos) + return NULL; + pos++; + + if (hexstr2bin(pos, pmkid, PMKID_LEN) < 0) + return NULL; + + pos = os_strchr(pos, ' '); + if (!pos) + return NULL; + pos++; + + if (hexstr2bin(pos, pmk, PMK_LEN) < 0) + return NULL; + + pos = os_strchr(pos, ' '); + if (!pos) + return NULL; + pos++; + + if (sscanf(pos, "%d", &expiration) != 1) + return NULL; + + return wpa_auth_pmksa_create_entry(aa, spa, pmk, PMK_LEN, + WPA_KEY_MGMT_SAE, pmkid, expiration); +} + +#endif /* CONFIG_MESH */ +#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */ + + +#ifdef CONFIG_WNM_AP + +int hostapd_ctrl_iface_disassoc_imminent(struct hostapd_data *hapd, + const char *cmd) +{ + u8 addr[ETH_ALEN]; + int disassoc_timer; + struct sta_info *sta; + + if (hwaddr_aton(cmd, addr)) + return -1; + if (cmd[17] != ' ') + return -1; + disassoc_timer = atoi(cmd + 17); + + sta = ap_get_sta(hapd, addr); + if (sta == NULL) { + wpa_printf(MSG_DEBUG, "Station " MACSTR + " not found for disassociation imminent message", + MAC2STR(addr)); + return -1; + } + + return wnm_send_disassoc_imminent(hapd, sta, disassoc_timer); +} + + +int hostapd_ctrl_iface_ess_disassoc(struct hostapd_data *hapd, + const char *cmd) +{ + u8 addr[ETH_ALEN]; + const char *url, *timerstr; + int disassoc_timer; + struct sta_info *sta; + + if (hwaddr_aton(cmd, addr)) + return -1; + + sta = ap_get_sta(hapd, addr); + if (sta == NULL) { + wpa_printf(MSG_DEBUG, "Station " MACSTR + " not found for ESS disassociation imminent message", + MAC2STR(addr)); + return -1; + } + + timerstr = cmd + 17; + if (*timerstr != ' ') + return -1; + timerstr++; + disassoc_timer = atoi(timerstr); + if (disassoc_timer < 0 || disassoc_timer > 65535) + return -1; + + url = os_strchr(timerstr, ' '); + if (url == NULL) + return -1; + url++; + + return wnm_send_ess_disassoc_imminent(hapd, sta, url, disassoc_timer); +} + + +int hostapd_ctrl_iface_bss_tm_req(struct hostapd_data *hapd, + const char *cmd) +{ + u8 addr[ETH_ALEN]; + const char *pos, *end; + int disassoc_timer = 0; + struct sta_info *sta; + u8 req_mode = 0, valid_int = 0x01, dialog_token = 0x01; + u8 bss_term_dur[12]; + char *url = NULL; + int ret; + u8 nei_rep[1000]; + int nei_len; + u8 mbo[10]; + size_t mbo_len = 0; + + if (hwaddr_aton(cmd, addr)) { + wpa_printf(MSG_DEBUG, "Invalid STA MAC address"); + return -1; + } + + sta = ap_get_sta(hapd, addr); + if (sta == NULL) { + wpa_printf(MSG_DEBUG, "Station " MACSTR + " not found for BSS TM Request message", + MAC2STR(addr)); + return -1; + } + + pos = os_strstr(cmd, " disassoc_timer="); + if (pos) { + pos += 16; + disassoc_timer = atoi(pos); + if (disassoc_timer < 0 || disassoc_timer > 65535) { + wpa_printf(MSG_DEBUG, "Invalid disassoc_timer"); + return -1; + } + } + + pos = os_strstr(cmd, " valid_int="); + if (pos) { + pos += 11; + valid_int = atoi(pos); + } + + pos = os_strstr(cmd, " dialog_token="); + if (pos) { + pos += 14; + dialog_token = atoi(pos); + } + + pos = os_strstr(cmd, " bss_term="); + if (pos) { + pos += 10; + req_mode |= WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED; + /* TODO: TSF configurable/learnable */ + bss_term_dur[0] = 4; /* Subelement ID */ + bss_term_dur[1] = 10; /* Length */ + os_memset(&bss_term_dur[2], 0, 8); + end = os_strchr(pos, ','); + if (end == NULL) { + wpa_printf(MSG_DEBUG, "Invalid bss_term data"); + return -1; + } + end++; + WPA_PUT_LE16(&bss_term_dur[10], atoi(end)); + } + + nei_len = ieee802_11_parse_candidate_list(cmd, nei_rep, + sizeof(nei_rep)); + if (nei_len < 0) + return -1; + + pos = os_strstr(cmd, " url="); + if (pos) { + size_t len; + pos += 5; + end = os_strchr(pos, ' '); + if (end) + len = end - pos; + else + len = os_strlen(pos); + url = os_malloc(len + 1); + if (url == NULL) + return -1; + os_memcpy(url, pos, len); + url[len] = '\0'; + req_mode |= WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT; + } + + if (os_strstr(cmd, " pref=1")) + req_mode |= WNM_BSS_TM_REQ_PREF_CAND_LIST_INCLUDED; + if (os_strstr(cmd, " abridged=1")) + req_mode |= WNM_BSS_TM_REQ_ABRIDGED; + if (os_strstr(cmd, " disassoc_imminent=1")) + req_mode |= WNM_BSS_TM_REQ_DISASSOC_IMMINENT; + if (os_strstr(cmd, " link_removal_imminent=1")) + req_mode |= WNM_BSS_TM_REQ_LINK_REMOVAL_IMMINENT; + +#ifdef CONFIG_MBO + pos = os_strstr(cmd, "mbo="); + if (pos) { + unsigned int mbo_reason, cell_pref, reassoc_delay; + u8 *mbo_pos = mbo; + + ret = sscanf(pos, "mbo=%u:%u:%u", &mbo_reason, + &reassoc_delay, &cell_pref); + if (ret != 3) { + wpa_printf(MSG_DEBUG, + "MBO requires three arguments: mbo=::"); + ret = -1; + goto fail; + } + + if (mbo_reason > MBO_TRANSITION_REASON_PREMIUM_AP) { + wpa_printf(MSG_DEBUG, + "Invalid MBO transition reason code %u", + mbo_reason); + ret = -1; + goto fail; + } + + /* Valid values for Cellular preference are: 0, 1, 255 */ + if (cell_pref != 0 && cell_pref != 1 && cell_pref != 255) { + wpa_printf(MSG_DEBUG, + "Invalid MBO cellular capability %u", + cell_pref); + ret = -1; + goto fail; + } + + if (reassoc_delay > 65535 || + (reassoc_delay && + !(req_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT))) { + wpa_printf(MSG_DEBUG, + "MBO: Assoc retry delay is only valid in disassoc imminent mode"); + ret = -1; + goto fail; + } + + *mbo_pos++ = MBO_ATTR_ID_TRANSITION_REASON; + *mbo_pos++ = 1; + *mbo_pos++ = mbo_reason; + *mbo_pos++ = MBO_ATTR_ID_CELL_DATA_PREF; + *mbo_pos++ = 1; + *mbo_pos++ = cell_pref; + + if (reassoc_delay) { + *mbo_pos++ = MBO_ATTR_ID_ASSOC_RETRY_DELAY; + *mbo_pos++ = 2; + WPA_PUT_LE16(mbo_pos, reassoc_delay); + mbo_pos += 2; + } + + mbo_len = mbo_pos - mbo; + } +#endif /* CONFIG_MBO */ + + ret = wnm_send_bss_tm_req(hapd, sta, req_mode, disassoc_timer, + valid_int, bss_term_dur, dialog_token, url, + nei_len ? nei_rep : NULL, nei_len, + mbo_len ? mbo : NULL, mbo_len); +#ifdef CONFIG_MBO +fail: +#endif /* CONFIG_MBO */ + os_free(url); + return ret; +} + +#endif /* CONFIG_WNM_AP */ + + +int hostapd_ctrl_iface_acl_del_mac(struct mac_acl_entry **acl, int *num, + const char *txtaddr) +{ + u8 addr[ETH_ALEN]; + struct vlan_description vlan_id; + + if (!(*num)) + return 0; + + if (hwaddr_aton(txtaddr, addr)) + return -1; + + if (hostapd_maclist_found(*acl, *num, addr, &vlan_id)) + hostapd_remove_acl_mac(acl, num, addr); + + return 0; +} + + +void hostapd_ctrl_iface_acl_clear_list(struct mac_acl_entry **acl, + int *num) +{ + while (*num) + hostapd_remove_acl_mac(acl, num, (*acl)[0].addr); +} + + +int hostapd_ctrl_iface_acl_show_mac(struct mac_acl_entry *acl, int num, + char *buf, size_t buflen) +{ + int i = 0, len = 0, ret = 0; + + if (!acl) + return 0; + + while (i < num) { + ret = os_snprintf(buf + len, buflen - len, + MACSTR " VLAN_ID=%d\n", + MAC2STR(acl[i].addr), + acl[i].vlan_id.untagged); + if (ret < 0 || (size_t) ret >= buflen - len) + return len; + i++; + len += ret; + } + return len; +} + + +int hostapd_ctrl_iface_acl_add_mac(struct mac_acl_entry **acl, int *num, + const char *cmd) +{ + u8 addr[ETH_ALEN]; + struct vlan_description vlan_id; + int ret = 0, vlanid = 0; + const char *pos; + + if (hwaddr_aton(cmd, addr)) + return -1; + + pos = os_strstr(cmd, "VLAN_ID="); + if (pos) + vlanid = atoi(pos + 8); + + if (!hostapd_maclist_found(*acl, *num, addr, &vlan_id)) { + ret = hostapd_add_acl_maclist(acl, num, vlanid, addr); + if (ret != -1 && *acl) + qsort(*acl, *num, sizeof(**acl), hostapd_acl_comp); + } + + return ret < 0 ? -1 : 0; +} + + +int hostapd_disassoc_accept_mac(struct hostapd_data *hapd) +{ + struct sta_info *sta; + struct vlan_description vlan_id; + + if (hapd->conf->macaddr_acl != DENY_UNLESS_ACCEPTED) + return 0; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (!hostapd_maclist_found(hapd->conf->accept_mac, + hapd->conf->num_accept_mac, + sta->addr, &vlan_id) || + (vlan_id.notempty && + vlan_compare(&vlan_id, sta->vlan_desc))) + ap_sta_disconnect(hapd, sta, sta->addr, + WLAN_REASON_UNSPECIFIED); + } + + return 0; +} + + +int hostapd_disassoc_deny_mac(struct hostapd_data *hapd) +{ + struct sta_info *sta; + struct vlan_description vlan_id; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (hostapd_maclist_found(hapd->conf->deny_mac, + hapd->conf->num_deny_mac, sta->addr, + &vlan_id) && + (!vlan_id.notempty || + !vlan_compare(&vlan_id, sta->vlan_desc))) + ap_sta_disconnect(hapd, sta, sta->addr, + WLAN_REASON_UNSPECIFIED); + } + + return 0; +} diff --git a/src/ap/ctrl_iface_ap.h b/src/ap/ctrl_iface_ap.h new file mode 100644 index 0000000..614f042 --- /dev/null +++ b/src/ap/ctrl_iface_ap.h @@ -0,0 +1,57 @@ +/* + * Control interface for shared AP commands + * Copyright (c) 2004-2013, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef CTRL_IFACE_AP_H +#define CTRL_IFACE_AP_H + +int hostapd_ctrl_iface_sta_first(struct hostapd_data *hapd, + char *buf, size_t buflen); +int hostapd_ctrl_iface_sta(struct hostapd_data *hapd, const char *txtaddr, + char *buf, size_t buflen); +int hostapd_ctrl_iface_sta_next(struct hostapd_data *hapd, const char *txtaddr, + char *buf, size_t buflen); +int hostapd_ctrl_iface_deauthenticate(struct hostapd_data *hapd, + const char *txtaddr); +int hostapd_ctrl_iface_disassociate(struct hostapd_data *hapd, + const char *txtaddr); +int hostapd_ctrl_iface_signature(struct hostapd_data *hapd, + const char *txtaddr, + char *buf, size_t buflen); +int hostapd_ctrl_iface_poll_sta(struct hostapd_data *hapd, + const char *txtaddr); +int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf, + size_t buflen); +int hostapd_parse_csa_settings(const char *pos, + struct csa_settings *settings); +int hostapd_ctrl_iface_stop_ap(struct hostapd_data *hapd); +int hostapd_ctrl_iface_pmksa_list(struct hostapd_data *hapd, char *buf, + size_t len); +void hostapd_ctrl_iface_pmksa_flush(struct hostapd_data *hapd); +int hostapd_ctrl_iface_pmksa_add(struct hostapd_data *hapd, char *cmd); +int hostapd_ctrl_iface_pmksa_list_mesh(struct hostapd_data *hapd, + const u8 *addr, char *buf, size_t len); +void * hostapd_ctrl_iface_pmksa_create_entry(const u8 *aa, char *cmd); + +int hostapd_ctrl_iface_disassoc_imminent(struct hostapd_data *hapd, + const char *cmd); +int hostapd_ctrl_iface_ess_disassoc(struct hostapd_data *hapd, + const char *cmd); +int hostapd_ctrl_iface_bss_tm_req(struct hostapd_data *hapd, + const char *cmd); +int hostapd_ctrl_iface_acl_add_mac(struct mac_acl_entry **acl, int *num, + const char *cmd); +int hostapd_ctrl_iface_acl_del_mac(struct mac_acl_entry **acl, int *num, + const char *txtaddr); +void hostapd_ctrl_iface_acl_clear_list(struct mac_acl_entry **acl, + int *num); +int hostapd_ctrl_iface_acl_show_mac(struct mac_acl_entry *acl, int num, + char *buf, size_t buflen); +int hostapd_disassoc_accept_mac(struct hostapd_data *hapd); +int hostapd_disassoc_deny_mac(struct hostapd_data *hapd); + +#endif /* CTRL_IFACE_AP_H */ diff --git a/src/ap/dfs.c b/src/ap/dfs.c new file mode 100644 index 0000000..af9dc16 --- /dev/null +++ b/src/ap/dfs.c @@ -0,0 +1,1669 @@ +/* + * DFS - Dynamic Frequency Selection + * Copyright (c) 2002-2013, Jouni Malinen + * Copyright (c) 2013-2017, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "common/hw_features_common.h" +#include "common/wpa_ctrl.h" +#include "hostapd.h" +#include "beacon.h" +#include "ap_drv_ops.h" +#include "drivers/driver.h" +#include "dfs.h" + + +enum dfs_channel_type { + DFS_ANY_CHANNEL, + DFS_AVAILABLE, /* non-radar or radar-available */ + DFS_NO_CAC_YET, /* radar-not-yet-available */ +}; + +static struct hostapd_channel_data * +dfs_downgrade_bandwidth(struct hostapd_iface *iface, int *secondary_channel, + u8 *oper_centr_freq_seg0_idx, + u8 *oper_centr_freq_seg1_idx, + enum dfs_channel_type *channel_type); + + +static bool dfs_use_radar_background(struct hostapd_iface *iface) +{ + return (iface->drv_flags2 & WPA_DRIVER_FLAGS2_RADAR_BACKGROUND) && + iface->conf->enable_background_radar; +} + + +static int dfs_get_used_n_chans(struct hostapd_iface *iface, int *seg1) +{ + int n_chans = 1; + + *seg1 = 0; + + if (iface->conf->ieee80211n && iface->conf->secondary_channel) + n_chans = 2; + + if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) { + switch (hostapd_get_oper_chwidth(iface->conf)) { + case CONF_OPER_CHWIDTH_USE_HT: + break; + case CONF_OPER_CHWIDTH_80MHZ: + n_chans = 4; + break; + case CONF_OPER_CHWIDTH_160MHZ: + n_chans = 8; + break; + case CONF_OPER_CHWIDTH_80P80MHZ: + n_chans = 4; + *seg1 = 4; + break; + default: + break; + } + } + + return n_chans; +} + + +/* dfs_channel_available: select new channel according to type parameter */ +static int dfs_channel_available(struct hostapd_channel_data *chan, + enum dfs_channel_type type) +{ + if (type == DFS_NO_CAC_YET) { + /* Select only radar channel where CAC has not been + * performed yet + */ + if ((chan->flag & HOSTAPD_CHAN_RADAR) && + (chan->flag & HOSTAPD_CHAN_DFS_MASK) == + HOSTAPD_CHAN_DFS_USABLE) + return 1; + return 0; + } + + /* + * When radar detection happens, CSA is performed. However, there's no + * time for CAC, so radar channels must be skipped when finding a new + * channel for CSA, unless they are available for immediate use. + */ + if (type == DFS_AVAILABLE && (chan->flag & HOSTAPD_CHAN_RADAR) && + ((chan->flag & HOSTAPD_CHAN_DFS_MASK) != + HOSTAPD_CHAN_DFS_AVAILABLE)) + return 0; + + if (chan->flag & HOSTAPD_CHAN_DISABLED) + return 0; + if ((chan->flag & HOSTAPD_CHAN_RADAR) && + ((chan->flag & HOSTAPD_CHAN_DFS_MASK) == + HOSTAPD_CHAN_DFS_UNAVAILABLE)) + return 0; + return 1; +} + + +static int dfs_is_chan_allowed(struct hostapd_channel_data *chan, int n_chans) +{ + /* + * The tables contain first valid channel number based on channel width. + * We will also choose this first channel as the control one. + */ + int allowed_40[] = { 36, 44, 52, 60, 100, 108, 116, 124, 132, 149, 157, + 165, 173, 184, 192 }; + /* + * VHT80, valid channels based on center frequency: + * 42, 58, 106, 122, 138, 155, 171 + */ + int allowed_80[] = { 36, 52, 100, 116, 132, 149, 165 }; + /* + * VHT160 valid channels based on center frequency: + * 50, 114, 163 + */ + int allowed_160[] = { 36, 100, 149 }; + int *allowed = allowed_40; + unsigned int i, allowed_no = 0; + + switch (n_chans) { + case 2: + allowed = allowed_40; + allowed_no = ARRAY_SIZE(allowed_40); + break; + case 4: + allowed = allowed_80; + allowed_no = ARRAY_SIZE(allowed_80); + break; + case 8: + allowed = allowed_160; + allowed_no = ARRAY_SIZE(allowed_160); + break; + default: + wpa_printf(MSG_DEBUG, "Unknown width for %d channels", n_chans); + break; + } + + for (i = 0; i < allowed_no; i++) { + if (chan->chan == allowed[i]) + return 1; + } + + return 0; +} + + +static struct hostapd_channel_data * +dfs_get_chan_data(struct hostapd_hw_modes *mode, int freq, int first_chan_idx) +{ + int i; + + for (i = first_chan_idx; i < mode->num_channels; i++) { + if (mode->channels[i].freq == freq) + return &mode->channels[i]; + } + + return NULL; +} + + +static int dfs_chan_range_available(struct hostapd_hw_modes *mode, + int first_chan_idx, int num_chans, + enum dfs_channel_type type) +{ + struct hostapd_channel_data *first_chan, *chan; + int i; + u32 bw = num_chan_to_bw(num_chans); + + if (first_chan_idx + num_chans > mode->num_channels) { + wpa_printf(MSG_DEBUG, + "DFS: some channels in range not defined"); + return 0; + } + + first_chan = &mode->channels[first_chan_idx]; + + /* hostapd DFS implementation assumes the first channel as primary. + * If it's not allowed to use the first channel as primary, decline the + * whole channel range. */ + if (!chan_pri_allowed(first_chan)) { + wpa_printf(MSG_DEBUG, "DFS: primary channel not allowed"); + return 0; + } + + for (i = 0; i < num_chans; i++) { + chan = dfs_get_chan_data(mode, first_chan->freq + i * 20, + first_chan_idx); + if (!chan) { + wpa_printf(MSG_DEBUG, "DFS: no channel data for %d", + first_chan->freq + i * 20); + return 0; + } + + /* HT 40 MHz secondary channel availability checked only for + * primary channel */ + if (!chan_bw_allowed(chan, bw, 1, !i)) { + wpa_printf(MSG_DEBUG, "DFS: bw now allowed for %d", + first_chan->freq + i * 20); + return 0; + } + + if (!dfs_channel_available(chan, type)) { + wpa_printf(MSG_DEBUG, "DFS: channel not available %d", + first_chan->freq + i * 20); + return 0; + } + } + + return 1; +} + + +static int is_in_chanlist(struct hostapd_iface *iface, + struct hostapd_channel_data *chan) +{ + if (!iface->conf->acs_ch_list.num) + return 1; + + return freq_range_list_includes(&iface->conf->acs_ch_list, chan->chan); +} + + +/* + * The function assumes HT40+ operation. + * Make sure to adjust the following variables after calling this: + * - hapd->secondary_channel + * - hapd->vht/he_oper_centr_freq_seg0_idx + * - hapd->vht/he_oper_centr_freq_seg1_idx + */ +static int dfs_find_channel(struct hostapd_iface *iface, + struct hostapd_channel_data **ret_chan, + int idx, enum dfs_channel_type type) +{ + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *chan; + int i, channel_idx = 0, n_chans, n_chans1; + + mode = iface->current_mode; + n_chans = dfs_get_used_n_chans(iface, &n_chans1); + + wpa_printf(MSG_DEBUG, "DFS new chan checking %d channels", n_chans); + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + + /* Skip HT40/VHT incompatible channels */ + if (iface->conf->ieee80211n && + iface->conf->secondary_channel && + (!dfs_is_chan_allowed(chan, n_chans) || + !(chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P))) { + wpa_printf(MSG_DEBUG, + "DFS: channel %d (%d) is incompatible", + chan->freq, chan->chan); + continue; + } + + /* Skip incompatible chandefs */ + if (!dfs_chan_range_available(mode, i, n_chans, type)) { + wpa_printf(MSG_DEBUG, + "DFS: range not available for %d (%d)", + chan->freq, chan->chan); + continue; + } + + if (!is_in_chanlist(iface, chan)) { + wpa_printf(MSG_DEBUG, + "DFS: channel %d (%d) not in chanlist", + chan->freq, chan->chan); + continue; + } + + if (chan->max_tx_power < iface->conf->min_tx_power) + continue; + + if ((chan->flag & HOSTAPD_CHAN_INDOOR_ONLY) && + iface->conf->country[2] == 0x4f) + continue; + + if (ret_chan && idx == channel_idx) { + wpa_printf(MSG_DEBUG, "Selected channel %d (%d)", + chan->freq, chan->chan); + *ret_chan = chan; + return idx; + } + wpa_printf(MSG_DEBUG, "Adding channel %d (%d)", + chan->freq, chan->chan); + channel_idx++; + } + return channel_idx; +} + + +static void dfs_adjust_center_freq(struct hostapd_iface *iface, + struct hostapd_channel_data *chan, + int secondary_channel, + int sec_chan_idx_80p80, + u8 *oper_centr_freq_seg0_idx, + u8 *oper_centr_freq_seg1_idx) +{ + if (!iface->conf->ieee80211ac && !iface->conf->ieee80211ax) + return; + + if (!chan) + return; + + *oper_centr_freq_seg1_idx = 0; + + switch (hostapd_get_oper_chwidth(iface->conf)) { + case CONF_OPER_CHWIDTH_USE_HT: + if (secondary_channel == 1) + *oper_centr_freq_seg0_idx = chan->chan + 2; + else if (secondary_channel == -1) + *oper_centr_freq_seg0_idx = chan->chan - 2; + else + *oper_centr_freq_seg0_idx = chan->chan; + break; + case CONF_OPER_CHWIDTH_80MHZ: + *oper_centr_freq_seg0_idx = chan->chan + 6; + break; + case CONF_OPER_CHWIDTH_160MHZ: + *oper_centr_freq_seg0_idx = chan->chan + 14; + break; + case CONF_OPER_CHWIDTH_80P80MHZ: + *oper_centr_freq_seg0_idx = chan->chan + 6; + *oper_centr_freq_seg1_idx = sec_chan_idx_80p80 + 6; + break; + + default: + wpa_printf(MSG_INFO, + "DFS: Unsupported channel width configuration"); + *oper_centr_freq_seg0_idx = 0; + break; + } + + wpa_printf(MSG_DEBUG, "DFS adjusting VHT center frequency: %d, %d", + *oper_centr_freq_seg0_idx, + *oper_centr_freq_seg1_idx); +} + + +/* Return start channel idx we will use for mode->channels[idx] */ +static int dfs_get_start_chan_idx(struct hostapd_iface *iface, int *seg1_start) +{ + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *chan; + int channel_no = iface->conf->channel; + int res = -1, i; + int chan_seg1 = -1; + + *seg1_start = -1; + + /* HT40- */ + if (iface->conf->ieee80211n && iface->conf->secondary_channel == -1) + channel_no -= 4; + + /* VHT/HE/EHT */ + if (iface->conf->ieee80211ac || iface->conf->ieee80211ax || + iface->conf->ieee80211be) { + switch (hostapd_get_oper_chwidth(iface->conf)) { + case CONF_OPER_CHWIDTH_USE_HT: + break; + case CONF_OPER_CHWIDTH_80MHZ: + channel_no = hostapd_get_oper_centr_freq_seg0_idx( + iface->conf) - 6; + break; + case CONF_OPER_CHWIDTH_160MHZ: + channel_no = hostapd_get_oper_centr_freq_seg0_idx( + iface->conf) - 14; + break; + case CONF_OPER_CHWIDTH_80P80MHZ: + channel_no = hostapd_get_oper_centr_freq_seg0_idx( + iface->conf) - 6; + chan_seg1 = hostapd_get_oper_centr_freq_seg1_idx( + iface->conf) - 6; + break; + case CONF_OPER_CHWIDTH_320MHZ: + channel_no = hostapd_get_oper_centr_freq_seg0_idx( + iface->conf) - 30; + break; + default: + wpa_printf(MSG_INFO, + "DFS only EHT20/40/80/160/80+80/320 is supported now"); + channel_no = -1; + break; + } + } + + /* Get idx */ + mode = iface->current_mode; + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + if (chan->chan == channel_no) { + res = i; + break; + } + } + + if (res != -1 && chan_seg1 > -1) { + int found = 0; + + /* Get idx for seg1 */ + mode = iface->current_mode; + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + if (chan->chan == chan_seg1) { + *seg1_start = i; + found = 1; + break; + } + } + if (!found) + res = -1; + } + + if (res == -1) { + wpa_printf(MSG_DEBUG, + "DFS chan_idx seems wrong; num-ch: %d ch-no: %d conf-ch-no: %d 11n: %d sec-ch: %d vht-oper-width: %d", + mode->num_channels, channel_no, iface->conf->channel, + iface->conf->ieee80211n, + iface->conf->secondary_channel, + hostapd_get_oper_chwidth(iface->conf)); + + for (i = 0; i < mode->num_channels; i++) { + wpa_printf(MSG_DEBUG, "Available channel: %d", + mode->channels[i].chan); + } + } + + return res; +} + + +/* At least one channel have radar flag */ +static int dfs_check_chans_radar(struct hostapd_iface *iface, + int start_chan_idx, int n_chans) +{ + struct hostapd_channel_data *channel; + struct hostapd_hw_modes *mode; + int i, res = 0; + + mode = iface->current_mode; + + for (i = 0; i < n_chans; i++) { + if (start_chan_idx + i >= mode->num_channels) + break; + channel = &mode->channels[start_chan_idx + i]; + if (channel->flag & HOSTAPD_CHAN_RADAR) + res++; + } + + return res; +} + + +/* All channels available */ +static int dfs_check_chans_available(struct hostapd_iface *iface, + int start_chan_idx, int n_chans) +{ + struct hostapd_channel_data *channel; + struct hostapd_hw_modes *mode; + int i; + + mode = iface->current_mode; + + for (i = 0; i < n_chans; i++) { + channel = &mode->channels[start_chan_idx + i]; + + if (channel->flag & HOSTAPD_CHAN_DISABLED) + break; + + if (!(channel->flag & HOSTAPD_CHAN_RADAR)) + continue; + + if ((channel->flag & HOSTAPD_CHAN_DFS_MASK) != + HOSTAPD_CHAN_DFS_AVAILABLE) + break; + } + + return i == n_chans; +} + + +/* At least one channel unavailable */ +static int dfs_check_chans_unavailable(struct hostapd_iface *iface, + int start_chan_idx, + int n_chans) +{ + struct hostapd_channel_data *channel; + struct hostapd_hw_modes *mode; + int i, res = 0; + + mode = iface->current_mode; + + for (i = 0; i < n_chans; i++) { + channel = &mode->channels[start_chan_idx + i]; + if (channel->flag & HOSTAPD_CHAN_DISABLED) + res++; + if ((channel->flag & HOSTAPD_CHAN_DFS_MASK) == + HOSTAPD_CHAN_DFS_UNAVAILABLE) + res++; + } + + return res; +} + + +static struct hostapd_channel_data * +dfs_get_valid_channel(struct hostapd_iface *iface, + int *secondary_channel, + u8 *oper_centr_freq_seg0_idx, + u8 *oper_centr_freq_seg1_idx, + enum dfs_channel_type type) +{ + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *chan = NULL; + struct hostapd_channel_data *chan2 = NULL; + int num_available_chandefs; + int chan_idx, chan_idx2; + int sec_chan_idx_80p80 = -1; + int i; + u32 _rand; + + wpa_printf(MSG_DEBUG, "DFS: Selecting random channel"); + *secondary_channel = 0; + *oper_centr_freq_seg0_idx = 0; + *oper_centr_freq_seg1_idx = 0; + + if (iface->current_mode == NULL) + return NULL; + + mode = iface->current_mode; + if (mode->mode != HOSTAPD_MODE_IEEE80211A) + return NULL; + + /* Get the count first */ + num_available_chandefs = dfs_find_channel(iface, NULL, 0, type); + wpa_printf(MSG_DEBUG, "DFS: num_available_chandefs=%d", + num_available_chandefs); + if (num_available_chandefs == 0) + return NULL; + + if (os_get_random((u8 *) &_rand, sizeof(_rand)) < 0) + return NULL; + chan_idx = _rand % num_available_chandefs; + wpa_printf(MSG_DEBUG, "DFS: Picked random entry from the list: %d/%d", + chan_idx, num_available_chandefs); + dfs_find_channel(iface, &chan, chan_idx, type); + if (!chan) { + wpa_printf(MSG_DEBUG, "DFS: no random channel found"); + return NULL; + } + wpa_printf(MSG_DEBUG, "DFS: got random channel %d (%d)", + chan->freq, chan->chan); + + /* dfs_find_channel() calculations assume HT40+ */ + if (iface->conf->secondary_channel) + *secondary_channel = 1; + else + *secondary_channel = 0; + + /* Get secondary channel for HT80P80 */ + if (hostapd_get_oper_chwidth(iface->conf) == + CONF_OPER_CHWIDTH_80P80MHZ) { + if (num_available_chandefs <= 1) { + wpa_printf(MSG_ERROR, + "only 1 valid chan, can't support 80+80"); + return NULL; + } + + /* + * Loop all channels except channel1 to find a valid channel2 + * that is not adjacent to channel1. + */ + for (i = 0; i < num_available_chandefs - 1; i++) { + /* start from chan_idx + 1, end when chan_idx - 1 */ + chan_idx2 = (chan_idx + 1 + i) % num_available_chandefs; + dfs_find_channel(iface, &chan2, chan_idx2, type); + if (chan2 && abs(chan2->chan - chan->chan) > 12) { + /* two channels are not adjacent */ + sec_chan_idx_80p80 = chan2->chan; + wpa_printf(MSG_DEBUG, + "DFS: got second chan: %d (%d)", + chan2->freq, chan2->chan); + break; + } + } + + /* Check if we got a valid secondary channel which is not + * adjacent to the first channel. + */ + if (sec_chan_idx_80p80 == -1) { + wpa_printf(MSG_INFO, + "DFS: failed to get chan2 for 80+80"); + return NULL; + } + } + + dfs_adjust_center_freq(iface, chan, + *secondary_channel, + sec_chan_idx_80p80, + oper_centr_freq_seg0_idx, + oper_centr_freq_seg1_idx); + + return chan; +} + + +static int dfs_set_valid_channel(struct hostapd_iface *iface, int skip_radar) +{ + struct hostapd_channel_data *channel; + u8 cf1 = 0, cf2 = 0; + int sec = 0; + + channel = dfs_get_valid_channel(iface, &sec, &cf1, &cf2, + skip_radar ? DFS_AVAILABLE : + DFS_ANY_CHANNEL); + if (!channel) { + wpa_printf(MSG_ERROR, "could not get valid channel"); + return -1; + } + + iface->freq = channel->freq; + iface->conf->channel = channel->chan; + iface->conf->secondary_channel = sec; + hostapd_set_oper_centr_freq_seg0_idx(iface->conf, cf1); + hostapd_set_oper_centr_freq_seg1_idx(iface->conf, cf2); + + return 0; +} + + +static int set_dfs_state_freq(struct hostapd_iface *iface, int freq, u32 state) +{ + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *chan = NULL; + int i; + + mode = iface->current_mode; + if (mode == NULL) + return 0; + + wpa_printf(MSG_DEBUG, "set_dfs_state 0x%X for %d MHz", state, freq); + for (i = 0; i < iface->current_mode->num_channels; i++) { + chan = &iface->current_mode->channels[i]; + if (chan->freq == freq) { + if (chan->flag & HOSTAPD_CHAN_RADAR) { + chan->flag &= ~HOSTAPD_CHAN_DFS_MASK; + chan->flag |= state; + return 1; /* Channel found */ + } + } + } + wpa_printf(MSG_WARNING, "Can't set DFS state for freq %d MHz", freq); + return 0; +} + + +static int set_dfs_state(struct hostapd_iface *iface, int freq, int ht_enabled, + int chan_offset, int chan_width, int cf1, + int cf2, u32 state) +{ + int n_chans = 1, i; + struct hostapd_hw_modes *mode; + int frequency = freq; + int frequency2 = 0; + int ret = 0; + + mode = iface->current_mode; + if (mode == NULL) + return 0; + + if (mode->mode != HOSTAPD_MODE_IEEE80211A) { + wpa_printf(MSG_WARNING, "current_mode != IEEE80211A"); + return 0; + } + + /* Seems cf1 and chan_width is enough here */ + switch (chan_width) { + case CHAN_WIDTH_20_NOHT: + case CHAN_WIDTH_20: + n_chans = 1; + if (frequency == 0) + frequency = cf1; + break; + case CHAN_WIDTH_40: + n_chans = 2; + frequency = cf1 - 10; + break; + case CHAN_WIDTH_80: + n_chans = 4; + frequency = cf1 - 30; + break; + case CHAN_WIDTH_80P80: + n_chans = 4; + frequency = cf1 - 30; + frequency2 = cf2 - 30; + break; + case CHAN_WIDTH_160: + n_chans = 8; + frequency = cf1 - 70; + break; + default: + wpa_printf(MSG_INFO, "DFS chan_width %d not supported", + chan_width); + break; + } + + wpa_printf(MSG_DEBUG, "DFS freq: %dMHz, n_chans: %d", frequency, + n_chans); + for (i = 0; i < n_chans; i++) { + ret += set_dfs_state_freq(iface, frequency, state); + frequency = frequency + 20; + + if (chan_width == CHAN_WIDTH_80P80) { + ret += set_dfs_state_freq(iface, frequency2, state); + frequency2 = frequency2 + 20; + } + } + + return ret; +} + + +static int dfs_are_channels_overlapped(struct hostapd_iface *iface, int freq, + int chan_width, int cf1, int cf2) +{ + int start_chan_idx, start_chan_idx1; + struct hostapd_hw_modes *mode; + struct hostapd_channel_data *chan; + int n_chans, n_chans1, i, j, frequency = freq, radar_n_chans = 1; + u8 radar_chan; + int res = 0; + + /* Our configuration */ + mode = iface->current_mode; + start_chan_idx = dfs_get_start_chan_idx(iface, &start_chan_idx1); + n_chans = dfs_get_used_n_chans(iface, &n_chans1); + + /* Check we are on DFS channel(s) */ + if (!dfs_check_chans_radar(iface, start_chan_idx, n_chans)) + return 0; + + /* Reported via radar event */ + switch (chan_width) { + case CHAN_WIDTH_20_NOHT: + case CHAN_WIDTH_20: + radar_n_chans = 1; + if (frequency == 0) + frequency = cf1; + break; + case CHAN_WIDTH_40: + radar_n_chans = 2; + frequency = cf1 - 10; + break; + case CHAN_WIDTH_80: + radar_n_chans = 4; + frequency = cf1 - 30; + break; + case CHAN_WIDTH_160: + radar_n_chans = 8; + frequency = cf1 - 70; + break; + default: + wpa_printf(MSG_INFO, "DFS chan_width %d not supported", + chan_width); + break; + } + + ieee80211_freq_to_chan(frequency, &radar_chan); + + for (i = 0; i < n_chans; i++) { + chan = &mode->channels[start_chan_idx + i]; + if (!(chan->flag & HOSTAPD_CHAN_RADAR)) + continue; + for (j = 0; j < radar_n_chans; j++) { + wpa_printf(MSG_DEBUG, "checking our: %d, radar: %d", + chan->chan, radar_chan + j * 4); + if (chan->chan == radar_chan + j * 4) + res++; + } + } + + wpa_printf(MSG_DEBUG, "overlapped: %d", res); + + return res; +} + + +static unsigned int dfs_get_cac_time(struct hostapd_iface *iface, + int start_chan_idx, int n_chans) +{ + struct hostapd_channel_data *channel; + struct hostapd_hw_modes *mode; + int i; + unsigned int cac_time_ms = 0; + + mode = iface->current_mode; + + for (i = 0; i < n_chans; i++) { + if (start_chan_idx + i >= mode->num_channels) + break; + channel = &mode->channels[start_chan_idx + i]; + if (!(channel->flag & HOSTAPD_CHAN_RADAR)) + continue; + if (channel->dfs_cac_ms > cac_time_ms) + cac_time_ms = channel->dfs_cac_ms; + } + + return cac_time_ms; +} + + +/* + * Main DFS handler + * 1 - continue channel/ap setup + * 0 - channel/ap setup will be continued after CAC + * -1 - hit critical error + */ +int hostapd_handle_dfs(struct hostapd_iface *iface) +{ + int res, n_chans, n_chans1, start_chan_idx, start_chan_idx1; + int skip_radar = 0; + + if (is_6ghz_freq(iface->freq)) + return 1; + + if (!iface->current_mode) { + /* + * This can happen with drivers that do not provide mode + * information and as such, cannot really use hostapd for DFS. + */ + wpa_printf(MSG_DEBUG, + "DFS: No current_mode information - assume no need to perform DFS operations by hostapd"); + return 1; + } + + iface->cac_started = 0; + + do { + /* Get start (first) channel for current configuration */ + start_chan_idx = dfs_get_start_chan_idx(iface, + &start_chan_idx1); + if (start_chan_idx == -1) + return -1; + + /* Get number of used channels, depend on width */ + n_chans = dfs_get_used_n_chans(iface, &n_chans1); + + /* Setup CAC time */ + iface->dfs_cac_ms = dfs_get_cac_time(iface, start_chan_idx, + n_chans); + + /* Check if any of configured channels require DFS */ + res = dfs_check_chans_radar(iface, start_chan_idx, n_chans); + wpa_printf(MSG_DEBUG, + "DFS %d channels required radar detection", + res); + if (!res) + return 1; + + /* Check if all channels are DFS available */ + res = dfs_check_chans_available(iface, start_chan_idx, n_chans); + wpa_printf(MSG_DEBUG, + "DFS all channels available, (SKIP CAC): %s", + res ? "yes" : "no"); + if (res) + return 1; + + /* Check if any of configured channels is unavailable */ + res = dfs_check_chans_unavailable(iface, start_chan_idx, + n_chans); + wpa_printf(MSG_DEBUG, "DFS %d chans unavailable - choose other channel: %s", + res, res ? "yes": "no"); + if (res) { + if (dfs_set_valid_channel(iface, skip_radar) < 0) { + hostapd_set_state(iface, HAPD_IFACE_DFS); + return 0; + } + } + } while (res); + + /* Finally start CAC */ + hostapd_set_state(iface, HAPD_IFACE_DFS); + wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz%s", iface->freq, + dfs_use_radar_background(iface) ? " (background)" : ""); + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_START + "freq=%d chan=%d sec_chan=%d, width=%d, seg0=%d, seg1=%d, cac_time=%ds", + iface->freq, + iface->conf->channel, iface->conf->secondary_channel, + hostapd_get_oper_chwidth(iface->conf), + hostapd_get_oper_centr_freq_seg0_idx(iface->conf), + hostapd_get_oper_centr_freq_seg1_idx(iface->conf), + iface->dfs_cac_ms / 1000); + + res = hostapd_start_dfs_cac( + iface, iface->conf->hw_mode, iface->freq, iface->conf->channel, + iface->conf->ieee80211n, iface->conf->ieee80211ac, + iface->conf->ieee80211ax, iface->conf->ieee80211be, + iface->conf->secondary_channel, + hostapd_get_oper_chwidth(iface->conf), + hostapd_get_oper_centr_freq_seg0_idx(iface->conf), + hostapd_get_oper_centr_freq_seg1_idx(iface->conf), + dfs_use_radar_background(iface)); + + if (res) { + wpa_printf(MSG_ERROR, "DFS start_dfs_cac() failed, %d", res); + return -1; + } + + if (dfs_use_radar_background(iface)) { + /* Cache background radar parameters. */ + iface->radar_background.channel = iface->conf->channel; + iface->radar_background.secondary_channel = + iface->conf->secondary_channel; + iface->radar_background.freq = iface->freq; + iface->radar_background.centr_freq_seg0_idx = + hostapd_get_oper_centr_freq_seg0_idx(iface->conf); + iface->radar_background.centr_freq_seg1_idx = + hostapd_get_oper_centr_freq_seg1_idx(iface->conf); + + /* + * Let's select a random channel according to the + * regulations and perform CAC on dedicated radar chain. + */ + res = dfs_set_valid_channel(iface, 1); + if (res < 0) + return res; + + iface->radar_background.temp_ch = 1; + return 1; + } + + return 0; +} + + +int hostapd_is_dfs_chan_available(struct hostapd_iface *iface) +{ + int n_chans, n_chans1, start_chan_idx, start_chan_idx1; + + /* Get the start (first) channel for current configuration */ + start_chan_idx = dfs_get_start_chan_idx(iface, &start_chan_idx1); + if (start_chan_idx < 0) + return 0; + + /* Get the number of used channels, depending on width */ + n_chans = dfs_get_used_n_chans(iface, &n_chans1); + + /* Check if all channels are DFS available */ + return dfs_check_chans_available(iface, start_chan_idx, n_chans); +} + + +static int hostapd_dfs_request_channel_switch(struct hostapd_iface *iface, + int channel, int freq, + int secondary_channel, + u8 current_vht_oper_chwidth, + u8 oper_centr_freq_seg0_idx, + u8 oper_centr_freq_seg1_idx) +{ + struct hostapd_hw_modes *cmode = iface->current_mode; + int ieee80211_mode = IEEE80211_MODE_AP, err; + struct csa_settings csa_settings; + u8 new_vht_oper_chwidth; + unsigned int i; + unsigned int num_err = 0; + + wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d", channel); + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NEW_CHANNEL + "freq=%d chan=%d sec_chan=%d", freq, channel, + secondary_channel); + + new_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf); + hostapd_set_oper_chwidth(iface->conf, current_vht_oper_chwidth); + + /* Setup CSA request */ + os_memset(&csa_settings, 0, sizeof(csa_settings)); + csa_settings.cs_count = 5; + csa_settings.block_tx = 1; + csa_settings.link_id = -1; +#ifdef CONFIG_IEEE80211BE + if (iface->bss[0]->conf->mld_ap) + csa_settings.link_id = iface->bss[0]->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_MESH + if (iface->mconf) + ieee80211_mode = IEEE80211_MODE_MESH; +#endif /* CONFIG_MESH */ + err = hostapd_set_freq_params(&csa_settings.freq_params, + iface->conf->hw_mode, + freq, channel, + iface->conf->enable_edmg, + iface->conf->edmg_channel, + iface->conf->ieee80211n, + iface->conf->ieee80211ac, + iface->conf->ieee80211ax, + iface->conf->ieee80211be, + secondary_channel, + new_vht_oper_chwidth, + oper_centr_freq_seg0_idx, + oper_centr_freq_seg1_idx, + cmode->vht_capab, + &cmode->he_capab[ieee80211_mode], + &cmode->eht_capab[ieee80211_mode], + hostapd_get_punct_bitmap(iface->bss[0])); + + if (err) { + wpa_printf(MSG_ERROR, + "DFS failed to calculate CSA freq params"); + hostapd_disable_iface(iface); + return err; + } + + for (i = 0; i < iface->num_bss; i++) { + err = hostapd_switch_channel(iface->bss[i], &csa_settings); + if (err) + num_err++; + } + + if (num_err == iface->num_bss) { + wpa_printf(MSG_WARNING, + "DFS failed to schedule CSA (%d) - trying fallback", + err); + iface->freq = freq; + iface->conf->channel = channel; + iface->conf->secondary_channel = secondary_channel; + hostapd_set_oper_chwidth(iface->conf, new_vht_oper_chwidth); + hostapd_set_oper_centr_freq_seg0_idx(iface->conf, + oper_centr_freq_seg0_idx); + hostapd_set_oper_centr_freq_seg1_idx(iface->conf, + oper_centr_freq_seg1_idx); + + hostapd_disable_iface(iface); + hostapd_enable_iface(iface); + + return 0; + } + + /* Channel configuration will be updated once CSA completes and + * ch_switch_notify event is received */ + wpa_printf(MSG_DEBUG, "DFS waiting channel switch event"); + + return 0; +} + + +static void hostapd_dfs_update_background_chain(struct hostapd_iface *iface) +{ + int sec = 0; + enum dfs_channel_type channel_type = DFS_NO_CAC_YET; + struct hostapd_channel_data *channel; + u8 oper_centr_freq_seg0_idx = 0; + u8 oper_centr_freq_seg1_idx = 0; + + /* + * Allow selection of DFS channel in ETSI to comply with + * uniform spreading. + */ + if (iface->dfs_domain == HOSTAPD_DFS_REGION_ETSI) + channel_type = DFS_ANY_CHANNEL; + + channel = dfs_get_valid_channel(iface, &sec, &oper_centr_freq_seg0_idx, + &oper_centr_freq_seg1_idx, + channel_type); + if (!channel || + channel->chan == iface->conf->channel || + channel->chan == iface->radar_background.channel) + channel = dfs_downgrade_bandwidth(iface, &sec, + &oper_centr_freq_seg0_idx, + &oper_centr_freq_seg1_idx, + &channel_type); + if (!channel || + hostapd_start_dfs_cac(iface, iface->conf->hw_mode, + channel->freq, channel->chan, + iface->conf->ieee80211n, + iface->conf->ieee80211ac, + iface->conf->ieee80211ax, + iface->conf->ieee80211be, + sec, hostapd_get_oper_chwidth(iface->conf), + oper_centr_freq_seg0_idx, + oper_centr_freq_seg1_idx, true)) { + wpa_printf(MSG_ERROR, "DFS failed to start CAC offchannel"); + iface->radar_background.channel = -1; + return; + } + + iface->radar_background.channel = channel->chan; + iface->radar_background.freq = channel->freq; + iface->radar_background.secondary_channel = sec; + iface->radar_background.centr_freq_seg0_idx = oper_centr_freq_seg0_idx; + iface->radar_background.centr_freq_seg1_idx = oper_centr_freq_seg1_idx; + + wpa_printf(MSG_DEBUG, + "%s: setting background chain to chan %d (%d MHz)", + __func__, channel->chan, channel->freq); +} + + +static bool +hostapd_dfs_is_background_event(struct hostapd_iface *iface, int freq) +{ + return dfs_use_radar_background(iface) && + iface->radar_background.channel != -1 && + iface->radar_background.freq == freq; +} + + +static int +hostapd_dfs_start_channel_switch_background(struct hostapd_iface *iface) +{ + u8 current_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf); + + iface->conf->channel = iface->radar_background.channel; + iface->freq = iface->radar_background.freq; + iface->conf->secondary_channel = + iface->radar_background.secondary_channel; + hostapd_set_oper_centr_freq_seg0_idx( + iface->conf, iface->radar_background.centr_freq_seg0_idx); + hostapd_set_oper_centr_freq_seg1_idx( + iface->conf, iface->radar_background.centr_freq_seg1_idx); + + hostapd_dfs_update_background_chain(iface); + + return hostapd_dfs_request_channel_switch( + iface, iface->conf->channel, iface->freq, + iface->conf->secondary_channel, current_vht_oper_chwidth, + hostapd_get_oper_centr_freq_seg0_idx(iface->conf), + hostapd_get_oper_centr_freq_seg1_idx(iface->conf)); +} + + +int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq, + int ht_enabled, int chan_offset, int chan_width, + int cf1, int cf2) +{ + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_COMPLETED + "success=%d freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d radar_detected=%d", + success, freq, ht_enabled, chan_offset, chan_width, cf1, cf2, + iface->radar_detected); + + if (success) { + /* Complete iface/ap configuration */ + if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) { + /* Complete AP configuration for the first bring up. If + * a radar was detected in this channel, interface setup + * will be handled in + * 1. hostapd_event_ch_switch() if switching to a + * non-DFS channel + * 2. on next CAC complete event if switching to another + * DFS channel. + */ + if (iface->state != HAPD_IFACE_ENABLED && + !iface->radar_detected) + hostapd_setup_interface_complete(iface, 0); + else + iface->cac_started = 0; + } else { + set_dfs_state(iface, freq, ht_enabled, chan_offset, + chan_width, cf1, cf2, + HOSTAPD_CHAN_DFS_AVAILABLE); + + /* + * Radar event from background chain for the selected + * channel. Perform CSA, move the main chain to the + * selected channel and configure the background chain + * to a new DFS channel. + */ + if (hostapd_dfs_is_background_event(iface, freq)) { + iface->radar_background.cac_started = 0; + if (!iface->radar_background.temp_ch) + return 0; + + iface->radar_background.temp_ch = 0; + return hostapd_dfs_start_channel_switch_background(iface); + } + + /* + * Just mark the channel available when CAC completion + * event is received in enabled state. CAC result could + * have been propagated from another radio having the + * same regulatory configuration. When CAC completion is + * received during non-HAPD_IFACE_ENABLED state, make + * sure the configured channel is available because this + * CAC completion event could have been propagated from + * another radio. + */ + if (iface->state != HAPD_IFACE_ENABLED && + hostapd_is_dfs_chan_available(iface)) { + hostapd_setup_interface_complete(iface, 0); + iface->cac_started = 0; + } + } + } else if (hostapd_dfs_is_background_event(iface, freq)) { + iface->radar_background.cac_started = 0; + hostapd_dfs_update_background_chain(iface); + } + + iface->radar_detected = false; + return 0; +} + + +int hostapd_dfs_pre_cac_expired(struct hostapd_iface *iface, int freq, + int ht_enabled, int chan_offset, int chan_width, + int cf1, int cf2) +{ + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_PRE_CAC_EXPIRED + "freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d", + freq, ht_enabled, chan_offset, chan_width, cf1, cf2); + + /* Proceed only if DFS is not offloaded to the driver */ + if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) + return 0; + + set_dfs_state(iface, freq, ht_enabled, chan_offset, chan_width, + cf1, cf2, HOSTAPD_CHAN_DFS_USABLE); + + return 0; +} + + +static struct hostapd_channel_data * +dfs_downgrade_bandwidth(struct hostapd_iface *iface, int *secondary_channel, + u8 *oper_centr_freq_seg0_idx, + u8 *oper_centr_freq_seg1_idx, + enum dfs_channel_type *channel_type) +{ + struct hostapd_channel_data *channel; + + for (;;) { + channel = dfs_get_valid_channel(iface, secondary_channel, + oper_centr_freq_seg0_idx, + oper_centr_freq_seg1_idx, + *channel_type); + if (channel) { + wpa_printf(MSG_DEBUG, "DFS: Selected channel: %d", + channel->chan); + return channel; + } + + if (*channel_type != DFS_ANY_CHANNEL) { + *channel_type = DFS_ANY_CHANNEL; + } else { + int oper_chwidth; + + oper_chwidth = hostapd_get_oper_chwidth(iface->conf); + if (oper_chwidth == CONF_OPER_CHWIDTH_USE_HT) + break; + *channel_type = DFS_AVAILABLE; + hostapd_set_oper_chwidth(iface->conf, oper_chwidth - 1); + } + } + + wpa_printf(MSG_INFO, + "%s: no DFS channels left, waiting for NOP to finish", + __func__); + return NULL; +} + + +static int hostapd_dfs_start_channel_switch_cac(struct hostapd_iface *iface) +{ + struct hostapd_channel_data *channel; + int secondary_channel; + u8 oper_centr_freq_seg0_idx = 0; + u8 oper_centr_freq_seg1_idx = 0; + enum dfs_channel_type channel_type = DFS_ANY_CHANNEL; + int err = 1; + + /* Radar detected during active CAC */ + iface->cac_started = 0; + channel = dfs_get_valid_channel(iface, &secondary_channel, + &oper_centr_freq_seg0_idx, + &oper_centr_freq_seg1_idx, + channel_type); + + if (!channel) { + channel = dfs_downgrade_bandwidth(iface, &secondary_channel, + &oper_centr_freq_seg0_idx, + &oper_centr_freq_seg1_idx, + &channel_type); + if (!channel) { + wpa_printf(MSG_ERROR, "No valid channel available"); + return err; + } + } + + wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d", + channel->chan); + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NEW_CHANNEL + "freq=%d chan=%d sec_chan=%d", channel->freq, + channel->chan, secondary_channel); + + iface->freq = channel->freq; + iface->conf->channel = channel->chan; + iface->conf->secondary_channel = secondary_channel; + hostapd_set_oper_centr_freq_seg0_idx(iface->conf, + oper_centr_freq_seg0_idx); + hostapd_set_oper_centr_freq_seg1_idx(iface->conf, + oper_centr_freq_seg1_idx); + err = 0; + + hostapd_setup_interface_complete(iface, err); + return err; +} + + +static int +hostapd_dfs_background_start_channel_switch(struct hostapd_iface *iface, + int freq) +{ + if (!dfs_use_radar_background(iface)) + return -1; /* Background radar chain not supported. */ + + wpa_printf(MSG_DEBUG, + "%s called (background CAC active: %s, CSA active: %s)", + __func__, iface->radar_background.cac_started ? "yes" : "no", + hostapd_csa_in_progress(iface) ? "yes" : "no"); + + /* Check if CSA in progress */ + if (hostapd_csa_in_progress(iface)) + return 0; + + if (hostapd_dfs_is_background_event(iface, freq)) { + /* + * Radar pattern is reported on the background chain. + * Just select a new random channel according to the + * regulations for monitoring. + */ + hostapd_dfs_update_background_chain(iface); + return 0; + } + + /* + * If background radar detection is supported and the radar channel + * monitored by the background chain is available switch to it without + * waiting for the CAC. + */ + if (iface->radar_background.channel == -1) + return -1; /* Background radar chain not available. */ + + if (iface->radar_background.cac_started) { + /* + * Background channel not available yet. Perform CAC on the + * main chain. + */ + iface->radar_background.temp_ch = 1; + return -1; + } + + return hostapd_dfs_start_channel_switch_background(iface); +} + + +static int hostapd_dfs_start_channel_switch(struct hostapd_iface *iface) +{ + struct hostapd_channel_data *channel; + int secondary_channel; + u8 oper_centr_freq_seg0_idx; + u8 oper_centr_freq_seg1_idx; + enum dfs_channel_type channel_type = DFS_AVAILABLE; + u8 current_vht_oper_chwidth = hostapd_get_oper_chwidth(iface->conf); + + wpa_printf(MSG_DEBUG, "%s called (CAC active: %s, CSA active: %s)", + __func__, iface->cac_started ? "yes" : "no", + hostapd_csa_in_progress(iface) ? "yes" : "no"); + + /* Check if CSA in progress */ + if (hostapd_csa_in_progress(iface)) + return 0; + + /* Check if active CAC */ + if (iface->cac_started) + return hostapd_dfs_start_channel_switch_cac(iface); + + /* + * Allow selection of DFS channel in ETSI to comply with + * uniform spreading. + */ + if (iface->dfs_domain == HOSTAPD_DFS_REGION_ETSI) + channel_type = DFS_ANY_CHANNEL; + + /* Perform channel switch/CSA */ + channel = dfs_get_valid_channel(iface, &secondary_channel, + &oper_centr_freq_seg0_idx, + &oper_centr_freq_seg1_idx, + channel_type); + + if (!channel) { + /* + * If there is no channel to switch immediately to, check if + * there is another channel where we can switch even if it + * requires to perform a CAC first. + */ + channel_type = DFS_ANY_CHANNEL; + channel = dfs_downgrade_bandwidth(iface, &secondary_channel, + &oper_centr_freq_seg0_idx, + &oper_centr_freq_seg1_idx, + &channel_type); + if (!channel) { + /* + * Toggle interface state to enter DFS state + * until NOP is finished. + */ + hostapd_disable_iface(iface); + hostapd_enable_iface(iface); + return 0; + } + + if (channel_type == DFS_ANY_CHANNEL) { + iface->freq = channel->freq; + iface->conf->channel = channel->chan; + iface->conf->secondary_channel = secondary_channel; + hostapd_set_oper_centr_freq_seg0_idx( + iface->conf, oper_centr_freq_seg0_idx); + hostapd_set_oper_centr_freq_seg1_idx( + iface->conf, oper_centr_freq_seg1_idx); + + hostapd_disable_iface(iface); + hostapd_enable_iface(iface); + return 0; + } + } + + return hostapd_dfs_request_channel_switch(iface, channel->chan, + channel->freq, + secondary_channel, + current_vht_oper_chwidth, + oper_centr_freq_seg0_idx, + oper_centr_freq_seg1_idx); +} + + +int hostapd_dfs_radar_detected(struct hostapd_iface *iface, int freq, + int ht_enabled, int chan_offset, int chan_width, + int cf1, int cf2) +{ + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_RADAR_DETECTED + "freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d", + freq, ht_enabled, chan_offset, chan_width, cf1, cf2); + + iface->radar_detected = true; + + /* Proceed only if DFS is not offloaded to the driver */ + if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) + return 0; + + if (!iface->conf->ieee80211h) + return 0; + + /* mark radar frequency as invalid */ + if (!set_dfs_state(iface, freq, ht_enabled, chan_offset, chan_width, + cf1, cf2, HOSTAPD_CHAN_DFS_UNAVAILABLE)) + return 0; + + if (!hostapd_dfs_is_background_event(iface, freq)) { + /* Skip if reported radar event not overlapped our channels */ + if (!dfs_are_channels_overlapped(iface, freq, chan_width, + cf1, cf2)) + return 0; + } + + if (hostapd_dfs_background_start_channel_switch(iface, freq)) { + /* Radar detected while operating, switch the channel. */ + return hostapd_dfs_start_channel_switch(iface); + } + + return 0; +} + + +int hostapd_dfs_nop_finished(struct hostapd_iface *iface, int freq, + int ht_enabled, int chan_offset, int chan_width, + int cf1, int cf2) +{ + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NOP_FINISHED + "freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d", + freq, ht_enabled, chan_offset, chan_width, cf1, cf2); + + /* Proceed only if DFS is not offloaded to the driver */ + if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) + return 0; + + /* TODO add correct implementation here */ + set_dfs_state(iface, freq, ht_enabled, chan_offset, chan_width, + cf1, cf2, HOSTAPD_CHAN_DFS_USABLE); + + if (iface->state == HAPD_IFACE_DFS && !iface->cac_started) { + /* Handle cases where all channels were initially unavailable */ + hostapd_handle_dfs(iface); + } else if (dfs_use_radar_background(iface) && + iface->radar_background.channel == -1) { + /* Reset radar background chain if disabled */ + hostapd_dfs_update_background_chain(iface); + } + + return 0; +} + + +int hostapd_is_dfs_required(struct hostapd_iface *iface) +{ + int n_chans, n_chans1, start_chan_idx, start_chan_idx1, res; + + if ((!(iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) && + !iface->conf->ieee80211h) || + !iface->current_mode || + iface->current_mode->mode != HOSTAPD_MODE_IEEE80211A) + return 0; + + /* Get start (first) channel for current configuration */ + start_chan_idx = dfs_get_start_chan_idx(iface, &start_chan_idx1); + if (start_chan_idx == -1) + return -1; + + /* Get number of used channels, depend on width */ + n_chans = dfs_get_used_n_chans(iface, &n_chans1); + + /* Check if any of configured channels require DFS */ + res = dfs_check_chans_radar(iface, start_chan_idx, n_chans); + if (res) + return res; + if (start_chan_idx1 >= 0 && n_chans1 > 0) + res = dfs_check_chans_radar(iface, start_chan_idx1, n_chans1); + return res; +} + + +int hostapd_dfs_start_cac(struct hostapd_iface *iface, int freq, + int ht_enabled, int chan_offset, int chan_width, + int cf1, int cf2) +{ + if (hostapd_dfs_is_background_event(iface, freq)) { + iface->radar_background.cac_started = 1; + } else { + /* This is called when the driver indicates that an offloaded + * DFS has started CAC. radar_detected might be set for previous + * DFS channel. Clear it for this new CAC process. */ + hostapd_set_state(iface, HAPD_IFACE_DFS); + iface->cac_started = 1; + + /* Clear radar_detected in case it is for the previous + * frequency. Also remove disabled link's information in RNR + * element from other links. */ + iface->radar_detected = false; + if (iface->interfaces && iface->interfaces->count > 1) + ieee802_11_set_beacons(iface); + } + /* TODO: How to check CAC time for ETSI weather channels? */ + iface->dfs_cac_ms = 60000; + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_START + "freq=%d chan=%d chan_offset=%d width=%d seg0=%d " + "seg1=%d cac_time=%ds%s", + freq, (freq - 5000) / 5, chan_offset, chan_width, cf1, cf2, + iface->dfs_cac_ms / 1000, + hostapd_dfs_is_background_event(iface, freq) ? + " (background)" : ""); + + os_get_reltime(&iface->dfs_cac_start); + return 0; +} + + +/* + * Main DFS handler for offloaded case. + * 2 - continue channel/AP setup for non-DFS channel + * 1 - continue channel/AP setup for DFS channel + * 0 - channel/AP setup will be continued after CAC + * -1 - hit critical error + */ +int hostapd_handle_dfs_offload(struct hostapd_iface *iface) +{ + int dfs_res; + + wpa_printf(MSG_DEBUG, "%s: iface->cac_started: %d", + __func__, iface->cac_started); + + /* + * If DFS has already been started, then we are being called from a + * callback to continue AP/channel setup. Reset the CAC start flag and + * return. + */ + if (iface->cac_started) { + wpa_printf(MSG_DEBUG, "%s: iface->cac_started: %d", + __func__, iface->cac_started); + iface->cac_started = 0; + return 1; + } + + dfs_res = hostapd_is_dfs_required(iface); + if (dfs_res > 0) { + wpa_printf(MSG_DEBUG, + "%s: freq %d MHz requires DFS for %d chans", + __func__, iface->freq, dfs_res); + return 0; + } + + wpa_printf(MSG_DEBUG, + "%s: freq %d MHz does not require DFS. Continue channel/AP setup", + __func__, iface->freq); + return 2; +} + + +int hostapd_is_dfs_overlap(struct hostapd_iface *iface, enum chan_width width, + int center_freq) +{ + struct hostapd_channel_data *chan; + struct hostapd_hw_modes *mode = iface->current_mode; + int half_width; + int res = 0; + int i; + + if (!iface->conf->ieee80211h || !mode || + mode->mode != HOSTAPD_MODE_IEEE80211A) + return 0; + + switch (width) { + case CHAN_WIDTH_20_NOHT: + case CHAN_WIDTH_20: + half_width = 10; + break; + case CHAN_WIDTH_40: + half_width = 20; + break; + case CHAN_WIDTH_80: + case CHAN_WIDTH_80P80: + half_width = 40; + break; + case CHAN_WIDTH_160: + half_width = 80; + break; + default: + wpa_printf(MSG_WARNING, "DFS chanwidth %d not supported", + width); + return 0; + } + + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + + if (!(chan->flag & HOSTAPD_CHAN_RADAR)) + continue; + + if ((chan->flag & HOSTAPD_CHAN_DFS_MASK) == + HOSTAPD_CHAN_DFS_AVAILABLE) + continue; + + if (center_freq - chan->freq < half_width && + chan->freq - center_freq < half_width) + res++; + } + + wpa_printf(MSG_DEBUG, "DFS CAC required: (%d, %d): in range: %s", + center_freq - half_width, center_freq + half_width, + res ? "yes" : "no"); + + return res; +} diff --git a/src/ap/dfs.h b/src/ap/dfs.h new file mode 100644 index 0000000..606c1b3 --- /dev/null +++ b/src/ap/dfs.h @@ -0,0 +1,36 @@ +/* + * DFS - Dynamic Frequency Selection + * Copyright (c) 2002-2013, Jouni Malinen + * Copyright (c) 2013-2017, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +#ifndef DFS_H +#define DFS_H + +int hostapd_handle_dfs(struct hostapd_iface *iface); + +int hostapd_dfs_complete_cac(struct hostapd_iface *iface, int success, int freq, + int ht_enabled, int chan_offset, int chan_width, + int cf1, int cf2); +int hostapd_dfs_pre_cac_expired(struct hostapd_iface *iface, int freq, + int ht_enabled, int chan_offset, int chan_width, + int cf1, int cf2); +int hostapd_dfs_radar_detected(struct hostapd_iface *iface, int freq, + int ht_enabled, + int chan_offset, int chan_width, + int cf1, int cf2); +int hostapd_dfs_nop_finished(struct hostapd_iface *iface, int freq, + int ht_enabled, + int chan_offset, int chan_width, int cf1, int cf2); +int hostapd_is_dfs_required(struct hostapd_iface *iface); +int hostapd_is_dfs_chan_available(struct hostapd_iface *iface); +int hostapd_dfs_start_cac(struct hostapd_iface *iface, int freq, + int ht_enabled, int chan_offset, int chan_width, + int cf1, int cf2); +int hostapd_handle_dfs_offload(struct hostapd_iface *iface); +int hostapd_is_dfs_overlap(struct hostapd_iface *iface, enum chan_width width, + int center_freq); + +#endif /* DFS_H */ diff --git a/src/ap/dhcp_snoop.c b/src/ap/dhcp_snoop.c new file mode 100644 index 0000000..551936b --- /dev/null +++ b/src/ap/dhcp_snoop.c @@ -0,0 +1,160 @@ +/* + * DHCP snooping for Proxy ARP + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/dhcp.h" +#include "l2_packet/l2_packet.h" +#include "hostapd.h" +#include "sta_info.h" +#include "ap_drv_ops.h" +#include "x_snoop.h" +#include "dhcp_snoop.h" + + +static const char * ipaddr_str(u32 addr) +{ + static char buf[17]; + + os_snprintf(buf, sizeof(buf), "%u.%u.%u.%u", + (addr >> 24) & 0xff, (addr >> 16) & 0xff, + (addr >> 8) & 0xff, addr & 0xff); + return buf; +} + + +static void handle_dhcp(void *ctx, const u8 *src_addr, const u8 *buf, + size_t len) +{ + struct hostapd_data *hapd = ctx; + const struct bootp_pkt *b; + struct sta_info *sta; + int exten_len; + const u8 *end, *pos; + int res, msgtype = 0, prefixlen = 32; + u32 subnet_mask = 0; + u16 ip_len; + + exten_len = len - ETH_HLEN - (sizeof(*b) - sizeof(b->exten)); + if (exten_len < 4) + return; + + b = (const struct bootp_pkt *) &buf[ETH_HLEN]; + ip_len = ntohs(b->iph.ip_len); + if (ip_len > (unsigned int) (len - ETH_HLEN)) + return; + + if (WPA_GET_BE32(b->exten) != DHCP_MAGIC) + return; + + /* Parse DHCP options */ + end = (const u8 *) b + ip_len; + pos = &b->exten[4]; + while (pos < end && *pos != DHCP_OPT_END) { + const u8 *opt = pos++; + + if (*opt == DHCP_OPT_PAD) + continue; + + if (pos >= end || 1 + *pos > end - pos) + break; + pos += *pos + 1; + if (pos >= end) + break; + + switch (*opt) { + case DHCP_OPT_SUBNET_MASK: + if (opt[1] == 4) + subnet_mask = WPA_GET_BE32(&opt[2]); + if (subnet_mask == 0) + return; + while (!(subnet_mask & 0x1)) { + subnet_mask >>= 1; + prefixlen--; + } + break; + case DHCP_OPT_MSG_TYPE: + if (opt[1]) + msgtype = opt[2]; + break; + default: + break; + } + } + +#ifdef CONFIG_HS20 + if (hapd->conf->disable_dgaf && is_broadcast_ether_addr(buf)) { + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (!(sta->flags & WLAN_STA_AUTHORIZED)) + continue; + x_snoop_mcast_to_ucast_convert_send(hapd, sta, + (u8 *) buf, len); + } + } +#endif /* CONFIG_HS20 */ + + if (msgtype == DHCPACK) { + if (b->your_ip == 0) + return; + + /* DHCPACK for DHCPREQUEST */ + sta = ap_get_sta(hapd, b->hw_addr); + if (!sta) + return; + + wpa_printf(MSG_DEBUG, "dhcp_snoop: Found DHCPACK for " MACSTR + " @ IPv4 address %s/%d", + MAC2STR(sta->addr), + ipaddr_str(be_to_host32(b->your_ip)), + prefixlen); + + if (sta->ipaddr == b->your_ip) + return; + + if (sta->ipaddr != 0) { + wpa_printf(MSG_DEBUG, + "dhcp_snoop: Removing IPv4 address %s from the ip neigh table", + ipaddr_str(be_to_host32(sta->ipaddr))); + hostapd_drv_br_delete_ip_neigh(hapd, 4, + (u8 *) &sta->ipaddr); + } + + res = hostapd_drv_br_add_ip_neigh(hapd, 4, (u8 *) &b->your_ip, + prefixlen, sta->addr); + if (res) { + wpa_printf(MSG_DEBUG, + "dhcp_snoop: Adding ip neigh table failed: %d", + res); + return; + } + sta->ipaddr = b->your_ip; + } +} + + +int dhcp_snoop_init(struct hostapd_data *hapd) +{ + hapd->sock_dhcp = x_snoop_get_l2_packet(hapd, handle_dhcp, + L2_PACKET_FILTER_DHCP); + if (hapd->sock_dhcp == NULL) { + wpa_printf(MSG_DEBUG, + "dhcp_snoop: Failed to initialize L2 packet processing for DHCP packet: %s", + strerror(errno)); + return -1; + } + + return 0; +} + + +void dhcp_snoop_deinit(struct hostapd_data *hapd) +{ + l2_packet_deinit(hapd->sock_dhcp); + hapd->sock_dhcp = NULL; +} diff --git a/src/ap/dhcp_snoop.h b/src/ap/dhcp_snoop.h new file mode 100644 index 0000000..93d0050 --- /dev/null +++ b/src/ap/dhcp_snoop.h @@ -0,0 +1,30 @@ +/* + * DHCP snooping for Proxy ARP + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef DHCP_SNOOP_H +#define DHCP_SNOOP_H + +#ifdef CONFIG_PROXYARP + +int dhcp_snoop_init(struct hostapd_data *hapd); +void dhcp_snoop_deinit(struct hostapd_data *hapd); + +#else /* CONFIG_PROXYARP */ + +static inline int dhcp_snoop_init(struct hostapd_data *hapd) +{ + return 0; +} + +static inline void dhcp_snoop_deinit(struct hostapd_data *hapd) +{ +} + +#endif /* CONFIG_PROXYARP */ + +#endif /* DHCP_SNOOP_H */ diff --git a/src/ap/dpp_hostapd.c b/src/ap/dpp_hostapd.c new file mode 100644 index 0000000..d1bffa8 --- /dev/null +++ b/src/ap/dpp_hostapd.c @@ -0,0 +1,4007 @@ +/* + * hostapd / DPP integration + * Copyright (c) 2017, Qualcomm Atheros, Inc. + * Copyright (c) 2018-2020, The Linux Foundation + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/dpp.h" +#include "common/gas.h" +#include "common/wpa_ctrl.h" +#include "crypto/random.h" +#include "hostapd.h" +#include "ap_drv_ops.h" +#include "gas_query_ap.h" +#include "gas_serv.h" +#include "wpa_auth.h" +#include "beacon.h" +#include "dpp_hostapd.h" + + +static void hostapd_dpp_reply_wait_timeout(void *eloop_ctx, void *timeout_ctx); +static void hostapd_dpp_auth_conf_wait_timeout(void *eloop_ctx, + void *timeout_ctx); +static void hostapd_dpp_auth_success(struct hostapd_data *hapd, int initiator); +static void hostapd_dpp_init_timeout(void *eloop_ctx, void *timeout_ctx); +static int hostapd_dpp_auth_init_next(struct hostapd_data *hapd); +static void hostapd_dpp_set_testing_options(struct hostapd_data *hapd, + struct dpp_authentication *auth); +static void hostapd_dpp_start_gas_client(struct hostapd_data *hapd); +#ifdef CONFIG_DPP2 +static void hostapd_dpp_reconfig_reply_wait_timeout(void *eloop_ctx, + void *timeout_ctx); +static void hostapd_dpp_handle_config_obj(struct hostapd_data *hapd, + struct dpp_authentication *auth, + struct dpp_config_obj *conf); +static int hostapd_dpp_process_conf_obj(void *ctx, + struct dpp_authentication *auth); +#endif /* CONFIG_DPP2 */ + +static const u8 broadcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + +/** + * hostapd_dpp_qr_code - Parse and add DPP bootstrapping info from a QR Code + * @hapd: Pointer to hostapd_data + * @cmd: DPP URI read from a QR Code + * Returns: Identifier of the stored info or -1 on failure + */ +int hostapd_dpp_qr_code(struct hostapd_data *hapd, const char *cmd) +{ + struct dpp_bootstrap_info *bi; + struct dpp_authentication *auth = hapd->dpp_auth; + + bi = dpp_add_qr_code(hapd->iface->interfaces->dpp, cmd); + if (!bi) + return -1; + + if (auth && auth->response_pending && + dpp_notify_new_qr_code(auth, bi) == 1) { + wpa_printf(MSG_DEBUG, + "DPP: Sending out pending authentication response"); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", + MAC2STR(auth->peer_mac_addr), auth->curr_freq, + DPP_PA_AUTHENTICATION_RESP); + hostapd_drv_send_action(hapd, auth->curr_freq, 0, + auth->peer_mac_addr, + wpabuf_head(hapd->dpp_auth->resp_msg), + wpabuf_len(hapd->dpp_auth->resp_msg)); + } + +#ifdef CONFIG_DPP2 + dpp_controller_new_qr_code(hapd->iface->interfaces->dpp, bi); +#endif /* CONFIG_DPP2 */ + + return bi->id; +} + + +/** + * hostapd_dpp_nfc_uri - Parse and add DPP bootstrapping info from NFC Tag (URI) + * @hapd: Pointer to hostapd_data + * @cmd: DPP URI read from a NFC Tag (URI NDEF message) + * Returns: Identifier of the stored info or -1 on failure + */ +int hostapd_dpp_nfc_uri(struct hostapd_data *hapd, const char *cmd) +{ + struct dpp_bootstrap_info *bi; + + bi = dpp_add_nfc_uri(hapd->iface->interfaces->dpp, cmd); + if (!bi) + return -1; + + return bi->id; +} + + +int hostapd_dpp_nfc_handover_req(struct hostapd_data *hapd, const char *cmd) +{ + const char *pos; + struct dpp_bootstrap_info *peer_bi, *own_bi; + + pos = os_strstr(cmd, " own="); + if (!pos) + return -1; + pos += 5; + own_bi = dpp_bootstrap_get_id(hapd->iface->interfaces->dpp, atoi(pos)); + if (!own_bi) + return -1; + + pos = os_strstr(cmd, " uri="); + if (!pos) + return -1; + pos += 5; + peer_bi = dpp_add_nfc_uri(hapd->iface->interfaces->dpp, pos); + if (!peer_bi) { + wpa_printf(MSG_INFO, + "DPP: Failed to parse URI from NFC Handover Request"); + return -1; + } + + if (dpp_nfc_update_bi(own_bi, peer_bi) < 0) + return -1; + + return peer_bi->id; +} + + +int hostapd_dpp_nfc_handover_sel(struct hostapd_data *hapd, const char *cmd) +{ + const char *pos; + struct dpp_bootstrap_info *peer_bi, *own_bi; + + pos = os_strstr(cmd, " own="); + if (!pos) + return -1; + pos += 5; + own_bi = dpp_bootstrap_get_id(hapd->iface->interfaces->dpp, atoi(pos)); + if (!own_bi) + return -1; + + pos = os_strstr(cmd, " uri="); + if (!pos) + return -1; + pos += 5; + peer_bi = dpp_add_nfc_uri(hapd->iface->interfaces->dpp, pos); + if (!peer_bi) { + wpa_printf(MSG_INFO, + "DPP: Failed to parse URI from NFC Handover Select"); + return -1; + } + + if (peer_bi->curve != own_bi->curve) { + wpa_printf(MSG_INFO, + "DPP: Peer (NFC Handover Selector) used different curve"); + return -1; + } + + return peer_bi->id; +} + + +static void hostapd_dpp_auth_resp_retry_timeout(void *eloop_ctx, + void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct dpp_authentication *auth = hapd->dpp_auth; + + if (!auth || !auth->resp_msg) + return; + + wpa_printf(MSG_DEBUG, + "DPP: Retry Authentication Response after timeout"); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", + MAC2STR(auth->peer_mac_addr), auth->curr_freq, + DPP_PA_AUTHENTICATION_RESP); + hostapd_drv_send_action(hapd, auth->curr_freq, 500, auth->peer_mac_addr, + wpabuf_head(auth->resp_msg), + wpabuf_len(auth->resp_msg)); +} + + +static void hostapd_dpp_auth_resp_retry(struct hostapd_data *hapd) +{ + struct dpp_authentication *auth = hapd->dpp_auth; + unsigned int wait_time, max_tries; + + if (!auth || !auth->resp_msg) + return; + + if (hapd->dpp_resp_max_tries) + max_tries = hapd->dpp_resp_max_tries; + else + max_tries = 5; + auth->auth_resp_tries++; + if (auth->auth_resp_tries >= max_tries) { + wpa_printf(MSG_INFO, + "DPP: No confirm received from initiator - stopping exchange"); + hostapd_drv_send_action_cancel_wait(hapd); + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; + return; + } + + if (hapd->dpp_resp_retry_time) + wait_time = hapd->dpp_resp_retry_time; + else + wait_time = 1000; + wpa_printf(MSG_DEBUG, + "DPP: Schedule retransmission of Authentication Response frame in %u ms", + wait_time); + eloop_cancel_timeout(hostapd_dpp_auth_resp_retry_timeout, hapd, NULL); + eloop_register_timeout(wait_time / 1000, + (wait_time % 1000) * 1000, + hostapd_dpp_auth_resp_retry_timeout, hapd, NULL); +} + + +static int hostapd_dpp_allow_ir(struct hostapd_data *hapd, unsigned int freq) +{ + int i, j; + + if (!hapd->iface->hw_features) + return -1; + + for (i = 0; i < hapd->iface->num_hw_features; i++) { + struct hostapd_hw_modes *mode = &hapd->iface->hw_features[i]; + + for (j = 0; j < mode->num_channels; j++) { + struct hostapd_channel_data *chan = &mode->channels[j]; + + if (chan->freq != (int) freq) + continue; + + if (chan->flag & (HOSTAPD_CHAN_DISABLED | + HOSTAPD_CHAN_NO_IR | + HOSTAPD_CHAN_RADAR)) + continue; + + return 1; + } + } + + wpa_printf(MSG_DEBUG, + "DPP: Frequency %u MHz not supported or does not allow PKEX initiation in the current channel list", + freq); + + return 0; +} + + +static int hostapd_dpp_pkex_next_channel(struct hostapd_data *hapd, + struct dpp_pkex *pkex) +{ + if (pkex->freq == 2437) + pkex->freq = 5745; + else if (pkex->freq == 5745) + pkex->freq = 5220; + else if (pkex->freq == 5220) + pkex->freq = 60480; + else + return -1; /* no more channels to try */ + + if (hostapd_dpp_allow_ir(hapd, pkex->freq) == 1) { + wpa_printf(MSG_DEBUG, "DPP: Try to initiate on %u MHz", + pkex->freq); + return 0; + } + + /* Could not use this channel - try the next one */ + return hostapd_dpp_pkex_next_channel(hapd, pkex); +} + + +static void hostapd_dpp_pkex_clear_code(struct hostapd_data *hapd) +{ + if (!hapd->dpp_pkex_code && !hapd->dpp_pkex_identifier) + return; + + /* Delete PKEX code and identifier on successful completion of + * PKEX. We are not supposed to reuse these without being + * explicitly requested to perform PKEX again. */ + wpa_printf(MSG_DEBUG, "DPP: Delete PKEX code/identifier"); + os_free(hapd->dpp_pkex_code); + hapd->dpp_pkex_code = NULL; + os_free(hapd->dpp_pkex_identifier); + hapd->dpp_pkex_identifier = NULL; +} + + +#ifdef CONFIG_DPP2 +static int hostapd_dpp_pkex_done(void *ctx, void *conn, + struct dpp_bootstrap_info *peer_bi) +{ + struct hostapd_data *hapd = ctx; + char cmd[500]; + const char *pos; + u8 allowed_roles = DPP_CAPAB_CONFIGURATOR; + struct dpp_bootstrap_info *own_bi = NULL; + struct dpp_authentication *auth; + + hostapd_dpp_pkex_clear_code(hapd); + + os_snprintf(cmd, sizeof(cmd), " peer=%u %s", peer_bi->id, + hapd->dpp_pkex_auth_cmd ? hapd->dpp_pkex_auth_cmd : ""); + wpa_printf(MSG_DEBUG, "DPP: Start authentication after PKEX (cmd: %s)", + cmd); + + pos = os_strstr(cmd, " own="); + if (pos) { + pos += 5; + own_bi = dpp_bootstrap_get_id(hapd->iface->interfaces->dpp, + atoi(pos)); + if (!own_bi) { + wpa_printf(MSG_INFO, + "DPP: Could not find bootstrapping info for the identified local entry"); + return -1; + } + + if (peer_bi->curve != own_bi->curve) { + wpa_printf(MSG_INFO, + "DPP: Mismatching curves in bootstrapping info (peer=%s own=%s)", + peer_bi->curve->name, own_bi->curve->name); + return -1; + } + } + + pos = os_strstr(cmd, " role="); + if (pos) { + pos += 6; + if (os_strncmp(pos, "configurator", 12) == 0) + allowed_roles = DPP_CAPAB_CONFIGURATOR; + else if (os_strncmp(pos, "enrollee", 8) == 0) + allowed_roles = DPP_CAPAB_ENROLLEE; + else if (os_strncmp(pos, "either", 6) == 0) + allowed_roles = DPP_CAPAB_CONFIGURATOR | + DPP_CAPAB_ENROLLEE; + else + return -1; + } + + auth = dpp_auth_init(hapd->iface->interfaces->dpp, hapd->msg_ctx, + peer_bi, own_bi, allowed_roles, 0, + hapd->iface->hw_features, + hapd->iface->num_hw_features); + if (!auth) + return -1; + + hostapd_dpp_set_testing_options(hapd, auth); + if (dpp_set_configurator(auth, cmd) < 0) { + dpp_auth_deinit(auth); + return -1; + } + + return dpp_tcp_auth(hapd->iface->interfaces->dpp, conn, auth, + hapd->conf->dpp_name, DPP_NETROLE_AP, + hapd->conf->dpp_mud_url, + hapd->conf->dpp_extra_conf_req_name, + hapd->conf->dpp_extra_conf_req_value, + hostapd_dpp_process_conf_obj, NULL); +} +#endif /* CONFIG_DPP2 */ + + +static int hostapd_dpp_pkex_init(struct hostapd_data *hapd, + enum dpp_pkex_ver ver, + const struct hostapd_ip_addr *ipaddr, + int tcp_port) +{ + struct dpp_pkex *pkex; + struct wpabuf *msg; + unsigned int wait_time; + bool v2 = ver != PKEX_VER_ONLY_1; + + wpa_printf(MSG_DEBUG, "DPP: Initiating PKEXv%d", v2 ? 2 : 1); + dpp_pkex_free(hapd->dpp_pkex); + hapd->dpp_pkex = NULL; + pkex = dpp_pkex_init(hapd->msg_ctx, hapd->dpp_pkex_bi, hapd->own_addr, + hapd->dpp_pkex_identifier, + hapd->dpp_pkex_code, hapd->dpp_pkex_code_len, v2); + if (!pkex) + return -1; + pkex->forced_ver = ver != PKEX_VER_AUTO; + + if (ipaddr) { +#ifdef CONFIG_DPP2 + return dpp_tcp_pkex_init(hapd->iface->interfaces->dpp, pkex, + ipaddr, tcp_port, + hapd->msg_ctx, hapd, + hostapd_dpp_pkex_done); +#else /* CONFIG_DPP2 */ + return -1; +#endif /* CONFIG_DPP2 */ + } + + hapd->dpp_pkex = pkex; + msg = hapd->dpp_pkex->exchange_req; + wait_time = 2000; /* TODO: hapd->max_remain_on_chan; */ + pkex->freq = 2437; + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", MAC2STR(broadcast), pkex->freq, + v2 ? DPP_PA_PKEX_EXCHANGE_REQ : + DPP_PA_PKEX_V1_EXCHANGE_REQ); + hostapd_drv_send_action(hapd, pkex->freq, 0, broadcast, + wpabuf_head(msg), wpabuf_len(msg)); + pkex->exch_req_wait_time = wait_time; + pkex->exch_req_tries = 1; + + return 0; +} + + +static void hostapd_dpp_pkex_retry_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct dpp_pkex *pkex = hapd->dpp_pkex; + + if (!pkex || !pkex->exchange_req) + return; + if (pkex->exch_req_tries >= 5) { + if (hostapd_dpp_pkex_next_channel(hapd, pkex) < 0) { +#ifdef CONFIG_DPP3 + if (pkex->v2 && !pkex->forced_ver) { + wpa_printf(MSG_DEBUG, + "DPP: Fall back to PKEXv1"); + hostapd_dpp_pkex_init(hapd, PKEX_VER_ONLY_1, + NULL, 0); + return; + } +#endif /* CONFIG_DPP3 */ + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "No response from PKEX peer"); + dpp_pkex_free(pkex); + hapd->dpp_pkex = NULL; + return; + } + pkex->exch_req_tries = 0; + } + + pkex->exch_req_tries++; + wpa_printf(MSG_DEBUG, "DPP: Retransmit PKEX Exchange Request (try %u)", + pkex->exch_req_tries); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", + MAC2STR(broadcast), pkex->freq, + pkex->v2 ? DPP_PA_PKEX_EXCHANGE_REQ : + DPP_PA_PKEX_V1_EXCHANGE_REQ); + hostapd_drv_send_action(hapd, pkex->freq, pkex->exch_req_wait_time, + broadcast, + wpabuf_head(pkex->exchange_req), + wpabuf_len(pkex->exchange_req)); +} + + +static void hostapd_dpp_pkex_tx_status(struct hostapd_data *hapd, const u8 *dst, + const u8 *data, size_t data_len, int ok) +{ + struct dpp_pkex *pkex = hapd->dpp_pkex; + + if (pkex->failed) { + wpa_printf(MSG_DEBUG, + "DPP: Terminate PKEX exchange due to an earlier error"); + if (pkex->t > pkex->own_bi->pkex_t) + pkex->own_bi->pkex_t = pkex->t; + dpp_pkex_free(pkex); + hapd->dpp_pkex = NULL; + return; + } + + if (pkex->exch_req_wait_time && pkex->exchange_req) { + /* Wait for PKEX Exchange Response frame and retry request if + * no response is seen. */ + eloop_cancel_timeout(hostapd_dpp_pkex_retry_timeout, hapd, + NULL); + eloop_register_timeout(pkex->exch_req_wait_time / 1000, + (pkex->exch_req_wait_time % 1000) * 1000, + hostapd_dpp_pkex_retry_timeout, hapd, + NULL); + } +} + + +void hostapd_dpp_tx_status(struct hostapd_data *hapd, const u8 *dst, + const u8 *data, size_t data_len, int ok) +{ + struct dpp_authentication *auth = hapd->dpp_auth; + + wpa_printf(MSG_DEBUG, "DPP: TX status: dst=" MACSTR " ok=%d", + MAC2STR(dst), ok); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX_STATUS "dst=" MACSTR + " result=%s", MAC2STR(dst), ok ? "SUCCESS" : "FAILED"); + + if (!hapd->dpp_auth) { + if (hapd->dpp_pkex) { + hostapd_dpp_pkex_tx_status(hapd, dst, data, data_len, + ok); + return; + } + wpa_printf(MSG_DEBUG, + "DPP: Ignore TX status since there is no ongoing authentication exchange"); + return; + } + +#ifdef CONFIG_DPP2 + if (auth->connect_on_tx_status) { + wpa_printf(MSG_DEBUG, + "DPP: Complete exchange on configuration result"); + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; + return; + } +#endif /* CONFIG_DPP2 */ + + if (hapd->dpp_auth->remove_on_tx_status) { + wpa_printf(MSG_DEBUG, + "DPP: Terminate authentication exchange due to an earlier error"); + eloop_cancel_timeout(hostapd_dpp_init_timeout, hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, + hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_auth_conf_wait_timeout, + hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_auth_resp_retry_timeout, hapd, + NULL); +#ifdef CONFIG_DPP2 + eloop_cancel_timeout(hostapd_dpp_reconfig_reply_wait_timeout, + hapd, NULL); +#endif /* CONFIG_DPP2 */ + hostapd_drv_send_action_cancel_wait(hapd); + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; + return; + } + + if (hapd->dpp_auth_ok_on_ack) { + hostapd_dpp_auth_success(hapd, 1); + if (!hapd->dpp_auth) { + /* The authentication session could have been removed in + * some error cases, e.g., when starting GAS client and + * failing to send the initial request. */ + return; + } + } + + if (!is_broadcast_ether_addr(dst) && !ok) { + wpa_printf(MSG_DEBUG, + "DPP: Unicast DPP Action frame was not ACKed"); + if (auth->waiting_auth_resp) { + /* In case of DPP Authentication Request frame, move to + * the next channel immediately. */ + hostapd_drv_send_action_cancel_wait(hapd); + hostapd_dpp_auth_init_next(hapd); + return; + } + if (auth->waiting_auth_conf) { + hostapd_dpp_auth_resp_retry(hapd); + return; + } + } + + if (auth->waiting_auth_conf && + auth->auth_resp_status == DPP_STATUS_OK) { + /* Make sure we do not get stuck waiting for Auth Confirm + * indefinitely after successfully transmitted Auth Response to + * allow new authentication exchanges to be started. */ + eloop_cancel_timeout(hostapd_dpp_auth_conf_wait_timeout, hapd, + NULL); + eloop_register_timeout(1, 0, hostapd_dpp_auth_conf_wait_timeout, + hapd, NULL); + } + + if (!is_broadcast_ether_addr(dst) && auth->waiting_auth_resp && ok) { + /* Allow timeout handling to stop iteration if no response is + * received from a peer that has ACKed a request. */ + auth->auth_req_ack = 1; + } + + if (!hapd->dpp_auth_ok_on_ack && hapd->dpp_auth->neg_freq > 0 && + hapd->dpp_auth->curr_freq != hapd->dpp_auth->neg_freq) { + wpa_printf(MSG_DEBUG, + "DPP: Move from curr_freq %u MHz to neg_freq %u MHz for response", + hapd->dpp_auth->curr_freq, + hapd->dpp_auth->neg_freq); + hostapd_drv_send_action_cancel_wait(hapd); + + if (hapd->dpp_auth->neg_freq != + (unsigned int) hapd->iface->freq && hapd->iface->freq > 0) { + /* TODO: Listen operation on non-operating channel */ + wpa_printf(MSG_INFO, + "DPP: Listen operation on non-operating channel (%d MHz) is not yet supported (operating channel: %d MHz)", + hapd->dpp_auth->neg_freq, hapd->iface->freq); + } + } + + if (hapd->dpp_auth_ok_on_ack) + hapd->dpp_auth_ok_on_ack = 0; +} + + +static void hostapd_dpp_reply_wait_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct dpp_authentication *auth = hapd->dpp_auth; + unsigned int freq; + struct os_reltime now, diff; + unsigned int wait_time, diff_ms; + + if (!auth || !auth->waiting_auth_resp) + return; + + wait_time = hapd->dpp_resp_wait_time ? + hapd->dpp_resp_wait_time : 2000; + os_get_reltime(&now); + os_reltime_sub(&now, &hapd->dpp_last_init, &diff); + diff_ms = diff.sec * 1000 + diff.usec / 1000; + wpa_printf(MSG_DEBUG, + "DPP: Reply wait timeout - wait_time=%u diff_ms=%u", + wait_time, diff_ms); + + if (auth->auth_req_ack && diff_ms >= wait_time) { + /* Peer ACK'ed Authentication Request frame, but did not reply + * with Authentication Response frame within two seconds. */ + wpa_printf(MSG_INFO, + "DPP: No response received from responder - stopping initiation attempt"); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_AUTH_INIT_FAILED); + hostapd_drv_send_action_cancel_wait(hapd); + hostapd_dpp_listen_stop(hapd); + dpp_auth_deinit(auth); + hapd->dpp_auth = NULL; + return; + } + + if (diff_ms >= wait_time) { + /* Authentication Request frame was not ACK'ed and no reply + * was receiving within two seconds. */ + wpa_printf(MSG_DEBUG, + "DPP: Continue Initiator channel iteration"); + hostapd_drv_send_action_cancel_wait(hapd); + hostapd_dpp_listen_stop(hapd); + hostapd_dpp_auth_init_next(hapd); + return; + } + + /* Driver did not support 2000 ms long wait_time with TX command, so + * schedule listen operation to continue waiting for the response. + * + * DPP listen operations continue until stopped, so simply schedule a + * new call to this function at the point when the two second reply + * wait has expired. */ + wait_time -= diff_ms; + + freq = auth->curr_freq; + if (auth->neg_freq > 0) + freq = auth->neg_freq; + wpa_printf(MSG_DEBUG, + "DPP: Continue reply wait on channel %u MHz for %u ms", + freq, wait_time); + hapd->dpp_in_response_listen = 1; + + if (freq != (unsigned int) hapd->iface->freq && hapd->iface->freq > 0) { + /* TODO: Listen operation on non-operating channel */ + wpa_printf(MSG_INFO, + "DPP: Listen operation on non-operating channel (%d MHz) is not yet supported (operating channel: %d MHz)", + freq, hapd->iface->freq); + } + + eloop_register_timeout(wait_time / 1000, (wait_time % 1000) * 1000, + hostapd_dpp_reply_wait_timeout, hapd, NULL); +} + + +static void hostapd_dpp_auth_conf_wait_timeout(void *eloop_ctx, + void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct dpp_authentication *auth = hapd->dpp_auth; + + if (!auth || !auth->waiting_auth_conf) + return; + + wpa_printf(MSG_DEBUG, + "DPP: Terminate authentication exchange due to Auth Confirm timeout"); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "No Auth Confirm received"); + hostapd_drv_send_action_cancel_wait(hapd); + dpp_auth_deinit(auth); + hapd->dpp_auth = NULL; +} + + +static void hostapd_dpp_set_testing_options(struct hostapd_data *hapd, + struct dpp_authentication *auth) +{ +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->dpp_config_obj_override) + auth->config_obj_override = + os_strdup(hapd->dpp_config_obj_override); + if (hapd->dpp_discovery_override) + auth->discovery_override = + os_strdup(hapd->dpp_discovery_override); + if (hapd->dpp_groups_override) + auth->groups_override = os_strdup(hapd->dpp_groups_override); + auth->ignore_netaccesskey_mismatch = + hapd->dpp_ignore_netaccesskey_mismatch; +#endif /* CONFIG_TESTING_OPTIONS */ +} + + +static void hostapd_dpp_init_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + + if (!hapd->dpp_auth) + return; + wpa_printf(MSG_DEBUG, "DPP: Retry initiation after timeout"); + hostapd_dpp_auth_init_next(hapd); +} + + +static int hostapd_dpp_auth_init_next(struct hostapd_data *hapd) +{ + struct dpp_authentication *auth = hapd->dpp_auth; + const u8 *dst; + unsigned int wait_time, max_wait_time, freq, max_tries, used; + struct os_reltime now, diff; + + if (!auth) + return -1; + + if (auth->freq_idx == 0) + os_get_reltime(&hapd->dpp_init_iter_start); + + if (auth->freq_idx >= auth->num_freq) { + auth->num_freq_iters++; + if (hapd->dpp_init_max_tries) + max_tries = hapd->dpp_init_max_tries; + else + max_tries = 5; + if (auth->num_freq_iters >= max_tries || auth->auth_req_ack) { + wpa_printf(MSG_INFO, + "DPP: No response received from responder - stopping initiation attempt"); + wpa_msg(hapd->msg_ctx, MSG_INFO, + DPP_EVENT_AUTH_INIT_FAILED); + eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, + hapd, NULL); + hostapd_drv_send_action_cancel_wait(hapd); + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; + return -1; + } + auth->freq_idx = 0; + eloop_cancel_timeout(hostapd_dpp_init_timeout, hapd, NULL); + if (hapd->dpp_init_retry_time) + wait_time = hapd->dpp_init_retry_time; + else + wait_time = 10000; + os_get_reltime(&now); + os_reltime_sub(&now, &hapd->dpp_init_iter_start, &diff); + used = diff.sec * 1000 + diff.usec / 1000; + if (used > wait_time) + wait_time = 0; + else + wait_time -= used; + wpa_printf(MSG_DEBUG, "DPP: Next init attempt in %u ms", + wait_time); + eloop_register_timeout(wait_time / 1000, + (wait_time % 1000) * 1000, + hostapd_dpp_init_timeout, hapd, + NULL); + return 0; + } + freq = auth->freq[auth->freq_idx++]; + auth->curr_freq = freq; + + if (!is_zero_ether_addr(auth->peer_mac_addr)) + dst = auth->peer_mac_addr; + else if (is_zero_ether_addr(auth->peer_bi->mac_addr)) + dst = broadcast; + else + dst = auth->peer_bi->mac_addr; + hapd->dpp_auth_ok_on_ack = 0; + eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, hapd, NULL); + wait_time = 2000; /* TODO: hapd->max_remain_on_chan; */ + max_wait_time = hapd->dpp_resp_wait_time ? + hapd->dpp_resp_wait_time : 2000; + if (wait_time > max_wait_time) + wait_time = max_wait_time; + wait_time += 10; /* give the driver some extra time to complete */ + eloop_register_timeout(wait_time / 1000, (wait_time % 1000) * 1000, + hostapd_dpp_reply_wait_timeout, hapd, NULL); + wait_time -= 10; + if (auth->neg_freq > 0 && freq != auth->neg_freq) { + wpa_printf(MSG_DEBUG, + "DPP: Initiate on %u MHz and move to neg_freq %u MHz for response", + freq, auth->neg_freq); + } + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", + MAC2STR(dst), freq, DPP_PA_AUTHENTICATION_REQ); + auth->auth_req_ack = 0; + os_get_reltime(&hapd->dpp_last_init); + return hostapd_drv_send_action(hapd, freq, wait_time, + dst, + wpabuf_head(hapd->dpp_auth->req_msg), + wpabuf_len(hapd->dpp_auth->req_msg)); +} + + +#ifdef CONFIG_DPP2 +static int hostapd_dpp_process_conf_obj(void *ctx, + struct dpp_authentication *auth) +{ + struct hostapd_data *hapd = ctx; + unsigned int i; + + for (i = 0; i < auth->num_conf_obj; i++) + hostapd_dpp_handle_config_obj(hapd, auth, + &auth->conf_obj[i]); + + return 0; +} +#endif /* CONFIG_DPP2 */ + + +int hostapd_dpp_auth_init(struct hostapd_data *hapd, const char *cmd) +{ + const char *pos; + struct dpp_bootstrap_info *peer_bi, *own_bi = NULL; + struct dpp_authentication *auth; + u8 allowed_roles = DPP_CAPAB_CONFIGURATOR; + unsigned int neg_freq = 0; + int tcp = 0; +#ifdef CONFIG_DPP2 + int tcp_port = DPP_TCP_PORT; + struct hostapd_ip_addr ipaddr; + char *addr; +#endif /* CONFIG_DPP2 */ + + pos = os_strstr(cmd, " peer="); + if (!pos) + return -1; + pos += 6; + peer_bi = dpp_bootstrap_get_id(hapd->iface->interfaces->dpp, atoi(pos)); + if (!peer_bi) { + wpa_printf(MSG_INFO, + "DPP: Could not find bootstrapping info for the identified peer"); + return -1; + } + +#ifdef CONFIG_DPP2 + pos = os_strstr(cmd, " tcp_port="); + if (pos) { + pos += 10; + tcp_port = atoi(pos); + } + + addr = get_param(cmd, " tcp_addr="); + if (addr && os_strcmp(addr, "from-uri") == 0) { + os_free(addr); + if (!peer_bi->host) { + wpa_printf(MSG_INFO, + "DPP: TCP address not available in peer URI"); + return -1; + } + tcp = 1; + os_memcpy(&ipaddr, peer_bi->host, sizeof(ipaddr)); + tcp_port = peer_bi->port; + } else if (addr) { + int res; + + res = hostapd_parse_ip_addr(addr, &ipaddr); + os_free(addr); + if (res) + return -1; + tcp = 1; + } +#endif /* CONFIG_DPP2 */ + + pos = os_strstr(cmd, " own="); + if (pos) { + pos += 5; + own_bi = dpp_bootstrap_get_id(hapd->iface->interfaces->dpp, + atoi(pos)); + if (!own_bi) { + wpa_printf(MSG_INFO, + "DPP: Could not find bootstrapping info for the identified local entry"); + return -1; + } + + if (peer_bi->curve != own_bi->curve) { + wpa_printf(MSG_INFO, + "DPP: Mismatching curves in bootstrapping info (peer=%s own=%s)", + peer_bi->curve->name, own_bi->curve->name); + return -1; + } + } + + pos = os_strstr(cmd, " role="); + if (pos) { + pos += 6; + if (os_strncmp(pos, "configurator", 12) == 0) + allowed_roles = DPP_CAPAB_CONFIGURATOR; + else if (os_strncmp(pos, "enrollee", 8) == 0) + allowed_roles = DPP_CAPAB_ENROLLEE; + else if (os_strncmp(pos, "either", 6) == 0) + allowed_roles = DPP_CAPAB_CONFIGURATOR | + DPP_CAPAB_ENROLLEE; + else + goto fail; + } + + pos = os_strstr(cmd, " neg_freq="); + if (pos) + neg_freq = atoi(pos + 10); + + if (!tcp && hapd->dpp_auth) { + eloop_cancel_timeout(hostapd_dpp_init_timeout, hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, + hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_auth_conf_wait_timeout, + hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_auth_resp_retry_timeout, hapd, + NULL); +#ifdef CONFIG_DPP2 + eloop_cancel_timeout(hostapd_dpp_reconfig_reply_wait_timeout, + hapd, NULL); +#endif /* CONFIG_DPP2 */ + hostapd_drv_send_action_cancel_wait(hapd); + dpp_auth_deinit(hapd->dpp_auth); + } + + auth = dpp_auth_init(hapd->iface->interfaces->dpp, hapd->msg_ctx, + peer_bi, own_bi, allowed_roles, neg_freq, + hapd->iface->hw_features, + hapd->iface->num_hw_features); + if (!auth) + goto fail; + hostapd_dpp_set_testing_options(hapd, auth); + if (dpp_set_configurator(auth, cmd) < 0) { + dpp_auth_deinit(auth); + goto fail; + } + + auth->neg_freq = neg_freq; + + if (!is_zero_ether_addr(peer_bi->mac_addr)) + os_memcpy(auth->peer_mac_addr, peer_bi->mac_addr, ETH_ALEN); + +#ifdef CONFIG_DPP2 + if (tcp) + return dpp_tcp_init(hapd->iface->interfaces->dpp, auth, + &ipaddr, tcp_port, hapd->conf->dpp_name, + DPP_NETROLE_AP, hapd->conf->dpp_mud_url, + hapd->conf->dpp_extra_conf_req_name, + hapd->conf->dpp_extra_conf_req_value, + hapd->msg_ctx, hapd, + hostapd_dpp_process_conf_obj, NULL); +#endif /* CONFIG_DPP2 */ + + hapd->dpp_auth = auth; + return hostapd_dpp_auth_init_next(hapd); +fail: + return -1; +} + + +int hostapd_dpp_listen(struct hostapd_data *hapd, const char *cmd) +{ + int freq; + + freq = atoi(cmd); + if (freq <= 0) + return -1; + + if (os_strstr(cmd, " role=configurator")) + hapd->dpp_allowed_roles = DPP_CAPAB_CONFIGURATOR; + else if (os_strstr(cmd, " role=enrollee")) + hapd->dpp_allowed_roles = DPP_CAPAB_ENROLLEE; + else + hapd->dpp_allowed_roles = DPP_CAPAB_CONFIGURATOR | + DPP_CAPAB_ENROLLEE; + hapd->dpp_qr_mutual = os_strstr(cmd, " qr=mutual") != NULL; + + if (freq != hapd->iface->freq && hapd->iface->freq > 0) { + /* TODO: Listen operation on non-operating channel */ + wpa_printf(MSG_INFO, + "DPP: Listen operation on non-operating channel (%d MHz) is not yet supported (operating channel: %d MHz)", + freq, hapd->iface->freq); + return -1; + } + + hostapd_drv_dpp_listen(hapd, true); + return 0; +} + + +void hostapd_dpp_listen_stop(struct hostapd_data *hapd) +{ + hostapd_drv_dpp_listen(hapd, false); + /* TODO: Stop listen operation on non-operating channel */ +} + + +#ifdef CONFIG_DPP2 +static void +hostapd_dpp_relay_needs_controller(struct hostapd_data *hapd, const u8 *src, + enum dpp_public_action_frame_type type) +{ + struct os_reltime now; + + if (!hapd->conf->dpp_relay_port) + return; + + os_get_reltime(&now); + if (hapd->dpp_relay_last_needs_ctrl.sec && + !os_reltime_expired(&now, &hapd->dpp_relay_last_needs_ctrl, 60)) + return; + hapd->dpp_relay_last_needs_ctrl = now; + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_RELAY_NEEDS_CONTROLLER + MACSTR " %u", MAC2STR(src), type); +} +#endif /* CONFIG_DPP2 */ + + +static void hostapd_dpp_rx_auth_req(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq) +{ + const u8 *r_bootstrap, *i_bootstrap; + u16 r_bootstrap_len, i_bootstrap_len; + struct dpp_bootstrap_info *own_bi = NULL, *peer_bi = NULL; + + if (!hapd->iface->interfaces->dpp) + return; + + wpa_printf(MSG_DEBUG, "DPP: Authentication Request from " MACSTR, + MAC2STR(src)); + +#ifdef CONFIG_DPP2 + hostapd_dpp_chirp_stop(hapd); +#endif /* CONFIG_DPP2 */ + + r_bootstrap = dpp_get_attr(buf, len, DPP_ATTR_R_BOOTSTRAP_KEY_HASH, + &r_bootstrap_len); + if (!r_bootstrap || r_bootstrap_len != SHA256_MAC_LEN) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "Missing or invalid required Responder Bootstrapping Key Hash attribute"); + return; + } + wpa_hexdump(MSG_MSGDUMP, "DPP: Responder Bootstrapping Key Hash", + r_bootstrap, r_bootstrap_len); + + i_bootstrap = dpp_get_attr(buf, len, DPP_ATTR_I_BOOTSTRAP_KEY_HASH, + &i_bootstrap_len); + if (!i_bootstrap || i_bootstrap_len != SHA256_MAC_LEN) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "Missing or invalid required Initiator Bootstrapping Key Hash attribute"); + return; + } + wpa_hexdump(MSG_MSGDUMP, "DPP: Initiator Bootstrapping Key Hash", + i_bootstrap, i_bootstrap_len); + + /* Try to find own and peer bootstrapping key matches based on the + * received hash values */ + dpp_bootstrap_find_pair(hapd->iface->interfaces->dpp, i_bootstrap, + r_bootstrap, &own_bi, &peer_bi); +#ifdef CONFIG_DPP2 + if (!own_bi) { + if (dpp_relay_rx_action(hapd->iface->interfaces->dpp, + src, hdr, buf, len, freq, i_bootstrap, + r_bootstrap, hapd) == 0) + return; + hostapd_dpp_relay_needs_controller(hapd, src, + DPP_PA_AUTHENTICATION_REQ); + } +#endif /* CONFIG_DPP2 */ + if (!own_bi) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "No matching own bootstrapping key found - ignore message"); + return; + } + + if (own_bi->type == DPP_BOOTSTRAP_PKEX) { + if (!peer_bi || peer_bi->type != DPP_BOOTSTRAP_PKEX) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "No matching peer bootstrapping key found for PKEX - ignore message"); + return; + } + + if (os_memcmp(peer_bi->pubkey_hash, own_bi->peer_pubkey_hash, + SHA256_MAC_LEN) != 0) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "Mismatching peer PKEX bootstrapping key - ignore message"); + return; + } + } + + if (hapd->dpp_auth) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "Already in DPP authentication exchange - ignore new one"); + return; + } + + hapd->dpp_auth_ok_on_ack = 0; + hapd->dpp_auth = dpp_auth_req_rx(hapd->iface->interfaces->dpp, + hapd->msg_ctx, hapd->dpp_allowed_roles, + hapd->dpp_qr_mutual, + peer_bi, own_bi, freq, hdr, buf, len); + if (!hapd->dpp_auth) { + wpa_printf(MSG_DEBUG, "DPP: No response generated"); + return; + } + hostapd_dpp_set_testing_options(hapd, hapd->dpp_auth); + if (dpp_set_configurator(hapd->dpp_auth, + hapd->dpp_configurator_params) < 0) { + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; + return; + } + os_memcpy(hapd->dpp_auth->peer_mac_addr, src, ETH_ALEN); + + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", + MAC2STR(src), hapd->dpp_auth->curr_freq, + DPP_PA_AUTHENTICATION_RESP); + hostapd_drv_send_action(hapd, hapd->dpp_auth->curr_freq, 0, + src, wpabuf_head(hapd->dpp_auth->resp_msg), + wpabuf_len(hapd->dpp_auth->resp_msg)); +} + + +static void hostapd_dpp_handle_config_obj(struct hostapd_data *hapd, + struct dpp_authentication *auth, + struct dpp_config_obj *conf) +{ + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_RECEIVED); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONFOBJ_AKM "%s", + dpp_akm_str(conf->akm)); + if (conf->ssid_len) + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONFOBJ_SSID "%s", + wpa_ssid_txt(conf->ssid, conf->ssid_len)); + if (conf->connector) { + /* TODO: Save the Connector and consider using a command + * to fetch the value instead of sending an event with + * it. The Connector could end up being larger than what + * most clients are ready to receive as an event + * message. */ + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONNECTOR "%s", + conf->connector); + } + if (conf->passphrase[0]) { + char hex[64 * 2 + 1]; + + wpa_snprintf_hex(hex, sizeof(hex), + (const u8 *) conf->passphrase, + os_strlen(conf->passphrase)); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONFOBJ_PASS "%s", + hex); + } else if (conf->psk_set) { + char hex[PMK_LEN * 2 + 1]; + + wpa_snprintf_hex(hex, sizeof(hex), conf->psk, PMK_LEN); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONFOBJ_PSK "%s", + hex); + } + if (conf->c_sign_key) { + char *hex; + size_t hexlen; + + hexlen = 2 * wpabuf_len(conf->c_sign_key) + 1; + hex = os_malloc(hexlen); + if (hex) { + wpa_snprintf_hex(hex, hexlen, + wpabuf_head(conf->c_sign_key), + wpabuf_len(conf->c_sign_key)); + wpa_msg(hapd->msg_ctx, MSG_INFO, + DPP_EVENT_C_SIGN_KEY "%s", hex); + os_free(hex); + } + } + if (auth->net_access_key) { + char *hex; + size_t hexlen; + + hexlen = 2 * wpabuf_len(auth->net_access_key) + 1; + hex = os_malloc(hexlen); + if (hex) { + wpa_snprintf_hex(hex, hexlen, + wpabuf_head(auth->net_access_key), + wpabuf_len(auth->net_access_key)); + if (auth->net_access_key_expiry) + wpa_msg(hapd->msg_ctx, MSG_INFO, + DPP_EVENT_NET_ACCESS_KEY "%s %lu", hex, + (unsigned long) + auth->net_access_key_expiry); + else + wpa_msg(hapd->msg_ctx, MSG_INFO, + DPP_EVENT_NET_ACCESS_KEY "%s", hex); + os_free(hex); + } + } +} + + +static int hostapd_dpp_handle_key_pkg(struct hostapd_data *hapd, + struct dpp_asymmetric_key *key) +{ +#ifdef CONFIG_DPP2 + int res; + + if (!key) + return 0; + + wpa_printf(MSG_DEBUG, "DPP: Received Configurator backup"); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_RECEIVED); + + while (key) { + res = dpp_configurator_from_backup( + hapd->iface->interfaces->dpp, key); + if (res < 0) + return -1; + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONFIGURATOR_ID "%d", + res); + key = key->next; + } +#endif /* CONFIG_DPP2 */ + + return 0; +} + + +#ifdef CONFIG_DPP3 +static void hostapd_dpp_build_new_key(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct dpp_authentication *auth = hapd->dpp_auth; + + if (!auth || !auth->waiting_new_key) + return; + + wpa_printf(MSG_DEBUG, "DPP: Build config request with a new key"); + hostapd_dpp_start_gas_client(hapd); +} +#endif /* CONFIG_DPP3 */ + + +static void hostapd_dpp_gas_resp_cb(void *ctx, const u8 *addr, u8 dialog_token, + enum gas_query_ap_result result, + const struct wpabuf *adv_proto, + const struct wpabuf *resp, u16 status_code) +{ + struct hostapd_data *hapd = ctx; + const u8 *pos; + struct dpp_authentication *auth = hapd->dpp_auth; + enum dpp_status_error status = DPP_STATUS_CONFIG_REJECTED; + int res; + + if (!auth || !auth->auth_success) { + wpa_printf(MSG_DEBUG, "DPP: No matching exchange in progress"); + return; + } + if (result != GAS_QUERY_AP_SUCCESS || + !resp || status_code != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_DEBUG, "DPP: GAS query did not succeed"); + goto fail; + } + + wpa_hexdump_buf(MSG_DEBUG, "DPP: Configuration Response adv_proto", + adv_proto); + wpa_hexdump_buf(MSG_DEBUG, "DPP: Configuration Response (GAS response)", + resp); + + if (wpabuf_len(adv_proto) != 10 || + !(pos = wpabuf_head(adv_proto)) || + pos[0] != WLAN_EID_ADV_PROTO || + pos[1] != 8 || + pos[3] != WLAN_EID_VENDOR_SPECIFIC || + pos[4] != 5 || + WPA_GET_BE24(&pos[5]) != OUI_WFA || + pos[8] != 0x1a || + pos[9] != 1) { + wpa_printf(MSG_DEBUG, + "DPP: Not a DPP Advertisement Protocol ID"); + goto fail; + } + + res = dpp_conf_resp_rx(auth, resp); +#ifdef CONFIG_DPP3 + if (res == -3) { + wpa_printf(MSG_DEBUG, "DPP: New protocol key needed"); + eloop_register_timeout(0, 0, hostapd_dpp_build_new_key, hapd, + NULL); + return; + } +#endif /* CONFIG_DPP3 */ + if (res < 0) { + wpa_printf(MSG_DEBUG, "DPP: Configuration attempt failed"); + goto fail; + } + + hostapd_dpp_handle_config_obj(hapd, auth, &auth->conf_obj[0]); + if (hostapd_dpp_handle_key_pkg(hapd, auth->conf_key_pkg) < 0) + goto fail; + + status = DPP_STATUS_OK; +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_REJECT_CONFIG) { + wpa_printf(MSG_INFO, "DPP: TESTING - Reject Config Object"); + status = DPP_STATUS_CONFIG_REJECTED; + } +#endif /* CONFIG_TESTING_OPTIONS */ +fail: + if (status != DPP_STATUS_OK) + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_FAILED); +#ifdef CONFIG_DPP2 + if (auth->peer_version >= 2 && + auth->conf_resp_status == DPP_STATUS_OK) { + struct wpabuf *msg; + + wpa_printf(MSG_DEBUG, "DPP: Send DPP Configuration Result"); + msg = dpp_build_conf_result(auth, status); + if (!msg) + goto fail2; + + wpa_msg(hapd->msg_ctx, MSG_INFO, + DPP_EVENT_TX "dst=" MACSTR " freq=%u type=%d", + MAC2STR(addr), auth->curr_freq, + DPP_PA_CONFIGURATION_RESULT); + hostapd_drv_send_action(hapd, auth->curr_freq, 0, + addr, wpabuf_head(msg), + wpabuf_len(msg)); + wpabuf_free(msg); + + /* This exchange will be terminated in the TX status handler */ + auth->connect_on_tx_status = 1; + return; + } +fail2: +#endif /* CONFIG_DPP2 */ + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; +} + + +static void hostapd_dpp_start_gas_client(struct hostapd_data *hapd) +{ + struct dpp_authentication *auth = hapd->dpp_auth; + struct wpabuf *buf; + int res; + + buf = dpp_build_conf_req_helper(auth, hapd->conf->dpp_name, + DPP_NETROLE_AP, + hapd->conf->dpp_mud_url, NULL, + hapd->conf->dpp_extra_conf_req_name, + hapd->conf->dpp_extra_conf_req_value); + if (!buf) { + wpa_printf(MSG_DEBUG, + "DPP: No configuration request data available"); + return; + } + + wpa_printf(MSG_DEBUG, "DPP: GAS request to " MACSTR " (freq %u MHz)", + MAC2STR(auth->peer_mac_addr), auth->curr_freq); + + res = gas_query_ap_req(hapd->gas, auth->peer_mac_addr, auth->curr_freq, + buf, hostapd_dpp_gas_resp_cb, hapd); + if (res < 0) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "GAS: Failed to send Query Request"); + wpabuf_free(buf); + } else { + wpa_printf(MSG_DEBUG, + "DPP: GAS query started with dialog token %u", res); + } +} + + +static void hostapd_dpp_auth_success(struct hostapd_data *hapd, int initiator) +{ + wpa_printf(MSG_DEBUG, "DPP: Authentication succeeded"); + dpp_notify_auth_success(hapd->dpp_auth, initiator); +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_STOP_AT_AUTH_CONF) { + wpa_printf(MSG_INFO, + "DPP: TESTING - stop at Authentication Confirm"); + if (hapd->dpp_auth->configurator) { + /* Prevent GAS response */ + hapd->dpp_auth->auth_success = 0; + } + return; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + if (!hapd->dpp_auth->configurator) + hostapd_dpp_start_gas_client(hapd); +} + + +static void hostapd_dpp_rx_auth_resp(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq) +{ + struct dpp_authentication *auth = hapd->dpp_auth; + struct wpabuf *msg; + + wpa_printf(MSG_DEBUG, "DPP: Authentication Response from " MACSTR, + MAC2STR(src)); + + if (!auth) { + wpa_printf(MSG_DEBUG, + "DPP: No DPP Authentication in progress - drop"); + return; + } + + if (!is_zero_ether_addr(auth->peer_mac_addr) && + !ether_addr_equal(src, auth->peer_mac_addr)) { + wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected " + MACSTR ") - drop", MAC2STR(auth->peer_mac_addr)); + return; + } + + eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, hapd, NULL); + + if (auth->curr_freq != freq && auth->neg_freq == freq) { + wpa_printf(MSG_DEBUG, + "DPP: Responder accepted request for different negotiation channel"); + auth->curr_freq = freq; + } + + eloop_cancel_timeout(hostapd_dpp_init_timeout, hapd, NULL); + msg = dpp_auth_resp_rx(auth, hdr, buf, len); + if (!msg) { + if (auth->auth_resp_status == DPP_STATUS_RESPONSE_PENDING) { + wpa_printf(MSG_DEBUG, "DPP: Wait for full response"); + return; + } + wpa_printf(MSG_DEBUG, "DPP: No confirm generated"); + return; + } + os_memcpy(auth->peer_mac_addr, src, ETH_ALEN); + + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", MAC2STR(src), auth->curr_freq, + DPP_PA_AUTHENTICATION_CONF); + hostapd_drv_send_action(hapd, auth->curr_freq, 0, src, + wpabuf_head(msg), wpabuf_len(msg)); + wpabuf_free(msg); + hapd->dpp_auth_ok_on_ack = 1; +} + + +static void hostapd_dpp_rx_auth_conf(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len) +{ + struct dpp_authentication *auth = hapd->dpp_auth; + + wpa_printf(MSG_DEBUG, "DPP: Authentication Confirmation from " MACSTR, + MAC2STR(src)); + + if (!auth) { + wpa_printf(MSG_DEBUG, + "DPP: No DPP Authentication in progress - drop"); + return; + } + + if (!ether_addr_equal(src, auth->peer_mac_addr)) { + wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected " + MACSTR ") - drop", MAC2STR(auth->peer_mac_addr)); + return; + } + + if (dpp_auth_conf_rx(auth, hdr, buf, len) < 0) { + wpa_printf(MSG_DEBUG, "DPP: Authentication failed"); + return; + } + + hostapd_dpp_auth_success(hapd, 0); +} + + +#ifdef CONFIG_DPP2 + +static void hostapd_dpp_config_result_wait_timeout(void *eloop_ctx, + void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct dpp_authentication *auth = hapd->dpp_auth; + + if (!auth || !auth->waiting_conf_result) + return; + + wpa_printf(MSG_DEBUG, + "DPP: Timeout while waiting for Configuration Result"); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_FAILED); + dpp_auth_deinit(auth); + hapd->dpp_auth = NULL; +} + + +static void hostapd_dpp_conn_status_result_wait_timeout(void *eloop_ctx, + void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct dpp_authentication *auth = hapd->dpp_auth; + + if (!auth || !auth->waiting_conf_result) + return; + + wpa_printf(MSG_DEBUG, + "DPP: Timeout while waiting for Connection Status Result"); + wpa_msg(hapd->msg_ctx, MSG_INFO, + DPP_EVENT_CONN_STATUS_RESULT "timeout"); + dpp_auth_deinit(auth); + hapd->dpp_auth = NULL; +} + + +#ifdef CONFIG_DPP3 + +static bool hostapd_dpp_pb_active(struct hostapd_data *hapd) +{ + struct hapd_interfaces *ifaces = hapd->iface->interfaces; + + return ifaces && (ifaces->dpp_pb_time.sec || + ifaces->dpp_pb_time.usec); +} + + +static void hostapd_dpp_remove_pb_hash(struct hostapd_data *hapd) +{ + struct hapd_interfaces *ifaces = hapd->iface->interfaces; + int i; + + if (!ifaces->dpp_pb_bi) + return; + for (i = 0; i < DPP_PB_INFO_COUNT; i++) { + struct dpp_pb_info *info = &ifaces->dpp_pb[i]; + + if (info->rx_time.sec == 0 && info->rx_time.usec == 0) + continue; + if (os_memcmp(info->hash, ifaces->dpp_pb_resp_hash, + SHA256_MAC_LEN) == 0) { + /* Allow a new push button session to be established + * immediately without the successfully completed + * session triggering session overlap. */ + info->rx_time.sec = 0; + info->rx_time.usec = 0; + wpa_printf(MSG_DEBUG, + "DPP: Removed PB hash from session overlap detection due to successfully completed provisioning"); + } + } +} + +#endif /* CONFIG_DPP3 */ + + +static void hostapd_dpp_rx_conf_result(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len) +{ + struct dpp_authentication *auth = hapd->dpp_auth; + enum dpp_status_error status; +#ifdef CONFIG_DPP3 + struct hapd_interfaces *ifaces = hapd->iface->interfaces; +#endif /* CONFIG_DPP3 */ + + wpa_printf(MSG_DEBUG, "DPP: Configuration Result from " MACSTR, + MAC2STR(src)); + + if (!auth || !auth->waiting_conf_result) { + wpa_printf(MSG_DEBUG, + "DPP: No DPP Configuration waiting for result - drop"); + return; + } + + if (!ether_addr_equal(src, auth->peer_mac_addr)) { + wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected " + MACSTR ") - drop", MAC2STR(auth->peer_mac_addr)); + return; + } + + status = dpp_conf_result_rx(auth, hdr, buf, len); + + if (status == DPP_STATUS_OK && auth->send_conn_status) { + wpa_msg(hapd->msg_ctx, MSG_INFO, + DPP_EVENT_CONF_SENT "wait_conn_status=1 conf_status=%d", + auth->conf_resp_status); + wpa_printf(MSG_DEBUG, "DPP: Wait for Connection Status Result"); + eloop_cancel_timeout(hostapd_dpp_config_result_wait_timeout, + hapd, NULL); + auth->waiting_conn_status_result = 1; + eloop_cancel_timeout( + hostapd_dpp_conn_status_result_wait_timeout, + hapd, NULL); + eloop_register_timeout( + 16, 0, hostapd_dpp_conn_status_result_wait_timeout, + hapd, NULL); + return; + } + hostapd_drv_send_action_cancel_wait(hapd); + hostapd_dpp_listen_stop(hapd); + if (status == DPP_STATUS_OK) + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_SENT + "conf_status=%d", auth->conf_resp_status); + else + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_FAILED); + dpp_auth_deinit(auth); + hapd->dpp_auth = NULL; + eloop_cancel_timeout(hostapd_dpp_config_result_wait_timeout, hapd, + NULL); +#ifdef CONFIG_DPP3 + if (!ifaces->dpp_pb_result_indicated && hostapd_dpp_pb_active(hapd)) { + if (status == DPP_STATUS_OK) + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_PB_RESULT + "success"); + else + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_PB_RESULT + "no-configuration-available"); + ifaces->dpp_pb_result_indicated = true; + if (status == DPP_STATUS_OK) + hostapd_dpp_remove_pb_hash(hapd); + hostapd_dpp_push_button_stop(hapd); + } +#endif /* CONFIG_DPP3 */ +} + + +static void hostapd_dpp_rx_conn_status_result(struct hostapd_data *hapd, + const u8 *src, const u8 *hdr, + const u8 *buf, size_t len) +{ + struct dpp_authentication *auth = hapd->dpp_auth; + enum dpp_status_error status; + u8 ssid[SSID_MAX_LEN]; + size_t ssid_len = 0; + char *channel_list = NULL; + + wpa_printf(MSG_DEBUG, "DPP: Connection Status Result"); + + if (!auth || !auth->waiting_conn_status_result) { + wpa_printf(MSG_DEBUG, + "DPP: No DPP Configuration waiting for connection status result - drop"); + return; + } + + status = dpp_conn_status_result_rx(auth, hdr, buf, len, + ssid, &ssid_len, &channel_list); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONN_STATUS_RESULT + "result=%d ssid=%s channel_list=%s", + status, wpa_ssid_txt(ssid, ssid_len), + channel_list ? channel_list : "N/A"); + os_free(channel_list); + hostapd_drv_send_action_cancel_wait(hapd); + hostapd_dpp_listen_stop(hapd); + dpp_auth_deinit(auth); + hapd->dpp_auth = NULL; + eloop_cancel_timeout(hostapd_dpp_conn_status_result_wait_timeout, + hapd, NULL); +} + + +static void +hostapd_dpp_rx_presence_announcement(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq) +{ + const u8 *r_bootstrap; + u16 r_bootstrap_len; + struct dpp_bootstrap_info *peer_bi; + struct dpp_authentication *auth; + + wpa_printf(MSG_DEBUG, "DPP: Presence Announcement from " MACSTR, + MAC2STR(src)); + + r_bootstrap = dpp_get_attr(buf, len, DPP_ATTR_R_BOOTSTRAP_KEY_HASH, + &r_bootstrap_len); + if (!r_bootstrap || r_bootstrap_len != SHA256_MAC_LEN) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "Missing or invalid required Responder Bootstrapping Key Hash attribute"); + return; + } + wpa_hexdump(MSG_MSGDUMP, "DPP: Responder Bootstrapping Key Hash", + r_bootstrap, r_bootstrap_len); + peer_bi = dpp_bootstrap_find_chirp(hapd->iface->interfaces->dpp, + r_bootstrap); + dpp_notify_chirp_received(hapd->msg_ctx, + peer_bi ? (int) peer_bi->id : -1, + src, freq, r_bootstrap); + if (!peer_bi) { + if (dpp_relay_rx_action(hapd->iface->interfaces->dpp, + src, hdr, buf, len, freq, NULL, + r_bootstrap, hapd) == 0) + return; + wpa_printf(MSG_DEBUG, + "DPP: No matching bootstrapping information found"); + hostapd_dpp_relay_needs_controller( + hapd, src, DPP_PA_PRESENCE_ANNOUNCEMENT); + return; + } + + if (hapd->dpp_auth) { + wpa_printf(MSG_DEBUG, + "DPP: Ignore Presence Announcement during ongoing Authentication"); + return; + } + + auth = dpp_auth_init(hapd->iface->interfaces->dpp, hapd->msg_ctx, + peer_bi, NULL, DPP_CAPAB_CONFIGURATOR, freq, NULL, + 0); + if (!auth) + return; + hostapd_dpp_set_testing_options(hapd, auth); + if (dpp_set_configurator(auth, + hapd->dpp_configurator_params) < 0) { + dpp_auth_deinit(auth); + return; + } + + auth->neg_freq = freq; + + /* The source address of the Presence Announcement frame overrides any + * MAC address information from the bootstrapping information. */ + os_memcpy(auth->peer_mac_addr, src, ETH_ALEN); + + hapd->dpp_auth = auth; + if (hostapd_dpp_auth_init_next(hapd) < 0) { + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; + } +} + + +static void hostapd_dpp_reconfig_reply_wait_timeout(void *eloop_ctx, + void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct dpp_authentication *auth = hapd->dpp_auth; + + if (!auth) + return; + + wpa_printf(MSG_DEBUG, "DPP: Reconfig Reply wait timeout"); + hostapd_dpp_listen_stop(hapd); + dpp_auth_deinit(auth); + hapd->dpp_auth = NULL; +} + + +static void +hostapd_dpp_rx_reconfig_announcement(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq) +{ + const u8 *csign_hash, *fcgroup, *a_nonce, *e_id; + u16 csign_hash_len, fcgroup_len, a_nonce_len, e_id_len; + struct dpp_configurator *conf; + struct dpp_authentication *auth; + unsigned int wait_time, max_wait_time; + u16 group; + + if (hapd->dpp_auth) { + wpa_printf(MSG_DEBUG, + "DPP: Ignore Reconfig Announcement during ongoing Authentication"); + return; + } + + wpa_printf(MSG_DEBUG, "DPP: Reconfig Announcement from " MACSTR, + MAC2STR(src)); + + csign_hash = dpp_get_attr(buf, len, DPP_ATTR_C_SIGN_KEY_HASH, + &csign_hash_len); + if (!csign_hash || csign_hash_len != SHA256_MAC_LEN) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "Missing or invalid required Configurator C-sign key Hash attribute"); + return; + } + wpa_hexdump(MSG_MSGDUMP, "DPP: Configurator C-sign key Hash (kid)", + csign_hash, csign_hash_len); + conf = dpp_configurator_find_kid(hapd->iface->interfaces->dpp, + csign_hash); + if (!conf) { + if (dpp_relay_rx_action(hapd->iface->interfaces->dpp, + src, hdr, buf, len, freq, NULL, + NULL, hapd) == 0) + return; + wpa_printf(MSG_DEBUG, + "DPP: No matching Configurator information found"); + hostapd_dpp_relay_needs_controller( + hapd, src, DPP_PA_RECONFIG_ANNOUNCEMENT); + return; + } + + fcgroup = dpp_get_attr(buf, len, DPP_ATTR_FINITE_CYCLIC_GROUP, + &fcgroup_len); + if (!fcgroup || fcgroup_len != 2) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_FAIL + "Missing or invalid required Finite Cyclic Group attribute"); + return; + } + group = WPA_GET_LE16(fcgroup); + wpa_printf(MSG_DEBUG, "DPP: Enrollee finite cyclic group: %u", group); + + a_nonce = dpp_get_attr(buf, len, DPP_ATTR_A_NONCE, &a_nonce_len); + e_id = dpp_get_attr(buf, len, DPP_ATTR_E_PRIME_ID, &e_id_len); + + auth = dpp_reconfig_init(hapd->iface->interfaces->dpp, hapd->msg_ctx, + conf, freq, group, a_nonce, a_nonce_len, + e_id, e_id_len); + if (!auth) + return; + hostapd_dpp_set_testing_options(hapd, auth); + if (dpp_set_configurator(auth, hapd->dpp_configurator_params) < 0) { + dpp_auth_deinit(auth); + return; + } + + os_memcpy(auth->peer_mac_addr, src, ETH_ALEN); + hapd->dpp_auth = auth; + + hapd->dpp_in_response_listen = 0; + hapd->dpp_auth_ok_on_ack = 0; + wait_time = 2000; /* TODO: hapd->max_remain_on_chan; */ + max_wait_time = hapd->dpp_resp_wait_time ? + hapd->dpp_resp_wait_time : 2000; + if (wait_time > max_wait_time) + wait_time = max_wait_time; + wait_time += 10; /* give the driver some extra time to complete */ + eloop_register_timeout(wait_time / 1000, (wait_time % 1000) * 1000, + hostapd_dpp_reconfig_reply_wait_timeout, + hapd, NULL); + wait_time -= 10; + + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", + MAC2STR(src), freq, DPP_PA_RECONFIG_AUTH_REQ); + if (hostapd_drv_send_action(hapd, freq, wait_time, src, + wpabuf_head(auth->reconfig_req_msg), + wpabuf_len(auth->reconfig_req_msg)) < 0) { + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; + } +} + + +static void +hostapd_dpp_rx_reconfig_auth_resp(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq) +{ + struct dpp_authentication *auth = hapd->dpp_auth; + struct wpabuf *conf; + + wpa_printf(MSG_DEBUG, "DPP: Reconfig Authentication Response from " + MACSTR, MAC2STR(src)); + + if (!auth || !auth->reconfig || !auth->configurator) { + wpa_printf(MSG_DEBUG, + "DPP: No DPP Reconfig Authentication in progress - drop"); + return; + } + + if (!ether_addr_equal(src, auth->peer_mac_addr)) { + wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected " + MACSTR ") - drop", MAC2STR(auth->peer_mac_addr)); + return; + } + + conf = dpp_reconfig_auth_resp_rx(auth, hdr, buf, len); + if (!conf) + return; + + eloop_cancel_timeout(hostapd_dpp_reconfig_reply_wait_timeout, + hapd, NULL); + + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", + MAC2STR(src), freq, DPP_PA_RECONFIG_AUTH_CONF); + if (hostapd_drv_send_action(hapd, freq, 500, src, + wpabuf_head(conf), wpabuf_len(conf)) < 0) { + wpabuf_free(conf); + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; + return; + } + wpabuf_free(conf); +} + +#endif /* CONFIG_DPP2 */ + + +static void hostapd_dpp_send_peer_disc_resp(struct hostapd_data *hapd, + const u8 *src, unsigned int freq, + u8 trans_id, + enum dpp_status_error status) +{ + struct wpabuf *msg; + size_t len; + + len = 5 + 5 + 4 + os_strlen(hapd->conf->dpp_connector); +#ifdef CONFIG_DPP2 + len += 5; +#endif /* CONFIG_DPP2 */ + msg = dpp_alloc_msg(DPP_PA_PEER_DISCOVERY_RESP, len); + if (!msg) + return; + +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_NO_TRANSACTION_ID_PEER_DISC_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - no Transaction ID"); + goto skip_trans_id; + } + if (dpp_test == DPP_TEST_INVALID_TRANSACTION_ID_PEER_DISC_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - invalid Transaction ID"); + trans_id ^= 0x01; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + /* Transaction ID */ + wpabuf_put_le16(msg, DPP_ATTR_TRANSACTION_ID); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, trans_id); + +#ifdef CONFIG_TESTING_OPTIONS +skip_trans_id: + if (dpp_test == DPP_TEST_NO_STATUS_PEER_DISC_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - no Status"); + goto skip_status; + } + if (dpp_test == DPP_TEST_INVALID_STATUS_PEER_DISC_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - invalid Status"); + status = 254; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + /* DPP Status */ + wpabuf_put_le16(msg, DPP_ATTR_STATUS); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, status); + +#ifdef CONFIG_TESTING_OPTIONS +skip_status: + if (dpp_test == DPP_TEST_NO_CONNECTOR_PEER_DISC_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - no Connector"); + goto skip_connector; + } + if (status == DPP_STATUS_OK && + dpp_test == DPP_TEST_INVALID_CONNECTOR_PEER_DISC_RESP) { + char *connector; + + wpa_printf(MSG_INFO, "DPP: TESTING - invalid Connector"); + connector = dpp_corrupt_connector_signature( + hapd->conf->dpp_connector); + if (!connector) { + wpabuf_free(msg); + return; + } + wpabuf_put_le16(msg, DPP_ATTR_CONNECTOR); + wpabuf_put_le16(msg, os_strlen(connector)); + wpabuf_put_str(msg, connector); + os_free(connector); + goto skip_connector; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + /* DPP Connector */ + if (status == DPP_STATUS_OK) { + wpabuf_put_le16(msg, DPP_ATTR_CONNECTOR); + wpabuf_put_le16(msg, os_strlen(hapd->conf->dpp_connector)); + wpabuf_put_str(msg, hapd->conf->dpp_connector); + } + +#ifdef CONFIG_TESTING_OPTIONS +skip_connector: + if (dpp_test == DPP_TEST_NO_PROTOCOL_VERSION_PEER_DISC_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - no Protocol Version"); + goto skip_proto_ver; + } +#endif /* CONFIG_TESTING_OPTIONS */ + +#ifdef CONFIG_DPP2 + if (DPP_VERSION > 1) { + u8 ver = DPP_VERSION; +#ifdef CONFIG_DPP3 + int conn_ver; + + conn_ver = dpp_get_connector_version(hapd->conf->dpp_connector); + if (conn_ver > 0 && ver != conn_ver) { + wpa_printf(MSG_DEBUG, + "DPP: Use Connector version %d instead of current protocol version %d", + conn_ver, ver); + ver = conn_ver; + } +#endif /* CONFIG_DPP3 */ + +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_INVALID_PROTOCOL_VERSION_PEER_DISC_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - invalid Protocol Version"); + ver = 1; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + /* Protocol Version */ + wpabuf_put_le16(msg, DPP_ATTR_PROTOCOL_VERSION); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, ver); + } +#endif /* CONFIG_DPP2 */ + +#ifdef CONFIG_TESTING_OPTIONS +skip_proto_ver: +#endif /* CONFIG_TESTING_OPTIONS */ + + wpa_printf(MSG_DEBUG, "DPP: Send Peer Discovery Response to " MACSTR + " status=%d", MAC2STR(src), status); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d status=%d", MAC2STR(src), freq, + DPP_PA_PEER_DISCOVERY_RESP, status); + hostapd_drv_send_action(hapd, freq, 0, src, + wpabuf_head(msg), wpabuf_len(msg)); + wpabuf_free(msg); +} + + +static bool hapd_dpp_connector_available(struct hostapd_data *hapd) +{ + if (!hapd->wpa_auth || + !(hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_DPP) || + !(hapd->conf->wpa & WPA_PROTO_RSN)) { + wpa_printf(MSG_DEBUG, "DPP: DPP AKM not in use"); + return false; + } + + if (!hapd->conf->dpp_connector || !hapd->conf->dpp_netaccesskey || + !hapd->conf->dpp_csign) { + wpa_printf(MSG_DEBUG, "DPP: No own Connector/keys set"); + return false; + } + + return true; +} + + +static void hostapd_dpp_rx_peer_disc_req(struct hostapd_data *hapd, + const u8 *src, + const u8 *buf, size_t len, + unsigned int freq) +{ + const u8 *connector, *trans_id; + u16 connector_len, trans_id_len; + struct os_time now; + struct dpp_introduction intro; + os_time_t expire; + int expiration; + enum dpp_status_error res; + u8 pkhash[SHA256_MAC_LEN]; + + os_memset(&intro, 0, sizeof(intro)); + + wpa_printf(MSG_DEBUG, "DPP: Peer Discovery Request from " MACSTR, + MAC2STR(src)); + if (!hapd_dpp_connector_available(hapd)) + return; + + os_get_time(&now); + + if (hapd->conf->dpp_netaccesskey_expiry && + (os_time_t) hapd->conf->dpp_netaccesskey_expiry < now.sec) { + wpa_printf(MSG_INFO, "DPP: Own netAccessKey expired"); + return; + } + + trans_id = dpp_get_attr(buf, len, DPP_ATTR_TRANSACTION_ID, + &trans_id_len); + if (!trans_id || trans_id_len != 1) { + wpa_printf(MSG_DEBUG, + "DPP: Peer did not include Transaction ID"); + return; + } + + connector = dpp_get_attr(buf, len, DPP_ATTR_CONNECTOR, &connector_len); + if (!connector) { + wpa_printf(MSG_DEBUG, + "DPP: Peer did not include its Connector"); + return; + } + + res = dpp_peer_intro(&intro, hapd->conf->dpp_connector, + wpabuf_head(hapd->conf->dpp_netaccesskey), + wpabuf_len(hapd->conf->dpp_netaccesskey), + wpabuf_head(hapd->conf->dpp_csign), + wpabuf_len(hapd->conf->dpp_csign), + connector, connector_len, &expire, pkhash); + if (res == 255) { + wpa_printf(MSG_INFO, + "DPP: Network Introduction protocol resulted in internal failure (peer " + MACSTR ")", MAC2STR(src)); + goto done; + } + if (res != DPP_STATUS_OK) { + wpa_printf(MSG_INFO, + "DPP: Network Introduction protocol resulted in failure (peer " + MACSTR " status %d)", MAC2STR(src), res); + hostapd_dpp_send_peer_disc_resp(hapd, src, freq, trans_id[0], + res); + goto done; + } + +#ifdef CONFIG_DPP3 + if (intro.peer_version && intro.peer_version >= 2) { + const u8 *version; + u16 version_len; + u8 attr_version = 1; + + version = dpp_get_attr(buf, len, DPP_ATTR_PROTOCOL_VERSION, + &version_len); + if (version && version_len >= 1) + attr_version = version[0]; + if (attr_version != intro.peer_version) { + wpa_printf(MSG_INFO, + "DPP: Protocol version mismatch (Connector: %d Attribute: %d", + intro.peer_version, attr_version); + hostapd_dpp_send_peer_disc_resp(hapd, src, freq, + trans_id[0], + DPP_STATUS_NO_MATCH); + goto done; + } + } +#endif /* CONFIG_DPP3 */ + + if (!expire || (os_time_t) hapd->conf->dpp_netaccesskey_expiry < expire) + expire = hapd->conf->dpp_netaccesskey_expiry; + if (expire) + expiration = expire - now.sec; + else + expiration = 0; + + if (wpa_auth_pmksa_add2(hapd->wpa_auth, src, intro.pmk, intro.pmk_len, + intro.pmkid, expiration, + WPA_KEY_MGMT_DPP, pkhash) < 0) { + wpa_printf(MSG_ERROR, "DPP: Failed to add PMKSA cache entry"); + goto done; + } + + hostapd_dpp_send_peer_disc_resp(hapd, src, freq, trans_id[0], + DPP_STATUS_OK); +done: + dpp_peer_intro_deinit(&intro); +} + + +static void +hostapd_dpp_rx_pkex_exchange_req(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq, bool v2) +{ + struct wpabuf *msg; + + wpa_printf(MSG_DEBUG, "DPP: PKEX Exchange Request from " MACSTR, + MAC2STR(src)); + + if (hapd->dpp_pkex_ver == PKEX_VER_ONLY_1 && v2) { + wpa_printf(MSG_DEBUG, + "DPP: Ignore PKEXv2 Exchange Request when configured to be PKEX v1 only"); + return; + } + if (hapd->dpp_pkex_ver == PKEX_VER_ONLY_2 && !v2) { + wpa_printf(MSG_DEBUG, + "DPP: Ignore PKEXv1 Exchange Request when configured to be PKEX v2 only"); + return; + } + + /* TODO: Support multiple PKEX codes by iterating over all the enabled + * values here */ + + if (!hapd->dpp_pkex_code || !hapd->dpp_pkex_bi) { + wpa_printf(MSG_DEBUG, + "DPP: No PKEX code configured - ignore request"); + goto try_relay; + } + +#ifdef CONFIG_DPP2 + if (dpp_controller_is_own_pkex_req(hapd->iface->interfaces->dpp, + buf, len)) { + wpa_printf(MSG_DEBUG, + "DPP: PKEX Exchange Request is from local Controller - ignore request"); + return; + } +#endif /* CONFIG_DPP2 */ + + if (hapd->dpp_pkex) { + /* TODO: Support parallel operations */ + wpa_printf(MSG_DEBUG, + "DPP: Already in PKEX session - ignore new request"); + goto try_relay; + } + + hapd->dpp_pkex = dpp_pkex_rx_exchange_req(hapd->msg_ctx, + hapd->dpp_pkex_bi, + hapd->own_addr, src, + hapd->dpp_pkex_identifier, + hapd->dpp_pkex_code, + hapd->dpp_pkex_code_len, + buf, len, v2); + if (!hapd->dpp_pkex) { + wpa_printf(MSG_DEBUG, + "DPP: Failed to process the request - ignore it"); + goto try_relay; + } + + msg = hapd->dpp_pkex->exchange_resp; + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", MAC2STR(src), freq, + DPP_PA_PKEX_EXCHANGE_RESP); + hostapd_drv_send_action(hapd, freq, 0, src, + wpabuf_head(msg), wpabuf_len(msg)); + if (hapd->dpp_pkex->failed) { + wpa_printf(MSG_DEBUG, + "DPP: Terminate PKEX exchange due to an earlier error"); + if (hapd->dpp_pkex->t > hapd->dpp_pkex->own_bi->pkex_t) + hapd->dpp_pkex->own_bi->pkex_t = hapd->dpp_pkex->t; + dpp_pkex_free(hapd->dpp_pkex); + hapd->dpp_pkex = NULL; + } + + return; + +try_relay: +#ifdef CONFIG_DPP2 + if (v2 && dpp_relay_rx_action(hapd->iface->interfaces->dpp, + src, hdr, buf, len, freq, NULL, NULL, + hapd) != 0) { + wpa_printf(MSG_DEBUG, + "DPP: No Relay available for the message"); + hostapd_dpp_relay_needs_controller(hapd, src, + DPP_PA_PKEX_EXCHANGE_REQ); + } +#else /* CONFIG_DPP2 */ + wpa_printf(MSG_DEBUG, "DPP: No relay functionality included - skip"); +#endif /* CONFIG_DPP2 */ +} + + +static void +hostapd_dpp_rx_pkex_exchange_resp(struct hostapd_data *hapd, const u8 *src, + const u8 *buf, size_t len, unsigned int freq) +{ + struct wpabuf *msg; + + wpa_printf(MSG_DEBUG, "DPP: PKEX Exchange Response from " MACSTR, + MAC2STR(src)); + + /* TODO: Support multiple PKEX codes by iterating over all the enabled + * values here */ + + if (!hapd->dpp_pkex || !hapd->dpp_pkex->initiator || + hapd->dpp_pkex->exchange_done) { + wpa_printf(MSG_DEBUG, "DPP: No matching PKEX session"); + return; + } + + eloop_cancel_timeout(hostapd_dpp_pkex_retry_timeout, hapd, NULL); + hapd->dpp_pkex->exch_req_wait_time = 0; + + msg = dpp_pkex_rx_exchange_resp(hapd->dpp_pkex, src, buf, len); + if (!msg) { + wpa_printf(MSG_DEBUG, "DPP: Failed to process the response"); + return; + } + + wpa_printf(MSG_DEBUG, "DPP: Send PKEX Commit-Reveal Request to " MACSTR, + MAC2STR(src)); + + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", MAC2STR(src), freq, + DPP_PA_PKEX_COMMIT_REVEAL_REQ); + hostapd_drv_send_action(hapd, freq, 0, src, + wpabuf_head(msg), wpabuf_len(msg)); + wpabuf_free(msg); +} + + +static void +hostapd_dpp_rx_pkex_commit_reveal_req(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq) +{ + struct wpabuf *msg; + struct dpp_pkex *pkex = hapd->dpp_pkex; + struct dpp_bootstrap_info *bi; + + wpa_printf(MSG_DEBUG, "DPP: PKEX Commit-Reveal Request from " MACSTR, + MAC2STR(src)); + + if (!pkex || pkex->initiator || !pkex->exchange_done) { + wpa_printf(MSG_DEBUG, "DPP: No matching PKEX session"); + return; + } + + msg = dpp_pkex_rx_commit_reveal_req(pkex, hdr, buf, len); + if (!msg) { + wpa_printf(MSG_DEBUG, "DPP: Failed to process the request"); + if (hapd->dpp_pkex->failed) { + wpa_printf(MSG_DEBUG, "DPP: Terminate PKEX exchange"); + if (hapd->dpp_pkex->t > hapd->dpp_pkex->own_bi->pkex_t) + hapd->dpp_pkex->own_bi->pkex_t = + hapd->dpp_pkex->t; + dpp_pkex_free(hapd->dpp_pkex); + hapd->dpp_pkex = NULL; + } + return; + } + + wpa_printf(MSG_DEBUG, "DPP: Send PKEX Commit-Reveal Response to " + MACSTR, MAC2STR(src)); + + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", MAC2STR(src), freq, + DPP_PA_PKEX_COMMIT_REVEAL_RESP); + hostapd_drv_send_action(hapd, freq, 0, src, + wpabuf_head(msg), wpabuf_len(msg)); + wpabuf_free(msg); + + hostapd_dpp_pkex_clear_code(hapd); + bi = dpp_pkex_finish(hapd->iface->interfaces->dpp, pkex, src, freq); + if (!bi) + return; + hapd->dpp_pkex = NULL; +} + + +static void +hostapd_dpp_rx_pkex_commit_reveal_resp(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq) +{ + struct hapd_interfaces *ifaces = hapd->iface->interfaces; + int res; + struct dpp_bootstrap_info *bi; + struct dpp_pkex *pkex = hapd->dpp_pkex; + char cmd[500]; + + wpa_printf(MSG_DEBUG, "DPP: PKEX Commit-Reveal Response from " MACSTR, + MAC2STR(src)); + + if (!pkex || !pkex->initiator || !pkex->exchange_done) { + wpa_printf(MSG_DEBUG, "DPP: No matching PKEX session"); + return; + } + + res = dpp_pkex_rx_commit_reveal_resp(pkex, hdr, buf, len); + if (res < 0) { + wpa_printf(MSG_DEBUG, "DPP: Failed to process the response"); + return; + } + + hostapd_dpp_pkex_clear_code(hapd); + bi = dpp_pkex_finish(ifaces->dpp, pkex, src, freq); + if (!bi) + return; + hapd->dpp_pkex = NULL; + +#ifdef CONFIG_DPP3 + if (ifaces->dpp_pb_bi && + os_memcmp(bi->pubkey_hash_chirp, ifaces->dpp_pb_resp_hash, + SHA256_MAC_LEN) != 0) { + char id[20]; + + wpa_printf(MSG_INFO, + "DPP: Peer bootstrap key from PKEX does not match PB announcement hash"); + wpa_hexdump(MSG_DEBUG, + "DPP: Peer provided bootstrap key hash(chirp) from PB PKEX", + bi->pubkey_hash_chirp, SHA256_MAC_LEN); + wpa_hexdump(MSG_DEBUG, + "DPP: Peer provided bootstrap key hash(chirp) from PB announcement", + ifaces->dpp_pb_resp_hash, SHA256_MAC_LEN); + + os_snprintf(id, sizeof(id), "%u", bi->id); + dpp_bootstrap_remove(ifaces->dpp, id); + hostapd_dpp_push_button_stop(hapd); + return; + } +#endif /* CONFIG_DPP3 */ + + os_snprintf(cmd, sizeof(cmd), " peer=%u %s", + bi->id, + hapd->dpp_pkex_auth_cmd ? hapd->dpp_pkex_auth_cmd : ""); + wpa_printf(MSG_DEBUG, + "DPP: Start authentication after PKEX with parameters: %s", + cmd); + if (hostapd_dpp_auth_init(hapd, cmd) < 0) { + wpa_printf(MSG_DEBUG, + "DPP: Authentication initialization failed"); + return; + } +} + + +#ifdef CONFIG_DPP3 + +static void hostapd_dpp_pb_pkex_init(struct hostapd_data *hapd, + unsigned int freq, const u8 *src, + const u8 *r_hash) +{ + struct hapd_interfaces *ifaces = hapd->iface->interfaces; + struct dpp_pkex *pkex; + struct wpabuf *msg; + char ssid_hex[2 * SSID_MAX_LEN + 1], *pass_hex = NULL; + char cmd[300]; + const char *password = NULL; +#ifdef CONFIG_SAE + struct sae_password_entry *e; +#endif /* CONFIG_SAE */ + int conf_id = -1; + bool sae = false, psk = false; + size_t len; + + if (hapd->dpp_pkex) { + wpa_printf(MSG_DEBUG, + "PDP: Sending previously generated PKEX Exchange Request to " + MACSTR, MAC2STR(src)); + msg = hapd->dpp_pkex->exchange_req; + hostapd_drv_send_action(hapd, freq, 0, src, + wpabuf_head(msg), wpabuf_len(msg)); + return; + } + + wpa_printf(MSG_DEBUG, "DPP: Initiate PKEX for push button with " + MACSTR, MAC2STR(src)); + + hapd->dpp_pkex_bi = ifaces->dpp_pb_bi; + os_memcpy(ifaces->dpp_pb_resp_hash, r_hash, SHA256_MAC_LEN); + + pkex = dpp_pkex_init(hapd->msg_ctx, hapd->dpp_pkex_bi, hapd->own_addr, + "PBPKEX", (const char *) ifaces->dpp_pb_c_nonce, + ifaces->dpp_pb_bi->curve->nonce_len, + true); + if (!pkex) { + hostapd_dpp_push_button_stop(hapd); + return; + } + pkex->freq = freq; + + hapd->dpp_pkex = pkex; + msg = hapd->dpp_pkex->exchange_req; + + if (ifaces->dpp_pb_cmd) { + /* Use the externally provided configuration */ + os_free(hapd->dpp_pkex_auth_cmd); + len = 30 + os_strlen(ifaces->dpp_pb_cmd); + hapd->dpp_pkex_auth_cmd = os_malloc(len); + if (!hapd->dpp_pkex_auth_cmd) { + hostapd_dpp_push_button_stop(hapd); + return; + } + os_snprintf(hapd->dpp_pkex_auth_cmd, len, " own=%d %s", + hapd->dpp_pkex_bi->id, ifaces->dpp_pb_cmd); + goto send_frame; + } + + /* Build config based on the current AP configuration */ + wpa_snprintf_hex(ssid_hex, sizeof(ssid_hex), + (const u8 *) hapd->conf->ssid.ssid, + hapd->conf->ssid.ssid_len); + + if (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_DPP) { + /* TODO: If a local Configurator has been enabled, allow a + * DPP AKM credential to be provisioned by setting conf_id. */ + } + + if (hapd->conf->wpa & WPA_PROTO_RSN) { + psk = hapd->conf->wpa_key_mgmt & (WPA_KEY_MGMT_PSK | + WPA_KEY_MGMT_PSK_SHA256); +#ifdef CONFIG_SAE + sae = hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_SAE; +#endif /* CONFIG_SAE */ + } + +#ifdef CONFIG_SAE + for (e = hapd->conf->sae_passwords; sae && e && !password; + e = e->next) { + if (e->identifier || !is_broadcast_ether_addr(e->peer_addr)) + continue; + password = e->password; + } +#endif /* CONFIG_SAE */ + if (!password && hapd->conf->ssid.wpa_passphrase_set && + hapd->conf->ssid.wpa_passphrase) + password = hapd->conf->ssid.wpa_passphrase; + if (password) { + len = 2 * os_strlen(password) + 1; + pass_hex = os_malloc(len); + if (!pass_hex) { + hostapd_dpp_push_button_stop(hapd); + return; + } + wpa_snprintf_hex(pass_hex, len, (const u8 *) password, + os_strlen(password)); + } + + if (conf_id > 0 && sae && psk && pass_hex) { + os_snprintf(cmd, sizeof(cmd), + "conf=sta-dpp+psk+sae configurator=%d ssid=%s pass=%s", + conf_id, ssid_hex, pass_hex); + } else if (conf_id > 0 && sae && pass_hex) { + os_snprintf(cmd, sizeof(cmd), + "conf=sta-dpp+sae configurator=%d ssid=%s pass=%s", + conf_id, ssid_hex, pass_hex); + } else if (conf_id > 0) { + os_snprintf(cmd, sizeof(cmd), + "conf=sta-dpp configurator=%d ssid=%s", + conf_id, ssid_hex); + } if (sae && psk && pass_hex) { + os_snprintf(cmd, sizeof(cmd), + "conf=sta-psk+sae ssid=%s pass=%s", + ssid_hex, pass_hex); + } else if (sae && pass_hex) { + os_snprintf(cmd, sizeof(cmd), + "conf=sta-sae ssid=%s pass=%s", + ssid_hex, pass_hex); + } else if (psk && pass_hex) { + os_snprintf(cmd, sizeof(cmd), + "conf=sta-psk ssid=%s pass=%s", + ssid_hex, pass_hex); + } else { + wpa_printf(MSG_INFO, + "DPP: Unsupported AP configuration for push button"); + str_clear_free(pass_hex); + hostapd_dpp_push_button_stop(hapd); + return; + } + str_clear_free(pass_hex); + + os_free(hapd->dpp_pkex_auth_cmd); + len = 30 + os_strlen(cmd); + hapd->dpp_pkex_auth_cmd = os_malloc(len); + if (hapd->dpp_pkex_auth_cmd) + os_snprintf(hapd->dpp_pkex_auth_cmd, len, " own=%d %s", + hapd->dpp_pkex_bi->id, cmd); + forced_memzero(cmd, sizeof(cmd)); + if (!hapd->dpp_pkex_auth_cmd) { + hostapd_dpp_push_button_stop(hapd); + return; + } + +send_frame: + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", MAC2STR(src), freq, + DPP_PA_PKEX_EXCHANGE_REQ); + hostapd_drv_send_action(hapd, pkex->freq, 0, src, + wpabuf_head(msg), wpabuf_len(msg)); + pkex->exch_req_wait_time = 2000; + pkex->exch_req_tries = 1; +} + + +static void +hostapd_dpp_rx_pb_presence_announcement(struct hostapd_data *hapd, + const u8 *src, const u8 *hdr, + const u8 *buf, size_t len, + unsigned int freq) +{ + struct hapd_interfaces *ifaces = hapd->iface->interfaces; + const u8 *r_hash; + u16 r_hash_len; + unsigned int i; + bool found = false; + struct dpp_pb_info *info, *tmp; + struct os_reltime now, age; + struct wpabuf *msg; + + if (!ifaces) + return; + + os_get_reltime(&now); + wpa_printf(MSG_DEBUG, "DPP: Push Button Presence Announcement from " + MACSTR, MAC2STR(src)); + + r_hash = dpp_get_attr(buf, len, DPP_ATTR_R_BOOTSTRAP_KEY_HASH, + &r_hash_len); + if (!r_hash || r_hash_len != SHA256_MAC_LEN) { + wpa_printf(MSG_DEBUG, + "DPP: Missing or invalid required Responder Bootstrapping Key Hash attribute"); + return; + } + wpa_hexdump(MSG_MSGDUMP, "DPP: Responder Bootstrapping Key Hash", + r_hash, r_hash_len); + + for (i = 0; i < DPP_PB_INFO_COUNT; i++) { + info = &ifaces->dpp_pb[i]; + if ((info->rx_time.sec == 0 && info->rx_time.usec == 0) || + os_memcmp(r_hash, info->hash, SHA256_MAC_LEN) != 0) + continue; + wpa_printf(MSG_DEBUG, + "DPP: Active push button Enrollee already known"); + found = true; + info->rx_time = now; + } + + if (!found) { + for (i = 0; i < DPP_PB_INFO_COUNT; i++) { + tmp = &ifaces->dpp_pb[i]; + if (tmp->rx_time.sec == 0 && tmp->rx_time.usec == 0) + continue; + + if (os_reltime_expired(&now, &tmp->rx_time, 120)) { + wpa_hexdump(MSG_DEBUG, + "DPP: Push button Enrollee hash expired", + tmp->hash, SHA256_MAC_LEN); + tmp->rx_time.sec = 0; + tmp->rx_time.usec = 0; + continue; + } + + wpa_hexdump(MSG_DEBUG, + "DPP: Push button session overlap with hash", + tmp->hash, SHA256_MAC_LEN); + if (!ifaces->dpp_pb_result_indicated && + hostapd_dpp_pb_active(hapd)) { + wpa_msg(hapd->msg_ctx, MSG_INFO, + DPP_EVENT_PB_RESULT "session-overlap"); + ifaces->dpp_pb_result_indicated = true; + } + hostapd_dpp_push_button_stop(hapd); + return; + } + + /* Replace the oldest entry */ + info = &ifaces->dpp_pb[0]; + for (i = 1; i < DPP_PB_INFO_COUNT; i++) { + tmp = &ifaces->dpp_pb[i]; + if (os_reltime_before(&tmp->rx_time, &info->rx_time)) + info = tmp; + } + wpa_printf(MSG_DEBUG, "DPP: New active push button Enrollee"); + os_memcpy(info->hash, r_hash, SHA256_MAC_LEN); + info->rx_time = now; + } + + if (!hostapd_dpp_pb_active(hapd)) { + wpa_printf(MSG_DEBUG, + "DPP: Discard message since own push button has not been pressed"); + return; + } + + if (ifaces->dpp_pb_announce_time.sec == 0 && + ifaces->dpp_pb_announce_time.usec == 0) { + /* Start a wait before allowing PKEX to be initiated */ + ifaces->dpp_pb_announce_time = now; + } + + if (!ifaces->dpp_pb_bi) { + int res; + + res = dpp_bootstrap_gen(ifaces->dpp, "type=pkex"); + if (res < 0) + return; + ifaces->dpp_pb_bi = dpp_bootstrap_get_id(ifaces->dpp, res); + if (!ifaces->dpp_pb_bi) + return; + + if (random_get_bytes(ifaces->dpp_pb_c_nonce, + ifaces->dpp_pb_bi->curve->nonce_len)) { + wpa_printf(MSG_ERROR, + "DPP: Failed to generate C-nonce"); + hostapd_dpp_push_button_stop(hapd); + return; + } + } + + /* Skip the response if one was sent within last 50 ms since the + * Enrollee is going to send out at least three announcement messages. + */ + os_reltime_sub(&now, &ifaces->dpp_pb_last_resp, &age); + if (age.sec == 0 && age.usec < 50000) { + wpa_printf(MSG_DEBUG, + "DPP: Skip Push Button Presence Announcement Response frame immediately after having sent one"); + return; + } + + msg = dpp_build_pb_announcement_resp( + ifaces->dpp_pb_bi, r_hash, ifaces->dpp_pb_c_nonce, + ifaces->dpp_pb_bi->curve->nonce_len); + if (!msg) { + hostapd_dpp_push_button_stop(hapd); + return; + } + + wpa_printf(MSG_DEBUG, + "DPP: Send Push Button Presence Announcement Response to " + MACSTR, MAC2STR(src)); + ifaces->dpp_pb_last_resp = now; + + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", MAC2STR(src), freq, + DPP_PA_PB_PRESENCE_ANNOUNCEMENT_RESP); + hostapd_drv_send_action(hapd, freq, 0, src, + wpabuf_head(msg), wpabuf_len(msg)); + wpabuf_free(msg); + + if (os_reltime_expired(&now, &ifaces->dpp_pb_announce_time, 15)) + hostapd_dpp_pb_pkex_init(hapd, freq, src, r_hash); +} + + +static void +hostapd_dpp_rx_priv_peer_intro_query(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq) +{ + const u8 *trans_id, *version; + u16 trans_id_len, version_len; + struct wpabuf *msg; + u8 ver = DPP_VERSION; + int conn_ver; + + wpa_printf(MSG_DEBUG, "DPP: Private Peer Introduction Query from " + MACSTR, MAC2STR(src)); + + if (!hapd_dpp_connector_available(hapd)) + return; + + trans_id = dpp_get_attr(buf, len, DPP_ATTR_TRANSACTION_ID, + &trans_id_len); + if (!trans_id || trans_id_len != 1) { + wpa_printf(MSG_DEBUG, + "DPP: Peer did not include Transaction ID"); + return; + } + + version = dpp_get_attr(buf, len, DPP_ATTR_PROTOCOL_VERSION, + &version_len); + if (!version || version_len != 1) { + wpa_printf(MSG_DEBUG, + "DPP: Peer did not include Protocol Version"); + return; + } + + wpa_printf(MSG_DEBUG, "DPP: Transaction ID %u, Version %u", + trans_id[0], version[0]); + + len = 5 + 5 + 4 + os_strlen(hapd->conf->dpp_connector); + msg = dpp_alloc_msg(DPP_PA_PRIV_PEER_INTRO_NOTIFY, len); + if (!msg) + return; + + /* Transaction ID */ + wpabuf_put_le16(msg, DPP_ATTR_TRANSACTION_ID); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, trans_id[0]); + + /* Protocol Version */ + conn_ver = dpp_get_connector_version(hapd->conf->dpp_connector); + if (conn_ver > 0 && ver != conn_ver) { + wpa_printf(MSG_DEBUG, + "DPP: Use Connector version %d instead of current protocol version %d", + conn_ver, ver); + ver = conn_ver; + } + wpabuf_put_le16(msg, DPP_ATTR_PROTOCOL_VERSION); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, ver); + + /* DPP Connector */ + wpabuf_put_le16(msg, DPP_ATTR_CONNECTOR); + wpabuf_put_le16(msg, os_strlen(hapd->conf->dpp_connector)); + wpabuf_put_str(msg, hapd->conf->dpp_connector); + + wpa_printf(MSG_DEBUG, "DPP: Send Private Peer Introduction Notify to " + MACSTR, MAC2STR(src)); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", MAC2STR(src), freq, + DPP_PA_PRIV_PEER_INTRO_NOTIFY); + hostapd_drv_send_action(hapd, freq, 0, src, + wpabuf_head(msg), wpabuf_len(msg)); + wpabuf_free(msg); +} + + +static void +hostapd_dpp_rx_priv_peer_intro_update(struct hostapd_data *hapd, const u8 *src, + const u8 *hdr, const u8 *buf, size_t len, + unsigned int freq) +{ + struct crypto_ec_key *own_key; + const struct dpp_curve_params *curve; + enum hpke_kem_id kem_id; + enum hpke_kdf_id kdf_id; + enum hpke_aead_id aead_id; + const u8 *aad = hdr; + size_t aad_len = DPP_HDR_LEN; + struct wpabuf *pt; + const u8 *trans_id, *wrapped, *version, *connector; + u16 trans_id_len, wrapped_len, version_len, connector_len; + struct os_time now; + struct dpp_introduction intro; + os_time_t expire; + int expiration; + enum dpp_status_error res; + u8 pkhash[SHA256_MAC_LEN]; + + os_memset(&intro, 0, sizeof(intro)); + + wpa_printf(MSG_DEBUG, "DPP: Private Peer Introduction Update from " + MACSTR, MAC2STR(src)); + + if (!hapd_dpp_connector_available(hapd)) + return; + + os_get_time(&now); + + if (hapd->conf->dpp_netaccesskey_expiry && + (os_time_t) hapd->conf->dpp_netaccesskey_expiry < now.sec) { + wpa_printf(MSG_INFO, "DPP: Own netAccessKey expired"); + return; + } + + trans_id = dpp_get_attr(buf, len, DPP_ATTR_TRANSACTION_ID, + &trans_id_len); + if (!trans_id || trans_id_len != 1) { + wpa_printf(MSG_DEBUG, + "DPP: Peer did not include Transaction ID"); + return; + } + + wrapped = dpp_get_attr(buf, len, DPP_ATTR_WRAPPED_DATA, + &wrapped_len); + if (!wrapped) { + wpa_printf(MSG_DEBUG, "DPP: Peer did not include Wrapped Data"); + return; + } + + own_key = dpp_set_keypair(&curve, + wpabuf_head(hapd->conf->dpp_netaccesskey), + wpabuf_len(hapd->conf->dpp_netaccesskey)); + if (!own_key) { + wpa_printf(MSG_ERROR, "DPP: Failed to parse own netAccessKey"); + return; + } + + if (dpp_hpke_suite(curve->ike_group, &kem_id, &kdf_id, &aead_id) < 0) { + wpa_printf(MSG_ERROR, "DPP: Unsupported curve %d", + curve->ike_group); + crypto_ec_key_deinit(own_key); + return; + } + + pt = hpke_base_open(kem_id, kdf_id, aead_id, own_key, NULL, 0, + aad, aad_len, wrapped, wrapped_len); + crypto_ec_key_deinit(own_key); + if (!pt) { + wpa_printf(MSG_INFO, "DPP: Failed to decrypt Connector"); + return; + } + wpa_hexdump_buf(MSG_MSGDUMP, "DPP: HPKE-Decrypted Wrapped Data", pt); + + connector = dpp_get_attr(wpabuf_head(pt), wpabuf_len(pt), + DPP_ATTR_CONNECTOR, &connector_len); + if (!connector) { + wpa_printf(MSG_DEBUG, + "DPP: Peer did not include its Connector"); + goto done; + } + + version = dpp_get_attr(wpabuf_head(pt), wpabuf_len(pt), + DPP_ATTR_PROTOCOL_VERSION, &version_len); + if (!version || version_len < 1) { + wpa_printf(MSG_DEBUG, + "DPP: Peer did not include Protocol Version"); + goto done; + } + + res = dpp_peer_intro(&intro, hapd->conf->dpp_connector, + wpabuf_head(hapd->conf->dpp_netaccesskey), + wpabuf_len(hapd->conf->dpp_netaccesskey), + wpabuf_head(hapd->conf->dpp_csign), + wpabuf_len(hapd->conf->dpp_csign), + connector, connector_len, &expire, pkhash); + if (res == 255) { + wpa_printf(MSG_INFO, + "DPP: Network Introduction protocol resulted in internal failure (peer " + MACSTR ")", MAC2STR(src)); + goto done; + } + if (res != DPP_STATUS_OK) { + wpa_printf(MSG_INFO, + "DPP: Network Introduction protocol resulted in failure (peer " + MACSTR " status %d)", MAC2STR(src), res); + goto done; + } + + if (intro.peer_version && intro.peer_version >= 2) { + u8 attr_version = 1; + + if (version && version_len >= 1) + attr_version = version[0]; + if (attr_version != intro.peer_version) { + wpa_printf(MSG_INFO, + "DPP: Protocol version mismatch (Connector: %d Attribute: %d", + intro.peer_version, attr_version); + goto done; + } + } + + if (!expire || (os_time_t) hapd->conf->dpp_netaccesskey_expiry < expire) + expire = hapd->conf->dpp_netaccesskey_expiry; + if (expire) + expiration = expire - now.sec; + else + expiration = 0; + + if (wpa_auth_pmksa_add2(hapd->wpa_auth, src, intro.pmk, intro.pmk_len, + intro.pmkid, expiration, + WPA_KEY_MGMT_DPP, pkhash) < 0) { + wpa_printf(MSG_ERROR, "DPP: Failed to add PMKSA cache entry"); + goto done; + } + + wpa_printf(MSG_DEBUG, "DPP: Private Peer Introduction completed with " + MACSTR, MAC2STR(src)); + +done: + dpp_peer_intro_deinit(&intro); + wpabuf_free(pt); +} + +#endif /* CONFIG_DPP3 */ + + +void hostapd_dpp_rx_action(struct hostapd_data *hapd, const u8 *src, + const u8 *buf, size_t len, unsigned int freq) +{ + u8 crypto_suite; + enum dpp_public_action_frame_type type; + const u8 *hdr; + unsigned int pkex_t; + + if (len < DPP_HDR_LEN) + return; + if (WPA_GET_BE24(buf) != OUI_WFA || buf[3] != DPP_OUI_TYPE) + return; + hdr = buf; + buf += 4; + len -= 4; + crypto_suite = *buf++; + type = *buf++; + len -= 2; + + wpa_printf(MSG_DEBUG, + "DPP: Received DPP Public Action frame crypto suite %u type %d from " + MACSTR " freq=%u", + crypto_suite, type, MAC2STR(src), freq); + if (crypto_suite != 1) { + wpa_printf(MSG_DEBUG, "DPP: Unsupported crypto suite %u", + crypto_suite); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_RX "src=" MACSTR + " freq=%u type=%d ignore=unsupported-crypto-suite", + MAC2STR(src), freq, type); + return; + } + wpa_hexdump(MSG_MSGDUMP, "DPP: Received message attributes", buf, len); + if (dpp_check_attrs(buf, len) < 0) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_RX "src=" MACSTR + " freq=%u type=%d ignore=invalid-attributes", + MAC2STR(src), freq, type); + return; + } + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_RX "src=" MACSTR + " freq=%u type=%d", MAC2STR(src), freq, type); + +#ifdef CONFIG_DPP2 + if (dpp_relay_rx_action(hapd->iface->interfaces->dpp, + src, hdr, buf, len, freq, NULL, NULL, + hapd) == 0) + return; +#endif /* CONFIG_DPP2 */ + + switch (type) { + case DPP_PA_AUTHENTICATION_REQ: + hostapd_dpp_rx_auth_req(hapd, src, hdr, buf, len, freq); + break; + case DPP_PA_AUTHENTICATION_RESP: + hostapd_dpp_rx_auth_resp(hapd, src, hdr, buf, len, freq); + break; + case DPP_PA_AUTHENTICATION_CONF: + hostapd_dpp_rx_auth_conf(hapd, src, hdr, buf, len); + break; + case DPP_PA_PEER_DISCOVERY_REQ: + hostapd_dpp_rx_peer_disc_req(hapd, src, buf, len, freq); + break; +#ifdef CONFIG_DPP3 + case DPP_PA_PKEX_EXCHANGE_REQ: + /* This is for PKEXv2, but for now, process only with + * CONFIG_DPP3 to avoid issues with a capability that has not + * been tested with other implementations. */ + hostapd_dpp_rx_pkex_exchange_req(hapd, src, hdr, buf, len, freq, + true); + break; +#endif /* CONFIG_DPP3 */ + case DPP_PA_PKEX_V1_EXCHANGE_REQ: + hostapd_dpp_rx_pkex_exchange_req(hapd, src, hdr, buf, len, freq, + false); + break; + case DPP_PA_PKEX_EXCHANGE_RESP: + hostapd_dpp_rx_pkex_exchange_resp(hapd, src, buf, len, freq); + break; + case DPP_PA_PKEX_COMMIT_REVEAL_REQ: + hostapd_dpp_rx_pkex_commit_reveal_req(hapd, src, hdr, buf, len, + freq); + break; + case DPP_PA_PKEX_COMMIT_REVEAL_RESP: + hostapd_dpp_rx_pkex_commit_reveal_resp(hapd, src, hdr, buf, len, + freq); + break; +#ifdef CONFIG_DPP2 + case DPP_PA_CONFIGURATION_RESULT: + hostapd_dpp_rx_conf_result(hapd, src, hdr, buf, len); + break; + case DPP_PA_CONNECTION_STATUS_RESULT: + hostapd_dpp_rx_conn_status_result(hapd, src, hdr, buf, len); + break; + case DPP_PA_PRESENCE_ANNOUNCEMENT: + hostapd_dpp_rx_presence_announcement(hapd, src, hdr, buf, len, + freq); + break; + case DPP_PA_RECONFIG_ANNOUNCEMENT: + hostapd_dpp_rx_reconfig_announcement(hapd, src, hdr, buf, len, + freq); + break; + case DPP_PA_RECONFIG_AUTH_RESP: + hostapd_dpp_rx_reconfig_auth_resp(hapd, src, hdr, buf, len, + freq); + break; +#endif /* CONFIG_DPP2 */ +#ifdef CONFIG_DPP3 + case DPP_PA_PB_PRESENCE_ANNOUNCEMENT: + hostapd_dpp_rx_pb_presence_announcement(hapd, src, hdr, + buf, len, freq); + break; + case DPP_PA_PRIV_PEER_INTRO_QUERY: + hostapd_dpp_rx_priv_peer_intro_query(hapd, src, hdr, + buf, len, freq); + break; + case DPP_PA_PRIV_PEER_INTRO_UPDATE: + hostapd_dpp_rx_priv_peer_intro_update(hapd, src, hdr, + buf, len, freq); + break; +#endif /* CONFIG_DPP3 */ + default: + wpa_printf(MSG_DEBUG, + "DPP: Ignored unsupported frame subtype %d", type); + break; + } + + if (hapd->dpp_pkex) + pkex_t = hapd->dpp_pkex->t; + else if (hapd->dpp_pkex_bi) + pkex_t = hapd->dpp_pkex_bi->pkex_t; + else + pkex_t = 0; + if (pkex_t >= PKEX_COUNTER_T_LIMIT) { + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_PKEX_T_LIMIT "id=0"); + hostapd_dpp_pkex_remove(hapd, "*"); + } +} + + +struct wpabuf * +hostapd_dpp_gas_req_handler(struct hostapd_data *hapd, const u8 *sa, + const u8 *query, size_t query_len, + const u8 *data, size_t data_len) +{ + struct dpp_authentication *auth = hapd->dpp_auth; + struct wpabuf *resp; + + wpa_printf(MSG_DEBUG, "DPP: GAS request from " MACSTR, MAC2STR(sa)); + if (!auth || (!auth->auth_success && !auth->reconfig_success) || + !ether_addr_equal(sa, auth->peer_mac_addr)) { +#ifdef CONFIG_DPP2 + if (dpp_relay_rx_gas_req(hapd->iface->interfaces->dpp, sa, data, + data_len) == 0) { + /* Response will be forwarded once received over TCP */ + return NULL; + } +#endif /* CONFIG_DPP2 */ + wpa_printf(MSG_DEBUG, "DPP: No matching exchange in progress"); + return NULL; + } + + if (hapd->dpp_auth_ok_on_ack && auth->configurator) { + wpa_printf(MSG_DEBUG, + "DPP: Have not received ACK for Auth Confirm yet - assume it was received based on this GAS request"); + /* hostapd_dpp_auth_success() would normally have been called + * from TX status handler, but since there was no such handler + * call yet, simply send out the event message and proceed with + * exchange. */ + dpp_notify_auth_success(hapd->dpp_auth, 1); + hapd->dpp_auth_ok_on_ack = 0; +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_STOP_AT_AUTH_CONF) { + wpa_printf(MSG_INFO, + "DPP: TESTING - stop at Authentication Confirm"); + return NULL; + } +#endif /* CONFIG_TESTING_OPTIONS */ + } + + wpa_hexdump(MSG_DEBUG, + "DPP: Received Configuration Request (GAS Query Request)", + query, query_len); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_REQ_RX "src=" MACSTR, + MAC2STR(sa)); + resp = dpp_conf_req_rx(auth, query, query_len); + if (!resp) + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_FAILED); + return resp; +} + + +void hostapd_dpp_gas_status_handler(struct hostapd_data *hapd, int ok) +{ + struct dpp_authentication *auth = hapd->dpp_auth; +#ifdef CONFIG_DPP3 + struct hapd_interfaces *ifaces = hapd->iface->interfaces; +#endif /* CONFIG_DPP3 */ + + if (!auth) + return; + +#ifdef CONFIG_DPP3 + if (auth->waiting_new_key && ok) { + wpa_printf(MSG_DEBUG, "DPP: Waiting for a new key"); + return; + } +#endif /* CONFIG_DPP3 */ + + wpa_printf(MSG_DEBUG, "DPP: Configuration exchange completed (ok=%d)", + ok); + eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_auth_conf_wait_timeout, hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_auth_resp_retry_timeout, hapd, NULL); +#ifdef CONFIG_DPP2 + eloop_cancel_timeout(hostapd_dpp_reconfig_reply_wait_timeout, + hapd, NULL); + if (ok && auth->peer_version >= 2 && + auth->conf_resp_status == DPP_STATUS_OK) { + wpa_printf(MSG_DEBUG, "DPP: Wait for Configuration Result"); + auth->waiting_conf_result = 1; + eloop_cancel_timeout(hostapd_dpp_config_result_wait_timeout, + hapd, NULL); + eloop_register_timeout(2, 0, + hostapd_dpp_config_result_wait_timeout, + hapd, NULL); + return; + } +#endif /* CONFIG_DPP2 */ + hostapd_drv_send_action_cancel_wait(hapd); + + if (ok) + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_SENT + "conf_status=%d", auth->conf_resp_status); + else + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CONF_FAILED); + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; +#ifdef CONFIG_DPP3 + if (!ifaces->dpp_pb_result_indicated && hostapd_dpp_pb_active(hapd)) { + if (ok) + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_PB_RESULT + "success"); + else + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_PB_RESULT + "could-not-connect"); + ifaces->dpp_pb_result_indicated = true; + if (ok) + hostapd_dpp_remove_pb_hash(hapd); + hostapd_dpp_push_button_stop(hapd); + } +#endif /* CONFIG_DPP3 */ +} + + +int hostapd_dpp_configurator_sign(struct hostapd_data *hapd, const char *cmd) +{ + struct dpp_authentication *auth; + int ret = -1; + char *curve = NULL; + + auth = dpp_alloc_auth(hapd->iface->interfaces->dpp, hapd->msg_ctx); + if (!auth) + return -1; + + curve = get_param(cmd, " curve="); + hostapd_dpp_set_testing_options(hapd, auth); + if (dpp_set_configurator(auth, cmd) == 0 && + dpp_configurator_own_config(auth, curve, 1) == 0) { + hostapd_dpp_handle_config_obj(hapd, auth, &auth->conf_obj[0]); + ret = 0; + } + + dpp_auth_deinit(auth); + os_free(curve); + + return ret; +} + + +int hostapd_dpp_pkex_add(struct hostapd_data *hapd, const char *cmd) +{ + struct dpp_bootstrap_info *own_bi; + const char *pos, *end; +#ifdef CONFIG_DPP3 + enum dpp_pkex_ver ver = PKEX_VER_AUTO; +#else /* CONFIG_DPP3 */ + enum dpp_pkex_ver ver = PKEX_VER_ONLY_1; +#endif /* CONFIG_DPP3 */ + int tcp_port = DPP_TCP_PORT; + struct hostapd_ip_addr *ipaddr = NULL; +#ifdef CONFIG_DPP2 + struct hostapd_ip_addr ipaddr_buf; + char *addr; + + pos = os_strstr(cmd, " tcp_port="); + if (pos) { + pos += 10; + tcp_port = atoi(pos); + } + + addr = get_param(cmd, " tcp_addr="); + if (addr) { + int res; + + res = hostapd_parse_ip_addr(addr, &ipaddr_buf); + os_free(addr); + if (res) + return -1; + ipaddr = &ipaddr_buf; + } +#endif /* CONFIG_DPP2 */ + + pos = os_strstr(cmd, " own="); + if (!pos) + return -1; + pos += 5; + own_bi = dpp_bootstrap_get_id(hapd->iface->interfaces->dpp, atoi(pos)); + if (!own_bi) { + wpa_printf(MSG_DEBUG, + "DPP: Identified bootstrap info not found"); + return -1; + } + if (own_bi->type != DPP_BOOTSTRAP_PKEX) { + wpa_printf(MSG_DEBUG, + "DPP: Identified bootstrap info not for PKEX"); + return -1; + } + hapd->dpp_pkex_bi = own_bi; + own_bi->pkex_t = 0; /* clear pending errors on new code */ + + os_free(hapd->dpp_pkex_identifier); + hapd->dpp_pkex_identifier = NULL; + pos = os_strstr(cmd, " identifier="); + if (pos) { + pos += 12; + end = os_strchr(pos, ' '); + if (!end) + return -1; + hapd->dpp_pkex_identifier = os_malloc(end - pos + 1); + if (!hapd->dpp_pkex_identifier) + return -1; + os_memcpy(hapd->dpp_pkex_identifier, pos, end - pos); + hapd->dpp_pkex_identifier[end - pos] = '\0'; + } + + pos = os_strstr(cmd, " code="); + if (!pos) + return -1; + os_free(hapd->dpp_pkex_code); + hapd->dpp_pkex_code = os_strdup(pos + 6); + if (!hapd->dpp_pkex_code) + return -1; + hapd->dpp_pkex_code_len = os_strlen(hapd->dpp_pkex_code); + + pos = os_strstr(cmd, " ver="); + if (pos) { + int v; + + pos += 5; + v = atoi(pos); + if (v == 1) + ver = PKEX_VER_ONLY_1; + else if (v == 2) + ver = PKEX_VER_ONLY_2; + else + return -1; + } + hapd->dpp_pkex_ver = ver; + + if (os_strstr(cmd, " init=1")) { + if (hostapd_dpp_pkex_init(hapd, ver, ipaddr, tcp_port) < 0) + return -1; + } else { +#ifdef CONFIG_DPP2 + dpp_controller_pkex_add(hapd->iface->interfaces->dpp, own_bi, + hapd->dpp_pkex_code, + hapd->dpp_pkex_identifier); +#endif /* CONFIG_DPP2 */ + } + + /* TODO: Support multiple PKEX info entries */ + + os_free(hapd->dpp_pkex_auth_cmd); + hapd->dpp_pkex_auth_cmd = os_strdup(cmd); + + return 1; +} + + +int hostapd_dpp_pkex_remove(struct hostapd_data *hapd, const char *id) +{ + unsigned int id_val; + + if (os_strcmp(id, "*") == 0) { + id_val = 0; + } else { + id_val = atoi(id); + if (id_val == 0) + return -1; + } + + if ((id_val != 0 && id_val != 1)) + return -1; + + /* TODO: Support multiple PKEX entries */ + os_free(hapd->dpp_pkex_code); + hapd->dpp_pkex_code = NULL; + os_free(hapd->dpp_pkex_identifier); + hapd->dpp_pkex_identifier = NULL; + os_free(hapd->dpp_pkex_auth_cmd); + hapd->dpp_pkex_auth_cmd = NULL; + hapd->dpp_pkex_bi = NULL; + /* TODO: Remove dpp_pkex only if it is for the identified PKEX code */ + dpp_pkex_free(hapd->dpp_pkex); + hapd->dpp_pkex = NULL; + return 0; +} + + +void hostapd_dpp_stop(struct hostapd_data *hapd) +{ + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; + dpp_pkex_free(hapd->dpp_pkex); + hapd->dpp_pkex = NULL; +#ifdef CONFIG_DPP3 + hostapd_dpp_push_button_stop(hapd); +#endif /* CONFIG_DPP3 */ +} + + +#ifdef CONFIG_DPP2 + +static void hostapd_dpp_relay_tx(void *ctx, const u8 *addr, unsigned int freq, + const u8 *msg, size_t len) +{ + struct hostapd_data *hapd = ctx; + u8 *buf; + + if (freq == 0) + freq = hapd->iface->freq; + + wpa_printf(MSG_DEBUG, "DPP: Send action frame dst=" MACSTR " freq=%u", + MAC2STR(addr), freq); + buf = os_malloc(2 + len); + if (!buf) + return; + buf[0] = WLAN_ACTION_PUBLIC; + buf[1] = WLAN_PA_VENDOR_SPECIFIC; + os_memcpy(buf + 2, msg, len); + hostapd_drv_send_action(hapd, freq, 0, addr, buf, 2 + len); + os_free(buf); +} + + +static void hostapd_dpp_relay_gas_resp_tx(void *ctx, const u8 *addr, + u8 dialog_token, int prot, + struct wpabuf *buf) +{ + struct hostapd_data *hapd = ctx; + + gas_serv_req_dpp_processing(hapd, addr, dialog_token, prot, buf, 0); +} + +#endif /* CONFIG_DPP2 */ + + +static int hostapd_dpp_add_controllers(struct hostapd_data *hapd) +{ +#ifdef CONFIG_DPP2 + struct dpp_controller_conf *ctrl; + struct dpp_relay_config config; + + os_memset(&config, 0, sizeof(config)); + config.msg_ctx = hapd->msg_ctx; + config.cb_ctx = hapd; + config.tx = hostapd_dpp_relay_tx; + config.gas_resp_tx = hostapd_dpp_relay_gas_resp_tx; + for (ctrl = hapd->conf->dpp_controller; ctrl; ctrl = ctrl->next) { + config.ipaddr = &ctrl->ipaddr; + config.pkhash = ctrl->pkhash; + if (dpp_relay_add_controller(hapd->iface->interfaces->dpp, + &config) < 0) + return -1; + } + + if (hapd->conf->dpp_relay_port) + dpp_relay_listen(hapd->iface->interfaces->dpp, + hapd->conf->dpp_relay_port, + &config); +#endif /* CONFIG_DPP2 */ + + return 0; +} + + +#ifdef CONFIG_DPP2 + +int hostapd_dpp_add_controller(struct hostapd_data *hapd, const char *cmd) +{ + struct dpp_relay_config config; + struct hostapd_ip_addr addr; + u8 pkhash[SHA256_MAC_LEN]; + char *pos, *tmp; + int ret = -1; + bool prev_state, new_state; + struct dpp_global *dpp = hapd->iface->interfaces->dpp; + + tmp = os_strdup(cmd); + if (!tmp) + goto fail; + pos = os_strchr(tmp, ' '); + if (!pos) + goto fail; + *pos++ = '\0'; + if (hostapd_parse_ip_addr(tmp, &addr) < 0 || + hexstr2bin(pos, pkhash, SHA256_MAC_LEN) < 0) + goto fail; + + os_memset(&config, 0, sizeof(config)); + config.msg_ctx = hapd->msg_ctx; + config.cb_ctx = hapd; + config.tx = hostapd_dpp_relay_tx; + config.gas_resp_tx = hostapd_dpp_relay_gas_resp_tx; + config.ipaddr = &addr; + config.pkhash = pkhash; + prev_state = dpp_relay_controller_available(dpp); + ret = dpp_relay_add_controller(dpp, &config); + new_state = dpp_relay_controller_available(dpp); + if (new_state != prev_state) + ieee802_11_update_beacons(hapd->iface); +fail: + os_free(tmp); + return ret; +} + + +void hostapd_dpp_remove_controller(struct hostapd_data *hapd, const char *cmd) +{ + struct hostapd_ip_addr addr; + bool prev_state, new_state; + struct dpp_global *dpp = hapd->iface->interfaces->dpp; + + if (hostapd_parse_ip_addr(cmd, &addr) < 0) + return; + prev_state = dpp_relay_controller_available(dpp); + dpp_relay_remove_controller(dpp, &addr); + new_state = dpp_relay_controller_available(dpp); + if (new_state != prev_state) + ieee802_11_update_beacons(hapd->iface); +} + +#endif /* CONFIG_DPP2 */ + + +int hostapd_dpp_init(struct hostapd_data *hapd) +{ + hapd->dpp_allowed_roles = DPP_CAPAB_CONFIGURATOR | DPP_CAPAB_ENROLLEE; + hapd->dpp_init_done = 1; + return hostapd_dpp_add_controllers(hapd); +} + + +void hostapd_dpp_deinit(struct hostapd_data *hapd) +{ +#ifdef CONFIG_TESTING_OPTIONS + os_free(hapd->dpp_config_obj_override); + hapd->dpp_config_obj_override = NULL; + os_free(hapd->dpp_discovery_override); + hapd->dpp_discovery_override = NULL; + os_free(hapd->dpp_groups_override); + hapd->dpp_groups_override = NULL; + hapd->dpp_ignore_netaccesskey_mismatch = 0; +#endif /* CONFIG_TESTING_OPTIONS */ + if (!hapd->dpp_init_done) + return; + eloop_cancel_timeout(hostapd_dpp_pkex_retry_timeout, hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_reply_wait_timeout, hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_auth_conf_wait_timeout, hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_init_timeout, hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_auth_resp_retry_timeout, hapd, NULL); +#ifdef CONFIG_DPP2 + eloop_cancel_timeout(hostapd_dpp_reconfig_reply_wait_timeout, + hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_config_result_wait_timeout, hapd, + NULL); + eloop_cancel_timeout(hostapd_dpp_conn_status_result_wait_timeout, hapd, + NULL); + hostapd_dpp_chirp_stop(hapd); + if (hapd->iface->interfaces) { + dpp_relay_stop_listen(hapd->iface->interfaces->dpp); + dpp_controller_stop_for_ctx(hapd->iface->interfaces->dpp, hapd); + } +#endif /* CONFIG_DPP2 */ +#ifdef CONFIG_DPP3 + eloop_cancel_timeout(hostapd_dpp_build_new_key, hapd, NULL); + hostapd_dpp_push_button_stop(hapd); +#endif /* CONFIG_DPP3 */ + dpp_auth_deinit(hapd->dpp_auth); + hapd->dpp_auth = NULL; + hostapd_dpp_pkex_remove(hapd, "*"); + hapd->dpp_pkex = NULL; + os_free(hapd->dpp_configurator_params); + hapd->dpp_configurator_params = NULL; + os_free(hapd->dpp_pkex_auth_cmd); + hapd->dpp_pkex_auth_cmd = NULL; +} + + +#ifdef CONFIG_DPP2 + +int hostapd_dpp_controller_start(struct hostapd_data *hapd, const char *cmd) +{ + struct dpp_controller_config config; + const char *pos; + + os_memset(&config, 0, sizeof(config)); + config.allowed_roles = DPP_CAPAB_ENROLLEE | DPP_CAPAB_CONFIGURATOR; + config.netrole = DPP_NETROLE_AP; + config.msg_ctx = hapd->msg_ctx; + config.cb_ctx = hapd; + config.process_conf_obj = hostapd_dpp_process_conf_obj; + if (cmd) { + pos = os_strstr(cmd, " tcp_port="); + if (pos) { + pos += 10; + config.tcp_port = atoi(pos); + } + + pos = os_strstr(cmd, " role="); + if (pos) { + pos += 6; + if (os_strncmp(pos, "configurator", 12) == 0) + config.allowed_roles = DPP_CAPAB_CONFIGURATOR; + else if (os_strncmp(pos, "enrollee", 8) == 0) + config.allowed_roles = DPP_CAPAB_ENROLLEE; + else if (os_strncmp(pos, "either", 6) == 0) + config.allowed_roles = DPP_CAPAB_CONFIGURATOR | + DPP_CAPAB_ENROLLEE; + else + return -1; + } + + config.qr_mutual = os_strstr(cmd, " qr=mutual") != NULL; + } + config.configurator_params = hapd->dpp_configurator_params; + return dpp_controller_start(hapd->iface->interfaces->dpp, &config); +} + + +static void hostapd_dpp_chirp_next(void *eloop_ctx, void *timeout_ctx); + +static void hostapd_dpp_chirp_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + + wpa_printf(MSG_DEBUG, "DPP: No chirp response received"); + hostapd_drv_send_action_cancel_wait(hapd); + hostapd_dpp_chirp_next(hapd, NULL); +} + + +static void hostapd_dpp_chirp_start(struct hostapd_data *hapd) +{ + struct wpabuf *msg; + int type; + + msg = hapd->dpp_presence_announcement; + type = DPP_PA_PRESENCE_ANNOUNCEMENT; + wpa_printf(MSG_DEBUG, "DPP: Chirp on %d MHz", hapd->dpp_chirp_freq); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR + " freq=%u type=%d", + MAC2STR(broadcast), hapd->dpp_chirp_freq, type); + if (hostapd_drv_send_action( + hapd, hapd->dpp_chirp_freq, 2000, broadcast, + wpabuf_head(msg), wpabuf_len(msg)) < 0 || + eloop_register_timeout(2, 0, hostapd_dpp_chirp_timeout, + hapd, NULL) < 0) + hostapd_dpp_chirp_stop(hapd); +} + + +static struct hostapd_hw_modes * +dpp_get_mode(struct hostapd_data *hapd, + enum hostapd_hw_mode mode) +{ + struct hostapd_hw_modes *modes = hapd->iface->hw_features; + u16 num_modes = hapd->iface->num_hw_features; + u16 i; + + for (i = 0; i < num_modes; i++) { + if (modes[i].mode != mode || + !modes[i].num_channels || !modes[i].channels) + continue; + return &modes[i]; + } + + return NULL; +} + + +static void +hostapd_dpp_chirp_scan_res_handler(struct hostapd_iface *iface) +{ + struct hostapd_data *hapd = iface->bss[0]; + struct wpa_scan_results *scan_res; + struct dpp_bootstrap_info *bi = hapd->dpp_chirp_bi; + unsigned int i; + struct hostapd_hw_modes *mode; + int c; + bool chan6 = hapd->iface->hw_features == NULL; + + if (!bi) + return; + + hapd->dpp_chirp_scan_done = 1; + + scan_res = hostapd_driver_get_scan_results(hapd); + + os_free(hapd->dpp_chirp_freqs); + hapd->dpp_chirp_freqs = NULL; + + /* Channels from own bootstrapping info */ + if (bi) { + for (i = 0; i < bi->num_freq; i++) + int_array_add_unique(&hapd->dpp_chirp_freqs, + bi->freq[i]); + } + + /* Preferred chirping channels */ + mode = dpp_get_mode(hapd, HOSTAPD_MODE_IEEE80211G); + if (mode) { + for (c = 0; c < mode->num_channels; c++) { + struct hostapd_channel_data *chan = &mode->channels[c]; + + if (chan->flag & (HOSTAPD_CHAN_DISABLED | + HOSTAPD_CHAN_RADAR) || + chan->freq != 2437) + continue; + chan6 = true; + break; + } + } + if (chan6) + int_array_add_unique(&hapd->dpp_chirp_freqs, 2437); + + mode = dpp_get_mode(hapd, HOSTAPD_MODE_IEEE80211A); + if (mode) { + int chan44 = 0, chan149 = 0; + + for (c = 0; c < mode->num_channels; c++) { + struct hostapd_channel_data *chan = &mode->channels[c]; + + if (chan->flag & (HOSTAPD_CHAN_DISABLED | + HOSTAPD_CHAN_RADAR)) + continue; + if (chan->freq == 5220) + chan44 = 1; + if (chan->freq == 5745) + chan149 = 1; + } + if (chan149) + int_array_add_unique(&hapd->dpp_chirp_freqs, 5745); + else if (chan44) + int_array_add_unique(&hapd->dpp_chirp_freqs, 5220); + } + + mode = dpp_get_mode(hapd, HOSTAPD_MODE_IEEE80211AD); + if (mode) { + for (c = 0; c < mode->num_channels; c++) { + struct hostapd_channel_data *chan = &mode->channels[c]; + + if ((chan->flag & (HOSTAPD_CHAN_DISABLED | + HOSTAPD_CHAN_RADAR)) || + chan->freq != 60480) + continue; + int_array_add_unique(&hapd->dpp_chirp_freqs, 60480); + break; + } + } + + /* Add channels from scan results for APs that advertise Configurator + * Connectivity element */ + for (i = 0; scan_res && i < scan_res->num; i++) { + struct wpa_scan_res *bss = scan_res->res[i]; + size_t ie_len = bss->ie_len; + + if (!ie_len) + ie_len = bss->beacon_ie_len; + if (get_vendor_ie((const u8 *) (bss + 1), ie_len, + DPP_CC_IE_VENDOR_TYPE)) + int_array_add_unique(&hapd->dpp_chirp_freqs, + bss->freq); + } + + if (!hapd->dpp_chirp_freqs || + eloop_register_timeout(0, 0, hostapd_dpp_chirp_next, + hapd, NULL) < 0) + hostapd_dpp_chirp_stop(hapd); + + wpa_scan_results_free(scan_res); +} + + +static void hostapd_dpp_chirp_next(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + int i; + + if (hapd->dpp_chirp_listen) + hostapd_dpp_listen_stop(hapd); + + if (hapd->dpp_chirp_freq == 0) { + if (hapd->dpp_chirp_round % 4 == 0 && + !hapd->dpp_chirp_scan_done) { + struct wpa_driver_scan_params params; + int ret; + + wpa_printf(MSG_DEBUG, + "DPP: Update channel list for chirping"); + os_memset(¶ms, 0, sizeof(params)); + ret = hostapd_driver_scan(hapd, ¶ms); + if (ret < 0) { + wpa_printf(MSG_DEBUG, + "DPP: Failed to request a scan ret=%d (%s)", + ret, strerror(-ret)); + hostapd_dpp_chirp_scan_res_handler(hapd->iface); + } else { + hapd->iface->scan_cb = + hostapd_dpp_chirp_scan_res_handler; + } + return; + } + hapd->dpp_chirp_freq = hapd->dpp_chirp_freqs[0]; + hapd->dpp_chirp_round++; + wpa_printf(MSG_DEBUG, "DPP: Start chirping round %d", + hapd->dpp_chirp_round); + } else { + for (i = 0; hapd->dpp_chirp_freqs[i]; i++) + if (hapd->dpp_chirp_freqs[i] == hapd->dpp_chirp_freq) + break; + if (!hapd->dpp_chirp_freqs[i]) { + wpa_printf(MSG_DEBUG, + "DPP: Previous chirp freq %d not found", + hapd->dpp_chirp_freq); + return; + } + i++; + if (hapd->dpp_chirp_freqs[i]) { + hapd->dpp_chirp_freq = hapd->dpp_chirp_freqs[i]; + } else { + hapd->dpp_chirp_iter--; + if (hapd->dpp_chirp_iter <= 0) { + wpa_printf(MSG_DEBUG, + "DPP: Chirping iterations completed"); + hostapd_dpp_chirp_stop(hapd); + return; + } + hapd->dpp_chirp_freq = 0; + hapd->dpp_chirp_scan_done = 0; + if (eloop_register_timeout(30, 0, + hostapd_dpp_chirp_next, + hapd, NULL) < 0) { + hostapd_dpp_chirp_stop(hapd); + return; + } + if (hapd->dpp_chirp_listen) { + wpa_printf(MSG_DEBUG, + "DPP: Listen on %d MHz during chirp 30 second wait", + hapd->dpp_chirp_listen); + /* TODO: start listen on the channel */ + } else { + wpa_printf(MSG_DEBUG, + "DPP: Wait 30 seconds before starting the next chirping round"); + } + return; + } + } + + hostapd_dpp_chirp_start(hapd); +} + + +int hostapd_dpp_chirp(struct hostapd_data *hapd, const char *cmd) +{ + const char *pos; + int iter = 1, listen_freq = 0; + struct dpp_bootstrap_info *bi; + + pos = os_strstr(cmd, " own="); + if (!pos) + return -1; + pos += 5; + bi = dpp_bootstrap_get_id(hapd->iface->interfaces->dpp, atoi(pos)); + if (!bi) { + wpa_printf(MSG_DEBUG, + "DPP: Identified bootstrap info not found"); + return -1; + } + + pos = os_strstr(cmd, " iter="); + if (pos) { + iter = atoi(pos + 6); + if (iter <= 0) + return -1; + } + + pos = os_strstr(cmd, " listen="); + if (pos) { + listen_freq = atoi(pos + 8); + if (listen_freq <= 0) + return -1; + } + + hostapd_dpp_chirp_stop(hapd); + hapd->dpp_allowed_roles = DPP_CAPAB_ENROLLEE; + hapd->dpp_qr_mutual = 0; + hapd->dpp_chirp_bi = bi; + hapd->dpp_presence_announcement = dpp_build_presence_announcement(bi); + if (!hapd->dpp_presence_announcement) + return -1; + hapd->dpp_chirp_iter = iter; + hapd->dpp_chirp_round = 0; + hapd->dpp_chirp_scan_done = 0; + hapd->dpp_chirp_listen = listen_freq; + + return eloop_register_timeout(0, 0, hostapd_dpp_chirp_next, hapd, NULL); +} + + +void hostapd_dpp_chirp_stop(struct hostapd_data *hapd) +{ + if (hapd->dpp_presence_announcement) { + hostapd_drv_send_action_cancel_wait(hapd); + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_CHIRP_STOPPED); + } + hapd->dpp_chirp_bi = NULL; + wpabuf_free(hapd->dpp_presence_announcement); + hapd->dpp_presence_announcement = NULL; + if (hapd->dpp_chirp_listen) + hostapd_dpp_listen_stop(hapd); + hapd->dpp_chirp_listen = 0; + hapd->dpp_chirp_freq = 0; + os_free(hapd->dpp_chirp_freqs); + hapd->dpp_chirp_freqs = NULL; + eloop_cancel_timeout(hostapd_dpp_chirp_next, hapd, NULL); + eloop_cancel_timeout(hostapd_dpp_chirp_timeout, hapd, NULL); + if (hapd->iface->scan_cb == hostapd_dpp_chirp_scan_res_handler) { + /* TODO: abort ongoing scan */ + hapd->iface->scan_cb = NULL; + } +} + + +static int handle_dpp_remove_bi(struct hostapd_iface *iface, void *ctx) +{ + struct dpp_bootstrap_info *bi = ctx; + size_t i; + + for (i = 0; i < iface->num_bss; i++) { + struct hostapd_data *hapd = iface->bss[i]; + + if (bi == hapd->dpp_chirp_bi) + hostapd_dpp_chirp_stop(hapd); + } + + return 0; +} + + +void hostapd_dpp_remove_bi(void *ctx, struct dpp_bootstrap_info *bi) +{ + struct hapd_interfaces *interfaces = ctx; + + hostapd_for_each_interface(interfaces, handle_dpp_remove_bi, bi); +} + +#endif /* CONFIG_DPP2 */ + + +#ifdef CONFIG_DPP3 + +static void hostapd_dpp_push_button_expire(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + + wpa_printf(MSG_DEBUG, "DPP: Active push button mode expired"); + hostapd_dpp_push_button_stop(hapd); +} + + +int hostapd_dpp_push_button(struct hostapd_data *hapd, const char *cmd) +{ + struct hapd_interfaces *ifaces = hapd->iface->interfaces; + + if (!ifaces || !ifaces->dpp) + return -1; + os_get_reltime(&ifaces->dpp_pb_time); + ifaces->dpp_pb_announce_time.sec = 0; + ifaces->dpp_pb_announce_time.usec = 0; + str_clear_free(ifaces->dpp_pb_cmd); + ifaces->dpp_pb_cmd = NULL; + if (cmd) { + ifaces->dpp_pb_cmd = os_strdup(cmd); + if (!ifaces->dpp_pb_cmd) + return -1; + } + eloop_register_timeout(100, 0, hostapd_dpp_push_button_expire, + hapd, NULL); + + wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_PB_STATUS "started"); + return 0; +} + + +void hostapd_dpp_push_button_stop(struct hostapd_data *hapd) +{ + struct hapd_interfaces *ifaces = hapd->iface->interfaces; + + if (!ifaces || !ifaces->dpp) + return; + eloop_cancel_timeout(hostapd_dpp_push_button_expire, hapd, NULL); + if (hostapd_dpp_pb_active(hapd)) { + wpa_printf(MSG_DEBUG, "DPP: Stop active push button mode"); + if (!ifaces->dpp_pb_result_indicated) + wpa_msg(hapd->msg_ctx, MSG_INFO, + DPP_EVENT_PB_RESULT "failed"); + } + ifaces->dpp_pb_time.sec = 0; + ifaces->dpp_pb_time.usec = 0; + dpp_pkex_free(hapd->dpp_pkex); + hapd->dpp_pkex = NULL; + hapd->dpp_pkex_bi = NULL; + os_free(hapd->dpp_pkex_auth_cmd); + hapd->dpp_pkex_auth_cmd = NULL; + + if (ifaces->dpp_pb_bi) { + char id[20]; + size_t i; + + for (i = 0; i < ifaces->count; i++) { + struct hostapd_iface *iface = ifaces->iface[i]; + size_t j; + + for (j = 0; iface && j < iface->num_bss; j++) { + struct hostapd_data *h = iface->bss[j]; + + if (h->dpp_pkex_bi == ifaces->dpp_pb_bi) + h->dpp_pkex_bi = NULL; + } + } + + os_snprintf(id, sizeof(id), "%u", ifaces->dpp_pb_bi->id); + dpp_bootstrap_remove(ifaces->dpp, id); + ifaces->dpp_pb_bi = NULL; + } + + ifaces->dpp_pb_result_indicated = false; + + str_clear_free(ifaces->dpp_pb_cmd); + ifaces->dpp_pb_cmd = NULL; +} + +#endif /* CONFIG_DPP3 */ + + +#ifdef CONFIG_DPP2 +bool hostapd_dpp_configurator_connectivity(struct hostapd_data *hapd) +{ + return hapd->conf->dpp_configurator_connectivity || + (hapd->iface->interfaces && + dpp_relay_controller_available(hapd->iface->interfaces->dpp)); +} +#endif /* CONFIG_DPP2 */ diff --git a/src/ap/dpp_hostapd.h b/src/ap/dpp_hostapd.h new file mode 100644 index 0000000..55f1fce --- /dev/null +++ b/src/ap/dpp_hostapd.h @@ -0,0 +1,54 @@ +/* + * hostapd / DPP integration + * Copyright (c) 2017, Qualcomm Atheros, Inc. + * Copyright (c) 2018-2020, The Linux Foundation + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef DPP_HOSTAPD_H +#define DPP_HOSTAPD_H + +struct dpp_bootstrap_info; + +int hostapd_dpp_qr_code(struct hostapd_data *hapd, const char *cmd); +int hostapd_dpp_nfc_uri(struct hostapd_data *hapd, const char *cmd); +int hostapd_dpp_nfc_handover_req(struct hostapd_data *hapd, const char *cmd); +int hostapd_dpp_nfc_handover_sel(struct hostapd_data *hapd, const char *cmd); +int hostapd_dpp_auth_init(struct hostapd_data *hapd, const char *cmd); +int hostapd_dpp_listen(struct hostapd_data *hapd, const char *cmd); +void hostapd_dpp_listen_stop(struct hostapd_data *hapd); +void hostapd_dpp_rx_action(struct hostapd_data *hapd, const u8 *src, + const u8 *buf, size_t len, unsigned int freq); +void hostapd_dpp_tx_status(struct hostapd_data *hapd, const u8 *dst, + const u8 *data, size_t data_len, int ok); +struct wpabuf * +hostapd_dpp_gas_req_handler(struct hostapd_data *hapd, const u8 *sa, + const u8 *query, size_t query_len, + const u8 *data, size_t data_len); +void hostapd_dpp_gas_status_handler(struct hostapd_data *hapd, int ok); +int hostapd_dpp_configurator_add(struct hostapd_data *hapd, const char *cmd); +int hostapd_dpp_configurator_remove(struct hostapd_data *hapd, const char *id); +int hostapd_dpp_configurator_sign(struct hostapd_data *hapd, const char *cmd); +int hostapd_dpp_configurator_get_key(struct hostapd_data *hapd, unsigned int id, + char *buf, size_t buflen); +int hostapd_dpp_pkex_add(struct hostapd_data *hapd, const char *cmd); +int hostapd_dpp_pkex_remove(struct hostapd_data *hapd, const char *id); +void hostapd_dpp_stop(struct hostapd_data *hapd); +int hostapd_dpp_init(struct hostapd_data *hapd); +void hostapd_dpp_deinit(struct hostapd_data *hapd); +void hostapd_dpp_init_global(struct hapd_interfaces *ifaces); +void hostapd_dpp_deinit_global(struct hapd_interfaces *ifaces); + +int hostapd_dpp_controller_start(struct hostapd_data *hapd, const char *cmd); +int hostapd_dpp_chirp(struct hostapd_data *hapd, const char *cmd); +void hostapd_dpp_chirp_stop(struct hostapd_data *hapd); +void hostapd_dpp_remove_bi(void *ctx, struct dpp_bootstrap_info *bi); +int hostapd_dpp_push_button(struct hostapd_data *hapd, const char *cmd); +void hostapd_dpp_push_button_stop(struct hostapd_data *hapd); +bool hostapd_dpp_configurator_connectivity(struct hostapd_data *hapd); +int hostapd_dpp_add_controller(struct hostapd_data *hapd, const char *cmd); +void hostapd_dpp_remove_controller(struct hostapd_data *hapd, const char *cmd); + +#endif /* DPP_HOSTAPD_H */ diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c new file mode 100644 index 0000000..8b49b53 --- /dev/null +++ b/src/ap/drv_callbacks.c @@ -0,0 +1,2798 @@ +/* + * hostapd / Callback functions for driver wrappers + * Copyright (c) 2002-2013, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "radius/radius.h" +#include "drivers/driver.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "common/wpa_ctrl.h" +#include "common/dpp.h" +#include "common/sae.h" +#include "common/hw_features_common.h" +#include "crypto/random.h" +#include "p2p/p2p.h" +#include "wps/wps.h" +#include "fst/fst.h" +#include "wnm_ap.h" +#include "hostapd.h" +#include "ieee802_11.h" +#include "ieee802_11_auth.h" +#include "sta_info.h" +#include "accounting.h" +#include "tkip_countermeasures.h" +#include "ieee802_1x.h" +#include "wpa_auth.h" +#include "wps_hostapd.h" +#include "ap_drv_ops.h" +#include "ap_config.h" +#include "ap_mlme.h" +#include "hw_features.h" +#include "dfs.h" +#include "beacon.h" +#include "mbo_ap.h" +#include "dpp_hostapd.h" +#include "fils_hlp.h" +#include "neighbor_db.h" +#include "nan_usd_ap.h" + + +#ifdef CONFIG_FILS +void hostapd_notify_assoc_fils_finish(struct hostapd_data *hapd, + struct sta_info *sta) +{ + u16 reply_res = WLAN_STATUS_SUCCESS; + struct ieee802_11_elems elems; + u8 buf[IEEE80211_MAX_MMPDU_SIZE], *p = buf; + int new_assoc; + bool updated; + + wpa_printf(MSG_DEBUG, "%s FILS: Finish association with " MACSTR, + __func__, MAC2STR(sta->addr)); + eloop_cancel_timeout(fils_hlp_timeout, hapd, sta); + if (!sta->fils_pending_assoc_req) + return; + + if (ieee802_11_parse_elems(sta->fils_pending_assoc_req, + sta->fils_pending_assoc_req_len, &elems, + 0) == ParseFailed || + !elems.fils_session) { + wpa_printf(MSG_DEBUG, "%s failed to find FILS Session element", + __func__); + return; + } + + p = hostapd_eid_assoc_fils_session(sta->wpa_sm, p, + elems.fils_session, + sta->fils_hlp_resp); + + reply_res = hostapd_sta_assoc(hapd, sta->addr, + sta->fils_pending_assoc_is_reassoc, + WLAN_STATUS_SUCCESS, + buf, p - buf); + updated = ap_sta_set_authorized_flag(hapd, sta, 1); + new_assoc = (sta->flags & WLAN_STA_ASSOC) == 0; + sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC; + sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE; + hostapd_set_sta_flags(hapd, sta); + if (updated) + ap_sta_set_authorized_event(hapd, sta, 1); + wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FILS); + ieee802_1x_notify_port_enabled(sta->eapol_sm, 1); + hostapd_new_assoc_sta(hapd, sta, !new_assoc); + os_free(sta->fils_pending_assoc_req); + sta->fils_pending_assoc_req = NULL; + sta->fils_pending_assoc_req_len = 0; + wpabuf_free(sta->fils_hlp_resp); + sta->fils_hlp_resp = NULL; + wpabuf_free(sta->hlp_dhcp_discover); + sta->hlp_dhcp_discover = NULL; + fils_hlp_deinit(hapd); + + /* + * Remove the station in case transmission of a success response fails + * (the STA was added associated to the driver) or if the station was + * previously added unassociated. + */ + if (reply_res != WLAN_STATUS_SUCCESS || sta->added_unassoc) { + hostapd_drv_sta_remove(hapd, sta->addr); + sta->added_unassoc = 0; + } +} +#endif /* CONFIG_FILS */ + + +static bool check_sa_query_need(struct hostapd_data *hapd, struct sta_info *sta) +{ + if ((sta->flags & + (WLAN_STA_ASSOC | WLAN_STA_MFP | WLAN_STA_AUTHORIZED)) != + (WLAN_STA_ASSOC | WLAN_STA_MFP | WLAN_STA_AUTHORIZED)) + return false; + + if (!sta->sa_query_timed_out && sta->sa_query_count > 0) + ap_check_sa_query_timeout(hapd, sta); + + if (!sta->sa_query_timed_out && (sta->auth_alg != WLAN_AUTH_FT)) { + /* + * STA has already been associated with MFP and SA Query timeout + * has not been reached. Reject the association attempt + * temporarily and start SA Query, if one is not pending. + */ + if (sta->sa_query_count == 0) + ap_sta_start_sa_query(hapd, sta); + + return true; + } + + return false; +} + + +#ifdef CONFIG_IEEE80211BE +static int hostapd_update_sta_links_status(struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *resp_ies, + size_t resp_ies_len) +{ + struct mld_info *info = &sta->mld_info; + struct wpabuf *mlebuf; + const u8 *mle, *pos; + struct ieee802_11_elems elems; + size_t mle_len, rem_len; + int ret = 0; + + if (!resp_ies) { + wpa_printf(MSG_DEBUG, + "MLO: (Re)Association Response frame elements not available"); + return -1; + } + + if (ieee802_11_parse_elems(resp_ies, resp_ies_len, &elems, 0) == + ParseFailed) { + wpa_printf(MSG_DEBUG, + "MLO: Failed to parse (Re)Association Response frame elements"); + return -1; + } + + mlebuf = ieee802_11_defrag(elems.basic_mle, elems.basic_mle_len, true); + if (!mlebuf) { + wpa_printf(MSG_ERROR, + "MLO: Basic Multi-Link element not found in (Re)Association Response frame"); + return -1; + } + + mle = wpabuf_head(mlebuf); + mle_len = wpabuf_len(mlebuf); + if (mle_len < MULTI_LINK_CONTROL_LEN + 1 || + mle_len - MULTI_LINK_CONTROL_LEN < mle[MULTI_LINK_CONTROL_LEN]) { + wpa_printf(MSG_ERROR, + "MLO: Invalid Multi-Link element in (Re)Association Response frame"); + ret = -1; + goto out; + } + + /* Skip Common Info */ + pos = mle + MULTI_LINK_CONTROL_LEN + mle[MULTI_LINK_CONTROL_LEN]; + rem_len = mle_len - + (MULTI_LINK_CONTROL_LEN + mle[MULTI_LINK_CONTROL_LEN]); + + /* Parse Subelements */ + while (rem_len > 2) { + size_t ie_len = 2 + pos[1]; + + if (rem_len < ie_len) + break; + + if (pos[0] == MULTI_LINK_SUB_ELEM_ID_PER_STA_PROFILE) { + u8 link_id; + const u8 *sta_profile; + size_t sta_profile_len; + u16 sta_ctrl; + + if (pos[1] < BASIC_MLE_STA_CTRL_LEN + 1) { + wpa_printf(MSG_DEBUG, + "MLO: Invalid per-STA profile IE"); + goto next_subelem; + } + + sta_profile_len = pos[1]; + sta_profile = &pos[2]; + sta_ctrl = WPA_GET_LE16(sta_profile); + link_id = sta_ctrl & BASIC_MLE_STA_CTRL_LINK_ID_MASK; + if (link_id >= MAX_NUM_MLD_LINKS) { + wpa_printf(MSG_DEBUG, + "MLO: Invalid link ID in per-STA profile IE"); + goto next_subelem; + } + + /* Skip STA Control and STA Info */ + if (sta_profile_len - BASIC_MLE_STA_CTRL_LEN < + sta_profile[BASIC_MLE_STA_CTRL_LEN]) { + wpa_printf(MSG_DEBUG, + "MLO: Invalid STA info in per-STA profile IE"); + goto next_subelem; + } + + sta_profile_len = sta_profile_len - + (BASIC_MLE_STA_CTRL_LEN + + sta_profile[BASIC_MLE_STA_CTRL_LEN]); + sta_profile = sta_profile + BASIC_MLE_STA_CTRL_LEN + + sta_profile[BASIC_MLE_STA_CTRL_LEN]; + + /* Skip Capabilities Information field */ + if (sta_profile_len < 2) + goto next_subelem; + sta_profile_len -= 2; + sta_profile += 2; + + /* Get status of the link */ + info->links[link_id].status = WPA_GET_LE16(sta_profile); + } +next_subelem: + pos += ie_len; + rem_len -= ie_len; + } + +out: + wpabuf_free(mlebuf); + return ret; +} +#endif /* CONFIG_IEEE80211BE */ + + +int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, + const u8 *req_ies, size_t req_ies_len, + const u8 *resp_ies, size_t resp_ies_len, + const u8 *link_addr, int reassoc) +{ + struct sta_info *sta; + int new_assoc; + enum wpa_validate_result res; + struct ieee802_11_elems elems; + const u8 *ie; + size_t ielen; + u8 buf[sizeof(struct ieee80211_mgmt) + 1024]; + u8 *p = buf; + u16 reason = WLAN_REASON_UNSPECIFIED; + int status = WLAN_STATUS_SUCCESS; + const u8 *p2p_dev_addr = NULL; +#ifdef CONFIG_OWE + struct hostapd_iface *iface = hapd->iface; +#endif /* CONFIG_OWE */ + bool updated = false; + + if (addr == NULL) { + /* + * This could potentially happen with unexpected event from the + * driver wrapper. This was seen at least in one case where the + * driver ended up being set to station mode while hostapd was + * running, so better make sure we stop processing such an + * event here. + */ + wpa_printf(MSG_DEBUG, + "hostapd_notif_assoc: Skip event with no address"); + return -1; + } + + if (is_multicast_ether_addr(addr) || + is_zero_ether_addr(addr) || + ether_addr_equal(addr, hapd->own_addr)) { + /* Do not process any frames with unexpected/invalid SA so that + * we do not add any state for unexpected STA addresses or end + * up sending out frames to unexpected destination. */ + wpa_printf(MSG_DEBUG, "%s: Invalid SA=" MACSTR + " in received indication - ignore this indication silently", + __func__, MAC2STR(addr)); + return 0; + } + + random_add_randomness(addr, ETH_ALEN); + + hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "associated"); + + if (ieee802_11_parse_elems(req_ies, req_ies_len, &elems, 0) == + ParseFailed) { + wpa_printf(MSG_DEBUG, "%s: Could not parse elements", __func__); + return -1; + } + + if (elems.wps_ie) { + ie = elems.wps_ie - 2; + ielen = elems.wps_ie_len + 2; + wpa_printf(MSG_DEBUG, "STA included WPS IE in (Re)AssocReq"); + } else if (elems.rsn_ie) { + ie = elems.rsn_ie - 2; + ielen = elems.rsn_ie_len + 2; + wpa_printf(MSG_DEBUG, "STA included RSN IE in (Re)AssocReq"); + } else if (elems.wpa_ie) { + ie = elems.wpa_ie - 2; + ielen = elems.wpa_ie_len + 2; + wpa_printf(MSG_DEBUG, "STA included WPA IE in (Re)AssocReq"); +#ifdef CONFIG_HS20 + } else if (elems.osen) { + ie = elems.osen - 2; + ielen = elems.osen_len + 2; + wpa_printf(MSG_DEBUG, "STA included OSEN IE in (Re)AssocReq"); +#endif /* CONFIG_HS20 */ + } else { + ie = NULL; + ielen = 0; + wpa_printf(MSG_DEBUG, + "STA did not include WPS/RSN/WPA IE in (Re)AssocReq"); + } + + sta = ap_get_sta(hapd, addr); + if (sta) { + ap_sta_no_session_timeout(hapd, sta); + accounting_sta_stop(hapd, sta); + + /* + * Make sure that the previously registered inactivity timer + * will not remove the STA immediately. + */ + sta->timeout_next = STA_NULLFUNC; + } else { + sta = ap_sta_add(hapd, addr); + if (sta == NULL) { + hostapd_drv_sta_disassoc(hapd, addr, + WLAN_REASON_DISASSOC_AP_BUSY); + return -1; + } + } + + if (hapd->conf->wpa && check_sa_query_need(hapd, sta)) { + status = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY; + p = hostapd_eid_assoc_comeback_time(hapd, sta, p); + hostapd_sta_assoc(hapd, addr, reassoc, status, buf, p - buf); + + return 0; + } + +#ifdef CONFIG_IEEE80211BE + if (link_addr) { + struct mld_info *info = &sta->mld_info; + int i, num_valid_links = 0; + u8 link_id = hapd->mld_link_id; + + ap_sta_set_mld(sta, true); + sta->mld_assoc_link_id = link_id; + os_memcpy(info->common_info.mld_addr, addr, ETH_ALEN); + info->links[link_id].valid = true; + os_memcpy(info->links[link_id].peer_addr, link_addr, ETH_ALEN); + os_memcpy(info->links[link_id].local_addr, hapd->own_addr, + ETH_ALEN); + + if (!elems.basic_mle || + hostapd_process_ml_assoc_req(hapd, &elems, sta) != + WLAN_STATUS_SUCCESS) { + reason = WLAN_REASON_UNSPECIFIED; + wpa_printf(MSG_DEBUG, + "Failed to get STA non-assoc links info"); + goto fail; + } + + for (i = 0 ; i < MAX_NUM_MLD_LINKS; i++) { + if (info->links[i].valid) + num_valid_links++; + } + if (num_valid_links > 1 && + hostapd_update_sta_links_status(hapd, sta, resp_ies, + resp_ies_len)) { + wpa_printf(MSG_DEBUG, + "Failed to get STA non-assoc links status info"); + reason = WLAN_REASON_UNSPECIFIED; + goto fail; + } + } +#endif /* CONFIG_IEEE80211BE */ + + sta->flags &= ~(WLAN_STA_WPS | WLAN_STA_MAYBE_WPS | WLAN_STA_WPS2); + + /* + * ACL configurations to the drivers (implementing AP SME and ACL + * offload) without hostapd's knowledge, can result in a disconnection + * though the driver accepts the connection. Skip the hostapd check for + * ACL if the driver supports ACL offload to avoid potentially + * conflicting ACL rules. + */ + if (hapd->iface->drv_max_acl_mac_addrs == 0 && + hostapd_check_acl(hapd, addr, NULL) != HOSTAPD_ACL_ACCEPT) { + wpa_printf(MSG_INFO, "STA " MACSTR " not allowed to connect", + MAC2STR(addr)); + reason = WLAN_REASON_UNSPECIFIED; + goto fail; + } + +#ifdef CONFIG_P2P + if (elems.p2p) { + wpabuf_free(sta->p2p_ie); + sta->p2p_ie = ieee802_11_vendor_ie_concat(req_ies, req_ies_len, + P2P_IE_VENDOR_TYPE); + if (sta->p2p_ie) + p2p_dev_addr = p2p_get_go_dev_addr(sta->p2p_ie); + } +#endif /* CONFIG_P2P */ + +#ifdef NEED_AP_MLME + if (elems.ht_capabilities && + (hapd->iface->conf->ht_capab & + HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET)) { + struct ieee80211_ht_capabilities *ht_cap = + (struct ieee80211_ht_capabilities *) + elems.ht_capabilities; + + if (le_to_host16(ht_cap->ht_capabilities_info) & + HT_CAP_INFO_40MHZ_INTOLERANT) + ht40_intolerant_add(hapd->iface, sta); + } +#endif /* NEED_AP_MLME */ + + check_ext_capab(hapd, sta, elems.ext_capab, elems.ext_capab_len); + +#ifdef CONFIG_HS20 + wpabuf_free(sta->hs20_ie); + if (elems.hs20 && elems.hs20_len > 4) { + sta->hs20_ie = wpabuf_alloc_copy(elems.hs20 + 4, + elems.hs20_len - 4); + } else + sta->hs20_ie = NULL; + + wpabuf_free(sta->roaming_consortium); + if (elems.roaming_cons_sel) + sta->roaming_consortium = wpabuf_alloc_copy( + elems.roaming_cons_sel + 4, + elems.roaming_cons_sel_len - 4); + else + sta->roaming_consortium = NULL; +#endif /* CONFIG_HS20 */ + +#ifdef CONFIG_FST + wpabuf_free(sta->mb_ies); + if (hapd->iface->fst) + sta->mb_ies = mb_ies_by_info(&elems.mb_ies); + else + sta->mb_ies = NULL; +#endif /* CONFIG_FST */ + + mbo_ap_check_sta_assoc(hapd, sta, &elems); + + ap_copy_sta_supp_op_classes(sta, elems.supp_op_classes, + elems.supp_op_classes_len); + + if (hapd->conf->wpa) { + if (ie == NULL || ielen == 0) { +#ifdef CONFIG_WPS + if (hapd->conf->wps_state) { + wpa_printf(MSG_DEBUG, + "STA did not include WPA/RSN IE in (Re)Association Request - possible WPS use"); + sta->flags |= WLAN_STA_MAYBE_WPS; + goto skip_wpa_check; + } +#endif /* CONFIG_WPS */ + + wpa_printf(MSG_DEBUG, "No WPA/RSN IE from STA"); + reason = WLAN_REASON_INVALID_IE; + status = WLAN_STATUS_INVALID_IE; + goto fail; + } +#ifdef CONFIG_WPS + if (hapd->conf->wps_state && ie[0] == 0xdd && ie[1] >= 4 && + os_memcmp(ie + 2, "\x00\x50\xf2\x04", 4) == 0) { + struct wpabuf *wps; + + sta->flags |= WLAN_STA_WPS; + wps = ieee802_11_vendor_ie_concat(ie, ielen, + WPS_IE_VENDOR_TYPE); + if (wps) { + if (wps_is_20(wps)) { + wpa_printf(MSG_DEBUG, + "WPS: STA supports WPS 2.0"); + sta->flags |= WLAN_STA_WPS2; + } + wpabuf_free(wps); + } + goto skip_wpa_check; + } +#endif /* CONFIG_WPS */ + + if (sta->wpa_sm == NULL) + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, + sta->addr, + p2p_dev_addr); + if (sta->wpa_sm == NULL) { + wpa_printf(MSG_ERROR, + "Failed to initialize WPA state machine"); + return -1; + } +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) { + wpa_printf(MSG_DEBUG, + "MLD: Set ML info in RSN Authenticator"); + wpa_auth_set_ml_info(sta->wpa_sm, + sta->mld_assoc_link_id, + &sta->mld_info); + } +#endif /* CONFIG_IEEE80211BE */ + res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm, + hapd->iface->freq, + ie, ielen, + elems.rsnxe ? elems.rsnxe - 2 : NULL, + elems.rsnxe ? elems.rsnxe_len + 2 : 0, + elems.mdie, elems.mdie_len, + elems.owe_dh, elems.owe_dh_len, NULL); + reason = WLAN_REASON_INVALID_IE; + status = WLAN_STATUS_INVALID_IE; + switch (res) { + case WPA_IE_OK: + reason = WLAN_REASON_UNSPECIFIED; + status = WLAN_STATUS_SUCCESS; + break; + case WPA_INVALID_IE: + reason = WLAN_REASON_INVALID_IE; + status = WLAN_STATUS_INVALID_IE; + break; + case WPA_INVALID_GROUP: + reason = WLAN_REASON_GROUP_CIPHER_NOT_VALID; + status = WLAN_STATUS_GROUP_CIPHER_NOT_VALID; + break; + case WPA_INVALID_PAIRWISE: + reason = WLAN_REASON_PAIRWISE_CIPHER_NOT_VALID; + status = WLAN_STATUS_PAIRWISE_CIPHER_NOT_VALID; + break; + case WPA_INVALID_AKMP: + reason = WLAN_REASON_AKMP_NOT_VALID; + status = WLAN_STATUS_AKMP_NOT_VALID; + break; + case WPA_NOT_ENABLED: + reason = WLAN_REASON_INVALID_IE; + status = WLAN_STATUS_INVALID_IE; + break; + case WPA_ALLOC_FAIL: + reason = WLAN_REASON_UNSPECIFIED; + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + break; + case WPA_MGMT_FRAME_PROTECTION_VIOLATION: + reason = WLAN_REASON_INVALID_IE; + status = WLAN_STATUS_INVALID_IE; + break; + case WPA_INVALID_MGMT_GROUP_CIPHER: + reason = WLAN_REASON_CIPHER_SUITE_REJECTED; + status = WLAN_STATUS_CIPHER_REJECTED_PER_POLICY; + break; + case WPA_INVALID_MDIE: + reason = WLAN_REASON_INVALID_MDE; + status = WLAN_STATUS_INVALID_MDIE; + break; + case WPA_INVALID_PROTO: + reason = WLAN_REASON_INVALID_IE; + status = WLAN_STATUS_INVALID_IE; + break; + case WPA_INVALID_PMKID: + reason = WLAN_REASON_INVALID_PMKID; + status = WLAN_STATUS_INVALID_PMKID; + break; + case WPA_DENIED_OTHER_REASON: + reason = WLAN_REASON_UNSPECIFIED; + status = WLAN_STATUS_ASSOC_DENIED_UNSPEC; + break; + } + if (status != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_DEBUG, + "WPA/RSN information element rejected? (res %u)", + res); + wpa_hexdump(MSG_DEBUG, "IE", ie, ielen); + goto fail; + } + + if (wpa_auth_uses_mfp(sta->wpa_sm)) + sta->flags |= WLAN_STA_MFP; + else + sta->flags &= ~WLAN_STA_MFP; + +#ifdef CONFIG_IEEE80211R_AP + if (sta->auth_alg == WLAN_AUTH_FT) { + status = wpa_ft_validate_reassoc(sta->wpa_sm, req_ies, + req_ies_len); + if (status != WLAN_STATUS_SUCCESS) { + if (status == WLAN_STATUS_INVALID_PMKID) + reason = WLAN_REASON_INVALID_IE; + if (status == WLAN_STATUS_INVALID_MDIE) + reason = WLAN_REASON_INVALID_IE; + if (status == WLAN_STATUS_INVALID_FTIE) + reason = WLAN_REASON_INVALID_IE; + goto fail; + } + } +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_SAE + if (hapd->conf->sae_pwe == SAE_PWE_BOTH && + sta->auth_alg == WLAN_AUTH_SAE && + sta->sae && !sta->sae->h2e && + ieee802_11_rsnx_capab_len(elems.rsnxe, elems.rsnxe_len, + WLAN_RSNX_CAPAB_SAE_H2E)) { + wpa_printf(MSG_INFO, "SAE: " MACSTR + " indicates support for SAE H2E, but did not use it", + MAC2STR(sta->addr)); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + reason = WLAN_REASON_UNSPECIFIED; + goto fail; + } +#endif /* CONFIG_SAE */ + } else if (hapd->conf->wps_state) { +#ifdef CONFIG_WPS + struct wpabuf *wps; + + if (req_ies) + wps = ieee802_11_vendor_ie_concat(req_ies, req_ies_len, + WPS_IE_VENDOR_TYPE); + else + wps = NULL; +#ifdef CONFIG_WPS_STRICT + if (wps && wps_validate_assoc_req(wps) < 0) { + reason = WLAN_REASON_INVALID_IE; + status = WLAN_STATUS_INVALID_IE; + wpabuf_free(wps); + goto fail; + } +#endif /* CONFIG_WPS_STRICT */ + if (wps) { + sta->flags |= WLAN_STA_WPS; + if (wps_is_20(wps)) { + wpa_printf(MSG_DEBUG, + "WPS: STA supports WPS 2.0"); + sta->flags |= WLAN_STA_WPS2; + } + } else + sta->flags |= WLAN_STA_MAYBE_WPS; + wpabuf_free(wps); +#endif /* CONFIG_WPS */ +#ifdef CONFIG_HS20 + } else if (hapd->conf->osen) { + if (elems.osen == NULL) { + hostapd_logger( + hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "No HS 2.0 OSEN element in association request"); + return WLAN_STATUS_INVALID_IE; + } + + wpa_printf(MSG_DEBUG, "HS 2.0: OSEN association"); + if (sta->wpa_sm == NULL) + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, + sta->addr, NULL); + if (sta->wpa_sm == NULL) { + wpa_printf(MSG_WARNING, + "Failed to initialize WPA state machine"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + if (wpa_validate_osen(hapd->wpa_auth, sta->wpa_sm, + elems.osen - 2, elems.osen_len + 2) < 0) + return WLAN_STATUS_INVALID_IE; +#endif /* CONFIG_HS20 */ + } +#ifdef CONFIG_WPS +skip_wpa_check: +#endif /* CONFIG_WPS */ + +#ifdef CONFIG_MBO + if (hapd->conf->mbo_enabled && (hapd->conf->wpa & 2) && + elems.mbo && sta->cell_capa && !(sta->flags & WLAN_STA_MFP) && + hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + wpa_printf(MSG_INFO, + "MBO: Reject WPA2 association without PMF"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } +#endif /* CONFIG_MBO */ + +#ifdef CONFIG_IEEE80211R_AP + p = wpa_sm_write_assoc_resp_ies(sta->wpa_sm, buf, sizeof(buf), + sta->auth_alg, req_ies, req_ies_len, + !elems.rsnxe); + if (!p) { + wpa_printf(MSG_DEBUG, "FT: Failed to write AssocResp IEs"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } +#endif /* CONFIG_IEEE80211R_AP */ + +#ifdef CONFIG_FILS + if (sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK) { + int delay_assoc = 0; + + if (!req_ies) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + if (!wpa_fils_validate_fils_session(sta->wpa_sm, req_ies, + req_ies_len, + sta->fils_session)) { + wpa_printf(MSG_DEBUG, + "FILS: Session validation failed"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + res = wpa_fils_validate_key_confirm(sta->wpa_sm, req_ies, + req_ies_len); + if (res < 0) { + wpa_printf(MSG_DEBUG, + "FILS: Key Confirm validation failed"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + if (fils_process_hlp(hapd, sta, req_ies, req_ies_len) > 0) { + wpa_printf(MSG_DEBUG, + "FILS: Delaying Assoc Response (HLP)"); + delay_assoc = 1; + } else { + wpa_printf(MSG_DEBUG, + "FILS: Going ahead with Assoc Response (no HLP)"); + } + + if (sta) { + wpa_printf(MSG_DEBUG, "FILS: HLP callback cleanup"); + eloop_cancel_timeout(fils_hlp_timeout, hapd, sta); + os_free(sta->fils_pending_assoc_req); + sta->fils_pending_assoc_req = NULL; + sta->fils_pending_assoc_req_len = 0; + wpabuf_free(sta->fils_hlp_resp); + sta->fils_hlp_resp = NULL; + sta->fils_drv_assoc_finish = 0; + } + + if (sta && delay_assoc && status == WLAN_STATUS_SUCCESS) { + u8 *req_tmp; + + req_tmp = os_malloc(req_ies_len); + if (!req_tmp) { + wpa_printf(MSG_DEBUG, + "FILS: buffer allocation failed for assoc req"); + goto fail; + } + os_memcpy(req_tmp, req_ies, req_ies_len); + sta->fils_pending_assoc_req = req_tmp; + sta->fils_pending_assoc_req_len = req_ies_len; + sta->fils_pending_assoc_is_reassoc = reassoc; + sta->fils_drv_assoc_finish = 1; + wpa_printf(MSG_DEBUG, + "FILS: Waiting for HLP processing before sending (Re)Association Response frame to " + MACSTR, MAC2STR(sta->addr)); + eloop_register_timeout( + 0, hapd->conf->fils_hlp_wait_time * 1024, + fils_hlp_timeout, hapd, sta); + return 0; + } + p = hostapd_eid_assoc_fils_session(sta->wpa_sm, p, + elems.fils_session, + sta->fils_hlp_resp); + wpa_hexdump(MSG_DEBUG, "FILS Assoc Resp BUF (IEs)", + buf, p - buf); + } +#endif /* CONFIG_FILS */ + +#ifdef CONFIG_OWE + if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) && + !(iface->drv_flags2 & WPA_DRIVER_FLAGS2_OWE_OFFLOAD_AP) && + wpa_auth_sta_key_mgmt(sta->wpa_sm) == WPA_KEY_MGMT_OWE && + elems.owe_dh) { + u8 *npos; + u16 ret_status; + + npos = owe_assoc_req_process(hapd, sta, + elems.owe_dh, elems.owe_dh_len, + p, sizeof(buf) - (p - buf), + &ret_status); + status = ret_status; + if (npos) + p = npos; + + if (!npos && + status == WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED) { + hostapd_sta_assoc(hapd, addr, reassoc, ret_status, buf, + p - buf); + return 0; + } + + if (!npos || status != WLAN_STATUS_SUCCESS) + goto fail; + } +#endif /* CONFIG_OWE */ + +#ifdef CONFIG_DPP2 + dpp_pfs_free(sta->dpp_pfs); + sta->dpp_pfs = NULL; + + if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_DPP) && + hapd->conf->dpp_netaccesskey && sta->wpa_sm && + wpa_auth_sta_key_mgmt(sta->wpa_sm) == WPA_KEY_MGMT_DPP && + elems.owe_dh) { + sta->dpp_pfs = dpp_pfs_init( + wpabuf_head(hapd->conf->dpp_netaccesskey), + wpabuf_len(hapd->conf->dpp_netaccesskey)); + if (!sta->dpp_pfs) { + wpa_printf(MSG_DEBUG, + "DPP: Could not initialize PFS"); + /* Try to continue without PFS */ + goto pfs_fail; + } + + if (dpp_pfs_process(sta->dpp_pfs, elems.owe_dh, + elems.owe_dh_len) < 0) { + dpp_pfs_free(sta->dpp_pfs); + sta->dpp_pfs = NULL; + reason = WLAN_REASON_UNSPECIFIED; + goto fail; + } + } + + wpa_auth_set_dpp_z(sta->wpa_sm, sta->dpp_pfs ? + sta->dpp_pfs->secret : NULL); + pfs_fail: +#endif /* CONFIG_DPP2 */ + + if (elems.rrm_enabled && + elems.rrm_enabled_len >= sizeof(sta->rrm_enabled_capa)) + os_memcpy(sta->rrm_enabled_capa, elems.rrm_enabled, + sizeof(sta->rrm_enabled_capa)); + +#if defined(CONFIG_IEEE80211R_AP) || defined(CONFIG_FILS) || defined(CONFIG_OWE) + hostapd_sta_assoc(hapd, addr, reassoc, status, buf, p - buf); + + if (sta->auth_alg == WLAN_AUTH_FT || + sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK) + updated = ap_sta_set_authorized_flag(hapd, sta, 1); +#else /* CONFIG_IEEE80211R_AP || CONFIG_FILS */ + /* Keep compiler silent about unused variables */ + if (status) { + } +#endif /* CONFIG_IEEE80211R_AP || CONFIG_FILS */ + +#ifdef CONFIG_IEEE80211BE + if (hostapd_process_assoc_ml_info(hapd, sta, req_ies, req_ies_len, + !!reassoc, WLAN_STATUS_SUCCESS, + true)) { + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + reason = WLAN_REASON_UNSPECIFIED; + goto fail; + } +#endif /* CONFIG_IEEE80211BE */ + + new_assoc = (sta->flags & WLAN_STA_ASSOC) == 0; + sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC; + sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE; + + hostapd_set_sta_flags(hapd, sta); + if (updated) + ap_sta_set_authorized_event(hapd, sta, 1); + + if (reassoc && (sta->auth_alg == WLAN_AUTH_FT)) + wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FT); +#ifdef CONFIG_FILS + else if (sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK) + wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FILS); +#endif /* CONFIG_FILS */ + else + wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC); + + hostapd_new_assoc_sta(hapd, sta, !new_assoc); + + ieee802_1x_notify_port_enabled(sta->eapol_sm, 1); + +#ifdef CONFIG_P2P + if (req_ies) { + p2p_group_notif_assoc(hapd->p2p_group, sta->addr, + req_ies, req_ies_len); + } +#endif /* CONFIG_P2P */ + + return 0; + +fail: +#ifdef CONFIG_IEEE80211R_AP + if (status >= 0) + hostapd_sta_assoc(hapd, addr, reassoc, status, buf, p - buf); +#endif /* CONFIG_IEEE80211R_AP */ + hostapd_drv_sta_disassoc(hapd, sta->addr, reason); + ap_free_sta(hapd, sta); + return -1; +} + + +static void hostapd_remove_sta(struct hostapd_data *hapd, struct sta_info *sta) +{ + ap_sta_set_authorized(hapd, sta, 0); + sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC); + hostapd_set_sta_flags(hapd, sta); + wpa_auth_sm_event(sta->wpa_sm, WPA_DISASSOC); + sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST; + ieee802_1x_notify_port_enabled(sta->eapol_sm, 0); + ap_free_sta(hapd, sta); +} + + +#ifdef CONFIG_IEEE80211BE +static void hostapd_notif_disassoc_mld(struct hostapd_data *assoc_hapd, + struct sta_info *sta, + const u8 *addr) +{ + unsigned int link_id, i; + struct hostapd_data *tmp_hapd; + struct hapd_interfaces *interfaces = assoc_hapd->iface->interfaces; + + /* Remove STA entry in non-assoc links */ + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + if (!sta->mld_info.links[link_id].valid) + continue; + + for (i = 0; i < interfaces->count; i++) { + struct sta_info *tmp_sta; + + tmp_hapd = interfaces->iface[i]->bss[0]; + + if (!tmp_hapd->conf->mld_ap || + assoc_hapd == tmp_hapd || + assoc_hapd->conf->mld_id != tmp_hapd->conf->mld_id) + continue; + + tmp_sta = ap_get_sta(tmp_hapd, addr); + if (tmp_sta) + ap_free_sta(tmp_hapd, tmp_sta); + } + } + + /* Remove STA in assoc link */ + hostapd_remove_sta(assoc_hapd, sta); +} +#endif /* CONFIG_IEEE80211BE */ + + +void hostapd_notif_disassoc(struct hostapd_data *hapd, const u8 *addr) +{ + struct sta_info *sta; + + if (addr == NULL) { + /* + * This could potentially happen with unexpected event from the + * driver wrapper. This was seen at least in one case where the + * driver ended up reporting a station mode event while hostapd + * was running, so better make sure we stop processing such an + * event here. + */ + wpa_printf(MSG_DEBUG, + "hostapd_notif_disassoc: Skip event with no address"); + return; + } + + hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "disassociated"); + + sta = ap_get_sta(hapd, addr); +#ifdef CONFIG_IEEE80211BE + if (hostapd_is_mld_ap(hapd)) { + struct hostapd_data *assoc_hapd; + unsigned int i; + + if (!sta) { + /* Find non-MLO cases from any of the affiliated AP + * links. */ + for (i = 0; i < hapd->iface->interfaces->count; ++i) { + struct hostapd_iface *h = + hapd->iface->interfaces->iface[i]; + struct hostapd_data *h_hapd = h->bss[0]; + struct hostapd_bss_config *hconf = h_hapd->conf; + + if (!hconf->mld_ap || + hconf->mld_id != hapd->conf->mld_id) + continue; + + sta = ap_get_sta(h_hapd, addr); + if (sta) { + if (!sta->mld_info.mld_sta) { + hapd = h_hapd; + goto legacy; + } + break; + } + } + } else if (!sta->mld_info.mld_sta) { + goto legacy; + } + if (!sta) { + wpa_printf(MSG_DEBUG, + "Disassociation notification for unknown STA " + MACSTR, MAC2STR(addr)); + return; + } + sta = hostapd_ml_get_assoc_sta(hapd, sta, &assoc_hapd); + if (sta) + hostapd_notif_disassoc_mld(assoc_hapd, sta, addr); + return; + } + +legacy: +#endif /* CONFIG_IEEE80211BE */ + if (sta == NULL) { + wpa_printf(MSG_DEBUG, + "Disassociation notification for unknown STA " + MACSTR, MAC2STR(addr)); + return; + } + + hostapd_remove_sta(hapd, sta); +} + + +void hostapd_event_sta_low_ack(struct hostapd_data *hapd, const u8 *addr) +{ + struct sta_info *sta = ap_get_sta(hapd, addr); + + if (!sta || !hapd->conf->disassoc_low_ack || sta->agreed_to_steer) + return; + + hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "disconnected due to excessive missing ACKs"); + hostapd_drv_sta_disassoc(hapd, addr, WLAN_REASON_DISASSOC_LOW_ACK); + ap_sta_disassociate(hapd, sta, WLAN_REASON_DISASSOC_LOW_ACK); +} + + +void hostapd_event_sta_opmode_changed(struct hostapd_data *hapd, const u8 *addr, + enum smps_mode smps_mode, + enum chan_width chan_width, u8 rx_nss) +{ + struct sta_info *sta = ap_get_sta(hapd, addr); + const char *txt; + + if (!sta) + return; + + switch (smps_mode) { + case SMPS_AUTOMATIC: + txt = "automatic"; + break; + case SMPS_OFF: + txt = "off"; + break; + case SMPS_DYNAMIC: + txt = "dynamic"; + break; + case SMPS_STATIC: + txt = "static"; + break; + default: + txt = NULL; + break; + } + if (txt) { + wpa_msg(hapd->msg_ctx, MSG_INFO, STA_OPMODE_SMPS_MODE_CHANGED + MACSTR " %s", MAC2STR(addr), txt); + } + + switch (chan_width) { + case CHAN_WIDTH_20_NOHT: + txt = "20(no-HT)"; + break; + case CHAN_WIDTH_20: + txt = "20"; + break; + case CHAN_WIDTH_40: + txt = "40"; + break; + case CHAN_WIDTH_80: + txt = "80"; + break; + case CHAN_WIDTH_80P80: + txt = "80+80"; + break; + case CHAN_WIDTH_160: + txt = "160"; + break; + case CHAN_WIDTH_320: + txt = "320"; + break; + default: + txt = NULL; + break; + } + if (txt) { + wpa_msg(hapd->msg_ctx, MSG_INFO, STA_OPMODE_MAX_BW_CHANGED + MACSTR " %s", MAC2STR(addr), txt); + } + + if (rx_nss != 0xff) { + wpa_msg(hapd->msg_ctx, MSG_INFO, STA_OPMODE_N_SS_CHANGED + MACSTR " %d", MAC2STR(addr), rx_nss); + } +} + + +void hostapd_event_ch_switch(struct hostapd_data *hapd, int freq, int ht, + int offset, int width, int cf1, int cf2, + u16 punct_bitmap, int finished) +{ +#ifdef NEED_AP_MLME + int channel, chwidth, is_dfs0, is_dfs; + u8 seg0_idx = 0, seg1_idx = 0, op_class, chan_no; + size_t i; + + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "driver %s channel switch: iface->freq=%d, freq=%d, ht=%d, vht_ch=0x%x, he_ch=0x%x, eht_ch=0x%x, offset=%d, width=%d (%s), cf1=%d, cf2=%d, puncturing_bitmap=0x%x", + finished ? "had" : "starting", + hapd->iface->freq, + freq, ht, hapd->iconf->ch_switch_vht_config, + hapd->iconf->ch_switch_he_config, + hapd->iconf->ch_switch_eht_config, offset, + width, channel_width_to_string(width), cf1, cf2, + punct_bitmap); + + if (!hapd->iface->current_mode) { + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_WARNING, + "ignore channel switch since the interface is not yet ready"); + return; + } + + /* Check if any of configured channels require DFS */ + is_dfs0 = hostapd_is_dfs_required(hapd->iface); + hapd->iface->freq = freq; + + channel = hostapd_hw_get_channel(hapd, freq); + if (!channel) { + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_WARNING, + "driver switched to bad channel!"); + return; + } + + switch (width) { + case CHAN_WIDTH_80: + chwidth = CONF_OPER_CHWIDTH_80MHZ; + break; + case CHAN_WIDTH_80P80: + chwidth = CONF_OPER_CHWIDTH_80P80MHZ; + break; + case CHAN_WIDTH_160: + chwidth = CONF_OPER_CHWIDTH_160MHZ; + break; + case CHAN_WIDTH_320: + chwidth = CONF_OPER_CHWIDTH_320MHZ; + break; + case CHAN_WIDTH_20_NOHT: + case CHAN_WIDTH_20: + case CHAN_WIDTH_40: + default: + chwidth = CONF_OPER_CHWIDTH_USE_HT; + break; + } + + /* The operating channel changed when CSA finished, so need to update + * hw_mode for all following operations to cover the cases where the + * driver changed the operating band. */ + if (finished && hostapd_csa_update_hwmode(hapd->iface)) + return; + + switch (hapd->iface->current_mode->mode) { + case HOSTAPD_MODE_IEEE80211A: + if (cf1 == 5935) + seg0_idx = (cf1 - 5925) / 5; + else if (cf1 > 5950) + seg0_idx = (cf1 - 5950) / 5; + else if (cf1 > 5000) + seg0_idx = (cf1 - 5000) / 5; + + if (cf2 == 5935) + seg1_idx = (cf2 - 5925) / 5; + else if (cf2 > 5950) + seg1_idx = (cf2 - 5950) / 5; + else if (cf2 > 5000) + seg1_idx = (cf2 - 5000) / 5; + break; + default: + ieee80211_freq_to_chan(cf1, &seg0_idx); + ieee80211_freq_to_chan(cf2, &seg1_idx); + break; + } + + hapd->iconf->channel = channel; + hapd->iconf->ieee80211n = ht; + if (!ht) + hapd->iconf->ieee80211ac = 0; + if (hapd->iconf->ch_switch_vht_config) { + /* CHAN_SWITCH VHT config */ + if (hapd->iconf->ch_switch_vht_config & + CH_SWITCH_VHT_ENABLED) + hapd->iconf->ieee80211ac = 1; + else if (hapd->iconf->ch_switch_vht_config & + CH_SWITCH_VHT_DISABLED) + hapd->iconf->ieee80211ac = 0; + } + if (hapd->iconf->ch_switch_he_config) { + /* CHAN_SWITCH HE config */ + if (hapd->iconf->ch_switch_he_config & + CH_SWITCH_HE_ENABLED) { + hapd->iconf->ieee80211ax = 1; + if (hapd->iface->freq > 4000 && + hapd->iface->freq < 5895) + hapd->iconf->ieee80211ac = 1; + } + else if (hapd->iconf->ch_switch_he_config & + CH_SWITCH_HE_DISABLED) + hapd->iconf->ieee80211ax = 0; + } +#ifdef CONFIG_IEEE80211BE + if (hapd->iconf->ch_switch_eht_config) { + /* CHAN_SWITCH EHT config */ + if (hapd->iconf->ch_switch_eht_config & + CH_SWITCH_EHT_ENABLED) { + hapd->iconf->ieee80211be = 1; + hapd->iconf->ieee80211ax = 1; + if (!is_6ghz_freq(hapd->iface->freq) && + hapd->iface->freq > 4000) + hapd->iconf->ieee80211ac = 1; + } else if (hapd->iconf->ch_switch_eht_config & + CH_SWITCH_EHT_DISABLED) + hapd->iconf->ieee80211be = 0; + } +#endif /* CONFIG_IEEE80211BE */ + hapd->iconf->ch_switch_vht_config = 0; + hapd->iconf->ch_switch_he_config = 0; + hapd->iconf->ch_switch_eht_config = 0; + + if (width == CHAN_WIDTH_40 || width == CHAN_WIDTH_80 || + width == CHAN_WIDTH_80P80 || width == CHAN_WIDTH_160 || + width == CHAN_WIDTH_320) + hapd->iconf->ht_capab |= HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET; + else if (width == CHAN_WIDTH_20 || width == CHAN_WIDTH_20_NOHT) + hapd->iconf->ht_capab &= ~HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET; + + hapd->iconf->secondary_channel = offset; + if (ieee80211_freq_to_channel_ext(freq, offset, chwidth, + &op_class, &chan_no) != + NUM_HOSTAPD_MODES) + hapd->iconf->op_class = op_class; + hostapd_set_oper_chwidth(hapd->iconf, chwidth); + hostapd_set_oper_centr_freq_seg0_idx(hapd->iconf, seg0_idx); + hostapd_set_oper_centr_freq_seg1_idx(hapd->iconf, seg1_idx); + /* Auto-detect new bw320_offset */ + hostapd_set_and_check_bw320_offset(hapd->iconf, 0); +#ifdef CONFIG_IEEE80211BE + hapd->iconf->punct_bitmap = punct_bitmap; +#endif /* CONFIG_IEEE80211BE */ + if (hapd->iconf->ieee80211ac) { + hapd->iconf->vht_capab &= ~VHT_CAP_SUPP_CHAN_WIDTH_MASK; + if (chwidth == CONF_OPER_CHWIDTH_160MHZ) + hapd->iconf->vht_capab |= + VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; + else if (chwidth == CONF_OPER_CHWIDTH_80P80MHZ) + hapd->iconf->vht_capab |= + VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ; + } + + is_dfs = ieee80211_is_dfs(freq, hapd->iface->hw_features, + hapd->iface->num_hw_features); + + wpa_msg(hapd->msg_ctx, MSG_INFO, + "%sfreq=%d ht_enabled=%d ch_offset=%d ch_width=%s cf1=%d cf2=%d is_dfs0=%d dfs=%d puncturing_bitmap=0x%04x", + finished ? WPA_EVENT_CHANNEL_SWITCH : + WPA_EVENT_CHANNEL_SWITCH_STARTED, + freq, ht, offset, channel_width_to_string(width), + cf1, cf2, is_dfs0, is_dfs, punct_bitmap); + if (!finished) + return; + + if (hapd->csa_in_progress && + freq == hapd->cs_freq_params.freq) { + hostapd_cleanup_cs_params(hapd); + ieee802_11_set_beacon(hapd); + + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_CSA_FINISHED + "freq=%d dfs=%d", freq, is_dfs); + } else if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) { + /* Complete AP configuration for the first bring up. */ + if (is_dfs0 > 0 && + hostapd_is_dfs_required(hapd->iface) <= 0 && + hapd->iface->state != HAPD_IFACE_ENABLED) { + /* Fake a CAC start bit to skip setting channel */ + hapd->iface->cac_started = 1; + hostapd_setup_interface_complete(hapd->iface, 0); + } + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_CSA_FINISHED + "freq=%d dfs=%d", freq, is_dfs); + } else if (is_dfs && + hostapd_is_dfs_required(hapd->iface) && + !hostapd_is_dfs_chan_available(hapd->iface) && + !hapd->iface->cac_started) { + hostapd_disable_iface(hapd->iface); + hostapd_enable_iface(hapd->iface); + } + + for (i = 0; i < hapd->iface->num_bss; i++) + hostapd_neighbor_set_own_report(hapd->iface->bss[i]); + +#ifdef CONFIG_OCV + if (hapd->conf->ocv && + !(hapd->iface->drv_flags2 & + WPA_DRIVER_FLAGS2_SA_QUERY_OFFLOAD_AP)) { + struct sta_info *sta; + bool check_sa_query = false; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (wpa_auth_uses_ocv(sta->wpa_sm) && + !(sta->flags & WLAN_STA_WNM_SLEEP_MODE)) { + sta->post_csa_sa_query = 1; + check_sa_query = true; + } + } + + if (check_sa_query) { + wpa_printf(MSG_DEBUG, + "OCV: Check post-CSA SA Query initiation in 15 seconds"); + eloop_register_timeout(15, 0, + hostapd_ocv_check_csa_sa_query, + hapd, NULL); + } + } +#endif /* CONFIG_OCV */ +#endif /* NEED_AP_MLME */ +} + + +void hostapd_event_connect_failed_reason(struct hostapd_data *hapd, + const u8 *addr, int reason_code) +{ + switch (reason_code) { + case MAX_CLIENT_REACHED: + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_REJECTED_MAX_STA MACSTR, + MAC2STR(addr)); + break; + case BLOCKED_CLIENT: + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_REJECTED_BLOCKED_STA MACSTR, + MAC2STR(addr)); + break; + } +} + + +#ifdef CONFIG_ACS +void hostapd_acs_channel_selected(struct hostapd_data *hapd, + struct acs_selected_channels *acs_res) +{ + int ret, i; + int err = 0; + struct hostapd_channel_data *pri_chan; + +#ifdef CONFIG_IEEE80211BE + if (acs_res->link_id != -1) { + hapd = hostapd_mld_get_link_bss(hapd, acs_res->link_id); + if (!hapd) { + wpa_printf(MSG_ERROR, + "MLD: Failed to get link BSS for EVENT_ACS_CHANNEL_SELECTED link_id=%d", + acs_res->link_id); + return; + } + } +#endif /* CONFIG_IEEE80211BE */ + + if (hapd->iconf->channel) { + wpa_printf(MSG_INFO, "ACS: Channel was already set to %d", + hapd->iconf->channel); + return; + } + + hapd->iface->freq = acs_res->pri_freq; + + if (!hapd->iface->current_mode) { + for (i = 0; i < hapd->iface->num_hw_features; i++) { + struct hostapd_hw_modes *mode = + &hapd->iface->hw_features[i]; + + if (mode->mode == acs_res->hw_mode) { + if (hapd->iface->freq > 0 && + !hw_get_chan(mode->mode, + hapd->iface->freq, + hapd->iface->hw_features, + hapd->iface->num_hw_features)) + continue; + hapd->iface->current_mode = mode; + break; + } + } + if (!hapd->iface->current_mode) { + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_WARNING, + "driver selected to bad hw_mode"); + err = 1; + goto out; + } + } + + if (!acs_res->pri_freq) { + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_WARNING, + "driver switched to bad channel"); + err = 1; + goto out; + } + pri_chan = hw_get_channel_freq(hapd->iface->current_mode->mode, + acs_res->pri_freq, NULL, + hapd->iface->hw_features, + hapd->iface->num_hw_features); + if (!pri_chan) { + wpa_printf(MSG_ERROR, + "ACS: Could not determine primary channel number from pri_freq %u", + acs_res->pri_freq); + err = 1; + goto out; + } + + hapd->iconf->channel = pri_chan->chan; + hapd->iconf->acs = 1; + + if (acs_res->sec_freq == 0) + hapd->iconf->secondary_channel = 0; + else if (acs_res->sec_freq < acs_res->pri_freq) + hapd->iconf->secondary_channel = -1; + else if (acs_res->sec_freq > acs_res->pri_freq) + hapd->iconf->secondary_channel = 1; + else { + wpa_printf(MSG_ERROR, "Invalid secondary channel!"); + err = 1; + goto out; + } + + hapd->iconf->edmg_channel = acs_res->edmg_channel; + + if (hapd->iface->conf->ieee80211ac || hapd->iface->conf->ieee80211ax) { + /* set defaults for backwards compatibility */ + hostapd_set_oper_centr_freq_seg1_idx(hapd->iconf, 0); + hostapd_set_oper_centr_freq_seg0_idx(hapd->iconf, 0); + hostapd_set_oper_chwidth(hapd->iconf, CONF_OPER_CHWIDTH_USE_HT); + if (acs_res->ch_width == 40) { + if (is_6ghz_freq(acs_res->pri_freq)) + hostapd_set_oper_centr_freq_seg0_idx( + hapd->iconf, + acs_res->vht_seg0_center_ch); + } else if (acs_res->ch_width == 80) { + hostapd_set_oper_centr_freq_seg0_idx( + hapd->iconf, acs_res->vht_seg0_center_ch); + if (acs_res->vht_seg1_center_ch == 0) { + hostapd_set_oper_chwidth( + hapd->iconf, CONF_OPER_CHWIDTH_80MHZ); + } else { + hostapd_set_oper_chwidth( + hapd->iconf, + CONF_OPER_CHWIDTH_80P80MHZ); + hostapd_set_oper_centr_freq_seg1_idx( + hapd->iconf, + acs_res->vht_seg1_center_ch); + } + } else if (acs_res->ch_width == 160) { + hostapd_set_oper_chwidth(hapd->iconf, + CONF_OPER_CHWIDTH_160MHZ); + hostapd_set_oper_centr_freq_seg0_idx( + hapd->iconf, acs_res->vht_seg1_center_ch); + } + } + +#ifdef CONFIG_IEEE80211BE + if (hapd->iface->conf->ieee80211be && acs_res->ch_width == 320) { + hostapd_set_oper_chwidth(hapd->iconf, CONF_OPER_CHWIDTH_320MHZ); + hostapd_set_oper_centr_freq_seg0_idx( + hapd->iconf, acs_res->vht_seg1_center_ch); + hostapd_set_oper_centr_freq_seg1_idx(hapd->iconf, 0); + } + + if (hapd->iface->conf->ieee80211be && acs_res->puncture_bitmap) + hapd->iconf->punct_bitmap = acs_res->puncture_bitmap; +#endif /* CONFIG_IEEE80211BE */ + +out: + ret = hostapd_acs_completed(hapd->iface, err); + if (ret) { + wpa_printf(MSG_ERROR, + "ACS: Possibly channel configuration is invalid"); + } +} +#endif /* CONFIG_ACS */ + + +int hostapd_probe_req_rx(struct hostapd_data *hapd, const u8 *sa, const u8 *da, + const u8 *bssid, const u8 *ie, size_t ie_len, + int ssi_signal) +{ + size_t i; + int ret = 0; + + if (sa == NULL || ie == NULL) + return -1; + + random_add_randomness(sa, ETH_ALEN); + for (i = 0; hapd->probereq_cb && i < hapd->num_probereq_cb; i++) { + if (hapd->probereq_cb[i].cb(hapd->probereq_cb[i].ctx, + sa, da, bssid, ie, ie_len, + ssi_signal) > 0) { + ret = 1; + break; + } + } + return ret; +} + + +#ifdef HOSTAPD + +#ifdef CONFIG_IEEE80211R_AP +static void hostapd_notify_auth_ft_finish(void *ctx, const u8 *dst, + u16 auth_transaction, u16 status, + const u8 *ies, size_t ies_len) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + sta = ap_get_sta(hapd, dst); + if (sta == NULL) + return; + + hostapd_logger(hapd, dst, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "authentication OK (FT)"); + sta->flags |= WLAN_STA_AUTH; + + hostapd_sta_auth(hapd, dst, auth_transaction, status, ies, ies_len); +} +#endif /* CONFIG_IEEE80211R_AP */ + + +#ifdef CONFIG_FILS +static void hostapd_notify_auth_fils_finish(struct hostapd_data *hapd, + struct sta_info *sta, u16 resp, + struct wpabuf *data, int pub) +{ + if (resp == WLAN_STATUS_SUCCESS) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "authentication OK (FILS)"); + sta->flags |= WLAN_STA_AUTH; + wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH); + sta->auth_alg = WLAN_AUTH_FILS_SK; + mlme_authenticate_indication(hapd, sta); + } else { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "authentication failed (FILS)"); + } + + hostapd_sta_auth(hapd, sta->addr, 2, resp, + data ? wpabuf_head(data) : NULL, + data ? wpabuf_len(data) : 0); + wpabuf_free(data); +} +#endif /* CONFIG_FILS */ + + +static void hostapd_notif_auth(struct hostapd_data *hapd, + struct auth_info *rx_auth) +{ + struct sta_info *sta; + u16 status = WLAN_STATUS_SUCCESS; + u8 resp_ies[2 + WLAN_AUTH_CHALLENGE_LEN]; + size_t resp_ies_len = 0; + + sta = ap_get_sta(hapd, rx_auth->peer); + if (!sta) { + sta = ap_sta_add(hapd, rx_auth->peer); + if (sta == NULL) { + status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; + goto fail; + } + } + sta->flags &= ~WLAN_STA_PREAUTH; + ieee802_1x_notify_pre_auth(sta->eapol_sm, 0); +#ifdef CONFIG_IEEE80211R_AP + if (rx_auth->auth_type == WLAN_AUTH_FT && hapd->wpa_auth) { + sta->auth_alg = WLAN_AUTH_FT; + if (sta->wpa_sm == NULL) + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, + sta->addr, NULL); + if (sta->wpa_sm == NULL) { + wpa_printf(MSG_DEBUG, + "FT: Failed to initialize WPA state machine"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + wpa_ft_process_auth(sta->wpa_sm, + rx_auth->auth_transaction, rx_auth->ies, + rx_auth->ies_len, + hostapd_notify_auth_ft_finish, hapd); + return; + } +#endif /* CONFIG_IEEE80211R_AP */ + +#ifdef CONFIG_FILS + if (rx_auth->auth_type == WLAN_AUTH_FILS_SK) { + sta->auth_alg = WLAN_AUTH_FILS_SK; + handle_auth_fils(hapd, sta, rx_auth->ies, rx_auth->ies_len, + rx_auth->auth_type, rx_auth->auth_transaction, + rx_auth->status_code, + hostapd_notify_auth_fils_finish); + return; + } +#endif /* CONFIG_FILS */ + +fail: + hostapd_sta_auth(hapd, rx_auth->peer, rx_auth->auth_transaction + 1, + status, resp_ies, resp_ies_len); +} + + +#ifndef NEED_AP_MLME +static void hostapd_action_rx(struct hostapd_data *hapd, + struct rx_mgmt *drv_mgmt) +{ + struct ieee80211_mgmt *mgmt; + struct sta_info *sta; + size_t plen __maybe_unused; + u16 fc; + u8 *action __maybe_unused; + + if (drv_mgmt->frame_len < IEEE80211_HDRLEN + 2 + 1) + return; + + plen = drv_mgmt->frame_len - IEEE80211_HDRLEN; + + mgmt = (struct ieee80211_mgmt *) drv_mgmt->frame; + fc = le_to_host16(mgmt->frame_control); + if (WLAN_FC_GET_STYPE(fc) != WLAN_FC_STYPE_ACTION) + return; /* handled by the driver */ + + action = (u8 *) &mgmt->u.action.u; + wpa_printf(MSG_DEBUG, "RX_ACTION category %u action %u sa " MACSTR + " da " MACSTR " plen %d", + mgmt->u.action.category, *action, + MAC2STR(mgmt->sa), MAC2STR(mgmt->da), (int) plen); + + sta = ap_get_sta(hapd, mgmt->sa); + if (sta == NULL) { + wpa_printf(MSG_DEBUG, "%s: station not found", __func__); + return; + } +#ifdef CONFIG_IEEE80211R_AP + if (mgmt->u.action.category == WLAN_ACTION_FT) { + wpa_ft_action_rx(sta->wpa_sm, (u8 *) &mgmt->u.action, plen); + return; + } +#endif /* CONFIG_IEEE80211R_AP */ + if (mgmt->u.action.category == WLAN_ACTION_SA_QUERY) { + ieee802_11_sa_query_action(hapd, mgmt, drv_mgmt->frame_len); + return; + } +#ifdef CONFIG_WNM_AP + if (mgmt->u.action.category == WLAN_ACTION_WNM) { + ieee802_11_rx_wnm_action_ap(hapd, mgmt, drv_mgmt->frame_len); + return; + } +#endif /* CONFIG_WNM_AP */ +#ifdef CONFIG_FST + if (mgmt->u.action.category == WLAN_ACTION_FST && hapd->iface->fst) { + fst_rx_action(hapd->iface->fst, mgmt, drv_mgmt->frame_len); + return; + } +#endif /* CONFIG_FST */ +#ifdef CONFIG_DPP + if (plen >= 2 + 4 && + mgmt->u.action.category == WLAN_ACTION_PUBLIC && + mgmt->u.action.u.vs_public_action.action == + WLAN_PA_VENDOR_SPECIFIC && + WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) == + OUI_WFA && + mgmt->u.action.u.vs_public_action.variable[0] == + DPP_OUI_TYPE) { + const u8 *pos, *end; + + pos = mgmt->u.action.u.vs_public_action.oui; + end = drv_mgmt->frame + drv_mgmt->frame_len; + hostapd_dpp_rx_action(hapd, mgmt->sa, pos, end - pos, + drv_mgmt->freq); + return; + } +#endif /* CONFIG_DPP */ +#ifdef CONFIG_NAN_USD + if (mgmt->u.action.category == WLAN_ACTION_PUBLIC && plen >= 5 && + mgmt->u.action.u.vs_public_action.action == + WLAN_PA_VENDOR_SPECIFIC && + WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) == + OUI_WFA && + mgmt->u.action.u.vs_public_action.variable[0] == NAN_OUI_TYPE) { + const u8 *pos, *end; + + pos = mgmt->u.action.u.vs_public_action.variable; + end = drv_mgmt->frame + drv_mgmt->frame_len; + pos++; + hostapd_nan_usd_rx_sdf(hapd, mgmt->sa, drv_mgmt->freq, + pos, end - pos); + return; + } +#endif /* CONFIG_NAN_USD */ +} +#endif /* NEED_AP_MLME */ + + +#ifdef NEED_AP_MLME + +static struct hostapd_data * +switch_link_hapd(struct hostapd_data *hapd, int link_id) +{ +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap && link_id >= 0) { + struct hostapd_data *link_bss; + + link_bss = hostapd_mld_get_link_bss(hapd, link_id); + if (link_bss) + return link_bss; + } +#endif /* CONFIG_IEEE80211BE */ + + return hapd; +} + + +static struct hostapd_data * +switch_link_scan(struct hostapd_data *hapd, u64 scan_cookie) +{ +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap && scan_cookie != 0) { + unsigned int i; + + for (i = 0; i < hapd->iface->interfaces->count; i++) { + struct hostapd_iface *h; + struct hostapd_data *h_hapd; + + h = hapd->iface->interfaces->iface[i]; + h_hapd = h->bss[0]; + if (!hostapd_is_ml_partner(hapd, h_hapd)) + continue; + + if (h_hapd->scan_cookie == scan_cookie) { + h_hapd->scan_cookie = 0; + return h_hapd; + } + } + } +#endif /* CONFIG_IEEE80211BE */ + + return hapd; +} + + +#define HAPD_BROADCAST ((struct hostapd_data *) -1) + +static struct hostapd_data * get_hapd_bssid(struct hostapd_iface *iface, + const u8 *bssid, int link_id) +{ + size_t i; + + if (bssid == NULL) + return NULL; + if (bssid[0] == 0xff && bssid[1] == 0xff && bssid[2] == 0xff && + bssid[3] == 0xff && bssid[4] == 0xff && bssid[5] == 0xff) + return HAPD_BROADCAST; + + for (i = 0; i < iface->num_bss; i++) { + struct hostapd_data *hapd; +#ifdef CONFIG_IEEE80211BE + struct hostapd_data *p_hapd; +#endif /* CONFIG_IEEE80211BE */ + + hapd = iface->bss[i]; + if (ether_addr_equal(bssid, hapd->own_addr)) + return hapd; + +#ifdef CONFIG_IEEE80211BE + if (ether_addr_equal(bssid, hapd->own_addr) || + (hapd->conf->mld_ap && + ether_addr_equal(bssid, hapd->mld->mld_addr) && + link_id == hapd->mld_link_id)) + return hapd; + + if (!hapd->conf->mld_ap) + continue; + + for_each_mld_link(p_hapd, hapd) { + if (p_hapd == hapd) + continue; + + if (ether_addr_equal(bssid, p_hapd->own_addr) || + (ether_addr_equal(bssid, p_hapd->mld->mld_addr) && + link_id == p_hapd->mld_link_id)) + return p_hapd; + } +#endif /* CONFIG_IEEE80211BE */ + } + + return NULL; +} + + +static void hostapd_rx_from_unknown_sta(struct hostapd_data *hapd, + const u8 *bssid, const u8 *addr, + int wds) +{ + hapd = get_hapd_bssid(hapd->iface, bssid, -1); + if (hapd == NULL || hapd == HAPD_BROADCAST) + return; + + ieee802_11_rx_from_unknown(hapd, addr, wds); +} + + +static int hostapd_mgmt_rx(struct hostapd_data *hapd, struct rx_mgmt *rx_mgmt) +{ + struct hostapd_iface *iface; + const struct ieee80211_hdr *hdr; + const u8 *bssid; + struct hostapd_frame_info fi; + int ret; + + if (rx_mgmt->ctx) + hapd = rx_mgmt->ctx; + hapd = switch_link_hapd(hapd, rx_mgmt->link_id); + iface = hapd->iface; + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->ext_mgmt_frame_handling) { + size_t hex_len = 2 * rx_mgmt->frame_len + 1; + char *hex = os_malloc(hex_len); + + if (hex) { + wpa_snprintf_hex(hex, hex_len, rx_mgmt->frame, + rx_mgmt->frame_len); + wpa_msg(hapd->msg_ctx, MSG_INFO, "MGMT-RX %s", hex); + os_free(hex); + } + return 1; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + hdr = (const struct ieee80211_hdr *) rx_mgmt->frame; + bssid = get_hdr_bssid(hdr, rx_mgmt->frame_len); + if (bssid == NULL) + return 0; + + hapd = get_hapd_bssid(iface, bssid, rx_mgmt->link_id); + + if (!hapd) { + u16 fc = le_to_host16(hdr->frame_control); + + /* + * Drop frames to unknown BSSIDs except for Beacon frames which + * could be used to update neighbor information. + */ + if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT && + WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_BEACON) + hapd = iface->bss[0]; + else + return 0; + } + + os_memset(&fi, 0, sizeof(fi)); + fi.freq = rx_mgmt->freq; + fi.datarate = rx_mgmt->datarate; + fi.ssi_signal = rx_mgmt->ssi_signal; + + if (hapd == HAPD_BROADCAST) { + size_t i; + + ret = 0; + for (i = 0; i < iface->num_bss; i++) { + /* if bss is set, driver will call this function for + * each bss individually. */ + if (rx_mgmt->drv_priv && + (iface->bss[i]->drv_priv != rx_mgmt->drv_priv)) + continue; + + if (ieee802_11_mgmt(iface->bss[i], rx_mgmt->frame, + rx_mgmt->frame_len, &fi) > 0) + ret = 1; + } + } else + ret = ieee802_11_mgmt(hapd, rx_mgmt->frame, rx_mgmt->frame_len, + &fi); + + random_add_randomness(&fi, sizeof(fi)); + + return ret; +} + + +static void hostapd_mgmt_tx_cb(struct hostapd_data *hapd, const u8 *buf, + size_t len, u16 stype, int ok, int link_id) +{ + struct ieee80211_hdr *hdr; + struct hostapd_data *orig_hapd, *tmp_hapd; + + orig_hapd = hapd; + + hdr = (struct ieee80211_hdr *) buf; + hapd = switch_link_hapd(hapd, link_id); + tmp_hapd = get_hapd_bssid(hapd->iface, get_hdr_bssid(hdr, len), link_id); + if (tmp_hapd) { + hapd = tmp_hapd; +#ifdef CONFIG_IEEE80211BE + } else if (hapd->conf->mld_ap && + ether_addr_equal(hapd->mld->mld_addr, + get_hdr_bssid(hdr, len))) { + /* AP MLD address match - use hapd pointer as-is */ +#endif /* CONFIG_IEEE80211BE */ + } else { + return; + } + + if (hapd == HAPD_BROADCAST) { + if (stype != WLAN_FC_STYPE_ACTION || len <= 25 || + buf[24] != WLAN_ACTION_PUBLIC) + return; + hapd = get_hapd_bssid(orig_hapd->iface, hdr->addr2, link_id); + if (!hapd || hapd == HAPD_BROADCAST) + return; + /* + * Allow processing of TX status for a Public Action frame that + * used wildcard BBSID. + */ + } + ieee802_11_mgmt_cb(hapd, buf, len, stype, ok); +} + +#endif /* NEED_AP_MLME */ + + +static int hostapd_event_new_sta(struct hostapd_data *hapd, const u8 *addr) +{ + struct sta_info *sta = ap_get_sta(hapd, addr); + + if (sta) + return 0; + + wpa_printf(MSG_DEBUG, "Data frame from unknown STA " MACSTR + " - adding a new STA", MAC2STR(addr)); + sta = ap_sta_add(hapd, addr); + if (sta) { + hostapd_new_assoc_sta(hapd, sta, 0); + } else { + wpa_printf(MSG_DEBUG, "Failed to add STA entry for " MACSTR, + MAC2STR(addr)); + return -1; + } + + return 0; +} + + +static struct hostapd_data * hostapd_find_by_sta(struct hostapd_iface *iface, + const u8 *src, bool rsn, + struct sta_info **sta_ret) +{ + struct hostapd_data *hapd; + struct sta_info *sta; + unsigned int j; + + if (sta_ret) + *sta_ret = NULL; + + for (j = 0; j < iface->num_bss; j++) { + hapd = iface->bss[j]; + sta = ap_get_sta(hapd, src); + if (sta && (sta->flags & WLAN_STA_ASSOC) && + (!rsn || sta->wpa_sm)) { + if (sta_ret) + *sta_ret = sta; + return hapd; + } +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) { + struct hostapd_data *p_hapd; + + for_each_mld_link(p_hapd, hapd) { + if (p_hapd == hapd) + continue; + + sta = ap_get_sta(p_hapd, src); + if (sta && (sta->flags & WLAN_STA_ASSOC) && + (!rsn || sta->wpa_sm)) { + if (sta_ret) + *sta_ret = sta; + return p_hapd; + } + } + } +#endif /* CONFIG_IEEE80211BE */ + } + + return NULL; +} + + +static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src, + const u8 *data, size_t data_len, + enum frame_encryption encrypted, + int link_id) +{ + struct hostapd_data *orig_hapd = hapd; + +#ifdef CONFIG_IEEE80211BE + hapd = switch_link_hapd(hapd, link_id); + hapd = hostapd_find_by_sta(hapd->iface, src, true, NULL); +#else /* CONFIG_IEEE80211BE */ + hapd = hostapd_find_by_sta(hapd->iface, src, false, NULL); +#endif /* CONFIG_IEEE80211BE */ + + if (!hapd) { + /* WLAN cases need to have an existing association, but non-WLAN + * cases (mainly, wired IEEE 802.1X) need to be able to process + * EAPOL frames from new devices that do not yet have a STA + * entry and as such, do not get a match in + * hostapd_find_by_sta(). */ + wpa_printf(MSG_DEBUG, + "No STA-specific hostapd instance for EAPOL RX found - fall back to initial context"); + hapd = orig_hapd; + } + + ieee802_1x_receive(hapd, src, data, data_len, encrypted); +} + +#endif /* HOSTAPD */ + + +static struct hostapd_channel_data * +hostapd_get_mode_chan(struct hostapd_hw_modes *mode, unsigned int freq) +{ + int i; + struct hostapd_channel_data *chan; + + for (i = 0; i < mode->num_channels; i++) { + chan = &mode->channels[i]; + if ((unsigned int) chan->freq == freq) + return chan; + } + + return NULL; +} + + +static struct hostapd_channel_data * hostapd_get_mode_channel( + struct hostapd_iface *iface, unsigned int freq) +{ + int i; + struct hostapd_channel_data *chan; + + for (i = 0; i < iface->num_hw_features; i++) { + if (hostapd_hw_skip_mode(iface, &iface->hw_features[i])) + continue; + chan = hostapd_get_mode_chan(&iface->hw_features[i], freq); + if (chan) + return chan; + } + + return NULL; +} + + +static void hostapd_update_nf(struct hostapd_iface *iface, + struct hostapd_channel_data *chan, + struct freq_survey *survey) +{ + if (!iface->chans_surveyed) { + chan->min_nf = survey->nf; + iface->lowest_nf = survey->nf; + } else { + if (dl_list_empty(&chan->survey_list)) + chan->min_nf = survey->nf; + else if (survey->nf < chan->min_nf) + chan->min_nf = survey->nf; + if (survey->nf < iface->lowest_nf) + iface->lowest_nf = survey->nf; + } +} + + +static void hostapd_single_channel_get_survey(struct hostapd_iface *iface, + struct survey_results *survey_res) +{ + struct hostapd_channel_data *chan; + struct freq_survey *survey; + u64 divisor, dividend; + + survey = dl_list_first(&survey_res->survey_list, struct freq_survey, + list); + if (!survey || !survey->freq) + return; + + chan = hostapd_get_mode_channel(iface, survey->freq); + if (!chan || chan->flag & HOSTAPD_CHAN_DISABLED) + return; + + wpa_printf(MSG_DEBUG, + "Single Channel Survey: (freq=%d channel_time=%ld channel_time_busy=%ld)", + survey->freq, + (unsigned long int) survey->channel_time, + (unsigned long int) survey->channel_time_busy); + + if (survey->channel_time > iface->last_channel_time && + survey->channel_time > survey->channel_time_busy) { + dividend = survey->channel_time_busy - + iface->last_channel_time_busy; + divisor = survey->channel_time - iface->last_channel_time; + + iface->channel_utilization = dividend * 255 / divisor; + wpa_printf(MSG_DEBUG, "Channel Utilization: %d", + iface->channel_utilization); + } + iface->last_channel_time = survey->channel_time; + iface->last_channel_time_busy = survey->channel_time_busy; +} + + +void hostapd_event_get_survey(struct hostapd_iface *iface, + struct survey_results *survey_results) +{ + struct freq_survey *survey, *tmp; + struct hostapd_channel_data *chan; + + if (dl_list_empty(&survey_results->survey_list)) { + wpa_printf(MSG_DEBUG, "No survey data received"); + return; + } + + if (survey_results->freq_filter) { + hostapd_single_channel_get_survey(iface, survey_results); + return; + } + + dl_list_for_each_safe(survey, tmp, &survey_results->survey_list, + struct freq_survey, list) { + chan = hostapd_get_mode_channel(iface, survey->freq); + if (!chan) + continue; + if (chan->flag & HOSTAPD_CHAN_DISABLED) + continue; + + dl_list_del(&survey->list); + dl_list_add_tail(&chan->survey_list, &survey->list); + + hostapd_update_nf(iface, chan, survey); + + iface->chans_surveyed++; + } +} + + +#ifdef HOSTAPD +#ifdef NEED_AP_MLME + +static void hostapd_event_iface_unavailable(struct hostapd_data *hapd) +{ + wpa_printf(MSG_DEBUG, "Interface %s is unavailable -- stopped", + hapd->conf->iface); + + if (hapd->csa_in_progress) { + wpa_printf(MSG_INFO, "CSA failed (%s was stopped)", + hapd->conf->iface); + hostapd_switch_channel_fallback(hapd->iface, + &hapd->cs_freq_params); + } +} + + +static void hostapd_event_dfs_radar_detected(struct hostapd_data *hapd, + struct dfs_event *radar) +{ + wpa_printf(MSG_DEBUG, "DFS radar detected on %d MHz", radar->freq); + hostapd_dfs_radar_detected(hapd->iface, radar->freq, radar->ht_enabled, + radar->chan_offset, radar->chan_width, + radar->cf1, radar->cf2); +} + + +static void hostapd_event_dfs_pre_cac_expired(struct hostapd_data *hapd, + struct dfs_event *radar) +{ + wpa_printf(MSG_DEBUG, "DFS Pre-CAC expired on %d MHz", radar->freq); + hostapd_dfs_pre_cac_expired(hapd->iface, radar->freq, radar->ht_enabled, + radar->chan_offset, radar->chan_width, + radar->cf1, radar->cf2); +} + + +static void hostapd_event_dfs_cac_finished(struct hostapd_data *hapd, + struct dfs_event *radar) +{ + wpa_printf(MSG_DEBUG, "DFS CAC finished on %d MHz", radar->freq); + hostapd_dfs_complete_cac(hapd->iface, 1, radar->freq, radar->ht_enabled, + radar->chan_offset, radar->chan_width, + radar->cf1, radar->cf2); +} + + +static void hostapd_event_dfs_cac_aborted(struct hostapd_data *hapd, + struct dfs_event *radar) +{ + wpa_printf(MSG_DEBUG, "DFS CAC aborted on %d MHz", radar->freq); + hostapd_dfs_complete_cac(hapd->iface, 0, radar->freq, radar->ht_enabled, + radar->chan_offset, radar->chan_width, + radar->cf1, radar->cf2); +} + + +static void hostapd_event_dfs_nop_finished(struct hostapd_data *hapd, + struct dfs_event *radar) +{ + wpa_printf(MSG_DEBUG, "DFS NOP finished on %d MHz", radar->freq); + hostapd_dfs_nop_finished(hapd->iface, radar->freq, radar->ht_enabled, + radar->chan_offset, radar->chan_width, + radar->cf1, radar->cf2); +} + + +static void hostapd_event_dfs_cac_started(struct hostapd_data *hapd, + struct dfs_event *radar) +{ + wpa_printf(MSG_DEBUG, "DFS offload CAC started on %d MHz", radar->freq); + hostapd_dfs_start_cac(hapd->iface, radar->freq, radar->ht_enabled, + radar->chan_offset, radar->chan_width, + radar->cf1, radar->cf2); +} + +#endif /* NEED_AP_MLME */ + + +static void hostapd_event_wds_sta_interface_status(struct hostapd_data *hapd, + int istatus, + const char *ifname, + const u8 *addr) +{ + struct sta_info *sta = ap_get_sta(hapd, addr); + + if (sta) { + os_free(sta->ifname_wds); + if (istatus == INTERFACE_ADDED) + sta->ifname_wds = os_strdup(ifname); + else + sta->ifname_wds = NULL; + } + + wpa_msg(hapd->msg_ctx, MSG_INFO, "%sifname=%s sta_addr=" MACSTR, + istatus == INTERFACE_ADDED ? + WDS_STA_INTERFACE_ADDED : WDS_STA_INTERFACE_REMOVED, + ifname, MAC2STR(addr)); +} + + +#ifdef CONFIG_OWE +static int hostapd_notif_update_dh_ie(struct hostapd_data *hapd, + const u8 *peer, const u8 *ie, + size_t ie_len, const u8 *link_addr) +{ + u16 status; + struct sta_info *sta; + struct ieee802_11_elems elems; + + if (!hapd || !hapd->wpa_auth) { + wpa_printf(MSG_DEBUG, "OWE: Invalid hapd context"); + return -1; + } + if (!peer) { + wpa_printf(MSG_DEBUG, "OWE: Peer unknown"); + return -1; + } + if (!(hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE)) { + wpa_printf(MSG_DEBUG, "OWE: No OWE AKM configured"); + status = WLAN_STATUS_AKMP_NOT_VALID; + goto err; + } + if (ieee802_11_parse_elems(ie, ie_len, &elems, 1) == ParseFailed) { + wpa_printf(MSG_DEBUG, "OWE: Failed to parse OWE IE for " + MACSTR, MAC2STR(peer)); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto err; + } + status = owe_validate_request(hapd, peer, elems.rsn_ie, + elems.rsn_ie_len, + elems.owe_dh, elems.owe_dh_len); + if (status != WLAN_STATUS_SUCCESS) + goto err; + + sta = ap_get_sta(hapd, peer); + if (sta) { + ap_sta_no_session_timeout(hapd, sta); + accounting_sta_stop(hapd, sta); + + /* + * Make sure that the previously registered inactivity timer + * will not remove the STA immediately. + */ + sta->timeout_next = STA_NULLFUNC; + } else { + sta = ap_sta_add(hapd, peer); + if (!sta) { + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto err; + } + } + sta->flags &= ~(WLAN_STA_WPS | WLAN_STA_MAYBE_WPS | WLAN_STA_WPS2); + +#ifdef CONFIG_IEEE80211BE + if (link_addr) { + struct mld_info *info = &sta->mld_info; + u8 link_id = hapd->mld_link_id; + + ap_sta_set_mld(sta, true); + sta->mld_assoc_link_id = link_id; + os_memcpy(info->common_info.mld_addr, peer, ETH_ALEN); + info->links[link_id].valid = true; + os_memcpy(info->links[link_id].local_addr, hapd->own_addr, + ETH_ALEN); + os_memcpy(info->links[link_id].peer_addr, link_addr, ETH_ALEN); + } +#endif /* CONFIG_IEEE80211BE */ + + status = owe_process_rsn_ie(hapd, sta, elems.rsn_ie, + elems.rsn_ie_len, elems.owe_dh, + elems.owe_dh_len, link_addr); + if (status != WLAN_STATUS_SUCCESS) + ap_free_sta(hapd, sta); + + return 0; +err: + hostapd_drv_update_dh_ie(hapd, link_addr ? link_addr : peer, status, + NULL, 0); + return 0; +} +#endif /* CONFIG_OWE */ + + +#ifdef NEED_AP_MLME +static void hostapd_eapol_tx_status(struct hostapd_data *hapd, const u8 *dst, + const u8 *data, size_t len, int ack, + int link_id) +{ + struct sta_info *sta; + + hapd = switch_link_hapd(hapd, link_id); + hapd = hostapd_find_by_sta(hapd->iface, dst, false, &sta); + + if (!sta) { + wpa_printf(MSG_DEBUG, "Ignore TX status for Data frame to STA " + MACSTR " that is not currently associated", + MAC2STR(dst)); + return; + } + + ieee802_1x_eapol_tx_status(hapd, sta, data, len, ack); +} +#endif /* NEED_AP_MLME */ + + +#ifdef CONFIG_IEEE80211AX +static void hostapd_event_color_change(struct hostapd_data *hapd, bool success) +{ + struct hostapd_data *bss; + size_t i; + + for (i = 0; i < hapd->iface->num_bss; i++) { + bss = hapd->iface->bss[i]; + if (bss->cca_color == 0) + continue; + + if (success) + hapd->iface->conf->he_op.he_bss_color = bss->cca_color; + + bss->cca_in_progress = 0; + if (ieee802_11_set_beacon(bss)) { + wpa_printf(MSG_ERROR, "Failed to remove BCCA element"); + bss->cca_in_progress = 1; + } else { + hostapd_cleanup_cca_params(bss); + } + } +} +#endif /* CONFIG_IEEE80211AX */ + + +void wpa_supplicant_event(void *ctx, enum wpa_event_type event, + union wpa_event_data *data) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; +#ifndef CONFIG_NO_STDOUT_DEBUG + int level = MSG_DEBUG; + + if (event == EVENT_RX_MGMT && data->rx_mgmt.frame && + data->rx_mgmt.frame_len >= 24) { + const struct ieee80211_hdr *hdr; + u16 fc; + + hdr = (const struct ieee80211_hdr *) data->rx_mgmt.frame; + fc = le_to_host16(hdr->frame_control); + if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT && + WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_BEACON) + level = MSG_EXCESSIVE; + if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT && + WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_PROBE_REQ) + level = MSG_EXCESSIVE; + } + + wpa_dbg(hapd->msg_ctx, level, "Event %s (%d) received", + event_to_string(event), event); +#endif /* CONFIG_NO_STDOUT_DEBUG */ + + switch (event) { + case EVENT_MICHAEL_MIC_FAILURE: + michael_mic_failure(hapd, data->michael_mic_failure.src, 1); + break; + case EVENT_SCAN_RESULTS: +#ifdef NEED_AP_MLME + if (data) + hapd = switch_link_scan(hapd, + data->scan_info.scan_cookie); +#endif /* NEED_AP_MLME */ + if (hapd->iface->scan_cb) + hapd->iface->scan_cb(hapd->iface); +#ifdef CONFIG_IEEE80211BE + if (!hapd->iface->scan_cb && hapd->conf->mld_ap) { + /* Other links may be waiting for HT scan result */ + unsigned int i; + + for (i = 0; i < hapd->iface->interfaces->count; i++) { + struct hostapd_iface *h = + hapd->iface->interfaces->iface[i]; + struct hostapd_data *h_hapd = h->bss[0]; + + if (hostapd_is_ml_partner(hapd, h_hapd) && + h_hapd->iface->scan_cb) + h_hapd->iface->scan_cb(h_hapd->iface); + } + } +#endif /* CONFIG_IEEE80211BE */ + break; + case EVENT_WPS_BUTTON_PUSHED: + hostapd_wps_button_pushed(hapd, NULL); + break; +#ifdef NEED_AP_MLME + case EVENT_TX_STATUS: + switch (data->tx_status.type) { + case WLAN_FC_TYPE_MGMT: + hostapd_mgmt_tx_cb(hapd, data->tx_status.data, + data->tx_status.data_len, + data->tx_status.stype, + data->tx_status.ack, + data->tx_status.link_id); + break; + case WLAN_FC_TYPE_DATA: + hostapd_tx_status(hapd, data->tx_status.dst, + data->tx_status.data, + data->tx_status.data_len, + data->tx_status.ack); + break; + } + break; + case EVENT_EAPOL_TX_STATUS: + hostapd_eapol_tx_status(hapd, data->eapol_tx_status.dst, + data->eapol_tx_status.data, + data->eapol_tx_status.data_len, + data->eapol_tx_status.ack, + data->eapol_tx_status.link_id); + break; + case EVENT_DRIVER_CLIENT_POLL_OK: + hostapd_client_poll_ok(hapd, data->client_poll.addr); + break; + case EVENT_RX_FROM_UNKNOWN: + hostapd_rx_from_unknown_sta(hapd, data->rx_from_unknown.bssid, + data->rx_from_unknown.addr, + data->rx_from_unknown.wds); + break; +#endif /* NEED_AP_MLME */ + case EVENT_RX_MGMT: + if (!data->rx_mgmt.frame) + break; +#ifdef NEED_AP_MLME + hostapd_mgmt_rx(hapd, &data->rx_mgmt); +#else /* NEED_AP_MLME */ + hostapd_action_rx(hapd, &data->rx_mgmt); +#endif /* NEED_AP_MLME */ + break; + case EVENT_RX_PROBE_REQ: + if (data->rx_probe_req.sa == NULL || + data->rx_probe_req.ie == NULL) + break; + hostapd_probe_req_rx(hapd, data->rx_probe_req.sa, + data->rx_probe_req.da, + data->rx_probe_req.bssid, + data->rx_probe_req.ie, + data->rx_probe_req.ie_len, + data->rx_probe_req.ssi_signal); + break; + case EVENT_NEW_STA: + hostapd_event_new_sta(hapd, data->new_sta.addr); + break; + case EVENT_EAPOL_RX: + hostapd_event_eapol_rx(hapd, data->eapol_rx.src, + data->eapol_rx.data, + data->eapol_rx.data_len, + data->eapol_rx.encrypted, + data->eapol_rx.link_id); + break; + case EVENT_ASSOC: + if (!data) + return; +#ifdef CONFIG_IEEE80211BE + if (data->assoc_info.assoc_link_id != -1) { + hapd = hostapd_mld_get_link_bss( + hapd, data->assoc_info.assoc_link_id); + if (!hapd) { + wpa_printf(MSG_ERROR, + "MLD: Failed to get link BSS for EVENT_ASSOC"); + return; + } + } +#endif /* CONFIG_IEEE80211BE */ + hostapd_notif_assoc(hapd, data->assoc_info.addr, + data->assoc_info.req_ies, + data->assoc_info.req_ies_len, + data->assoc_info.resp_ies, + data->assoc_info.resp_ies_len, + data->assoc_info.link_addr, + data->assoc_info.reassoc); + break; + case EVENT_PORT_AUTHORIZED: + /* Port authorized event for an associated STA */ + sta = ap_get_sta(hapd, data->port_authorized.sta_addr); + if (sta) + ap_sta_set_authorized(hapd, sta, 1); + else + wpa_printf(MSG_DEBUG, + "No STA info matching port authorized event found"); + break; +#ifdef CONFIG_OWE + case EVENT_UPDATE_DH: + if (!data) + return; +#ifdef CONFIG_IEEE80211BE + if (data->update_dh.assoc_link_id != -1) { + hapd = hostapd_mld_get_link_bss( + hapd, data->update_dh.assoc_link_id); + if (!hapd) { + wpa_printf(MSG_ERROR, + "MLD: Failed to get link BSS for EVENT_UPDATE_DH assoc_link_id=%d", + data->update_dh.assoc_link_id); + return; + } + } +#endif /* CONFIG_IEEE80211BE */ + hostapd_notif_update_dh_ie(hapd, data->update_dh.peer, + data->update_dh.ie, + data->update_dh.ie_len, + data->update_dh.link_addr); + break; +#endif /* CONFIG_OWE */ + case EVENT_DISASSOC: + if (data) + hostapd_notif_disassoc(hapd, data->disassoc_info.addr); + break; + case EVENT_DEAUTH: + if (data) + hostapd_notif_disassoc(hapd, data->deauth_info.addr); + break; + case EVENT_STATION_LOW_ACK: + if (!data) + break; + hostapd_event_sta_low_ack(hapd, data->low_ack.addr); + break; + case EVENT_AUTH: + hostapd_notif_auth(hapd, &data->auth); + break; + case EVENT_CH_SWITCH_STARTED: + case EVENT_CH_SWITCH: + if (!data) + break; +#ifdef CONFIG_IEEE80211BE + if (data->ch_switch.link_id != -1) { + hapd = hostapd_mld_get_link_bss( + hapd, data->ch_switch.link_id); + if (!hapd) { + wpa_printf(MSG_ERROR, + "MLD: Failed to get link (ID %d) BSS for EVENT_CH_SWITCH/EVENT_CH_SWITCH_STARTED", + data->ch_switch.link_id); + break; + } + } +#endif /* CONFIG_IEEE80211BE */ + hostapd_event_ch_switch(hapd, data->ch_switch.freq, + data->ch_switch.ht_enabled, + data->ch_switch.ch_offset, + data->ch_switch.ch_width, + data->ch_switch.cf1, + data->ch_switch.cf2, + data->ch_switch.punct_bitmap, + event == EVENT_CH_SWITCH); + break; + case EVENT_CONNECT_FAILED_REASON: + if (!data) + break; + hostapd_event_connect_failed_reason( + hapd, data->connect_failed_reason.addr, + data->connect_failed_reason.code); + break; + case EVENT_SURVEY: + hostapd_event_get_survey(hapd->iface, &data->survey_results); + break; +#ifdef NEED_AP_MLME + case EVENT_INTERFACE_UNAVAILABLE: + hostapd_event_iface_unavailable(hapd); + break; + case EVENT_DFS_RADAR_DETECTED: + if (!data) + break; + hapd = switch_link_hapd(hapd, data->dfs_event.link_id); + hostapd_event_dfs_radar_detected(hapd, &data->dfs_event); + break; + case EVENT_DFS_PRE_CAC_EXPIRED: + if (!data) + break; + hapd = switch_link_hapd(hapd, data->dfs_event.link_id); + hostapd_event_dfs_pre_cac_expired(hapd, &data->dfs_event); + break; + case EVENT_DFS_CAC_FINISHED: + if (!data) + break; + hapd = switch_link_hapd(hapd, data->dfs_event.link_id); + hostapd_event_dfs_cac_finished(hapd, &data->dfs_event); + break; + case EVENT_DFS_CAC_ABORTED: + if (!data) + break; + hapd = switch_link_hapd(hapd, data->dfs_event.link_id); + hostapd_event_dfs_cac_aborted(hapd, &data->dfs_event); + break; + case EVENT_DFS_NOP_FINISHED: + if (!data) + break; + hapd = switch_link_hapd(hapd, data->dfs_event.link_id); + hostapd_event_dfs_nop_finished(hapd, &data->dfs_event); + break; + case EVENT_CHANNEL_LIST_CHANGED: + /* channel list changed (regulatory?), update channel list */ + /* TODO: check this. hostapd_get_hw_features() initializes + * too much stuff. */ + /* hostapd_get_hw_features(hapd->iface); */ + hostapd_channel_list_updated( + hapd->iface, data->channel_list_changed.initiator); + break; + case EVENT_DFS_CAC_STARTED: + if (!data) + break; + hapd = switch_link_hapd(hapd, data->dfs_event.link_id); + hostapd_event_dfs_cac_started(hapd, &data->dfs_event); + break; +#endif /* NEED_AP_MLME */ + case EVENT_INTERFACE_ENABLED: + wpa_msg(hapd->msg_ctx, MSG_INFO, INTERFACE_ENABLED); + if (hapd->disabled && hapd->started) { + hapd->disabled = 0; + /* + * Try to re-enable interface if the driver stopped it + * when the interface got disabled. + */ + if (hapd->wpa_auth) + wpa_auth_reconfig_group_keys(hapd->wpa_auth); + else + hostapd_reconfig_encryption(hapd); + hapd->reenable_beacon = 1; + ieee802_11_set_beacon(hapd); +#ifdef NEED_AP_MLME + } else if (hapd->disabled && hapd->iface->cac_started) { + wpa_printf(MSG_DEBUG, "DFS: restarting pending CAC"); + hostapd_handle_dfs(hapd->iface); +#endif /* NEED_AP_MLME */ + } + break; + case EVENT_INTERFACE_DISABLED: + hostapd_free_stas(hapd); + wpa_msg(hapd->msg_ctx, MSG_INFO, INTERFACE_DISABLED); + hapd->disabled = 1; + break; +#ifdef CONFIG_ACS + case EVENT_ACS_CHANNEL_SELECTED: + hostapd_acs_channel_selected(hapd, + &data->acs_selected_channels); + break; +#endif /* CONFIG_ACS */ + case EVENT_STATION_OPMODE_CHANGED: + hostapd_event_sta_opmode_changed(hapd, data->sta_opmode.addr, + data->sta_opmode.smps_mode, + data->sta_opmode.chan_width, + data->sta_opmode.rx_nss); + break; + case EVENT_WDS_STA_INTERFACE_STATUS: + hostapd_event_wds_sta_interface_status( + hapd, data->wds_sta_interface.istatus, + data->wds_sta_interface.ifname, + data->wds_sta_interface.sta_addr); + break; +#ifdef CONFIG_IEEE80211AX + case EVENT_BSS_COLOR_COLLISION: + /* The BSS color is shared amongst all BBSs on a specific phy. + * Therefore we always start the color change on the primary + * BSS. */ + hapd = switch_link_hapd(hapd, + data->bss_color_collision.link_id); + wpa_printf(MSG_DEBUG, "BSS color collision on %s", + hapd->conf->iface); + hostapd_switch_color(hapd->iface->bss[0], + data->bss_color_collision.bitmap); + break; + case EVENT_CCA_STARTED_NOTIFY: + hapd = switch_link_hapd(hapd, + data->bss_color_collision.link_id); + wpa_printf(MSG_DEBUG, "CCA started on %s", + hapd->conf->iface); + break; + case EVENT_CCA_ABORTED_NOTIFY: + hapd = switch_link_hapd(hapd, + data->bss_color_collision.link_id); + wpa_printf(MSG_DEBUG, "CCA aborted on %s", + hapd->conf->iface); + hostapd_event_color_change(hapd, false); + break; + case EVENT_CCA_NOTIFY: + hapd = switch_link_hapd(hapd, + data->bss_color_collision.link_id); + wpa_printf(MSG_DEBUG, "CCA finished on %s", + hapd->conf->iface); + hostapd_event_color_change(hapd, true); + break; +#endif /* CONFIG_IEEE80211AX */ + default: + wpa_printf(MSG_DEBUG, "Unknown event %d", event); + break; + } +} + + +void wpa_supplicant_event_global(void *ctx, enum wpa_event_type event, + union wpa_event_data *data) +{ + struct hapd_interfaces *interfaces = ctx; + struct hostapd_data *hapd; + + if (event != EVENT_INTERFACE_STATUS) + return; + + hapd = hostapd_get_iface(interfaces, data->interface_status.ifname); + if (hapd && hapd->driver && hapd->driver->get_ifindex && + hapd->drv_priv) { + unsigned int ifindex; + + ifindex = hapd->driver->get_ifindex(hapd->drv_priv); + if (ifindex != data->interface_status.ifindex) { + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, + "interface status ifindex %d mismatch (%d)", + ifindex, data->interface_status.ifindex); + return; + } + } + if (hapd) + wpa_supplicant_event(hapd, event, data); +} + +#endif /* HOSTAPD */ diff --git a/src/ap/eap_user_db.c b/src/ap/eap_user_db.c new file mode 100644 index 0000000..a510ee3 --- /dev/null +++ b/src/ap/eap_user_db.c @@ -0,0 +1,290 @@ +/* + * hostapd / EAP user database + * Copyright (c) 2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" +#ifdef CONFIG_SQLITE +#include +#endif /* CONFIG_SQLITE */ + +#include "common.h" +#include "eap_common/eap_wsc_common.h" +#include "eap_server/eap_methods.h" +#include "eap_server/eap.h" +#include "ap_config.h" +#include "hostapd.h" + +#ifdef CONFIG_SQLITE + +static void set_user_methods(struct hostapd_eap_user *user, const char *methods) +{ + char *buf, *start; + int num_methods; + + buf = os_strdup(methods); + if (buf == NULL) + return; + + os_memset(&user->methods, 0, sizeof(user->methods)); + num_methods = 0; + start = buf; + while (*start) { + char *pos3 = os_strchr(start, ','); + if (pos3) + *pos3++ = '\0'; + user->methods[num_methods].method = + eap_server_get_type(start, + &user->methods[num_methods].vendor); + if (user->methods[num_methods].vendor == EAP_VENDOR_IETF && + user->methods[num_methods].method == EAP_TYPE_NONE) { + if (os_strcmp(start, "TTLS-PAP") == 0) { + user->ttls_auth |= EAP_TTLS_AUTH_PAP; + goto skip_eap; + } + if (os_strcmp(start, "TTLS-CHAP") == 0) { + user->ttls_auth |= EAP_TTLS_AUTH_CHAP; + goto skip_eap; + } + if (os_strcmp(start, "TTLS-MSCHAP") == 0) { + user->ttls_auth |= EAP_TTLS_AUTH_MSCHAP; + goto skip_eap; + } + if (os_strcmp(start, "TTLS-MSCHAPV2") == 0) { + user->ttls_auth |= EAP_TTLS_AUTH_MSCHAPV2; + goto skip_eap; + } + wpa_printf(MSG_INFO, "DB: Unsupported EAP type '%s'", + start); + os_free(buf); + return; + } + + num_methods++; + if (num_methods >= EAP_MAX_METHODS) + break; + skip_eap: + if (pos3 == NULL) + break; + start = pos3; + } + + os_free(buf); +} + + +static int get_user_cb(void *ctx, int argc, char *argv[], char *col[]) +{ + struct hostapd_eap_user *user = ctx; + int i; + + for (i = 0; i < argc; i++) { + if (os_strcmp(col[i], "password") == 0 && argv[i]) { + bin_clear_free(user->password, user->password_len); + user->password_len = os_strlen(argv[i]); + user->password = (u8 *) os_strdup(argv[i]); + user->next = (void *) 1; + } else if (os_strcmp(col[i], "methods") == 0 && argv[i]) { + set_user_methods(user, argv[i]); + } else if (os_strcmp(col[i], "remediation") == 0 && argv[i]) { + user->remediation = strlen(argv[i]) > 0; + } else if (os_strcmp(col[i], "t_c_timestamp") == 0 && argv[i]) { + user->t_c_timestamp = strtol(argv[i], NULL, 10); + } + } + + return 0; +} + + +static int get_wildcard_cb(void *ctx, int argc, char *argv[], char *col[]) +{ + struct hostapd_eap_user *user = ctx; + int i, id = -1, methods = -1; + size_t len; + + for (i = 0; i < argc; i++) { + if (os_strcmp(col[i], "identity") == 0 && argv[i]) + id = i; + else if (os_strcmp(col[i], "methods") == 0 && argv[i]) + methods = i; + } + + if (id < 0 || methods < 0) + return 0; + + len = os_strlen(argv[id]); + if (len <= user->identity_len && + os_memcmp(argv[id], user->identity, len) == 0 && + (user->password == NULL || len > user->password_len)) { + bin_clear_free(user->password, user->password_len); + user->password_len = os_strlen(argv[id]); + user->password = (u8 *) os_strdup(argv[id]); + user->next = (void *) 1; + set_user_methods(user, argv[methods]); + } + + return 0; +} + + +static const struct hostapd_eap_user * +eap_user_sqlite_get(struct hostapd_data *hapd, const u8 *identity, + size_t identity_len, int phase2) +{ + sqlite3 *db; + struct hostapd_eap_user *user = NULL; + char id_str[256], cmd[300]; + size_t i; + int res; + + if (identity_len >= sizeof(id_str)) { + wpa_printf(MSG_DEBUG, "%s: identity len too big: %d >= %d", + __func__, (int) identity_len, + (int) (sizeof(id_str))); + return NULL; + } + os_memcpy(id_str, identity, identity_len); + id_str[identity_len] = '\0'; + for (i = 0; i < identity_len; i++) { + if (id_str[i] >= 'a' && id_str[i] <= 'z') + continue; + if (id_str[i] >= 'A' && id_str[i] <= 'Z') + continue; + if (id_str[i] >= '0' && id_str[i] <= '9') + continue; + if (id_str[i] == '-' || id_str[i] == '_' || id_str[i] == '.' || + id_str[i] == ',' || id_str[i] == '@' || id_str[i] == '\\' || + id_str[i] == '!' || id_str[i] == '#' || id_str[i] == '%' || + id_str[i] == '=' || id_str[i] == ' ') + continue; + wpa_printf(MSG_INFO, "DB: Unsupported character in identity"); + return NULL; + } + + bin_clear_free(hapd->tmp_eap_user.identity, + hapd->tmp_eap_user.identity_len); + bin_clear_free(hapd->tmp_eap_user.password, + hapd->tmp_eap_user.password_len); + os_memset(&hapd->tmp_eap_user, 0, sizeof(hapd->tmp_eap_user)); + hapd->tmp_eap_user.phase2 = phase2; + hapd->tmp_eap_user.identity = os_zalloc(identity_len + 1); + if (hapd->tmp_eap_user.identity == NULL) + return NULL; + os_memcpy(hapd->tmp_eap_user.identity, identity, identity_len); + hapd->tmp_eap_user.identity_len = identity_len; + + if (sqlite3_open(hapd->conf->eap_user_sqlite, &db)) { + wpa_printf(MSG_INFO, "DB: Failed to open database %s: %s", + hapd->conf->eap_user_sqlite, sqlite3_errmsg(db)); + sqlite3_close(db); + return NULL; + } + + res = os_snprintf(cmd, sizeof(cmd), + "SELECT * FROM users WHERE identity='%s' AND phase2=%d;", + id_str, phase2); + if (os_snprintf_error(sizeof(cmd), res)) + goto fail; + + wpa_printf(MSG_DEBUG, "DB: %s", cmd); + if (sqlite3_exec(db, cmd, get_user_cb, &hapd->tmp_eap_user, NULL) != + SQLITE_OK) { + wpa_printf(MSG_DEBUG, + "DB: Failed to complete SQL operation: %s db: %s", + sqlite3_errmsg(db), hapd->conf->eap_user_sqlite); + } else if (hapd->tmp_eap_user.next) + user = &hapd->tmp_eap_user; + + if (user == NULL && !phase2) { + os_snprintf(cmd, sizeof(cmd), + "SELECT identity,methods FROM wildcards;"); + wpa_printf(MSG_DEBUG, "DB: %s", cmd); + if (sqlite3_exec(db, cmd, get_wildcard_cb, &hapd->tmp_eap_user, + NULL) != SQLITE_OK) { + wpa_printf(MSG_DEBUG, + "DB: Failed to complete SQL operation: %s db: %s", + sqlite3_errmsg(db), + hapd->conf->eap_user_sqlite); + } else if (hapd->tmp_eap_user.next) { + user = &hapd->tmp_eap_user; + os_free(user->identity); + user->identity = user->password; + user->identity_len = user->password_len; + user->password = NULL; + user->password_len = 0; + } + } + +fail: + sqlite3_close(db); + + return user; +} + +#endif /* CONFIG_SQLITE */ + + +const struct hostapd_eap_user * +hostapd_get_eap_user(struct hostapd_data *hapd, const u8 *identity, + size_t identity_len, int phase2) +{ + const struct hostapd_bss_config *conf = hapd->conf; + struct hostapd_eap_user *user = conf->eap_user; + +#ifdef CONFIG_WPS + if (conf->wps_state && identity_len == WSC_ID_ENROLLEE_LEN && + os_memcmp(identity, WSC_ID_ENROLLEE, WSC_ID_ENROLLEE_LEN) == 0) { + static struct hostapd_eap_user wsc_enrollee; + os_memset(&wsc_enrollee, 0, sizeof(wsc_enrollee)); + wsc_enrollee.methods[0].method = eap_server_get_type( + "WSC", &wsc_enrollee.methods[0].vendor); + return &wsc_enrollee; + } + + if (conf->wps_state && identity_len == WSC_ID_REGISTRAR_LEN && + os_memcmp(identity, WSC_ID_REGISTRAR, WSC_ID_REGISTRAR_LEN) == 0) { + static struct hostapd_eap_user wsc_registrar; + os_memset(&wsc_registrar, 0, sizeof(wsc_registrar)); + wsc_registrar.methods[0].method = eap_server_get_type( + "WSC", &wsc_registrar.methods[0].vendor); + wsc_registrar.password = (u8 *) conf->ap_pin; + wsc_registrar.password_len = conf->ap_pin ? + os_strlen(conf->ap_pin) : 0; + return &wsc_registrar; + } +#endif /* CONFIG_WPS */ + + while (user) { + if (!phase2 && user->identity == NULL) { + /* Wildcard match */ + break; + } + + if (user->phase2 == !!phase2 && user->wildcard_prefix && + identity_len >= user->identity_len && + os_memcmp(user->identity, identity, user->identity_len) == + 0) { + /* Wildcard prefix match */ + break; + } + + if (user->phase2 == !!phase2 && + user->identity_len == identity_len && + os_memcmp(user->identity, identity, identity_len) == 0) + break; + user = user->next; + } + +#ifdef CONFIG_SQLITE + if (user == NULL && conf->eap_user_sqlite) { + return eap_user_sqlite_get(hapd, identity, identity_len, + phase2); + } +#endif /* CONFIG_SQLITE */ + + return user; +} diff --git a/src/ap/eth_p_oui.c b/src/ap/eth_p_oui.c new file mode 100644 index 0000000..aba901e --- /dev/null +++ b/src/ap/eth_p_oui.c @@ -0,0 +1,191 @@ +/* + * hostapd / IEEE 802 OUI Extended EtherType 88-B7 + * Copyright (c) 2016, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "l2_packet/l2_packet.h" +#include "hostapd.h" +#include "eth_p_oui.h" + +/* + * See IEEE Std 802-2014, Clause 9.2.4 for the definition of the OUI Extended + * EtherType 88-B7. This file implements this with OUI 00:13:74 and + * vendor-specific subtype 0x0001. + */ +static const u8 global_oui[] = { 0x00, 0x13, 0x74, 0x00, 0x01 }; + +struct eth_p_oui_iface { + struct dl_list list; + char ifname[IFNAMSIZ + 1]; + struct l2_packet_data *l2; + struct dl_list receiver; +}; + +struct eth_p_oui_ctx { + struct dl_list list; + struct eth_p_oui_iface *iface; + /* all data needed to deliver and unregister */ + u8 oui_suffix; /* last byte of OUI */ + void (*rx_callback)(void *ctx, const u8 *src_addr, + const u8 *dst_addr, u8 oui_suffix, + const u8 *buf, size_t len); + void *rx_callback_ctx; +}; + + +void eth_p_oui_deliver(struct eth_p_oui_ctx *ctx, const u8 *src_addr, + const u8 *dst_addr, const u8 *buf, size_t len) +{ + ctx->rx_callback(ctx->rx_callback_ctx, src_addr, dst_addr, + ctx->oui_suffix, buf, len); +} + + +static void eth_p_rx(void *ctx, const u8 *src_addr, const u8 *buf, size_t len) +{ + struct eth_p_oui_iface *iface = ctx; + struct eth_p_oui_ctx *receiver; + const struct l2_ethhdr *ethhdr; + + if (len < sizeof(*ethhdr) + sizeof(global_oui) + 1) { + /* too short packet */ + return; + } + + ethhdr = (struct l2_ethhdr *) buf; + /* trim eth_hdr from buf and len */ + buf += sizeof(*ethhdr); + len -= sizeof(*ethhdr); + + /* verify OUI and vendor-specific subtype match */ + if (os_memcmp(buf, global_oui, sizeof(global_oui)) != 0) + return; + buf += sizeof(global_oui); + len -= sizeof(global_oui); + + dl_list_for_each(receiver, &iface->receiver, + struct eth_p_oui_ctx, list) { + if (buf[0] != receiver->oui_suffix) + continue; + + eth_p_oui_deliver(receiver, ethhdr->h_source, ethhdr->h_dest, + buf + 1, len - 1); + } +} + + +struct eth_p_oui_ctx * +eth_p_oui_register(struct hostapd_data *hapd, const char *ifname, u8 oui_suffix, + void (*rx_callback)(void *ctx, const u8 *src_addr, + const u8 *dst_addr, u8 oui_suffix, + const u8 *buf, size_t len), + void *rx_callback_ctx) +{ + struct eth_p_oui_iface *iface; + struct eth_p_oui_ctx *receiver; + int found = 0; + struct hapd_interfaces *interfaces; + + receiver = os_zalloc(sizeof(*receiver)); + if (!receiver) + goto err; + + receiver->oui_suffix = oui_suffix; + receiver->rx_callback = rx_callback; + receiver->rx_callback_ctx = rx_callback_ctx; + + interfaces = hapd->iface->interfaces; + + dl_list_for_each(iface, &interfaces->eth_p_oui, struct eth_p_oui_iface, + list) { + if (os_strcmp(iface->ifname, ifname) != 0) + continue; + found = 1; + break; + } + + if (!found) { + iface = os_zalloc(sizeof(*iface)); + if (!iface) + goto err; + + os_strlcpy(iface->ifname, ifname, sizeof(iface->ifname)); + iface->l2 = l2_packet_init(ifname, NULL, ETH_P_OUI, eth_p_rx, + iface, 1); + if (!iface->l2) { + os_free(iface); + goto err; + } + dl_list_init(&iface->receiver); + + dl_list_add_tail(&interfaces->eth_p_oui, &iface->list); + } + + dl_list_add_tail(&iface->receiver, &receiver->list); + receiver->iface = iface; + + return receiver; +err: + os_free(receiver); + return NULL; +} + + +void eth_p_oui_unregister(struct eth_p_oui_ctx *ctx) +{ + struct eth_p_oui_iface *iface; + + if (!ctx) + return; + + iface = ctx->iface; + + dl_list_del(&ctx->list); + os_free(ctx); + + if (dl_list_empty(&iface->receiver)) { + dl_list_del(&iface->list); + l2_packet_deinit(iface->l2); + os_free(iface); + } +} + + +int eth_p_oui_send(struct eth_p_oui_ctx *ctx, const u8 *src_addr, + const u8 *dst_addr, const u8 *buf, size_t len) +{ + struct eth_p_oui_iface *iface = ctx->iface; + u8 *packet, *p; + size_t packet_len; + int ret; + struct l2_ethhdr *ethhdr; + + packet_len = sizeof(*ethhdr) + sizeof(global_oui) + 1 + len; + packet = os_zalloc(packet_len); + if (!packet) + return -1; + p = packet; + + ethhdr = (struct l2_ethhdr *) packet; + os_memcpy(ethhdr->h_source, src_addr, ETH_ALEN); + os_memcpy(ethhdr->h_dest, dst_addr, ETH_ALEN); + ethhdr->h_proto = host_to_be16(ETH_P_OUI); + p += sizeof(*ethhdr); + + os_memcpy(p, global_oui, sizeof(global_oui)); + p[sizeof(global_oui)] = ctx->oui_suffix; + p += sizeof(global_oui) + 1; + + os_memcpy(p, buf, len); + + ret = l2_packet_send(iface->l2, NULL, 0, packet, packet_len); + os_free(packet); + return ret; +} diff --git a/src/ap/eth_p_oui.h b/src/ap/eth_p_oui.h new file mode 100644 index 0000000..466fdc3 --- /dev/null +++ b/src/ap/eth_p_oui.h @@ -0,0 +1,28 @@ +/* + * hostapd / IEEE 802 OUI Extended Ethertype + * Copyright (c) 2016, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef ETH_P_OUI_H +#define ETH_P_OUI_H + +struct eth_p_oui_ctx; +struct hostapd_data; + +/* rx_callback only gets payload after OUI passed as buf */ +struct eth_p_oui_ctx * +eth_p_oui_register(struct hostapd_data *hapd, const char *ifname, u8 oui_suffix, + void (*rx_callback)(void *ctx, const u8 *src_addr, + const u8 *dst_addr, u8 oui_suffix, + const u8 *buf, size_t len), + void *rx_callback_ctx); +void eth_p_oui_unregister(struct eth_p_oui_ctx *eth_p_oui); +int eth_p_oui_send(struct eth_p_oui_ctx *ctx, const u8 *src_addr, + const u8 *dst_addr, const u8 *buf, size_t len); +void eth_p_oui_deliver(struct eth_p_oui_ctx *ctx, const u8 *src_addr, + const u8 *dst_addr, const u8 *buf, size_t len); + +#endif /* ETH_P_OUI_H */ diff --git a/src/ap/fils_hlp.c b/src/ap/fils_hlp.c new file mode 100644 index 0000000..a34b5ba --- /dev/null +++ b/src/ap/fils_hlp.c @@ -0,0 +1,654 @@ +/* + * FILS HLP request processing + * Copyright (c) 2017, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/dhcp.h" +#include "hostapd.h" +#include "sta_info.h" +#include "ieee802_11.h" +#include "fils_hlp.h" + + +static be16 ip_checksum(const void *buf, size_t len) +{ + u32 sum = 0; + const u16 *pos; + + for (pos = buf; len >= 2; len -= 2) + sum += ntohs(*pos++); + if (len) + sum += ntohs(*pos << 8); + + sum = (sum >> 16) + (sum & 0xffff); + sum += sum >> 16; + return htons(~sum); +} + + +static int fils_dhcp_request(struct hostapd_data *hapd, struct sta_info *sta, + struct dhcp_data *dhcpoffer, u8 *dhcpofferend) +{ + u8 *pos, *end; + struct dhcp_data *dhcp; + struct sockaddr_in addr; + ssize_t res; + const u8 *server_id = NULL; + + if (!sta->hlp_dhcp_discover) { + wpa_printf(MSG_DEBUG, + "FILS: No pending HLP DHCPDISCOVER available"); + return -1; + } + + /* Convert to DHCPREQUEST, remove rapid commit option, replace requested + * IP address option with yiaddr. */ + pos = wpabuf_mhead(sta->hlp_dhcp_discover); + end = pos + wpabuf_len(sta->hlp_dhcp_discover); + dhcp = (struct dhcp_data *) pos; + pos = (u8 *) (dhcp + 1); + pos += 4; /* skip magic */ + while (pos < end && *pos != DHCP_OPT_END) { + u8 opt, olen; + + opt = *pos++; + if (opt == DHCP_OPT_PAD) + continue; + if (pos >= end) + break; + olen = *pos++; + if (olen > end - pos) + break; + + switch (opt) { + case DHCP_OPT_MSG_TYPE: + if (olen > 0) + *pos = DHCPREQUEST; + break; + case DHCP_OPT_RAPID_COMMIT: + case DHCP_OPT_REQUESTED_IP_ADDRESS: + case DHCP_OPT_SERVER_ID: + /* Remove option */ + pos -= 2; + os_memmove(pos, pos + 2 + olen, end - pos - 2 - olen); + end -= 2 + olen; + olen = 0; + break; + } + pos += olen; + } + if (pos >= end || *pos != DHCP_OPT_END) { + wpa_printf(MSG_DEBUG, "FILS: Could not update DHCPDISCOVER"); + return -1; + } + sta->hlp_dhcp_discover->used = pos - (u8 *) dhcp; + + /* Copy Server ID option from DHCPOFFER to DHCPREQUEST */ + pos = (u8 *) (dhcpoffer + 1); + end = dhcpofferend; + pos += 4; /* skip magic */ + while (pos < end && *pos != DHCP_OPT_END) { + u8 opt, olen; + + opt = *pos++; + if (opt == DHCP_OPT_PAD) + continue; + if (pos >= end) + break; + olen = *pos++; + if (olen > end - pos) + break; + + switch (opt) { + case DHCP_OPT_SERVER_ID: + server_id = pos - 2; + break; + } + pos += olen; + } + + if (wpabuf_resize(&sta->hlp_dhcp_discover, + 6 + 1 + (server_id ? 2 + server_id[1] : 0))) + return -1; + if (server_id) + wpabuf_put_data(sta->hlp_dhcp_discover, server_id, + 2 + server_id[1]); + wpabuf_put_u8(sta->hlp_dhcp_discover, DHCP_OPT_REQUESTED_IP_ADDRESS); + wpabuf_put_u8(sta->hlp_dhcp_discover, 4); + wpabuf_put_data(sta->hlp_dhcp_discover, &dhcpoffer->your_ip, 4); + wpabuf_put_u8(sta->hlp_dhcp_discover, DHCP_OPT_END); + + os_memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = hapd->conf->dhcp_server.u.v4.s_addr; + addr.sin_port = htons(hapd->conf->dhcp_server_port); + res = sendto(hapd->dhcp_sock, wpabuf_head(sta->hlp_dhcp_discover), + wpabuf_len(sta->hlp_dhcp_discover), 0, + (const struct sockaddr *) &addr, sizeof(addr)); + if (res < 0) { + wpa_printf(MSG_ERROR, "FILS: DHCP sendto failed: %s", + strerror(errno)); + return -1; + } + wpa_printf(MSG_DEBUG, + "FILS: Acting as DHCP rapid commit proxy for %s:%d", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + wpabuf_free(sta->hlp_dhcp_discover); + sta->hlp_dhcp_discover = NULL; + sta->fils_dhcp_rapid_commit_proxy = 1; + return 0; +} + + +static void fils_dhcp_handler(int sd, void *eloop_ctx, void *sock_ctx) +{ + struct hostapd_data *hapd = sock_ctx; + struct sta_info *sta; + u8 buf[1500], *pos, *end, *end_opt = NULL; + struct dhcp_data *dhcp; + struct sockaddr_in addr; + socklen_t addr_len; + ssize_t res; + u8 msgtype = 0; + int rapid_commit = 0; + struct ip *iph; + struct udphdr *udph; + struct wpabuf *resp; + const u8 *rpos; + size_t left, len; + + addr_len = sizeof(addr); + res = recvfrom(sd, buf, sizeof(buf), 0, + (struct sockaddr *) &addr, &addr_len); + if (res < 0) { + wpa_printf(MSG_DEBUG, "FILS: DHCP read failed: %s", + strerror(errno)); + return; + } + wpa_printf(MSG_DEBUG, "FILS: DHCP response from server %s:%d (len=%d)", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), (int) res); + wpa_hexdump(MSG_MSGDUMP, "FILS: HLP - DHCP server response", buf, res); + if ((size_t) res < sizeof(*dhcp)) + return; + dhcp = (struct dhcp_data *) buf; + if (dhcp->op != 2) + return; /* Not a BOOTREPLY */ + if (dhcp->relay_ip != hapd->conf->own_ip_addr.u.v4.s_addr) { + wpa_printf(MSG_DEBUG, + "FILS: HLP - DHCP response to unknown relay address 0x%x", + dhcp->relay_ip); + return; + } + dhcp->relay_ip = 0; + pos = (u8 *) (dhcp + 1); + end = &buf[res]; + + if (end - pos < 4 || WPA_GET_BE32(pos) != DHCP_MAGIC) { + wpa_printf(MSG_DEBUG, "FILS: HLP - no DHCP magic in response"); + return; + } + pos += 4; + + wpa_hexdump(MSG_DEBUG, "FILS: HLP - DHCP options in response", + pos, end - pos); + while (pos < end && *pos != DHCP_OPT_END) { + u8 opt, olen; + + opt = *pos++; + if (opt == DHCP_OPT_PAD) + continue; + if (pos >= end) + break; + olen = *pos++; + if (olen > end - pos) + break; + + switch (opt) { + case DHCP_OPT_MSG_TYPE: + if (olen > 0) + msgtype = pos[0]; + break; + case DHCP_OPT_RAPID_COMMIT: + rapid_commit = 1; + break; + } + pos += olen; + } + if (pos < end && *pos == DHCP_OPT_END) + end_opt = pos; + + wpa_printf(MSG_DEBUG, + "FILS: HLP - DHCP message type %u (rapid_commit=%d hw_addr=" + MACSTR ")", + msgtype, rapid_commit, MAC2STR(dhcp->hw_addr)); + + sta = ap_get_sta(hapd, dhcp->hw_addr); + if (!sta || !sta->fils_pending_assoc_req) { + wpa_printf(MSG_DEBUG, + "FILS: No pending HLP DHCP exchange with hw_addr " + MACSTR, MAC2STR(dhcp->hw_addr)); + return; + } + + if (hapd->conf->dhcp_rapid_commit_proxy && msgtype == DHCPOFFER && + !rapid_commit) { + /* Use hostapd to take care of 4-message exchange and convert + * the final DHCPACK to rapid commit version. */ + if (fils_dhcp_request(hapd, sta, dhcp, end) == 0) + return; + /* failed, so send the server response as-is */ + } else if (msgtype != DHCPACK) { + wpa_printf(MSG_DEBUG, + "FILS: No DHCPACK available from the server and cannot do rapid commit proxying"); + } + + pos = buf; + resp = wpabuf_alloc(2 * ETH_ALEN + 6 + 2 + + sizeof(*iph) + sizeof(*udph) + (end - pos) + 2); + if (!resp) + return; + wpabuf_put_data(resp, sta->addr, ETH_ALEN); + wpabuf_put_data(resp, hapd->own_addr, ETH_ALEN); + wpabuf_put_data(resp, "\xaa\xaa\x03\x00\x00\x00", 6); + wpabuf_put_be16(resp, ETH_P_IP); + iph = wpabuf_put(resp, sizeof(*iph)); + iph->ip_v = 4; + iph->ip_hl = sizeof(*iph) / 4; + iph->ip_len = htons(sizeof(*iph) + sizeof(*udph) + (end - pos)); + iph->ip_ttl = 1; + iph->ip_p = 17; /* UDP */ + iph->ip_src.s_addr = hapd->conf->dhcp_server.u.v4.s_addr; + iph->ip_dst.s_addr = dhcp->client_ip; + iph->ip_sum = ip_checksum(iph, sizeof(*iph)); + udph = wpabuf_put(resp, sizeof(*udph)); + udph->uh_sport = htons(DHCP_SERVER_PORT); + udph->uh_dport = htons(DHCP_CLIENT_PORT); + udph->uh_ulen = htons(sizeof(*udph) + (end - pos)); + udph->uh_sum = htons(0x0000); /* TODO: calculate checksum */ + if (hapd->conf->dhcp_rapid_commit_proxy && msgtype == DHCPACK && + !rapid_commit && sta->fils_dhcp_rapid_commit_proxy && end_opt) { + /* Add rapid commit option */ + wpabuf_put_data(resp, pos, end_opt - pos); + wpabuf_put_u8(resp, DHCP_OPT_RAPID_COMMIT); + wpabuf_put_u8(resp, 0); + wpabuf_put_data(resp, end_opt, end - end_opt); + } else { + wpabuf_put_data(resp, pos, end - pos); + } + if (wpabuf_resize(&sta->fils_hlp_resp, wpabuf_len(resp) + + 2 * wpabuf_len(resp) / 255 + 100)) { + wpabuf_free(resp); + return; + } + + rpos = wpabuf_head(resp); + left = wpabuf_len(resp); + + wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_EXTENSION); /* Element ID */ + if (left <= 254) + len = 1 + left; + else + len = 255; + wpabuf_put_u8(sta->fils_hlp_resp, len); /* Length */ + /* Element ID Extension */ + wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_EXT_FILS_HLP_CONTAINER); + /* Destination MAC Address, Source MAC Address, HLP Packet. + * HLP Packet is in MSDU format (i.e., including the LLC/SNAP header + * when LPD is used). */ + wpabuf_put_data(sta->fils_hlp_resp, rpos, len - 1); + rpos += len - 1; + left -= len - 1; + while (left) { + wpabuf_put_u8(sta->fils_hlp_resp, WLAN_EID_FRAGMENT); + len = left > 255 ? 255 : left; + wpabuf_put_u8(sta->fils_hlp_resp, len); + wpabuf_put_data(sta->fils_hlp_resp, rpos, len); + rpos += len; + left -= len; + } + wpabuf_free(resp); + + if (sta->fils_drv_assoc_finish) + hostapd_notify_assoc_fils_finish(hapd, sta); + else + fils_hlp_finish_assoc(hapd, sta); +} + + +static int fils_process_hlp_dhcp(struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *msg, size_t len) +{ + const struct dhcp_data *dhcp; + struct wpabuf *dhcp_buf; + struct dhcp_data *dhcp_msg; + u8 msgtype = 0; + int rapid_commit = 0; + const u8 *pos = msg, *end; + struct sockaddr_in addr; + ssize_t res; + + if (len < sizeof(*dhcp)) + return 0; + dhcp = (const struct dhcp_data *) pos; + end = pos + len; + wpa_printf(MSG_DEBUG, + "FILS: HLP request DHCP: op=%u htype=%u hlen=%u hops=%u xid=0x%x", + dhcp->op, dhcp->htype, dhcp->hlen, dhcp->hops, + ntohl(dhcp->xid)); + pos += sizeof(*dhcp); + if (dhcp->op != 1) + return 0; /* Not a BOOTREQUEST */ + + if (end - pos < 4) + return 0; + if (WPA_GET_BE32(pos) != DHCP_MAGIC) { + wpa_printf(MSG_DEBUG, "FILS: HLP - no DHCP magic"); + return 0; + } + pos += 4; + + wpa_hexdump(MSG_DEBUG, "FILS: HLP - DHCP options", pos, end - pos); + while (pos < end && *pos != DHCP_OPT_END) { + u8 opt, olen; + + opt = *pos++; + if (opt == DHCP_OPT_PAD) + continue; + if (pos >= end) + break; + olen = *pos++; + if (olen > end - pos) + break; + + switch (opt) { + case DHCP_OPT_MSG_TYPE: + if (olen > 0) + msgtype = pos[0]; + break; + case DHCP_OPT_RAPID_COMMIT: + rapid_commit = 1; + break; + } + pos += olen; + } + + wpa_printf(MSG_DEBUG, "FILS: HLP - DHCP message type %u", msgtype); + if (msgtype != DHCPDISCOVER) + return 0; + + if (hapd->conf->dhcp_server.af != AF_INET || + hapd->conf->dhcp_server.u.v4.s_addr == 0) { + wpa_printf(MSG_DEBUG, + "FILS: HLP - no DHCPv4 server configured - drop request"); + return 0; + } + + if (hapd->conf->own_ip_addr.af != AF_INET || + hapd->conf->own_ip_addr.u.v4.s_addr == 0) { + wpa_printf(MSG_DEBUG, + "FILS: HLP - no IPv4 own_ip_addr configured - drop request"); + return 0; + } + + if (hapd->dhcp_sock < 0) { + int s; + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) { + wpa_printf(MSG_ERROR, + "FILS: Failed to open DHCP socket: %s", + strerror(errno)); + return 0; + } + + if (hapd->conf->dhcp_relay_port) { + os_memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = + hapd->conf->own_ip_addr.u.v4.s_addr; + addr.sin_port = htons(hapd->conf->dhcp_relay_port); + if (bind(s, (struct sockaddr *) &addr, sizeof(addr))) { + wpa_printf(MSG_ERROR, + "FILS: Failed to bind DHCP socket: %s", + strerror(errno)); + close(s); + return 0; + } + } + if (eloop_register_sock(s, EVENT_TYPE_READ, + fils_dhcp_handler, NULL, hapd)) { + close(s); + return 0; + } + + hapd->dhcp_sock = s; + } + + dhcp_buf = wpabuf_alloc(len); + if (!dhcp_buf) + return 0; + dhcp_msg = wpabuf_put(dhcp_buf, len); + os_memcpy(dhcp_msg, msg, len); + dhcp_msg->relay_ip = hapd->conf->own_ip_addr.u.v4.s_addr; + os_memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = hapd->conf->dhcp_server.u.v4.s_addr; + addr.sin_port = htons(hapd->conf->dhcp_server_port); + res = sendto(hapd->dhcp_sock, dhcp_msg, len, 0, + (const struct sockaddr *) &addr, sizeof(addr)); + if (res < 0) { + wpa_printf(MSG_ERROR, "FILS: DHCP sendto failed: %s", + strerror(errno)); + wpabuf_free(dhcp_buf); + /* Close the socket to try to recover from error */ + eloop_unregister_read_sock(hapd->dhcp_sock); + close(hapd->dhcp_sock); + hapd->dhcp_sock = -1; + return 0; + } + + wpa_printf(MSG_DEBUG, + "FILS: HLP relayed DHCP request to server %s:%d (rapid_commit=%d)", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), + rapid_commit); + if (hapd->conf->dhcp_rapid_commit_proxy && rapid_commit) { + /* Store a copy of the DHCPDISCOVER for rapid commit proxying + * purposes if the server does not support the rapid commit + * option. */ + wpa_printf(MSG_DEBUG, + "FILS: Store DHCPDISCOVER for rapid commit proxy"); + wpabuf_free(sta->hlp_dhcp_discover); + sta->hlp_dhcp_discover = dhcp_buf; + } else { + wpabuf_free(dhcp_buf); + } + + return 1; +} + + +static int fils_process_hlp_udp(struct hostapd_data *hapd, + struct sta_info *sta, const u8 *dst, + const u8 *pos, size_t len) +{ + const struct ip *iph; + const struct udphdr *udph; + u16 sport, dport, ulen; + + if (len < sizeof(*iph) + sizeof(*udph)) + return 0; + iph = (const struct ip *) pos; + udph = (const struct udphdr *) (iph + 1); + sport = ntohs(udph->uh_sport); + dport = ntohs(udph->uh_dport); + ulen = ntohs(udph->uh_ulen); + wpa_printf(MSG_DEBUG, + "FILS: HLP request UDP: sport=%u dport=%u ulen=%u sum=0x%x", + sport, dport, ulen, ntohs(udph->uh_sum)); + /* TODO: Check UDP checksum */ + if (ulen < sizeof(*udph) || ulen > len - sizeof(*iph)) + return 0; + + if (dport == DHCP_SERVER_PORT && sport == DHCP_CLIENT_PORT) { + return fils_process_hlp_dhcp(hapd, sta, (const u8 *) (udph + 1), + ulen - sizeof(*udph)); + } + + return 0; +} + + +static int fils_process_hlp_ip(struct hostapd_data *hapd, + struct sta_info *sta, const u8 *dst, + const u8 *pos, size_t len) +{ + const struct ip *iph; + uint16_t ip_len; + + if (len < sizeof(*iph)) + return 0; + iph = (const struct ip *) pos; + if (ip_checksum(iph, sizeof(*iph)) != 0) { + wpa_printf(MSG_DEBUG, + "FILS: HLP request IPv4 packet had invalid header checksum - dropped"); + return 0; + } + ip_len = ntohs(iph->ip_len); + if (ip_len > len) + return 0; + wpa_printf(MSG_DEBUG, + "FILS: HLP request IPv4: saddr=%08x daddr=%08x protocol=%u", + iph->ip_src.s_addr, iph->ip_dst.s_addr, iph->ip_p); + switch (iph->ip_p) { + case 17: + return fils_process_hlp_udp(hapd, sta, dst, pos, len); + default: + return 0; + } +} + + +static int fils_process_hlp_req(struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *pos, size_t len) +{ + const u8 *pkt, *end; + + wpa_printf(MSG_DEBUG, "FILS: HLP request from " MACSTR " (dst=" MACSTR + " src=" MACSTR " len=%u)", + MAC2STR(sta->addr), MAC2STR(pos), MAC2STR(pos + ETH_ALEN), + (unsigned int) len); + if (!ether_addr_equal(sta->addr, pos + ETH_ALEN)) { + wpa_printf(MSG_DEBUG, + "FILS: Ignore HLP request with unexpected source address" + MACSTR, MAC2STR(pos + ETH_ALEN)); + return 0; + } + + end = pos + len; + pkt = pos + 2 * ETH_ALEN; + if (end - pkt >= 6 && + os_memcmp(pkt, "\xaa\xaa\x03\x00\x00\x00", 6) == 0) + pkt += 6; /* Remove SNAP/LLC header */ + wpa_hexdump(MSG_MSGDUMP, "FILS: HLP request packet", pkt, end - pkt); + + if (end - pkt < 2) + return 0; + + switch (WPA_GET_BE16(pkt)) { + case ETH_P_IP: + return fils_process_hlp_ip(hapd, sta, pos, pkt + 2, + end - pkt - 2); + default: + return 0; + } +} + + +int fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *pos, int left) +{ + const u8 *end = pos + left; + u8 *tmp, *tmp_pos; + int ret = 0; + + if (sta->fils_pending_assoc_req && + eloop_is_timeout_registered(fils_hlp_timeout, hapd, sta)) { + /* Do not process FILS HLP request again if the station + * retransmits (Re)Association Request frame before the previous + * HLP response has either been received or timed out. */ + wpa_printf(MSG_DEBUG, + "FILS: Do not relay another HLP request from " + MACSTR + " before processing of the already pending one has been completed", + MAC2STR(sta->addr)); + return 1; + } + + /* Old DHCPDISCOVER is not needed anymore, if it was still pending */ + wpabuf_free(sta->hlp_dhcp_discover); + sta->hlp_dhcp_discover = NULL; + sta->fils_dhcp_rapid_commit_proxy = 0; + + /* Check if there are any FILS HLP Container elements */ + while (end - pos >= 2) { + if (2 + pos[1] > end - pos) + return 0; + if (pos[0] == WLAN_EID_EXTENSION && + pos[1] >= 1 + 2 * ETH_ALEN && + pos[2] == WLAN_EID_EXT_FILS_HLP_CONTAINER) + break; + pos += 2 + pos[1]; + } + if (end - pos < 2) + return 0; /* No FILS HLP Container elements */ + + tmp = os_malloc(end - pos); + if (!tmp) + return 0; + + while (end - pos >= 2) { + if (2 + pos[1] > end - pos || + pos[0] != WLAN_EID_EXTENSION || + pos[1] < 1 + 2 * ETH_ALEN || + pos[2] != WLAN_EID_EXT_FILS_HLP_CONTAINER) + break; + tmp_pos = tmp; + os_memcpy(tmp_pos, pos + 3, pos[1] - 1); + tmp_pos += pos[1] - 1; + pos += 2 + pos[1]; + + /* Add possible fragments */ + while (end - pos >= 2 && pos[0] == WLAN_EID_FRAGMENT && + 2 + pos[1] <= end - pos) { + os_memcpy(tmp_pos, pos + 2, pos[1]); + tmp_pos += pos[1]; + pos += 2 + pos[1]; + } + + if (fils_process_hlp_req(hapd, sta, tmp, tmp_pos - tmp) > 0) + ret = 1; + } + + os_free(tmp); + + return ret; +} + + +void fils_hlp_deinit(struct hostapd_data *hapd) +{ + if (hapd->dhcp_sock >= 0) { + eloop_unregister_read_sock(hapd->dhcp_sock); + close(hapd->dhcp_sock); + hapd->dhcp_sock = -1; + } +} diff --git a/src/ap/fils_hlp.h b/src/ap/fils_hlp.h new file mode 100644 index 0000000..e14a6bf --- /dev/null +++ b/src/ap/fils_hlp.h @@ -0,0 +1,27 @@ +/* + * FILS HLP request processing + * Copyright (c) 2017, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef FILS_HLP_H +#define FILS_HLP_H + +int fils_process_hlp(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *pos, int left); + +#ifdef CONFIG_FILS + +void fils_hlp_deinit(struct hostapd_data *hapd); + +#else /* CONFIG_FILS */ + +static inline void fils_hlp_deinit(struct hostapd_data *hapd) +{ +} + +#endif /* CONFIG_FILS */ + +#endif /* FILS_HLP_H */ diff --git a/src/ap/gas_query_ap.c b/src/ap/gas_query_ap.c new file mode 100644 index 0000000..a471c79 --- /dev/null +++ b/src/ap/gas_query_ap.c @@ -0,0 +1,718 @@ +/* + * Generic advertisement service (GAS) query (hostapd) + * Copyright (c) 2009, Atheros Communications + * Copyright (c) 2011-2017, Qualcomm Atheros, Inc. + * Copyright (c) 2011-2014, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "utils/eloop.h" +#include "utils/list.h" +#include "common/ieee802_11_defs.h" +#include "common/gas.h" +#include "common/wpa_ctrl.h" +#include "hostapd.h" +#include "sta_info.h" +#include "ap_drv_ops.h" +#include "gas_query_ap.h" + + +/** GAS query timeout in seconds */ +#define GAS_QUERY_TIMEOUT_PERIOD 2 + +/* GAS query wait-time / duration in ms */ +#define GAS_QUERY_WAIT_TIME_INITIAL 1000 +#define GAS_QUERY_WAIT_TIME_COMEBACK 150 + +#define GAS_QUERY_MAX_COMEBACK_DELAY 60000 + +/** + * struct gas_query_pending - Pending GAS query + */ +struct gas_query_pending { + struct dl_list list; + struct gas_query_ap *gas; + u8 addr[ETH_ALEN]; + u8 dialog_token; + u8 next_frag_id; + unsigned int wait_comeback:1; + unsigned int offchannel_tx_started:1; + unsigned int retry:1; + int freq; + u16 status_code; + struct wpabuf *req; + struct wpabuf *adv_proto; + struct wpabuf *resp; + struct os_reltime last_oper; + void (*cb)(void *ctx, const u8 *dst, u8 dialog_token, + enum gas_query_ap_result result, + const struct wpabuf *adv_proto, + const struct wpabuf *resp, u16 status_code); + void *ctx; + u8 sa[ETH_ALEN]; +}; + +/** + * struct gas_query_ap - Internal GAS query data + */ +struct gas_query_ap { + struct hostapd_data *hapd; + void *msg_ctx; + struct dl_list pending; /* struct gas_query_pending */ + struct gas_query_pending *current; +}; + + +static void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx); +static void gas_query_timeout(void *eloop_data, void *user_ctx); +static void gas_query_rx_comeback_timeout(void *eloop_data, void *user_ctx); +static void gas_query_tx_initial_req(struct gas_query_ap *gas, + struct gas_query_pending *query); +static int gas_query_new_dialog_token(struct gas_query_ap *gas, const u8 *dst); + + +static int ms_from_time(struct os_reltime *last) +{ + struct os_reltime now, res; + + os_get_reltime(&now); + os_reltime_sub(&now, last, &res); + return res.sec * 1000 + res.usec / 1000; +} + + +/** + * gas_query_ap_init - Initialize GAS query component + * @hapd: Pointer to hostapd data + * Returns: Pointer to GAS query data or %NULL on failure + */ +struct gas_query_ap * gas_query_ap_init(struct hostapd_data *hapd, + void *msg_ctx) +{ + struct gas_query_ap *gas; + + gas = os_zalloc(sizeof(*gas)); + if (!gas) + return NULL; + + gas->hapd = hapd; + gas->msg_ctx = msg_ctx; + dl_list_init(&gas->pending); + + return gas; +} + + +static const char * gas_result_txt(enum gas_query_ap_result result) +{ + switch (result) { + case GAS_QUERY_AP_SUCCESS: + return "SUCCESS"; + case GAS_QUERY_AP_FAILURE: + return "FAILURE"; + case GAS_QUERY_AP_TIMEOUT: + return "TIMEOUT"; + case GAS_QUERY_AP_PEER_ERROR: + return "PEER_ERROR"; + case GAS_QUERY_AP_INTERNAL_ERROR: + return "INTERNAL_ERROR"; + case GAS_QUERY_AP_DELETED_AT_DEINIT: + return "DELETED_AT_DEINIT"; + } + + return "N/A"; +} + + +static void gas_query_free(struct gas_query_pending *query, int del_list) +{ + if (del_list) + dl_list_del(&query->list); + + wpabuf_free(query->req); + wpabuf_free(query->adv_proto); + wpabuf_free(query->resp); + os_free(query); +} + + +static void gas_query_done(struct gas_query_ap *gas, + struct gas_query_pending *query, + enum gas_query_ap_result result) +{ + wpa_msg(gas->msg_ctx, MSG_INFO, GAS_QUERY_DONE "addr=" MACSTR + " dialog_token=%u freq=%d status_code=%u result=%s", + MAC2STR(query->addr), query->dialog_token, query->freq, + query->status_code, gas_result_txt(result)); + if (gas->current == query) + gas->current = NULL; + eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query); + eloop_cancel_timeout(gas_query_timeout, gas, query); + eloop_cancel_timeout(gas_query_rx_comeback_timeout, gas, query); + dl_list_del(&query->list); + query->cb(query->ctx, query->addr, query->dialog_token, result, + query->adv_proto, query->resp, query->status_code); + gas_query_free(query, 0); +} + + +/** + * gas_query_ap_deinit - Deinitialize GAS query component + * @gas: GAS query data from gas_query_init() + */ +void gas_query_ap_deinit(struct gas_query_ap *gas) +{ + struct gas_query_pending *query, *next; + + if (gas == NULL) + return; + + dl_list_for_each_safe(query, next, &gas->pending, + struct gas_query_pending, list) + gas_query_done(gas, query, GAS_QUERY_AP_DELETED_AT_DEINIT); + + os_free(gas); +} + + +static struct gas_query_pending * +gas_query_get_pending(struct gas_query_ap *gas, const u8 *addr, u8 dialog_token) +{ + struct gas_query_pending *q; + dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) { + if (ether_addr_equal(q->addr, addr) && + q->dialog_token == dialog_token) + return q; + } + return NULL; +} + + +static int gas_query_append(struct gas_query_pending *query, const u8 *data, + size_t len) +{ + if (wpabuf_resize(&query->resp, len) < 0) { + wpa_printf(MSG_DEBUG, "GAS: No memory to store the response"); + return -1; + } + wpabuf_put_data(query->resp, data, len); + return 0; +} + + +void gas_query_ap_tx_status(struct gas_query_ap *gas, const u8 *dst, + const u8 *data, size_t data_len, int ok) +{ + struct gas_query_pending *query; + int dur; + + if (!gas || !gas->current) { + wpa_printf(MSG_DEBUG, "GAS: Unexpected TX status: dst=" MACSTR + " ok=%d - no query in progress", MAC2STR(dst), ok); + return; + } + + query = gas->current; + + dur = ms_from_time(&query->last_oper); + wpa_printf(MSG_DEBUG, "GAS: TX status: dst=" MACSTR + " ok=%d query=%p dialog_token=%u dur=%d ms", + MAC2STR(dst), ok, query, query->dialog_token, dur); + if (!ether_addr_equal(dst, query->addr)) { + wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination"); + return; + } + os_get_reltime(&query->last_oper); + + eloop_cancel_timeout(gas_query_timeout, gas, query); + if (!ok) { + wpa_printf(MSG_DEBUG, "GAS: No ACK to GAS request"); + eloop_register_timeout(0, 250000, gas_query_timeout, + gas, query); + } else { + eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0, + gas_query_timeout, gas, query); + } + if (query->wait_comeback && !query->retry) { + eloop_cancel_timeout(gas_query_rx_comeback_timeout, + gas, query); + eloop_register_timeout( + 0, (GAS_QUERY_WAIT_TIME_COMEBACK + 10) * 1000, + gas_query_rx_comeback_timeout, gas, query); + } +} + + +static int pmf_in_use(struct hostapd_data *hapd, const u8 *addr) +{ + struct sta_info *sta; + + sta = ap_get_sta(hapd, addr); + return sta && (sta->flags & WLAN_STA_MFP); +} + + +static int gas_query_tx(struct gas_query_ap *gas, + struct gas_query_pending *query, + struct wpabuf *req, unsigned int wait_time) +{ + int res, prot = pmf_in_use(gas->hapd, query->addr); + + wpa_printf(MSG_DEBUG, "GAS: Send action frame to " MACSTR " len=%u " + "freq=%d prot=%d using src addr " MACSTR, + MAC2STR(query->addr), (unsigned int) wpabuf_len(req), + query->freq, prot, MAC2STR(query->sa)); + if (prot) { + u8 *categ = wpabuf_mhead_u8(req); + *categ = WLAN_ACTION_PROTECTED_DUAL; + } + os_get_reltime(&query->last_oper); + res = hostapd_drv_send_action(gas->hapd, query->freq, wait_time, + query->addr, wpabuf_head(req), + wpabuf_len(req)); + return res; +} + + +static void gas_query_tx_comeback_req(struct gas_query_ap *gas, + struct gas_query_pending *query) +{ + struct wpabuf *req; + unsigned int wait_time; + + req = gas_build_comeback_req(query->dialog_token); + if (req == NULL) { + gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR); + return; + } + + wait_time = (query->retry || !query->offchannel_tx_started) ? + GAS_QUERY_WAIT_TIME_INITIAL : GAS_QUERY_WAIT_TIME_COMEBACK; + + if (gas_query_tx(gas, query, req, wait_time) < 0) { + wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to " + MACSTR, MAC2STR(query->addr)); + gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR); + } + + wpabuf_free(req); +} + + +static void gas_query_rx_comeback_timeout(void *eloop_data, void *user_ctx) +{ + struct gas_query_ap *gas = eloop_data; + struct gas_query_pending *query = user_ctx; + int dialog_token; + + wpa_printf(MSG_DEBUG, + "GAS: No response to comeback request received (retry=%u)", + query->retry); + if (gas->current != query || query->retry) + return; + dialog_token = gas_query_new_dialog_token(gas, query->addr); + if (dialog_token < 0) + return; + wpa_printf(MSG_DEBUG, + "GAS: Retry GAS query due to comeback response timeout"); + query->retry = 1; + query->dialog_token = dialog_token; + *(wpabuf_mhead_u8(query->req) + 2) = dialog_token; + query->wait_comeback = 0; + query->next_frag_id = 0; + wpabuf_free(query->adv_proto); + query->adv_proto = NULL; + eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query); + eloop_cancel_timeout(gas_query_timeout, gas, query); + gas_query_tx_initial_req(gas, query); +} + + +static void gas_query_tx_comeback_timeout(void *eloop_data, void *user_ctx) +{ + struct gas_query_ap *gas = eloop_data; + struct gas_query_pending *query = user_ctx; + + wpa_printf(MSG_DEBUG, "GAS: Comeback timeout for request to " MACSTR, + MAC2STR(query->addr)); + gas_query_tx_comeback_req(gas, query); +} + + +static void gas_query_tx_comeback_req_delay(struct gas_query_ap *gas, + struct gas_query_pending *query, + u16 comeback_delay) +{ + unsigned int secs, usecs; + + secs = (comeback_delay * 1024) / 1000000; + usecs = comeback_delay * 1024 - secs * 1000000; + wpa_printf(MSG_DEBUG, "GAS: Send comeback request to " MACSTR + " in %u secs %u usecs", MAC2STR(query->addr), secs, usecs); + eloop_cancel_timeout(gas_query_tx_comeback_timeout, gas, query); + eloop_register_timeout(secs, usecs, gas_query_tx_comeback_timeout, + gas, query); +} + + +static void gas_query_rx_initial(struct gas_query_ap *gas, + struct gas_query_pending *query, + const u8 *adv_proto, const u8 *resp, + size_t len, u16 comeback_delay) +{ + wpa_printf(MSG_DEBUG, "GAS: Received initial response from " + MACSTR " (dialog_token=%u comeback_delay=%u)", + MAC2STR(query->addr), query->dialog_token, comeback_delay); + + query->adv_proto = wpabuf_alloc_copy(adv_proto, 2 + adv_proto[1]); + if (query->adv_proto == NULL) { + gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR); + return; + } + + if (comeback_delay) { + eloop_cancel_timeout(gas_query_timeout, gas, query); + query->wait_comeback = 1; + gas_query_tx_comeback_req_delay(gas, query, comeback_delay); + return; + } + + /* Query was completed without comeback mechanism */ + if (gas_query_append(query, resp, len) < 0) { + gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR); + return; + } + + gas_query_done(gas, query, GAS_QUERY_AP_SUCCESS); +} + + +static void gas_query_rx_comeback(struct gas_query_ap *gas, + struct gas_query_pending *query, + const u8 *adv_proto, const u8 *resp, + size_t len, u8 frag_id, u8 more_frags, + u16 comeback_delay) +{ + wpa_printf(MSG_DEBUG, "GAS: Received comeback response from " + MACSTR " (dialog_token=%u frag_id=%u more_frags=%u " + "comeback_delay=%u)", + MAC2STR(query->addr), query->dialog_token, frag_id, + more_frags, comeback_delay); + eloop_cancel_timeout(gas_query_rx_comeback_timeout, gas, query); + + if ((size_t) 2 + adv_proto[1] != wpabuf_len(query->adv_proto) || + os_memcmp(adv_proto, wpabuf_head(query->adv_proto), + wpabuf_len(query->adv_proto)) != 0) { + wpa_printf(MSG_DEBUG, "GAS: Advertisement Protocol changed " + "between initial and comeback response from " + MACSTR, MAC2STR(query->addr)); + gas_query_done(gas, query, GAS_QUERY_AP_PEER_ERROR); + return; + } + + if (comeback_delay) { + if (frag_id) { + wpa_printf(MSG_DEBUG, "GAS: Invalid comeback response " + "with non-zero frag_id and comeback_delay " + "from " MACSTR, MAC2STR(query->addr)); + gas_query_done(gas, query, GAS_QUERY_AP_PEER_ERROR); + return; + } + gas_query_tx_comeback_req_delay(gas, query, comeback_delay); + return; + } + + if (frag_id != query->next_frag_id) { + wpa_printf(MSG_DEBUG, "GAS: Unexpected frag_id in response " + "from " MACSTR, MAC2STR(query->addr)); + if (frag_id + 1 == query->next_frag_id) { + wpa_printf(MSG_DEBUG, "GAS: Drop frame as possible " + "retry of previous fragment"); + return; + } + gas_query_done(gas, query, GAS_QUERY_AP_PEER_ERROR); + return; + } + query->next_frag_id++; + + if (gas_query_append(query, resp, len) < 0) { + gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR); + return; + } + + if (more_frags) { + gas_query_tx_comeback_req(gas, query); + return; + } + + gas_query_done(gas, query, GAS_QUERY_AP_SUCCESS); +} + + +/** + * gas_query_ap_rx - Indicate reception of a Public Action or Protected Dual + * frame + * @gas: GAS query data from gas_query_init() + * @sa: Source MAC address of the Action frame + * @categ: Category of the Action frame + * @data: Payload of the Action frame + * @len: Length of @data + * @freq: Frequency (in MHz) on which the frame was received + * Returns: 0 if the Public Action frame was a GAS frame or -1 if not + */ +int gas_query_ap_rx(struct gas_query_ap *gas, const u8 *sa, u8 categ, + const u8 *data, size_t len, int freq) +{ + struct gas_query_pending *query; + u8 action, dialog_token, frag_id = 0, more_frags = 0; + u16 comeback_delay, resp_len; + const u8 *pos, *adv_proto; + int prot, pmf; + unsigned int left; + + if (!gas || len < 4) + return -1; + + pos = data; + action = *pos++; + dialog_token = *pos++; + + if (action != WLAN_PA_GAS_INITIAL_RESP && + action != WLAN_PA_GAS_COMEBACK_RESP) + return -1; /* Not a GAS response */ + + prot = categ == WLAN_ACTION_PROTECTED_DUAL; + pmf = pmf_in_use(gas->hapd, sa); + if (prot && !pmf) { + wpa_printf(MSG_DEBUG, "GAS: Drop unexpected protected GAS frame when PMF is disabled"); + return 0; + } + if (!prot && pmf) { + wpa_printf(MSG_DEBUG, "GAS: Drop unexpected unprotected GAS frame when PMF is enabled"); + return 0; + } + + query = gas_query_get_pending(gas, sa, dialog_token); + if (query == NULL) { + wpa_printf(MSG_DEBUG, "GAS: No pending query found for " MACSTR + " dialog token %u", MAC2STR(sa), dialog_token); + return -1; + } + + wpa_printf(MSG_DEBUG, "GAS: Response in %d ms from " MACSTR, + ms_from_time(&query->last_oper), MAC2STR(sa)); + + if (query->wait_comeback && action == WLAN_PA_GAS_INITIAL_RESP) { + wpa_printf(MSG_DEBUG, "GAS: Unexpected initial response from " + MACSTR " dialog token %u when waiting for comeback " + "response", MAC2STR(sa), dialog_token); + return 0; + } + + if (!query->wait_comeback && action == WLAN_PA_GAS_COMEBACK_RESP) { + wpa_printf(MSG_DEBUG, "GAS: Unexpected comeback response from " + MACSTR " dialog token %u when waiting for initial " + "response", MAC2STR(sa), dialog_token); + return 0; + } + + query->status_code = WPA_GET_LE16(pos); + pos += 2; + + if (query->status_code == WLAN_STATUS_QUERY_RESP_OUTSTANDING && + action == WLAN_PA_GAS_COMEBACK_RESP) { + wpa_printf(MSG_DEBUG, "GAS: Allow non-zero status for outstanding comeback response"); + } else if (query->status_code != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_DEBUG, "GAS: Query to " MACSTR " dialog token " + "%u failed - status code %u", + MAC2STR(sa), dialog_token, query->status_code); + gas_query_done(gas, query, GAS_QUERY_AP_FAILURE); + return 0; + } + + if (action == WLAN_PA_GAS_COMEBACK_RESP) { + if (pos + 1 > data + len) + return 0; + frag_id = *pos & 0x7f; + more_frags = (*pos & 0x80) >> 7; + pos++; + } + + /* Comeback Delay */ + if (pos + 2 > data + len) + return 0; + comeback_delay = WPA_GET_LE16(pos); + if (comeback_delay > GAS_QUERY_MAX_COMEBACK_DELAY) + comeback_delay = GAS_QUERY_MAX_COMEBACK_DELAY; + pos += 2; + + /* Advertisement Protocol element */ + if (pos + 2 > data + len || pos + 2 + pos[1] > data + len) { + wpa_printf(MSG_DEBUG, "GAS: No room for Advertisement " + "Protocol element in the response from " MACSTR, + MAC2STR(sa)); + return 0; + } + + if (*pos != WLAN_EID_ADV_PROTO) { + wpa_printf(MSG_DEBUG, "GAS: Unexpected Advertisement " + "Protocol element ID %u in response from " MACSTR, + *pos, MAC2STR(sa)); + return 0; + } + + adv_proto = pos; + pos += 2 + pos[1]; + + /* Query Response Length */ + if (pos + 2 > data + len) { + wpa_printf(MSG_DEBUG, "GAS: No room for GAS Response Length"); + return 0; + } + resp_len = WPA_GET_LE16(pos); + pos += 2; + + left = data + len - pos; + if (resp_len > left) { + wpa_printf(MSG_DEBUG, "GAS: Truncated Query Response in " + "response from " MACSTR, MAC2STR(sa)); + return 0; + } + + if (resp_len < left) { + wpa_printf(MSG_DEBUG, "GAS: Ignore %u octets of extra data " + "after Query Response from " MACSTR, + left - resp_len, MAC2STR(sa)); + } + + if (action == WLAN_PA_GAS_COMEBACK_RESP) + gas_query_rx_comeback(gas, query, adv_proto, pos, resp_len, + frag_id, more_frags, comeback_delay); + else + gas_query_rx_initial(gas, query, adv_proto, pos, resp_len, + comeback_delay); + + return 0; +} + + +static void gas_query_timeout(void *eloop_data, void *user_ctx) +{ + struct gas_query_ap *gas = eloop_data; + struct gas_query_pending *query = user_ctx; + + wpa_printf(MSG_DEBUG, "GAS: No response received for query to " MACSTR + " dialog token %u", + MAC2STR(query->addr), query->dialog_token); + gas_query_done(gas, query, GAS_QUERY_AP_TIMEOUT); +} + + +static int gas_query_dialog_token_available(struct gas_query_ap *gas, + const u8 *dst, u8 dialog_token) +{ + struct gas_query_pending *q; + dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) { + if (ether_addr_equal(dst, q->addr) && + dialog_token == q->dialog_token) + return 0; + } + + return 1; +} + + +static void gas_query_tx_initial_req(struct gas_query_ap *gas, + struct gas_query_pending *query) +{ + if (gas_query_tx(gas, query, query->req, + GAS_QUERY_WAIT_TIME_INITIAL) < 0) { + wpa_printf(MSG_DEBUG, "GAS: Failed to send Action frame to " + MACSTR, MAC2STR(query->addr)); + gas_query_done(gas, query, GAS_QUERY_AP_INTERNAL_ERROR); + return; + } + gas->current = query; + + wpa_printf(MSG_DEBUG, "GAS: Starting query timeout for dialog token %u", + query->dialog_token); + eloop_register_timeout(GAS_QUERY_TIMEOUT_PERIOD, 0, + gas_query_timeout, gas, query); +} + + +static int gas_query_new_dialog_token(struct gas_query_ap *gas, const u8 *dst) +{ + static int next_start = 0; + int dialog_token; + + for (dialog_token = 0; dialog_token < 256; dialog_token++) { + if (gas_query_dialog_token_available( + gas, dst, (next_start + dialog_token) % 256)) + break; + } + if (dialog_token == 256) + return -1; /* Too many pending queries */ + dialog_token = (next_start + dialog_token) % 256; + next_start = (dialog_token + 1) % 256; + return dialog_token; +} + + +/** + * gas_query_ap_req - Request a GAS query + * @gas: GAS query data from gas_query_init() + * @dst: Destination MAC address for the query + * @freq: Frequency (in MHz) for the channel on which to send the query + * @req: GAS query payload (to be freed by gas_query module in case of success + * return) + * @cb: Callback function for reporting GAS query result and response + * @ctx: Context pointer to use with the @cb call + * Returns: dialog token (>= 0) on success or -1 on failure + */ +int gas_query_ap_req(struct gas_query_ap *gas, const u8 *dst, int freq, + struct wpabuf *req, + void (*cb)(void *ctx, const u8 *dst, u8 dialog_token, + enum gas_query_ap_result result, + const struct wpabuf *adv_proto, + const struct wpabuf *resp, u16 status_code), + void *ctx) +{ + struct gas_query_pending *query; + int dialog_token; + + if (!gas || wpabuf_len(req) < 3) + return -1; + + dialog_token = gas_query_new_dialog_token(gas, dst); + if (dialog_token < 0) + return -1; + + query = os_zalloc(sizeof(*query)); + if (query == NULL) + return -1; + + query->gas = gas; + os_memcpy(query->addr, dst, ETH_ALEN); + query->dialog_token = dialog_token; + query->freq = freq; + query->cb = cb; + query->ctx = ctx; + query->req = req; + dl_list_add(&gas->pending, &query->list); + + *(wpabuf_mhead_u8(req) + 2) = dialog_token; + + wpa_msg(gas->msg_ctx, MSG_INFO, GAS_QUERY_START "addr=" MACSTR + " dialog_token=%u freq=%d", + MAC2STR(query->addr), query->dialog_token, query->freq); + + gas_query_tx_initial_req(gas, query); + + return dialog_token; +} diff --git a/src/ap/gas_query_ap.h b/src/ap/gas_query_ap.h new file mode 100644 index 0000000..70f1f05 --- /dev/null +++ b/src/ap/gas_query_ap.h @@ -0,0 +1,43 @@ +/* + * Generic advertisement service (GAS) query + * Copyright (c) 2009, Atheros Communications + * Copyright (c) 2011-2017, Qualcomm Atheros + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef GAS_QUERY_AP_H +#define GAS_QUERY_AP_H + +struct gas_query_ap; + +struct gas_query_ap * gas_query_ap_init(struct hostapd_data *hapd, + void *msg_ctx); +void gas_query_ap_deinit(struct gas_query_ap *gas); +int gas_query_ap_rx(struct gas_query_ap *gas, const u8 *sa, u8 categ, + const u8 *data, size_t len, int freq); + +/** + * enum gas_query_ap_result - GAS query result + */ +enum gas_query_ap_result { + GAS_QUERY_AP_SUCCESS, + GAS_QUERY_AP_FAILURE, + GAS_QUERY_AP_TIMEOUT, + GAS_QUERY_AP_PEER_ERROR, + GAS_QUERY_AP_INTERNAL_ERROR, + GAS_QUERY_AP_DELETED_AT_DEINIT +}; + +int gas_query_ap_req(struct gas_query_ap *gas, const u8 *dst, int freq, + struct wpabuf *req, + void (*cb)(void *ctx, const u8 *dst, u8 dialog_token, + enum gas_query_ap_result result, + const struct wpabuf *adv_proto, + const struct wpabuf *resp, u16 status_code), + void *ctx); +void gas_query_ap_tx_status(struct gas_query_ap *gas, const u8 *dst, + const u8 *data, size_t data_len, int ok); + +#endif /* GAS_QUERY_AP_H */ diff --git a/src/ap/gas_serv.c b/src/ap/gas_serv.c new file mode 100644 index 0000000..4642e49 --- /dev/null +++ b/src/ap/gas_serv.c @@ -0,0 +1,1895 @@ +/* + * Generic advertisement service (GAS) server + * Copyright (c) 2011-2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "common/ieee802_11_defs.h" +#include "common/gas.h" +#include "common/wpa_ctrl.h" +#include "utils/eloop.h" +#include "hostapd.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "dpp_hostapd.h" +#include "sta_info.h" +#include "gas_serv.h" + + +#ifdef CONFIG_DPP +static void gas_serv_write_dpp_adv_proto(struct wpabuf *buf) +{ + wpabuf_put_u8(buf, WLAN_EID_ADV_PROTO); + wpabuf_put_u8(buf, 8); /* Length */ + wpabuf_put_u8(buf, 0x7f); + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + wpabuf_put_u8(buf, 5); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, DPP_OUI_TYPE); + wpabuf_put_u8(buf, 0x01); +} +#endif /* CONFIG_DPP */ + + +static void convert_to_protected_dual(struct wpabuf *msg) +{ + u8 *categ = wpabuf_mhead_u8(msg); + *categ = WLAN_ACTION_PROTECTED_DUAL; +} + + +static struct gas_dialog_info * +gas_dialog_create(struct hostapd_data *hapd, const u8 *addr, u8 dialog_token) +{ + struct sta_info *sta; + struct gas_dialog_info *dia = NULL; + int i, j; + + sta = ap_get_sta(hapd, addr); + if (!sta) { + /* + * We need a STA entry to be able to maintain state for + * the GAS query. + */ + wpa_printf(MSG_DEBUG, "ANQP: Add a temporary STA entry for " + "GAS query"); + sta = ap_sta_add(hapd, addr); + if (!sta) { + wpa_printf(MSG_DEBUG, "Failed to add STA " MACSTR + " for GAS query", MAC2STR(addr)); + return NULL; + } + sta->flags |= WLAN_STA_GAS; + /* + * The default inactivity is 300 seconds. We don't need + * it to be that long. Use five second timeout and increase this + * with the comeback_delay for testing cases. + */ + ap_sta_session_timeout(hapd, sta, + hapd->conf->gas_comeback_delay / 1024 + + 5); + } else { + ap_sta_replenish_timeout(hapd, sta, 5); + } + + if (sta->gas_dialog == NULL) { + sta->gas_dialog = os_calloc(GAS_DIALOG_MAX, + sizeof(struct gas_dialog_info)); + if (sta->gas_dialog == NULL) + return NULL; + } + + for (i = sta->gas_dialog_next, j = 0; j < GAS_DIALOG_MAX; i++, j++) { + if (i == GAS_DIALOG_MAX) + i = 0; + if (sta->gas_dialog[i].valid) + continue; + dia = &sta->gas_dialog[i]; + dia->valid = 1; + dia->dialog_token = dialog_token; + sta->gas_dialog_next = (++i == GAS_DIALOG_MAX) ? 0 : i; + return dia; + } + + wpa_msg(hapd->msg_ctx, MSG_ERROR, "ANQP: Could not create dialog for " + MACSTR " dialog_token %u. Consider increasing " + "GAS_DIALOG_MAX.", MAC2STR(addr), dialog_token); + + return NULL; +} + + +struct gas_dialog_info * +gas_serv_dialog_find(struct hostapd_data *hapd, const u8 *addr, + u8 dialog_token) +{ + struct sta_info *sta; + int i; + + sta = ap_get_sta(hapd, addr); + if (!sta) { + wpa_printf(MSG_DEBUG, "ANQP: could not find STA " MACSTR, + MAC2STR(addr)); + return NULL; + } + for (i = 0; sta->gas_dialog && i < GAS_DIALOG_MAX; i++) { + if (sta->gas_dialog[i].dialog_token != dialog_token || + !sta->gas_dialog[i].valid) + continue; + ap_sta_replenish_timeout(hapd, sta, 5); + return &sta->gas_dialog[i]; + } + wpa_printf(MSG_DEBUG, "ANQP: Could not find dialog for " + MACSTR " dialog_token %u", MAC2STR(addr), dialog_token); + return NULL; +} + + +void gas_serv_dialog_clear(struct gas_dialog_info *dia) +{ + wpabuf_free(dia->sd_resp); + os_memset(dia, 0, sizeof(*dia)); +} + + +static void gas_serv_free_dialogs(struct hostapd_data *hapd, + const u8 *sta_addr) +{ + struct sta_info *sta; + int i; + + sta = ap_get_sta(hapd, sta_addr); + if (sta == NULL || sta->gas_dialog == NULL) + return; + + for (i = 0; i < GAS_DIALOG_MAX; i++) { + if (sta->gas_dialog[i].valid) + return; + } + + os_free(sta->gas_dialog); + sta->gas_dialog = NULL; +} + + +#ifdef CONFIG_HS20 +static void anqp_add_hs_capab_list(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + u8 *len; + + len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_CAPABILITY_LIST); + wpabuf_put_u8(buf, 0); /* Reserved */ + wpabuf_put_u8(buf, HS20_STYPE_CAPABILITY_LIST); + if (hapd->conf->hs20_oper_friendly_name) + wpabuf_put_u8(buf, HS20_STYPE_OPERATOR_FRIENDLY_NAME); + if (hapd->conf->hs20_wan_metrics) + wpabuf_put_u8(buf, HS20_STYPE_WAN_METRICS); + if (hapd->conf->hs20_connection_capability) + wpabuf_put_u8(buf, HS20_STYPE_CONNECTION_CAPABILITY); + if (hapd->conf->nai_realm_data) + wpabuf_put_u8(buf, HS20_STYPE_NAI_HOME_REALM_QUERY); + if (hapd->conf->hs20_operating_class) + wpabuf_put_u8(buf, HS20_STYPE_OPERATING_CLASS); + if (hapd->conf->hs20_osu_providers_count) + wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_LIST); + if (hapd->conf->hs20_osu_providers_nai_count) + wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_NAI_LIST); + if (hapd->conf->hs20_icons_count) + wpabuf_put_u8(buf, HS20_STYPE_ICON_REQUEST); + if (hapd->conf->hs20_operator_icon_count) + wpabuf_put_u8(buf, HS20_STYPE_OPERATOR_ICON_METADATA); + gas_anqp_set_element_len(buf, len); +} +#endif /* CONFIG_HS20 */ + + +static struct anqp_element * get_anqp_elem(struct hostapd_data *hapd, + u16 infoid) +{ + struct anqp_element *elem; + + dl_list_for_each(elem, &hapd->conf->anqp_elem, struct anqp_element, + list) { + if (elem->infoid == infoid) + return elem; + } + + return NULL; +} + + +static void anqp_add_elem(struct hostapd_data *hapd, struct wpabuf *buf, + u16 infoid) +{ + struct anqp_element *elem; + + elem = get_anqp_elem(hapd, infoid); + if (!elem) + return; + if (wpabuf_tailroom(buf) < 2 + 2 + wpabuf_len(elem->payload)) { + wpa_printf(MSG_DEBUG, "ANQP: No room for InfoID %u payload", + infoid); + return; + } + + wpabuf_put_le16(buf, infoid); + wpabuf_put_le16(buf, wpabuf_len(elem->payload)); + wpabuf_put_buf(buf, elem->payload); +} + + +static int anqp_add_override(struct hostapd_data *hapd, struct wpabuf *buf, + u16 infoid) +{ + if (get_anqp_elem(hapd, infoid)) { + anqp_add_elem(hapd, buf, infoid); + return 1; + } + + return 0; +} + + +static void anqp_add_capab_list(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + u8 *len; + u16 id; + + if (anqp_add_override(hapd, buf, ANQP_CAPABILITY_LIST)) + return; + + len = gas_anqp_add_element(buf, ANQP_CAPABILITY_LIST); + wpabuf_put_le16(buf, ANQP_CAPABILITY_LIST); + if (hapd->conf->venue_name || get_anqp_elem(hapd, ANQP_VENUE_NAME)) + wpabuf_put_le16(buf, ANQP_VENUE_NAME); + if (get_anqp_elem(hapd, ANQP_EMERGENCY_CALL_NUMBER)) + wpabuf_put_le16(buf, ANQP_EMERGENCY_CALL_NUMBER); + if (hapd->conf->network_auth_type || + get_anqp_elem(hapd, ANQP_NETWORK_AUTH_TYPE)) + wpabuf_put_le16(buf, ANQP_NETWORK_AUTH_TYPE); + if (hapd->conf->roaming_consortium || + get_anqp_elem(hapd, ANQP_ROAMING_CONSORTIUM)) + wpabuf_put_le16(buf, ANQP_ROAMING_CONSORTIUM); + if (hapd->conf->ipaddr_type_configured || + get_anqp_elem(hapd, ANQP_IP_ADDR_TYPE_AVAILABILITY)) + wpabuf_put_le16(buf, ANQP_IP_ADDR_TYPE_AVAILABILITY); + if (hapd->conf->nai_realm_data || + get_anqp_elem(hapd, ANQP_NAI_REALM)) + wpabuf_put_le16(buf, ANQP_NAI_REALM); + if (hapd->conf->anqp_3gpp_cell_net || + get_anqp_elem(hapd, ANQP_3GPP_CELLULAR_NETWORK)) + wpabuf_put_le16(buf, ANQP_3GPP_CELLULAR_NETWORK); + if (get_anqp_elem(hapd, ANQP_AP_GEOSPATIAL_LOCATION)) + wpabuf_put_le16(buf, ANQP_AP_GEOSPATIAL_LOCATION); + if (get_anqp_elem(hapd, ANQP_AP_CIVIC_LOCATION)) + wpabuf_put_le16(buf, ANQP_AP_CIVIC_LOCATION); + if (get_anqp_elem(hapd, ANQP_AP_LOCATION_PUBLIC_URI)) + wpabuf_put_le16(buf, ANQP_AP_LOCATION_PUBLIC_URI); + if (hapd->conf->domain_name || get_anqp_elem(hapd, ANQP_DOMAIN_NAME)) + wpabuf_put_le16(buf, ANQP_DOMAIN_NAME); + if (get_anqp_elem(hapd, ANQP_EMERGENCY_ALERT_URI)) + wpabuf_put_le16(buf, ANQP_EMERGENCY_ALERT_URI); + if (get_anqp_elem(hapd, ANQP_TDLS_CAPABILITY)) + wpabuf_put_le16(buf, ANQP_TDLS_CAPABILITY); + if (get_anqp_elem(hapd, ANQP_EMERGENCY_NAI)) + wpabuf_put_le16(buf, ANQP_EMERGENCY_NAI); + if (get_anqp_elem(hapd, ANQP_NEIGHBOR_REPORT)) + wpabuf_put_le16(buf, ANQP_NEIGHBOR_REPORT); +#ifdef CONFIG_FILS + if (!dl_list_empty(&hapd->conf->fils_realms) || + get_anqp_elem(hapd, ANQP_FILS_REALM_INFO)) + wpabuf_put_le16(buf, ANQP_FILS_REALM_INFO); +#endif /* CONFIG_FILS */ + if (get_anqp_elem(hapd, ANQP_CAG)) + wpabuf_put_le16(buf, ANQP_CAG); + if (hapd->conf->venue_url || get_anqp_elem(hapd, ANQP_VENUE_URL)) + wpabuf_put_le16(buf, ANQP_VENUE_URL); + if (get_anqp_elem(hapd, ANQP_ADVICE_OF_CHARGE)) + wpabuf_put_le16(buf, ANQP_ADVICE_OF_CHARGE); + if (get_anqp_elem(hapd, ANQP_LOCAL_CONTENT)) + wpabuf_put_le16(buf, ANQP_LOCAL_CONTENT); + for (id = 280; id < 300; id++) { + if (get_anqp_elem(hapd, id)) + wpabuf_put_le16(buf, id); + } +#ifdef CONFIG_HS20 + anqp_add_hs_capab_list(hapd, buf); +#endif /* CONFIG_HS20 */ + gas_anqp_set_element_len(buf, len); +} + + +static void anqp_add_venue_name(struct hostapd_data *hapd, struct wpabuf *buf) +{ + if (anqp_add_override(hapd, buf, ANQP_VENUE_NAME)) + return; + + if (hapd->conf->venue_name) { + u8 *len; + unsigned int i; + len = gas_anqp_add_element(buf, ANQP_VENUE_NAME); + wpabuf_put_u8(buf, hapd->conf->venue_group); + wpabuf_put_u8(buf, hapd->conf->venue_type); + for (i = 0; i < hapd->conf->venue_name_count; i++) { + struct hostapd_lang_string *vn; + vn = &hapd->conf->venue_name[i]; + wpabuf_put_u8(buf, 3 + vn->name_len); + wpabuf_put_data(buf, vn->lang, 3); + wpabuf_put_data(buf, vn->name, vn->name_len); + } + gas_anqp_set_element_len(buf, len); + } +} + + +static void anqp_add_venue_url(struct hostapd_data *hapd, struct wpabuf *buf) +{ + if (anqp_add_override(hapd, buf, ANQP_VENUE_URL)) + return; + + if (hapd->conf->venue_url) { + u8 *len; + unsigned int i; + + len = gas_anqp_add_element(buf, ANQP_VENUE_URL); + for (i = 0; i < hapd->conf->venue_url_count; i++) { + struct hostapd_venue_url *url; + + url = &hapd->conf->venue_url[i]; + wpabuf_put_u8(buf, 1 + url->url_len); + wpabuf_put_u8(buf, url->venue_number); + wpabuf_put_data(buf, url->url, url->url_len); + } + gas_anqp_set_element_len(buf, len); + } +} + + +static void anqp_add_network_auth_type(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (anqp_add_override(hapd, buf, ANQP_NETWORK_AUTH_TYPE)) + return; + + if (hapd->conf->network_auth_type) { + wpabuf_put_le16(buf, ANQP_NETWORK_AUTH_TYPE); + wpabuf_put_le16(buf, hapd->conf->network_auth_type_len); + wpabuf_put_data(buf, hapd->conf->network_auth_type, + hapd->conf->network_auth_type_len); + } +} + + +static void anqp_add_roaming_consortium(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + unsigned int i; + u8 *len; + + if (anqp_add_override(hapd, buf, ANQP_ROAMING_CONSORTIUM)) + return; + + len = gas_anqp_add_element(buf, ANQP_ROAMING_CONSORTIUM); + for (i = 0; i < hapd->conf->roaming_consortium_count; i++) { + struct hostapd_roaming_consortium *rc; + rc = &hapd->conf->roaming_consortium[i]; + wpabuf_put_u8(buf, rc->len); + wpabuf_put_data(buf, rc->oi, rc->len); + } + gas_anqp_set_element_len(buf, len); +} + + +static void anqp_add_ip_addr_type_availability(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (anqp_add_override(hapd, buf, ANQP_IP_ADDR_TYPE_AVAILABILITY)) + return; + + if (hapd->conf->ipaddr_type_configured) { + wpabuf_put_le16(buf, ANQP_IP_ADDR_TYPE_AVAILABILITY); + wpabuf_put_le16(buf, 1); + wpabuf_put_u8(buf, hapd->conf->ipaddr_type_availability); + } +} + + +static void anqp_add_nai_realm_eap(struct wpabuf *buf, + struct hostapd_nai_realm_data *realm) +{ + unsigned int i, j; + + wpabuf_put_u8(buf, realm->eap_method_count); + + for (i = 0; i < realm->eap_method_count; i++) { + struct hostapd_nai_realm_eap *eap = &realm->eap_method[i]; + wpabuf_put_u8(buf, 2 + (3 * eap->num_auths)); + wpabuf_put_u8(buf, eap->eap_method); + wpabuf_put_u8(buf, eap->num_auths); + for (j = 0; j < eap->num_auths; j++) { + wpabuf_put_u8(buf, eap->auth_id[j]); + wpabuf_put_u8(buf, 1); + wpabuf_put_u8(buf, eap->auth_val[j]); + } + } +} + + +static void anqp_add_nai_realm_data(struct wpabuf *buf, + struct hostapd_nai_realm_data *realm, + unsigned int realm_idx) +{ + u8 *realm_data_len; + + wpa_printf(MSG_DEBUG, "realm=%s, len=%d", realm->realm[realm_idx], + (int) os_strlen(realm->realm[realm_idx])); + realm_data_len = wpabuf_put(buf, 2); + wpabuf_put_u8(buf, realm->encoding); + wpabuf_put_u8(buf, os_strlen(realm->realm[realm_idx])); + wpabuf_put_str(buf, realm->realm[realm_idx]); + anqp_add_nai_realm_eap(buf, realm); + gas_anqp_set_element_len(buf, realm_data_len); +} + + +static int hs20_add_nai_home_realm_matches(struct hostapd_data *hapd, + struct wpabuf *buf, + const u8 *home_realm, + size_t home_realm_len) +{ + unsigned int i, j, k; + u8 num_realms, num_matching = 0, encoding, realm_len, *realm_list_len; + struct hostapd_nai_realm_data *realm; + const u8 *pos, *realm_name, *end; + struct { + unsigned int realm_data_idx; + unsigned int realm_idx; + } matches[10]; + + pos = home_realm; + end = pos + home_realm_len; + if (end - pos < 1) { + wpa_hexdump(MSG_DEBUG, "Too short NAI Home Realm Query", + home_realm, home_realm_len); + return -1; + } + num_realms = *pos++; + + for (i = 0; i < num_realms && num_matching < 10; i++) { + if (end - pos < 2) { + wpa_hexdump(MSG_DEBUG, + "Truncated NAI Home Realm Query", + home_realm, home_realm_len); + return -1; + } + encoding = *pos++; + realm_len = *pos++; + if (realm_len > end - pos) { + wpa_hexdump(MSG_DEBUG, + "Truncated NAI Home Realm Query", + home_realm, home_realm_len); + return -1; + } + realm_name = pos; + for (j = 0; j < hapd->conf->nai_realm_count && + num_matching < 10; j++) { + const u8 *rpos, *rend; + realm = &hapd->conf->nai_realm_data[j]; + if (encoding != realm->encoding) + continue; + + rpos = realm_name; + while (rpos < realm_name + realm_len && + num_matching < 10) { + for (rend = rpos; + rend < realm_name + realm_len; rend++) { + if (*rend == ';') + break; + } + for (k = 0; k < MAX_NAI_REALMS && + realm->realm[k] && + num_matching < 10; k++) { + if ((int) os_strlen(realm->realm[k]) != + rend - rpos || + os_strncmp((char *) rpos, + realm->realm[k], + rend - rpos) != 0) + continue; + matches[num_matching].realm_data_idx = + j; + matches[num_matching].realm_idx = k; + num_matching++; + } + rpos = rend + 1; + } + } + pos += realm_len; + } + + realm_list_len = gas_anqp_add_element(buf, ANQP_NAI_REALM); + wpabuf_put_le16(buf, num_matching); + + /* + * There are two ways to format. 1. each realm in a NAI Realm Data unit + * 2. all realms that share the same EAP methods in a NAI Realm Data + * unit. The first format is likely to be bigger in size than the + * second, but may be easier to parse and process by the receiver. + */ + for (i = 0; i < num_matching; i++) { + wpa_printf(MSG_DEBUG, "realm_idx %d, realm_data_idx %d", + matches[i].realm_data_idx, matches[i].realm_idx); + realm = &hapd->conf->nai_realm_data[matches[i].realm_data_idx]; + anqp_add_nai_realm_data(buf, realm, matches[i].realm_idx); + } + gas_anqp_set_element_len(buf, realm_list_len); + return 0; +} + + +static void anqp_add_nai_realm(struct hostapd_data *hapd, struct wpabuf *buf, + const u8 *home_realm, size_t home_realm_len, + int nai_realm, int nai_home_realm) +{ + if (nai_realm && !nai_home_realm && + anqp_add_override(hapd, buf, ANQP_NAI_REALM)) + return; + + if (nai_realm && hapd->conf->nai_realm_data) { + u8 *len; + unsigned int i, j; + len = gas_anqp_add_element(buf, ANQP_NAI_REALM); + wpabuf_put_le16(buf, hapd->conf->nai_realm_count); + for (i = 0; i < hapd->conf->nai_realm_count; i++) { + u8 *realm_data_len, *realm_len; + struct hostapd_nai_realm_data *realm; + + realm = &hapd->conf->nai_realm_data[i]; + realm_data_len = wpabuf_put(buf, 2); + wpabuf_put_u8(buf, realm->encoding); + realm_len = wpabuf_put(buf, 1); + for (j = 0; realm->realm[j]; j++) { + if (j > 0) + wpabuf_put_u8(buf, ';'); + wpabuf_put_str(buf, realm->realm[j]); + } + *realm_len = (u8 *) wpabuf_put(buf, 0) - realm_len - 1; + anqp_add_nai_realm_eap(buf, realm); + gas_anqp_set_element_len(buf, realm_data_len); + } + gas_anqp_set_element_len(buf, len); + } else if (nai_home_realm && hapd->conf->nai_realm_data && home_realm) { + hs20_add_nai_home_realm_matches(hapd, buf, home_realm, + home_realm_len); + } +} + + +static void anqp_add_3gpp_cellular_network(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (anqp_add_override(hapd, buf, ANQP_3GPP_CELLULAR_NETWORK)) + return; + + if (hapd->conf->anqp_3gpp_cell_net) { + wpabuf_put_le16(buf, ANQP_3GPP_CELLULAR_NETWORK); + wpabuf_put_le16(buf, + hapd->conf->anqp_3gpp_cell_net_len); + wpabuf_put_data(buf, hapd->conf->anqp_3gpp_cell_net, + hapd->conf->anqp_3gpp_cell_net_len); + } +} + + +static void anqp_add_domain_name(struct hostapd_data *hapd, struct wpabuf *buf) +{ + if (anqp_add_override(hapd, buf, ANQP_DOMAIN_NAME)) + return; + + if (hapd->conf->domain_name) { + wpabuf_put_le16(buf, ANQP_DOMAIN_NAME); + wpabuf_put_le16(buf, hapd->conf->domain_name_len); + wpabuf_put_data(buf, hapd->conf->domain_name, + hapd->conf->domain_name_len); + } +} + + +#ifdef CONFIG_FILS +static void anqp_add_fils_realm_info(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + size_t count; + + if (anqp_add_override(hapd, buf, ANQP_FILS_REALM_INFO)) + return; + + count = dl_list_len(&hapd->conf->fils_realms); + if (count > 10000) + count = 10000; + if (count) { + struct fils_realm *realm; + + wpabuf_put_le16(buf, ANQP_FILS_REALM_INFO); + wpabuf_put_le16(buf, 2 * count); + + dl_list_for_each(realm, &hapd->conf->fils_realms, + struct fils_realm, list) { + if (count == 0) + break; + wpabuf_put_data(buf, realm->hash, 2); + count--; + } + } +} +#endif /* CONFIG_FILS */ + + +#ifdef CONFIG_HS20 + +static void anqp_add_operator_friendly_name(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (hapd->conf->hs20_oper_friendly_name) { + u8 *len; + unsigned int i; + len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_OPERATOR_FRIENDLY_NAME); + wpabuf_put_u8(buf, 0); /* Reserved */ + for (i = 0; i < hapd->conf->hs20_oper_friendly_name_count; i++) + { + struct hostapd_lang_string *vn; + vn = &hapd->conf->hs20_oper_friendly_name[i]; + wpabuf_put_u8(buf, 3 + vn->name_len); + wpabuf_put_data(buf, vn->lang, 3); + wpabuf_put_data(buf, vn->name, vn->name_len); + } + gas_anqp_set_element_len(buf, len); + } +} + + +static void anqp_add_wan_metrics(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (hapd->conf->hs20_wan_metrics) { + u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_WAN_METRICS); + wpabuf_put_u8(buf, 0); /* Reserved */ + wpabuf_put_data(buf, hapd->conf->hs20_wan_metrics, 13); + gas_anqp_set_element_len(buf, len); + } +} + + +static void anqp_add_connection_capability(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (hapd->conf->hs20_connection_capability) { + u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_CONNECTION_CAPABILITY); + wpabuf_put_u8(buf, 0); /* Reserved */ + wpabuf_put_data(buf, hapd->conf->hs20_connection_capability, + hapd->conf->hs20_connection_capability_len); + gas_anqp_set_element_len(buf, len); + } +} + + +static void anqp_add_operating_class(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (hapd->conf->hs20_operating_class) { + u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_OPERATING_CLASS); + wpabuf_put_u8(buf, 0); /* Reserved */ + wpabuf_put_data(buf, hapd->conf->hs20_operating_class, + hapd->conf->hs20_operating_class_len); + gas_anqp_set_element_len(buf, len); + } +} + + +static void anqp_add_icon(struct wpabuf *buf, struct hostapd_bss_config *bss, + const char *name) +{ + size_t j; + struct hs20_icon *icon = NULL; + + for (j = 0; j < bss->hs20_icons_count && !icon; j++) { + if (os_strcmp(name, bss->hs20_icons[j].name) == 0) + icon = &bss->hs20_icons[j]; + } + if (!icon) + return; /* icon info not found */ + + wpabuf_put_le16(buf, icon->width); + wpabuf_put_le16(buf, icon->height); + wpabuf_put_data(buf, icon->language, 3); + wpabuf_put_u8(buf, os_strlen(icon->type)); + wpabuf_put_str(buf, icon->type); + wpabuf_put_u8(buf, os_strlen(icon->name)); + wpabuf_put_str(buf, icon->name); +} + + +static void anqp_add_osu_provider(struct wpabuf *buf, + struct hostapd_bss_config *bss, + struct hs20_osu_provider *p) +{ + u8 *len, *len2, *count; + unsigned int i; + + len = wpabuf_put(buf, 2); /* OSU Provider Length to be filled */ + + /* OSU Friendly Name Duples */ + len2 = wpabuf_put(buf, 2); + for (i = 0; i < p->friendly_name_count; i++) { + struct hostapd_lang_string *s = &p->friendly_name[i]; + wpabuf_put_u8(buf, 3 + s->name_len); + wpabuf_put_data(buf, s->lang, 3); + wpabuf_put_data(buf, s->name, s->name_len); + } + WPA_PUT_LE16(len2, (u8 *) wpabuf_put(buf, 0) - len2 - 2); + + /* OSU Server URI */ + if (p->server_uri) { + wpabuf_put_u8(buf, os_strlen(p->server_uri)); + wpabuf_put_str(buf, p->server_uri); + } else + wpabuf_put_u8(buf, 0); + + /* OSU Method List */ + count = wpabuf_put(buf, 1); + for (i = 0; p->method_list && p->method_list[i] >= 0; i++) + wpabuf_put_u8(buf, p->method_list[i]); + *count = i; + + /* Icons Available */ + len2 = wpabuf_put(buf, 2); + for (i = 0; i < p->icons_count; i++) + anqp_add_icon(buf, bss, p->icons[i]); + WPA_PUT_LE16(len2, (u8 *) wpabuf_put(buf, 0) - len2 - 2); + + /* OSU_NAI */ + if (p->osu_nai) { + wpabuf_put_u8(buf, os_strlen(p->osu_nai)); + wpabuf_put_str(buf, p->osu_nai); + } else + wpabuf_put_u8(buf, 0); + + /* OSU Service Description Duples */ + len2 = wpabuf_put(buf, 2); + for (i = 0; i < p->service_desc_count; i++) { + struct hostapd_lang_string *s = &p->service_desc[i]; + wpabuf_put_u8(buf, 3 + s->name_len); + wpabuf_put_data(buf, s->lang, 3); + wpabuf_put_data(buf, s->name, s->name_len); + } + WPA_PUT_LE16(len2, (u8 *) wpabuf_put(buf, 0) - len2 - 2); + + WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2); +} + + +static void anqp_add_osu_providers_list(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (hapd->conf->hs20_osu_providers_count) { + size_t i; + u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_LIST); + wpabuf_put_u8(buf, 0); /* Reserved */ + + /* OSU SSID */ + wpabuf_put_u8(buf, hapd->conf->osu_ssid_len); + wpabuf_put_data(buf, hapd->conf->osu_ssid, + hapd->conf->osu_ssid_len); + + /* Number of OSU Providers */ + wpabuf_put_u8(buf, hapd->conf->hs20_osu_providers_count); + + for (i = 0; i < hapd->conf->hs20_osu_providers_count; i++) { + anqp_add_osu_provider( + buf, hapd->conf, + &hapd->conf->hs20_osu_providers[i]); + } + + gas_anqp_set_element_len(buf, len); + } +} + + +static void anqp_add_osu_provider_nai(struct wpabuf *buf, + struct hs20_osu_provider *p) +{ + /* OSU_NAI for shared BSS (Single SSID) */ + if (p->osu_nai2) { + wpabuf_put_u8(buf, os_strlen(p->osu_nai2)); + wpabuf_put_str(buf, p->osu_nai2); + } else { + wpabuf_put_u8(buf, 0); + } +} + + +static void anqp_add_osu_providers_nai_list(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (hapd->conf->hs20_osu_providers_nai_count) { + size_t i; + u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_NAI_LIST); + wpabuf_put_u8(buf, 0); /* Reserved */ + + for (i = 0; i < hapd->conf->hs20_osu_providers_count; i++) { + anqp_add_osu_provider_nai( + buf, &hapd->conf->hs20_osu_providers[i]); + } + + gas_anqp_set_element_len(buf, len); + } +} + + +static void anqp_add_icon_binary_file(struct hostapd_data *hapd, + struct wpabuf *buf, + const u8 *name, size_t name_len) +{ + struct hs20_icon *icon; + size_t i; + u8 *len; + + wpa_hexdump_ascii(MSG_DEBUG, "HS 2.0: Requested Icon Filename", + name, name_len); + for (i = 0; i < hapd->conf->hs20_icons_count; i++) { + icon = &hapd->conf->hs20_icons[i]; + if (name_len == os_strlen(icon->name) && + os_memcmp(name, icon->name, name_len) == 0) + break; + } + + if (i < hapd->conf->hs20_icons_count) + icon = &hapd->conf->hs20_icons[i]; + else + icon = NULL; + + len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_ICON_BINARY_FILE); + wpabuf_put_u8(buf, 0); /* Reserved */ + + if (icon) { + char *data; + size_t data_len; + + data = os_readfile(icon->file, &data_len); + if (data == NULL || data_len > 65535) { + wpabuf_put_u8(buf, 2); /* Download Status: + * Unspecified file error */ + wpabuf_put_u8(buf, 0); + wpabuf_put_le16(buf, 0); + } else { + wpabuf_put_u8(buf, 0); /* Download Status: Success */ + wpabuf_put_u8(buf, os_strlen(icon->type)); + wpabuf_put_str(buf, icon->type); + wpabuf_put_le16(buf, data_len); + wpabuf_put_data(buf, data, data_len); + } + os_free(data); + } else { + wpabuf_put_u8(buf, 1); /* Download Status: File not found */ + wpabuf_put_u8(buf, 0); + wpabuf_put_le16(buf, 0); + } + + gas_anqp_set_element_len(buf, len); +} + + +static void anqp_add_operator_icon_metadata(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + struct hostapd_bss_config *bss = hapd->conf; + size_t i; + u8 *len; + + if (!bss->hs20_operator_icon_count) + return; + + len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_OPERATOR_ICON_METADATA); + wpabuf_put_u8(buf, 0); /* Reserved */ + + for (i = 0; i < bss->hs20_operator_icon_count; i++) + anqp_add_icon(buf, bss, bss->hs20_operator_icon[i]); + + gas_anqp_set_element_len(buf, len); +} + +#endif /* CONFIG_HS20 */ + + +#ifdef CONFIG_MBO +static void anqp_add_mbo_cell_data_conn_pref(struct hostapd_data *hapd, + struct wpabuf *buf) +{ + if (hapd->conf->mbo_cell_data_conn_pref >= 0) { + u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, MBO_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, MBO_ANQP_SUBTYPE_CELL_CONN_PREF); + wpabuf_put_u8(buf, hapd->conf->mbo_cell_data_conn_pref); + gas_anqp_set_element_len(buf, len); + } +} +#endif /* CONFIG_MBO */ + + +static size_t anqp_get_required_len(struct hostapd_data *hapd, + const u16 *infoid, + unsigned int num_infoid) +{ + size_t len = 0; + unsigned int i; + + for (i = 0; i < num_infoid; i++) { + struct anqp_element *elem = get_anqp_elem(hapd, infoid[i]); + + if (elem) + len += 2 + 2 + wpabuf_len(elem->payload); + } + + return len; +} + + +static struct wpabuf * +gas_serv_build_gas_resp_payload(struct hostapd_data *hapd, + unsigned int request, + const u8 *home_realm, size_t home_realm_len, + const u8 *icon_name, size_t icon_name_len, + const u16 *extra_req, + unsigned int num_extra_req) +{ + struct wpabuf *buf; + size_t len; + unsigned int i; + + len = 1400; + if (request & (ANQP_REQ_NAI_REALM | ANQP_REQ_NAI_HOME_REALM)) + len += 1000; + if (request & ANQP_REQ_ICON_REQUEST) + len += 65536; +#ifdef CONFIG_FILS + if (request & ANQP_FILS_REALM_INFO) + len += 2 * dl_list_len(&hapd->conf->fils_realms); +#endif /* CONFIG_FILS */ + len += anqp_get_required_len(hapd, extra_req, num_extra_req); + + buf = wpabuf_alloc(len); + if (buf == NULL) + return NULL; + + if (request & ANQP_REQ_CAPABILITY_LIST) + anqp_add_capab_list(hapd, buf); + if (request & ANQP_REQ_VENUE_NAME) + anqp_add_venue_name(hapd, buf); + if (request & ANQP_REQ_EMERGENCY_CALL_NUMBER) + anqp_add_elem(hapd, buf, ANQP_EMERGENCY_CALL_NUMBER); + if (request & ANQP_REQ_NETWORK_AUTH_TYPE) + anqp_add_network_auth_type(hapd, buf); + if (request & ANQP_REQ_ROAMING_CONSORTIUM) + anqp_add_roaming_consortium(hapd, buf); + if (request & ANQP_REQ_IP_ADDR_TYPE_AVAILABILITY) + anqp_add_ip_addr_type_availability(hapd, buf); + if (request & (ANQP_REQ_NAI_REALM | ANQP_REQ_NAI_HOME_REALM)) + anqp_add_nai_realm(hapd, buf, home_realm, home_realm_len, + request & ANQP_REQ_NAI_REALM, + request & ANQP_REQ_NAI_HOME_REALM); + if (request & ANQP_REQ_3GPP_CELLULAR_NETWORK) + anqp_add_3gpp_cellular_network(hapd, buf); + if (request & ANQP_REQ_AP_GEOSPATIAL_LOCATION) + anqp_add_elem(hapd, buf, ANQP_AP_GEOSPATIAL_LOCATION); + if (request & ANQP_REQ_AP_CIVIC_LOCATION) + anqp_add_elem(hapd, buf, ANQP_AP_CIVIC_LOCATION); + if (request & ANQP_REQ_AP_LOCATION_PUBLIC_URI) + anqp_add_elem(hapd, buf, ANQP_AP_LOCATION_PUBLIC_URI); + if (request & ANQP_REQ_DOMAIN_NAME) + anqp_add_domain_name(hapd, buf); + if (request & ANQP_REQ_EMERGENCY_ALERT_URI) + anqp_add_elem(hapd, buf, ANQP_EMERGENCY_ALERT_URI); + if (request & ANQP_REQ_TDLS_CAPABILITY) + anqp_add_elem(hapd, buf, ANQP_TDLS_CAPABILITY); + if (request & ANQP_REQ_EMERGENCY_NAI) + anqp_add_elem(hapd, buf, ANQP_EMERGENCY_NAI); + + for (i = 0; i < num_extra_req; i++) { +#ifdef CONFIG_FILS + if (extra_req[i] == ANQP_FILS_REALM_INFO) { + anqp_add_fils_realm_info(hapd, buf); + continue; + } +#endif /* CONFIG_FILS */ + if (extra_req[i] == ANQP_VENUE_URL) { + anqp_add_venue_url(hapd, buf); + continue; + } + anqp_add_elem(hapd, buf, extra_req[i]); + } + +#ifdef CONFIG_HS20 + if (request & ANQP_REQ_HS_CAPABILITY_LIST) + anqp_add_hs_capab_list(hapd, buf); + if (request & ANQP_REQ_OPERATOR_FRIENDLY_NAME) + anqp_add_operator_friendly_name(hapd, buf); + if (request & ANQP_REQ_WAN_METRICS) + anqp_add_wan_metrics(hapd, buf); + if (request & ANQP_REQ_CONNECTION_CAPABILITY) + anqp_add_connection_capability(hapd, buf); + if (request & ANQP_REQ_OPERATING_CLASS) + anqp_add_operating_class(hapd, buf); + if (request & ANQP_REQ_OSU_PROVIDERS_LIST) + anqp_add_osu_providers_list(hapd, buf); + if (request & ANQP_REQ_ICON_REQUEST) + anqp_add_icon_binary_file(hapd, buf, icon_name, icon_name_len); + if (request & ANQP_REQ_OPERATOR_ICON_METADATA) + anqp_add_operator_icon_metadata(hapd, buf); + if (request & ANQP_REQ_OSU_PROVIDERS_NAI_LIST) + anqp_add_osu_providers_nai_list(hapd, buf); +#endif /* CONFIG_HS20 */ + +#ifdef CONFIG_MBO + if (request & ANQP_REQ_MBO_CELL_DATA_CONN_PREF) + anqp_add_mbo_cell_data_conn_pref(hapd, buf); +#endif /* CONFIG_MBO */ + + return buf; +} + + +#define ANQP_MAX_EXTRA_REQ 20 + +struct anqp_query_info { + unsigned int request; + const u8 *home_realm_query; + size_t home_realm_query_len; + const u8 *icon_name; + size_t icon_name_len; + int p2p_sd; + u16 extra_req[ANQP_MAX_EXTRA_REQ]; + unsigned int num_extra_req; +}; + + +static void set_anqp_req(unsigned int bit, const char *name, int local, + struct anqp_query_info *qi) +{ + qi->request |= bit; + if (local) { + wpa_printf(MSG_DEBUG, "ANQP: %s (local)", name); + } else { + wpa_printf(MSG_DEBUG, "ANQP: %s not available", name); + } +} + + +static void rx_anqp_query_list_id(struct hostapd_data *hapd, u16 info_id, + struct anqp_query_info *qi) +{ + switch (info_id) { + case ANQP_CAPABILITY_LIST: + set_anqp_req(ANQP_REQ_CAPABILITY_LIST, "Capability List", 1, + qi); + break; + case ANQP_VENUE_NAME: + set_anqp_req(ANQP_REQ_VENUE_NAME, "Venue Name", + hapd->conf->venue_name != NULL, qi); + break; + case ANQP_EMERGENCY_CALL_NUMBER: + set_anqp_req(ANQP_REQ_EMERGENCY_CALL_NUMBER, + "Emergency Call Number", + get_anqp_elem(hapd, info_id) != NULL, qi); + break; + case ANQP_NETWORK_AUTH_TYPE: + set_anqp_req(ANQP_REQ_NETWORK_AUTH_TYPE, "Network Auth Type", + hapd->conf->network_auth_type != NULL, qi); + break; + case ANQP_ROAMING_CONSORTIUM: + set_anqp_req(ANQP_REQ_ROAMING_CONSORTIUM, "Roaming Consortium", + hapd->conf->roaming_consortium != NULL, qi); + break; + case ANQP_IP_ADDR_TYPE_AVAILABILITY: + set_anqp_req(ANQP_REQ_IP_ADDR_TYPE_AVAILABILITY, + "IP Addr Type Availability", + hapd->conf->ipaddr_type_configured, qi); + break; + case ANQP_NAI_REALM: + set_anqp_req(ANQP_REQ_NAI_REALM, "NAI Realm", + hapd->conf->nai_realm_data != NULL, qi); + break; + case ANQP_3GPP_CELLULAR_NETWORK: + set_anqp_req(ANQP_REQ_3GPP_CELLULAR_NETWORK, + "3GPP Cellular Network", + hapd->conf->anqp_3gpp_cell_net != NULL, qi); + break; + case ANQP_AP_GEOSPATIAL_LOCATION: + set_anqp_req(ANQP_REQ_AP_GEOSPATIAL_LOCATION, + "AP Geospatial Location", + get_anqp_elem(hapd, info_id) != NULL, qi); + break; + case ANQP_AP_CIVIC_LOCATION: + set_anqp_req(ANQP_REQ_AP_CIVIC_LOCATION, + "AP Civic Location", + get_anqp_elem(hapd, info_id) != NULL, qi); + break; + case ANQP_AP_LOCATION_PUBLIC_URI: + set_anqp_req(ANQP_REQ_AP_LOCATION_PUBLIC_URI, + "AP Location Public URI", + get_anqp_elem(hapd, info_id) != NULL, qi); + break; + case ANQP_DOMAIN_NAME: + set_anqp_req(ANQP_REQ_DOMAIN_NAME, "Domain Name", + hapd->conf->domain_name != NULL, qi); + break; + case ANQP_EMERGENCY_ALERT_URI: + set_anqp_req(ANQP_REQ_EMERGENCY_ALERT_URI, + "Emergency Alert URI", + get_anqp_elem(hapd, info_id) != NULL, qi); + break; + case ANQP_TDLS_CAPABILITY: + set_anqp_req(ANQP_REQ_TDLS_CAPABILITY, + "TDLS Capability", + get_anqp_elem(hapd, info_id) != NULL, qi); + break; + case ANQP_EMERGENCY_NAI: + set_anqp_req(ANQP_REQ_EMERGENCY_NAI, + "Emergency NAI", + get_anqp_elem(hapd, info_id) != NULL, qi); + break; + default: +#ifdef CONFIG_FILS + if (info_id == ANQP_FILS_REALM_INFO && + !dl_list_empty(&hapd->conf->fils_realms)) { + wpa_printf(MSG_DEBUG, + "ANQP: FILS Realm Information (local)"); + } else +#endif /* CONFIG_FILS */ + if (info_id == ANQP_VENUE_URL && hapd->conf->venue_url) { + wpa_printf(MSG_DEBUG, + "ANQP: Venue URL (local)"); + } else if (!get_anqp_elem(hapd, info_id)) { + wpa_printf(MSG_DEBUG, "ANQP: Unsupported Info Id %u", + info_id); + break; + } + if (qi->num_extra_req == ANQP_MAX_EXTRA_REQ) { + wpa_printf(MSG_DEBUG, + "ANQP: No more room for extra requests - ignore Info Id %u", + info_id); + break; + } + wpa_printf(MSG_DEBUG, "ANQP: Info Id %u (local)", info_id); + qi->extra_req[qi->num_extra_req] = info_id; + qi->num_extra_req++; + break; + } +} + + +static void rx_anqp_query_list(struct hostapd_data *hapd, + const u8 *pos, const u8 *end, + struct anqp_query_info *qi) +{ + wpa_printf(MSG_DEBUG, "ANQP: %u Info IDs requested in Query list", + (unsigned int) (end - pos) / 2); + + while (end - pos >= 2) { + rx_anqp_query_list_id(hapd, WPA_GET_LE16(pos), qi); + pos += 2; + } +} + + +#ifdef CONFIG_HS20 + +static void rx_anqp_hs_query_list(struct hostapd_data *hapd, u8 subtype, + struct anqp_query_info *qi) +{ + switch (subtype) { + case HS20_STYPE_CAPABILITY_LIST: + set_anqp_req(ANQP_REQ_HS_CAPABILITY_LIST, "HS Capability List", + 1, qi); + break; + case HS20_STYPE_OPERATOR_FRIENDLY_NAME: + set_anqp_req(ANQP_REQ_OPERATOR_FRIENDLY_NAME, + "Operator Friendly Name", + hapd->conf->hs20_oper_friendly_name != NULL, qi); + break; + case HS20_STYPE_WAN_METRICS: + set_anqp_req(ANQP_REQ_WAN_METRICS, "WAN Metrics", + hapd->conf->hs20_wan_metrics != NULL, qi); + break; + case HS20_STYPE_CONNECTION_CAPABILITY: + set_anqp_req(ANQP_REQ_CONNECTION_CAPABILITY, + "Connection Capability", + hapd->conf->hs20_connection_capability != NULL, + qi); + break; + case HS20_STYPE_OPERATING_CLASS: + set_anqp_req(ANQP_REQ_OPERATING_CLASS, "Operating Class", + hapd->conf->hs20_operating_class != NULL, qi); + break; + case HS20_STYPE_OSU_PROVIDERS_LIST: + set_anqp_req(ANQP_REQ_OSU_PROVIDERS_LIST, "OSU Providers list", + hapd->conf->hs20_osu_providers_count, qi); + break; + case HS20_STYPE_OPERATOR_ICON_METADATA: + set_anqp_req(ANQP_REQ_OPERATOR_ICON_METADATA, + "Operator Icon Metadata", + hapd->conf->hs20_operator_icon_count, qi); + break; + case HS20_STYPE_OSU_PROVIDERS_NAI_LIST: + set_anqp_req(ANQP_REQ_OSU_PROVIDERS_NAI_LIST, + "OSU Providers NAI List", + hapd->conf->hs20_osu_providers_nai_count, qi); + break; + default: + wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 subtype %u", + subtype); + break; + } +} + + +static void rx_anqp_hs_nai_home_realm(struct hostapd_data *hapd, + const u8 *pos, const u8 *end, + struct anqp_query_info *qi) +{ + qi->request |= ANQP_REQ_NAI_HOME_REALM; + qi->home_realm_query = pos; + qi->home_realm_query_len = end - pos; + if (hapd->conf->nai_realm_data != NULL) { + wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 NAI Home Realm Query " + "(local)"); + } else { + wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 NAI Home Realm Query not " + "available"); + } +} + + +static void rx_anqp_hs_icon_request(struct hostapd_data *hapd, + const u8 *pos, const u8 *end, + struct anqp_query_info *qi) +{ + qi->request |= ANQP_REQ_ICON_REQUEST; + qi->icon_name = pos; + qi->icon_name_len = end - pos; + if (hapd->conf->hs20_icons_count) { + wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Icon Request Query " + "(local)"); + } else { + wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Icon Request Query not " + "available"); + } +} + + +static void rx_anqp_vendor_specific_hs20(struct hostapd_data *hapd, + const u8 *pos, const u8 *end, + struct anqp_query_info *qi) +{ + u8 subtype; + + if (end - pos <= 1) + return; + + subtype = *pos++; + pos++; /* Reserved */ + switch (subtype) { + case HS20_STYPE_QUERY_LIST: + wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Query List"); + while (pos < end) { + rx_anqp_hs_query_list(hapd, *pos, qi); + pos++; + } + break; + case HS20_STYPE_NAI_HOME_REALM_QUERY: + rx_anqp_hs_nai_home_realm(hapd, pos, end, qi); + break; + case HS20_STYPE_ICON_REQUEST: + rx_anqp_hs_icon_request(hapd, pos, end, qi); + break; + default: + wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 query subtype " + "%u", subtype); + break; + } +} + +#endif /* CONFIG_HS20 */ + + +#ifdef CONFIG_P2P +static void rx_anqp_vendor_specific_p2p(struct hostapd_data *hapd, + struct anqp_query_info *qi) +{ + /* + * This is for P2P SD and will be taken care of by the P2P + * implementation. This query needs to be ignored in the generic + * GAS server to avoid duplicated response. + */ + wpa_printf(MSG_DEBUG, + "ANQP: Ignore WFA vendor type %u (P2P SD) in generic GAS server", + P2P_OUI_TYPE); + qi->p2p_sd = 1; + return; +} +#endif /* CONFIG_P2P */ + + +#ifdef CONFIG_MBO + +static void rx_anqp_mbo_query_list(struct hostapd_data *hapd, u8 subtype, + struct anqp_query_info *qi) +{ + switch (subtype) { + case MBO_ANQP_SUBTYPE_CELL_CONN_PREF: + set_anqp_req(ANQP_REQ_MBO_CELL_DATA_CONN_PREF, + "Cellular Data Connection Preference", + hapd->conf->mbo_cell_data_conn_pref >= 0, qi); + break; + default: + wpa_printf(MSG_DEBUG, "ANQP: Unsupported MBO subtype %u", + subtype); + break; + } +} + + +static void rx_anqp_vendor_specific_mbo(struct hostapd_data *hapd, + const u8 *pos, const u8 *end, + struct anqp_query_info *qi) +{ + u8 subtype; + + if (end - pos < 1) + return; + + subtype = *pos++; + switch (subtype) { + case MBO_ANQP_SUBTYPE_QUERY_LIST: + wpa_printf(MSG_DEBUG, "ANQP: MBO Query List"); + while (pos < end) { + rx_anqp_mbo_query_list(hapd, *pos, qi); + pos++; + } + break; + default: + wpa_printf(MSG_DEBUG, "ANQP: Unsupported MBO query subtype %u", + subtype); + break; + } +} + +#endif /* CONFIG_MBO */ + + +static void rx_anqp_vendor_specific(struct hostapd_data *hapd, + const u8 *pos, const u8 *end, + struct anqp_query_info *qi) +{ + u32 oui; + + if (end - pos < 4) { + wpa_printf(MSG_DEBUG, "ANQP: Too short vendor specific ANQP " + "Query element"); + return; + } + + oui = WPA_GET_BE24(pos); + pos += 3; + if (oui != OUI_WFA) { + wpa_printf(MSG_DEBUG, "ANQP: Unsupported vendor OUI %06x", + oui); + return; + } + + switch (*pos) { +#ifdef CONFIG_P2P + case P2P_OUI_TYPE: + rx_anqp_vendor_specific_p2p(hapd, qi); + break; +#endif /* CONFIG_P2P */ +#ifdef CONFIG_HS20 + case HS20_ANQP_OUI_TYPE: + rx_anqp_vendor_specific_hs20(hapd, pos + 1, end, qi); + break; +#endif /* CONFIG_HS20 */ +#ifdef CONFIG_MBO + case MBO_ANQP_OUI_TYPE: + rx_anqp_vendor_specific_mbo(hapd, pos + 1, end, qi); + break; +#endif /* CONFIG_MBO */ + default: + wpa_printf(MSG_DEBUG, "ANQP: Unsupported WFA vendor type %u", + *pos); + break; + } +} + + +static void gas_serv_req_local_processing(struct hostapd_data *hapd, + const u8 *sa, u8 dialog_token, + struct anqp_query_info *qi, int prot, + int std_addr3) +{ + struct wpabuf *buf, *tx_buf; + + buf = gas_serv_build_gas_resp_payload(hapd, qi->request, + qi->home_realm_query, + qi->home_realm_query_len, + qi->icon_name, qi->icon_name_len, + qi->extra_req, qi->num_extra_req); + wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Locally generated ANQP responses", + buf); + if (!buf) + return; +#ifdef CONFIG_P2P + if (wpabuf_len(buf) == 0 && qi->p2p_sd) { + wpa_printf(MSG_DEBUG, + "ANQP: Do not send response to P2P SD from generic GAS service (P2P SD implementation will process this)"); + wpabuf_free(buf); + return; + } +#endif /* CONFIG_P2P */ + + if (wpabuf_len(buf) > hapd->conf->gas_frag_limit || + hapd->conf->gas_comeback_delay) { + struct gas_dialog_info *di; + u16 comeback_delay = 1; + + if (hapd->conf->gas_comeback_delay) { + /* Testing - allow overriding of the delay value */ + comeback_delay = hapd->conf->gas_comeback_delay; + } + + wpa_printf(MSG_DEBUG, "ANQP: Too long response to fit in " + "initial response - use GAS comeback"); + di = gas_dialog_create(hapd, sa, dialog_token); + if (!di) { + wpa_printf(MSG_INFO, "ANQP: Could not create dialog " + "for " MACSTR " (dialog token %u)", + MAC2STR(sa), dialog_token); + wpabuf_free(buf); + tx_buf = gas_anqp_build_initial_resp_buf( + dialog_token, WLAN_STATUS_UNSPECIFIED_FAILURE, + 0, NULL); + } else { + di->prot = prot; + di->sd_resp = buf; + di->sd_resp_pos = 0; + tx_buf = gas_anqp_build_initial_resp_buf( + dialog_token, WLAN_STATUS_SUCCESS, + comeback_delay, NULL); + } + } else { + wpa_printf(MSG_DEBUG, "ANQP: Initial response (no comeback)"); + tx_buf = gas_anqp_build_initial_resp_buf( + dialog_token, WLAN_STATUS_SUCCESS, 0, buf); + wpabuf_free(buf); + } + if (!tx_buf) + return; + if (prot) + convert_to_protected_dual(tx_buf); + if (std_addr3) + hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa, + wpabuf_head(tx_buf), + wpabuf_len(tx_buf)); + else + hostapd_drv_send_action_addr3_ap(hapd, hapd->iface->freq, 0, sa, + wpabuf_head(tx_buf), + wpabuf_len(tx_buf)); + wpabuf_free(tx_buf); +} + + +#ifdef CONFIG_DPP +void gas_serv_req_dpp_processing(struct hostapd_data *hapd, + const u8 *sa, u8 dialog_token, + int prot, struct wpabuf *buf, int freq) +{ + struct wpabuf *tx_buf; + + if (wpabuf_len(buf) > hapd->conf->gas_frag_limit || + hapd->conf->gas_comeback_delay) { + struct gas_dialog_info *di; + u16 comeback_delay = 1; + + if (hapd->conf->gas_comeback_delay) { + /* Testing - allow overriding of the delay value */ + comeback_delay = hapd->conf->gas_comeback_delay; + } + + wpa_printf(MSG_DEBUG, + "DPP: Too long response to fit in initial response - use GAS comeback"); + di = gas_dialog_create(hapd, sa, dialog_token); + if (!di) { + wpa_printf(MSG_INFO, "DPP: Could not create dialog for " + MACSTR " (dialog token %u)", + MAC2STR(sa), dialog_token); + wpabuf_free(buf); + tx_buf = gas_build_initial_resp( + dialog_token, WLAN_STATUS_UNSPECIFIED_FAILURE, + 0, 10); + if (tx_buf) + gas_serv_write_dpp_adv_proto(tx_buf); + } else { + di->prot = prot; + di->sd_resp = buf; + di->sd_resp_pos = 0; + di->dpp = 1; + tx_buf = gas_build_initial_resp( + dialog_token, WLAN_STATUS_SUCCESS, + comeback_delay, 10 + 2); + if (tx_buf) { + gas_serv_write_dpp_adv_proto(tx_buf); + wpabuf_put_le16(tx_buf, 0); + } + } + } else { + wpa_printf(MSG_DEBUG, + "DPP: GAS Initial response (no comeback)"); + tx_buf = gas_build_initial_resp( + dialog_token, WLAN_STATUS_SUCCESS, 0, + 10 + 2 + wpabuf_len(buf)); + if (tx_buf) { + gas_serv_write_dpp_adv_proto(tx_buf); + wpabuf_put_le16(tx_buf, wpabuf_len(buf)); + wpabuf_put_buf(tx_buf, buf); + hostapd_dpp_gas_status_handler(hapd, 1); + } + wpabuf_free(buf); + } + if (!tx_buf) + return; + if (prot) + convert_to_protected_dual(tx_buf); + hostapd_drv_send_action(hapd, freq ? freq : hapd->iface->freq, 0, sa, + wpabuf_head(tx_buf), + wpabuf_len(tx_buf)); + wpabuf_free(tx_buf); +} +#endif /* CONFIG_DPP */ + + +static void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd, + const u8 *sa, + const u8 *data, size_t len, int prot, + int std_addr3, int freq) +{ + const u8 *pos = data; + const u8 *end = data + len; + const u8 *next; + u8 dialog_token; + u16 slen; + struct anqp_query_info qi; + const u8 *adv_proto; +#ifdef CONFIG_DPP + int dpp = 0; +#endif /* CONFIG_DPP */ + + if (len < 1 + 2) + return; + + os_memset(&qi, 0, sizeof(qi)); + + dialog_token = *pos++; + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "GAS: GAS Initial Request from " MACSTR " (dialog token %u) ", + MAC2STR(sa), dialog_token); + + if (*pos != WLAN_EID_ADV_PROTO) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "GAS: Unexpected IE in GAS Initial Request: %u", *pos); + return; + } + adv_proto = pos++; + + slen = *pos++; + if (slen > end - pos || slen < 2) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "GAS: Invalid IE in GAS Initial Request"); + return; + } + next = pos + slen; + pos++; /* skip QueryRespLenLimit and PAME-BI */ + +#ifdef CONFIG_DPP + if (slen == 8 && *pos == WLAN_EID_VENDOR_SPECIFIC && + pos[1] == 5 && WPA_GET_BE24(&pos[2]) == OUI_WFA && + pos[5] == DPP_OUI_TYPE && pos[6] == 0x01) { + wpa_printf(MSG_DEBUG, "DPP: Configuration Request"); + dpp = 1; + } else +#endif /* CONFIG_DPP */ + + if (*pos != ACCESS_NETWORK_QUERY_PROTOCOL) { + struct wpabuf *buf; + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "GAS: Unsupported GAS advertisement protocol id %u", + *pos); + if (sa[0] & 0x01) + return; /* Invalid source address - drop silently */ + buf = gas_build_initial_resp( + dialog_token, WLAN_STATUS_GAS_ADV_PROTO_NOT_SUPPORTED, + 0, 2 + slen + 2); + if (buf == NULL) + return; + wpabuf_put_data(buf, adv_proto, 2 + slen); + wpabuf_put_le16(buf, 0); /* Query Response Length */ + if (prot) + convert_to_protected_dual(buf); + if (std_addr3) + hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa, + wpabuf_head(buf), + wpabuf_len(buf)); + else + hostapd_drv_send_action_addr3_ap(hapd, + hapd->iface->freq, 0, + sa, wpabuf_head(buf), + wpabuf_len(buf)); + wpabuf_free(buf); + return; + } + + pos = next; + /* Query Request */ + if (end - pos < 2) + return; + slen = WPA_GET_LE16(pos); + pos += 2; + if (slen > end - pos) + return; + end = pos + slen; + +#ifdef CONFIG_DPP + if (dpp) { + struct wpabuf *msg; + + msg = hostapd_dpp_gas_req_handler(hapd, sa, pos, slen, + data, len); + if (!msg) + return; + gas_serv_req_dpp_processing(hapd, sa, dialog_token, prot, msg, + freq); + return; + } +#endif /* CONFIG_DPP */ + + /* ANQP Query Request */ + while (pos < end) { + u16 info_id, elen; + + if (end - pos < 4) + return; + + info_id = WPA_GET_LE16(pos); + pos += 2; + elen = WPA_GET_LE16(pos); + pos += 2; + + if (elen > end - pos) { + wpa_printf(MSG_DEBUG, "ANQP: Invalid Query Request"); + return; + } + + switch (info_id) { + case ANQP_QUERY_LIST: + rx_anqp_query_list(hapd, pos, pos + elen, &qi); + break; + case ANQP_VENDOR_SPECIFIC: + rx_anqp_vendor_specific(hapd, pos, pos + elen, &qi); + break; + default: + wpa_printf(MSG_DEBUG, "ANQP: Unsupported Query " + "Request element %u", info_id); + break; + } + + pos += elen; + } + + gas_serv_req_local_processing(hapd, sa, dialog_token, &qi, prot, + std_addr3); +} + + +static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd, + const u8 *sa, + const u8 *data, size_t len, int prot, + int std_addr3) +{ + struct gas_dialog_info *dialog; + struct wpabuf *buf, *tx_buf; + u8 dialog_token; + size_t frag_len; + int more = 0; + + wpa_hexdump(MSG_DEBUG, "GAS: RX GAS Comeback Request", data, len); + if (len < 1) + return; + dialog_token = *data; + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Dialog Token: %u", + dialog_token); + + dialog = gas_serv_dialog_find(hapd, sa, dialog_token); + if (!dialog) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: No pending SD " + "response fragment for " MACSTR " dialog token %u", + MAC2STR(sa), dialog_token); + + if (sa[0] & 0x01) + return; /* Invalid source address - drop silently */ + tx_buf = gas_anqp_build_comeback_resp_buf( + dialog_token, WLAN_STATUS_NO_OUTSTANDING_GAS_REQ, 0, 0, + 0, NULL); + if (tx_buf == NULL) + return; + goto send_resp; + } + + frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos; + if (frag_len > hapd->conf->gas_frag_limit) { + frag_len = hapd->conf->gas_frag_limit; + more = 1; + } + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: resp frag_len %u", + (unsigned int) frag_len); + buf = wpabuf_alloc_copy(wpabuf_head_u8(dialog->sd_resp) + + dialog->sd_resp_pos, frag_len); + if (buf == NULL) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Failed to allocate " + "buffer"); + gas_serv_dialog_clear(dialog); + return; + } +#ifdef CONFIG_DPP + if (dialog->dpp) { + tx_buf = gas_build_comeback_resp(dialog_token, + WLAN_STATUS_SUCCESS, + dialog->sd_frag_id, more, 0, + 10 + 2 + frag_len); + if (tx_buf) { + gas_serv_write_dpp_adv_proto(tx_buf); + wpabuf_put_le16(tx_buf, frag_len); + wpabuf_put_buf(tx_buf, buf); + } + } else +#endif /* CONFIG_DPP */ + tx_buf = gas_anqp_build_comeback_resp_buf(dialog_token, + WLAN_STATUS_SUCCESS, + dialog->sd_frag_id, + more, 0, buf); + wpabuf_free(buf); + if (tx_buf == NULL) { + gas_serv_dialog_clear(dialog); + return; + } + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Tx GAS Comeback Response " + "(frag_id %d more=%d frag_len=%d)", + dialog->sd_frag_id, more, (int) frag_len); + dialog->sd_frag_id++; + dialog->sd_resp_pos += frag_len; + + if (more) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: %d more bytes remain " + "to be sent", + (int) (wpabuf_len(dialog->sd_resp) - + dialog->sd_resp_pos)); + } else { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: All fragments of " + "SD response sent"); +#ifdef CONFIG_DPP + if (dialog->dpp) + hostapd_dpp_gas_status_handler(hapd, 1); +#endif /* CONFIG_DPP */ + gas_serv_dialog_clear(dialog); + gas_serv_free_dialogs(hapd, sa); + } + +send_resp: + if (prot) + convert_to_protected_dual(tx_buf); + if (std_addr3) + hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa, + wpabuf_head(tx_buf), + wpabuf_len(tx_buf)); + else + hostapd_drv_send_action_addr3_ap(hapd, hapd->iface->freq, 0, sa, + wpabuf_head(tx_buf), + wpabuf_len(tx_buf)); + wpabuf_free(tx_buf); +} + + +static void gas_serv_rx_public_action(void *ctx, const u8 *buf, size_t len, + int freq) +{ + struct hostapd_data *hapd = ctx; + const struct ieee80211_mgmt *mgmt; + const u8 *sa, *data; + int prot, std_addr3; + + mgmt = (const struct ieee80211_mgmt *) buf; + if (len < IEEE80211_HDRLEN + 2) + return; + if (mgmt->u.action.category != WLAN_ACTION_PUBLIC && + mgmt->u.action.category != WLAN_ACTION_PROTECTED_DUAL) + return; + /* + * Note: Public Action and Protected Dual of Public Action frames share + * the same payload structure, so it is fine to use definitions of + * Public Action frames to process both. + */ + prot = mgmt->u.action.category == WLAN_ACTION_PROTECTED_DUAL; + sa = mgmt->sa; + if (hapd->conf->gas_address3 == 1) + std_addr3 = 1; + else if (hapd->conf->gas_address3 == 2) + std_addr3 = 0; + else + std_addr3 = is_broadcast_ether_addr(mgmt->bssid); + len -= IEEE80211_HDRLEN + 1; + data = buf + IEEE80211_HDRLEN + 1; + switch (data[0]) { + case WLAN_PA_GAS_INITIAL_REQ: + gas_serv_rx_gas_initial_req(hapd, sa, data + 1, len - 1, prot, + std_addr3, freq); + break; + case WLAN_PA_GAS_COMEBACK_REQ: + gas_serv_rx_gas_comeback_req(hapd, sa, data + 1, len - 1, prot, + std_addr3); + break; + } +} + + +int gas_serv_init(struct hostapd_data *hapd) +{ + hapd->public_action_cb2 = gas_serv_rx_public_action; + hapd->public_action_cb2_ctx = hapd; + return 0; +} + + +void gas_serv_deinit(struct hostapd_data *hapd) +{ +} diff --git a/src/ap/gas_serv.h b/src/ap/gas_serv.h new file mode 100644 index 0000000..7646a98 --- /dev/null +++ b/src/ap/gas_serv.h @@ -0,0 +1,95 @@ +/* + * Generic advertisement service (GAS) server + * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef GAS_SERV_H +#define GAS_SERV_H + +/* First 16 ANQP InfoIDs can be included in the optimized bitmap */ +#define ANQP_REQ_CAPABILITY_LIST \ + (1 << (ANQP_CAPABILITY_LIST - ANQP_QUERY_LIST)) +#define ANQP_REQ_VENUE_NAME \ + (1 << (ANQP_VENUE_NAME - ANQP_QUERY_LIST)) +#define ANQP_REQ_EMERGENCY_CALL_NUMBER \ + (1 << (ANQP_EMERGENCY_CALL_NUMBER - ANQP_QUERY_LIST)) +#define ANQP_REQ_NETWORK_AUTH_TYPE \ + (1 << (ANQP_NETWORK_AUTH_TYPE - ANQP_QUERY_LIST)) +#define ANQP_REQ_ROAMING_CONSORTIUM \ + (1 << (ANQP_ROAMING_CONSORTIUM - ANQP_QUERY_LIST)) +#define ANQP_REQ_IP_ADDR_TYPE_AVAILABILITY \ + (1 << (ANQP_IP_ADDR_TYPE_AVAILABILITY - ANQP_QUERY_LIST)) +#define ANQP_REQ_NAI_REALM \ + (1 << (ANQP_NAI_REALM - ANQP_QUERY_LIST)) +#define ANQP_REQ_3GPP_CELLULAR_NETWORK \ + (1 << (ANQP_3GPP_CELLULAR_NETWORK - ANQP_QUERY_LIST)) +#define ANQP_REQ_AP_GEOSPATIAL_LOCATION \ + (1 << (ANQP_AP_GEOSPATIAL_LOCATION - ANQP_QUERY_LIST)) +#define ANQP_REQ_AP_CIVIC_LOCATION \ + (1 << (ANQP_AP_CIVIC_LOCATION - ANQP_QUERY_LIST)) +#define ANQP_REQ_AP_LOCATION_PUBLIC_URI \ + (1 << (ANQP_AP_LOCATION_PUBLIC_URI - ANQP_QUERY_LIST)) +#define ANQP_REQ_DOMAIN_NAME \ + (1 << (ANQP_DOMAIN_NAME - ANQP_QUERY_LIST)) +#define ANQP_REQ_EMERGENCY_ALERT_URI \ + (1 << (ANQP_EMERGENCY_ALERT_URI - ANQP_QUERY_LIST)) +#define ANQP_REQ_TDLS_CAPABILITY \ + (1 << (ANQP_TDLS_CAPABILITY - ANQP_QUERY_LIST)) +#define ANQP_REQ_EMERGENCY_NAI \ + (1 << (ANQP_EMERGENCY_NAI - ANQP_QUERY_LIST)) +/* + * First 15 Hotspot 2.0 vendor specific ANQP-elements can be included in the + * optimized bitmap. + */ +#define ANQP_REQ_HS_CAPABILITY_LIST \ + (0x10000 << HS20_STYPE_CAPABILITY_LIST) +#define ANQP_REQ_OPERATOR_FRIENDLY_NAME \ + (0x10000 << HS20_STYPE_OPERATOR_FRIENDLY_NAME) +#define ANQP_REQ_WAN_METRICS \ + (0x10000 << HS20_STYPE_WAN_METRICS) +#define ANQP_REQ_CONNECTION_CAPABILITY \ + (0x10000 << HS20_STYPE_CONNECTION_CAPABILITY) +#define ANQP_REQ_NAI_HOME_REALM \ + (0x10000 << HS20_STYPE_NAI_HOME_REALM_QUERY) +#define ANQP_REQ_OPERATING_CLASS \ + (0x10000 << HS20_STYPE_OPERATING_CLASS) +#define ANQP_REQ_OSU_PROVIDERS_LIST \ + (0x10000 << HS20_STYPE_OSU_PROVIDERS_LIST) +#define ANQP_REQ_ICON_REQUEST \ + (0x10000 << HS20_STYPE_ICON_REQUEST) +#define ANQP_REQ_OPERATOR_ICON_METADATA \ + (0x10000 << HS20_STYPE_OPERATOR_ICON_METADATA) +#define ANQP_REQ_OSU_PROVIDERS_NAI_LIST \ + (0x10000 << HS20_STYPE_OSU_PROVIDERS_NAI_LIST) +/* The first MBO ANQP-element can be included in the optimized bitmap. */ +#define ANQP_REQ_MBO_CELL_DATA_CONN_PREF \ + (BIT(29) << MBO_ANQP_SUBTYPE_CELL_CONN_PREF) + +struct gas_dialog_info { + u8 valid; + struct wpabuf *sd_resp; /* Fragmented response */ + u8 dialog_token; + size_t sd_resp_pos; /* Offset in sd_resp */ + u8 sd_frag_id; + int prot; /* whether Protected Dual of Public Action frame is used */ + int dpp; /* whether this is a DPP Config Response */ +}; + +struct hostapd_data; + +struct gas_dialog_info * +gas_serv_dialog_find(struct hostapd_data *hapd, const u8 *addr, + u8 dialog_token); +void gas_serv_dialog_clear(struct gas_dialog_info *dialog); + +int gas_serv_init(struct hostapd_data *hapd); +void gas_serv_deinit(struct hostapd_data *hapd); + +void gas_serv_req_dpp_processing(struct hostapd_data *hapd, + const u8 *sa, u8 dialog_token, + int prot, struct wpabuf *buf, int freq); + +#endif /* GAS_SERV_H */ diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c new file mode 100644 index 0000000..a05de03 --- /dev/null +++ b/src/ap/hostapd.c @@ -0,0 +1,4951 @@ +/* + * hostapd / Initialization and configuration + * Copyright (c) 2002-2021, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#ifdef CONFIG_SQLITE +#include +#endif /* CONFIG_SQLITE */ + +#include "utils/common.h" +#include "utils/eloop.h" +#include "utils/crc32.h" +#include "common/ieee802_11_defs.h" +#include "common/wpa_ctrl.h" +#include "common/hw_features_common.h" +#include "radius/radius_client.h" +#include "radius/radius_das.h" +#include "eap_server/tncs.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "eapol_auth/eapol_auth_sm_i.h" +#include "fst/fst.h" +#include "hostapd.h" +#include "authsrv.h" +#include "sta_info.h" +#include "accounting.h" +#include "ap_list.h" +#include "beacon.h" +#include "ieee802_1x.h" +#include "ieee802_11_auth.h" +#include "vlan_init.h" +#include "wpa_auth.h" +#include "wps_hostapd.h" +#include "dpp_hostapd.h" +#include "nan_usd_ap.h" +#include "gas_query_ap.h" +#include "hw_features.h" +#include "wpa_auth_glue.h" +#include "ap_drv_ops.h" +#include "ap_config.h" +#include "p2p_hostapd.h" +#include "gas_serv.h" +#include "dfs.h" +#include "ieee802_11.h" +#include "bss_load.h" +#include "x_snoop.h" +#include "dhcp_snoop.h" +#include "ndisc_snoop.h" +#include "neighbor_db.h" +#include "rrm.h" +#include "fils_hlp.h" +#include "acs.h" +#include "hs20.h" +#include "airtime_policy.h" +#include "wpa_auth_kay.h" +#include "hw_features.h" + + +static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason); +#ifdef CONFIG_WEP +static int hostapd_setup_encryption(char *iface, struct hostapd_data *hapd); +static int hostapd_broadcast_wep_clear(struct hostapd_data *hapd); +#endif /* CONFIG_WEP */ +static int setup_interface2(struct hostapd_iface *iface); +static void channel_list_update_timeout(void *eloop_ctx, void *timeout_ctx); +static void hostapd_interface_setup_failure_handler(void *eloop_ctx, + void *timeout_ctx); +#ifdef CONFIG_IEEE80211AX +static void hostapd_switch_color_timeout_handler(void *eloop_data, + void *user_ctx); +#endif /* CONFIG_IEEE80211AX */ + + +int hostapd_for_each_interface(struct hapd_interfaces *interfaces, + int (*cb)(struct hostapd_iface *iface, + void *ctx), void *ctx) +{ + size_t i; + int ret; + + for (i = 0; i < interfaces->count; i++) { + if (!interfaces->iface[i]) + continue; + ret = cb(interfaces->iface[i], ctx); + if (ret) + return ret; + } + + return 0; +} + + +struct hostapd_data * hostapd_mbssid_get_tx_bss(struct hostapd_data *hapd) +{ + if (hapd->iconf->mbssid) + return hapd->iface->bss[0]; + + return hapd; +} + + +int hostapd_mbssid_get_bss_index(struct hostapd_data *hapd) +{ + if (hapd->iconf->mbssid) { + size_t i; + + for (i = 1; i < hapd->iface->num_bss; i++) + if (hapd->iface->bss[i] == hapd) + return i; + } + + return 0; +} + + +void hostapd_reconfig_encryption(struct hostapd_data *hapd) +{ + if (hapd->wpa_auth) + return; + + hostapd_set_privacy(hapd, 0); +#ifdef CONFIG_WEP + hostapd_setup_encryption(hapd->conf->iface, hapd); +#endif /* CONFIG_WEP */ +} + + +static void hostapd_reload_bss(struct hostapd_data *hapd) +{ + struct hostapd_ssid *ssid; + + if (!hapd->started) + return; + + if (hapd->conf->wmm_enabled < 0) + hapd->conf->wmm_enabled = hapd->iconf->ieee80211n | + hapd->iconf->ieee80211ax; + +#ifndef CONFIG_NO_RADIUS + radius_client_reconfig(hapd->radius, hapd->conf->radius); +#endif /* CONFIG_NO_RADIUS */ + + ssid = &hapd->conf->ssid; + if (!ssid->wpa_psk_set && ssid->wpa_psk && !ssid->wpa_psk->next && + ssid->wpa_passphrase_set && ssid->wpa_passphrase) { + /* + * Force PSK to be derived again since SSID or passphrase may + * have changed. + */ + hostapd_config_clear_wpa_psk(&hapd->conf->ssid.wpa_psk); + } + if (hostapd_setup_wpa_psk(hapd->conf)) { + wpa_printf(MSG_ERROR, "Failed to re-configure WPA PSK " + "after reloading configuration"); + } + + if (hapd->conf->ieee802_1x || hapd->conf->wpa) + hostapd_set_drv_ieee8021x(hapd, hapd->conf->iface, 1); + else + hostapd_set_drv_ieee8021x(hapd, hapd->conf->iface, 0); + + if ((hapd->conf->wpa || hapd->conf->osen) && hapd->wpa_auth == NULL) { + hostapd_setup_wpa(hapd); + if (hapd->wpa_auth) + wpa_init_keys(hapd->wpa_auth); + } else if (hapd->conf->wpa) { + const u8 *wpa_ie; + size_t wpa_ie_len; + hostapd_reconfig_wpa(hapd); + wpa_ie = wpa_auth_get_wpa_ie(hapd->wpa_auth, &wpa_ie_len); + if (hostapd_set_generic_elem(hapd, wpa_ie, wpa_ie_len)) + wpa_printf(MSG_ERROR, "Failed to configure WPA IE for " + "the kernel driver."); + } else if (hapd->wpa_auth) { + wpa_deinit(hapd->wpa_auth); + hapd->wpa_auth = NULL; + hostapd_set_privacy(hapd, 0); +#ifdef CONFIG_WEP + hostapd_setup_encryption(hapd->conf->iface, hapd); +#endif /* CONFIG_WEP */ + hostapd_set_generic_elem(hapd, (u8 *) "", 0); + } + + hostapd_neighbor_sync_own_report(hapd); + + ieee802_11_set_beacon(hapd); + hostapd_update_wps(hapd); + + if (hapd->conf->ssid.ssid_set && + hostapd_set_ssid(hapd, hapd->conf->ssid.ssid, + hapd->conf->ssid.ssid_len)) { + wpa_printf(MSG_ERROR, "Could not set SSID for kernel driver"); + /* try to continue */ + } + wpa_printf(MSG_DEBUG, "Reconfigured interface %s", hapd->conf->iface); +} + + +static void hostapd_clear_old_bss(struct hostapd_data *bss) +{ + wpa_printf(MSG_DEBUG, "BSS %s changed - clear old state", + bss->conf->iface); + + /* + * Deauthenticate all stations since the new configuration may not + * allow them to use the BSS anymore. + */ + hostapd_flush_old_stations(bss, WLAN_REASON_PREV_AUTH_NOT_VALID); +#ifdef CONFIG_WEP + hostapd_broadcast_wep_clear(bss); +#endif /* CONFIG_WEP */ + +#ifndef CONFIG_NO_RADIUS + /* TODO: update dynamic data based on changed configuration + * items (e.g., open/close sockets, etc.) */ + radius_client_flush(bss->radius, 0); +#endif /* CONFIG_NO_RADIUS */ +} + + +static void hostapd_clear_old(struct hostapd_iface *iface) +{ + size_t j; + + for (j = 0; j < iface->num_bss; j++) + hostapd_clear_old_bss(iface->bss[j]); +} + + +static int hostapd_iface_conf_changed(struct hostapd_config *newconf, + struct hostapd_config *oldconf) +{ + size_t i; + + if (newconf->num_bss != oldconf->num_bss) + return 1; + + for (i = 0; i < newconf->num_bss; i++) { + if (os_strcmp(newconf->bss[i]->iface, + oldconf->bss[i]->iface) != 0) + return 1; + } + + return 0; +} + + +int hostapd_reload_config(struct hostapd_iface *iface) +{ + struct hapd_interfaces *interfaces = iface->interfaces; + struct hostapd_data *hapd = iface->bss[0]; + struct hostapd_config *newconf, *oldconf; + size_t j; + + if (iface->config_fname == NULL) { + /* Only in-memory config in use - assume it has been updated */ + hostapd_clear_old(iface); + for (j = 0; j < iface->num_bss; j++) + hostapd_reload_bss(iface->bss[j]); + return 0; + } + + if (iface->interfaces == NULL || + iface->interfaces->config_read_cb == NULL) + return -1; + newconf = iface->interfaces->config_read_cb(iface->config_fname); + if (newconf == NULL) + return -1; + + oldconf = hapd->iconf; + if (hostapd_iface_conf_changed(newconf, oldconf)) { + char *fname; + int res; + + hostapd_clear_old(iface); + + wpa_printf(MSG_DEBUG, + "Configuration changes include interface/BSS modification - force full disable+enable sequence"); + fname = os_strdup(iface->config_fname); + if (!fname) { + hostapd_config_free(newconf); + return -1; + } + hostapd_remove_iface(interfaces, hapd->conf->iface); + iface = hostapd_init(interfaces, fname); + os_free(fname); + hostapd_config_free(newconf); + if (!iface) { + wpa_printf(MSG_ERROR, + "Failed to initialize interface on config reload"); + return -1; + } + iface->interfaces = interfaces; + interfaces->iface[interfaces->count] = iface; + interfaces->count++; + res = hostapd_enable_iface(iface); + if (res < 0) + wpa_printf(MSG_ERROR, + "Failed to enable interface on config reload"); + return res; + } + iface->conf = newconf; + + for (j = 0; j < iface->num_bss; j++) { + hapd = iface->bss[j]; + if (!hapd->conf->config_id || !newconf->bss[j]->config_id || + os_strcmp(hapd->conf->config_id, + newconf->bss[j]->config_id) != 0) + hostapd_clear_old_bss(hapd); + hapd->iconf = newconf; + hapd->iconf->channel = oldconf->channel; + hapd->iconf->acs = oldconf->acs; + hapd->iconf->secondary_channel = oldconf->secondary_channel; + hapd->iconf->ieee80211n = oldconf->ieee80211n; + hapd->iconf->ieee80211ac = oldconf->ieee80211ac; + hapd->iconf->ht_capab = oldconf->ht_capab; + hapd->iconf->vht_capab = oldconf->vht_capab; + hostapd_set_oper_chwidth(hapd->iconf, + hostapd_get_oper_chwidth(oldconf)); + hostapd_set_oper_centr_freq_seg0_idx( + hapd->iconf, + hostapd_get_oper_centr_freq_seg0_idx(oldconf)); + hostapd_set_oper_centr_freq_seg1_idx( + hapd->iconf, + hostapd_get_oper_centr_freq_seg1_idx(oldconf)); + hapd->conf = newconf->bss[j]; + hostapd_reload_bss(hapd); + } + + hostapd_config_free(oldconf); + + + return 0; +} + + +#ifdef CONFIG_WEP + +static void hostapd_broadcast_key_clear_iface(struct hostapd_data *hapd, + const char *ifname) +{ + int i; + + if (!ifname || !hapd->drv_priv) + return; + for (i = 0; i < NUM_WEP_KEYS; i++) { + if (hostapd_drv_set_key(ifname, hapd, WPA_ALG_NONE, NULL, i, 0, + 0, NULL, 0, NULL, 0, KEY_FLAG_GROUP)) { + wpa_printf(MSG_DEBUG, "Failed to clear default " + "encryption keys (ifname=%s keyidx=%d)", + ifname, i); + } + } + if (hapd->conf->ieee80211w) { + for (i = NUM_WEP_KEYS; i < NUM_WEP_KEYS + 2; i++) { + if (hostapd_drv_set_key(ifname, hapd, WPA_ALG_NONE, + NULL, i, 0, 0, NULL, + 0, NULL, 0, KEY_FLAG_GROUP)) { + wpa_printf(MSG_DEBUG, "Failed to clear " + "default mgmt encryption keys " + "(ifname=%s keyidx=%d)", ifname, i); + } + } + } +} + + +static int hostapd_broadcast_wep_clear(struct hostapd_data *hapd) +{ + hostapd_broadcast_key_clear_iface(hapd, hapd->conf->iface); + return 0; +} + + +static int hostapd_broadcast_wep_set(struct hostapd_data *hapd) +{ + int errors = 0, idx; + struct hostapd_ssid *ssid = &hapd->conf->ssid; + + idx = ssid->wep.idx; + if (ssid->wep.default_len && ssid->wep.key[idx] && + hostapd_drv_set_key(hapd->conf->iface, + hapd, WPA_ALG_WEP, broadcast_ether_addr, idx, 0, + 1, NULL, 0, ssid->wep.key[idx], + ssid->wep.len[idx], + KEY_FLAG_GROUP_RX_TX_DEFAULT)) { + wpa_printf(MSG_WARNING, "Could not set WEP encryption."); + errors++; + } + + return errors; +} + +#endif /* CONFIG_WEP */ + + +#ifdef CONFIG_IEEE80211BE +#ifdef CONFIG_TESTING_OPTIONS + +#define TU_TO_USEC(_val) ((_val) * 1024) + +static void hostapd_link_remove_timeout_handler(void *eloop_data, + void *user_ctx) +{ + struct hostapd_data *hapd = (struct hostapd_data *) eloop_data; + + if (hapd->eht_mld_link_removal_count == 0) + return; + hapd->eht_mld_link_removal_count--; + + wpa_printf(MSG_DEBUG, "MLD: Remove link_id=%u in %u beacons", + hapd->mld_link_id, + hapd->eht_mld_link_removal_count); + + ieee802_11_set_beacon(hapd); + + if (!hapd->eht_mld_link_removal_count) { + hostapd_free_link_stas(hapd); + hostapd_disable_iface(hapd->iface); + return; + } + + eloop_register_timeout(0, TU_TO_USEC(hapd->iconf->beacon_int), + hostapd_link_remove_timeout_handler, + hapd, NULL); +} + + +int hostapd_link_remove(struct hostapd_data *hapd, u32 count) +{ + if (!hapd->conf->mld_ap) + return -1; + + wpa_printf(MSG_DEBUG, + "MLD: Remove link_id=%u in %u beacons", + hapd->mld_link_id, count); + + hapd->eht_mld_link_removal_count = count; + hapd->eht_mld_bss_param_change++; + + eloop_register_timeout(0, TU_TO_USEC(hapd->iconf->beacon_int), + hostapd_link_remove_timeout_handler, + hapd, NULL); + + ieee802_11_set_beacon(hapd); + return 0; +} + +#endif /* CONFIG_TESTING_OPTIONS */ +#endif /* CONFIG_IEEE80211BE */ + + +void hostapd_free_hapd_data(struct hostapd_data *hapd) +{ + os_free(hapd->probereq_cb); + hapd->probereq_cb = NULL; + hapd->num_probereq_cb = 0; + +#ifdef CONFIG_P2P + wpabuf_free(hapd->p2p_beacon_ie); + hapd->p2p_beacon_ie = NULL; + wpabuf_free(hapd->p2p_probe_resp_ie); + hapd->p2p_probe_resp_ie = NULL; +#endif /* CONFIG_P2P */ + + if (!hapd->started) { + wpa_printf(MSG_ERROR, "%s: Interface %s wasn't started", + __func__, hapd->conf ? hapd->conf->iface : "N/A"); + return; + } + hapd->started = 0; + hapd->beacon_set_done = 0; + + wpa_printf(MSG_DEBUG, "%s(%s)", __func__, hapd->conf->iface); + accounting_deinit(hapd); + hostapd_deinit_wpa(hapd); + vlan_deinit(hapd); + hostapd_acl_deinit(hapd); +#ifndef CONFIG_NO_RADIUS + if (hostapd_mld_is_first_bss(hapd)) { +#ifdef CONFIG_IEEE80211BE + struct hapd_interfaces *ifaces = hapd->iface->interfaces; + size_t i; + + for (i = 0; i < ifaces->count; i++) { + struct hostapd_iface *iface = ifaces->iface[i]; + size_t j; + + for (j = 0; iface && j < iface->num_bss; j++) { + struct hostapd_data *h = iface->bss[j]; + + if (hapd == h) + continue; + if (h->radius == hapd->radius) + h->radius = NULL; + if (h->radius_das == hapd->radius_das) + h->radius_das = NULL; + } + } +#endif /* CONFIG_IEEE80211BE */ + radius_client_deinit(hapd->radius); + radius_das_deinit(hapd->radius_das); + } + hapd->radius = NULL; + hapd->radius_das = NULL; +#endif /* CONFIG_NO_RADIUS */ + + hostapd_deinit_wps(hapd); + ieee802_1x_dealloc_kay_sm_hapd(hapd); +#ifdef CONFIG_DPP + hostapd_dpp_deinit(hapd); + gas_query_ap_deinit(hapd->gas); + hapd->gas = NULL; +#endif /* CONFIG_DPP */ +#ifdef CONFIG_NAN_USD + hostapd_nan_usd_deinit(hapd); +#endif /* CONFIG_NAN_USD */ + + authsrv_deinit(hapd); + + if (hapd->interface_added) { + hapd->interface_added = 0; + if (hostapd_if_remove(hapd, WPA_IF_AP_BSS, hapd->conf->iface)) { + wpa_printf(MSG_WARNING, + "Failed to remove BSS interface %s", + hapd->conf->iface); + hapd->interface_added = 1; + } else { + /* + * Since this was a dynamically added interface, the + * driver wrapper may have removed its internal instance + * and hapd->drv_priv is not valid anymore. + */ + hapd->drv_priv = NULL; + } + } + +#ifdef CONFIG_IEEE80211BE + /* If the interface was not added as well as it is not the first BSS, + * at least the link should be removed here since deinit will take care + * of only the first BSS. */ + if (hapd->conf->mld_ap && !hapd->interface_added && + hapd->iface->bss[0] != hapd) + hostapd_if_link_remove(hapd, WPA_IF_AP_BSS, hapd->conf->iface, + hapd->mld_link_id); +#endif /* CONFIG_IEEE80211BE */ + + wpabuf_free(hapd->time_adv); + hapd->time_adv = NULL; + +#ifdef CONFIG_INTERWORKING + gas_serv_deinit(hapd); +#endif /* CONFIG_INTERWORKING */ + + bss_load_update_deinit(hapd); + ndisc_snoop_deinit(hapd); + dhcp_snoop_deinit(hapd); + x_snoop_deinit(hapd); + +#ifdef CONFIG_SQLITE + bin_clear_free(hapd->tmp_eap_user.identity, + hapd->tmp_eap_user.identity_len); + bin_clear_free(hapd->tmp_eap_user.password, + hapd->tmp_eap_user.password_len); + os_memset(&hapd->tmp_eap_user, 0, sizeof(hapd->tmp_eap_user)); +#endif /* CONFIG_SQLITE */ + +#ifdef CONFIG_MESH + wpabuf_free(hapd->mesh_pending_auth); + hapd->mesh_pending_auth = NULL; + /* handling setup failure is already done */ + hapd->setup_complete_cb = NULL; +#endif /* CONFIG_MESH */ + +#ifndef CONFIG_NO_RRM + hostapd_clean_rrm(hapd); +#endif /* CONFIG_NO_RRM */ + fils_hlp_deinit(hapd); + +#ifdef CONFIG_OCV + eloop_cancel_timeout(hostapd_ocv_check_csa_sa_query, hapd, NULL); +#endif /* CONFIG_OCV */ + +#ifdef CONFIG_SAE + { + struct hostapd_sae_commit_queue *q; + + while ((q = dl_list_first(&hapd->sae_commit_queue, + struct hostapd_sae_commit_queue, + list))) { + dl_list_del(&q->list); + os_free(q); + } + } + eloop_cancel_timeout(auth_sae_process_commit, hapd, NULL); +#endif /* CONFIG_SAE */ + +#ifdef CONFIG_IEEE80211AX + eloop_cancel_timeout(hostapd_switch_color_timeout_handler, hapd, NULL); +#ifdef CONFIG_TESTING_OPTIONS +#ifdef CONFIG_IEEE80211BE + eloop_cancel_timeout(hostapd_link_remove_timeout_handler, hapd, NULL); +#endif /* CONFIG_IEEE80211BE */ +#endif /* CONFIG_TESTING_OPTIONS */ + +#endif /* CONFIG_IEEE80211AX */ +} + + +/* hostapd_bss_link_deinit - Per-BSS ML cleanup (deinitialization) + * @hapd: Pointer to BSS data + * + * This function is used to unlink the BSS from the AP MLD. + * If the BSS being removed is the first link, the next link becomes the first + * link. + */ +static void hostapd_bss_link_deinit(struct hostapd_data *hapd) +{ +#ifdef CONFIG_IEEE80211BE + if (!hapd->conf || !hapd->conf->mld_ap) + return; + + if (!hapd->mld->num_links) + return; + + /* If not started, not yet linked to the MLD. However, the first + * BSS is always linked since it is linked during driver_init(), and + * hence, need to remove it from the AP MLD. + */ + if (!hapd->started && hapd->iface->bss[0] != hapd) + return; + + /* The first BSS can also be only linked when at least driver_init() is + * executed. But if previous interface fails, it is not, and hence, + * safe to skip. + */ + if (hapd->iface->bss[0] == hapd && !hapd->drv_priv) + return; + + hostapd_mld_remove_link(hapd); +#endif /* CONFIG_IEEE80211BE */ +} + + +/** + * hostapd_cleanup - Per-BSS cleanup (deinitialization) + * @hapd: Pointer to BSS data + * + * This function is used to free all per-BSS data structures and resources. + * Most of the modules that are initialized in hostapd_setup_bss() are + * deinitialized here. + */ +static void hostapd_cleanup(struct hostapd_data *hapd) +{ + wpa_printf(MSG_DEBUG, "%s(hapd=%p (%s))", __func__, hapd, + hapd->conf ? hapd->conf->iface : "N/A"); + if (hapd->iface->interfaces && + hapd->iface->interfaces->ctrl_iface_deinit) { + wpa_msg(hapd->msg_ctx, MSG_INFO, WPA_EVENT_TERMINATING); + hapd->iface->interfaces->ctrl_iface_deinit(hapd); + } + hostapd_free_hapd_data(hapd); +} + + +static void sta_track_deinit(struct hostapd_iface *iface) +{ + struct hostapd_sta_info *info; + + if (!iface->num_sta_seen) + return; + + while ((info = dl_list_first(&iface->sta_seen, struct hostapd_sta_info, + list))) { + dl_list_del(&info->list); + iface->num_sta_seen--; + sta_track_del(info); + } +} + + +void hostapd_cleanup_iface_partial(struct hostapd_iface *iface) +{ + wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface); + eloop_cancel_timeout(channel_list_update_timeout, iface, NULL); +#ifdef NEED_AP_MLME + hostapd_stop_setup_timers(iface); +#endif /* NEED_AP_MLME */ + if (iface->current_mode) + acs_cleanup(iface); + hostapd_free_hw_features(iface->hw_features, iface->num_hw_features); + iface->hw_features = NULL; + iface->current_mode = NULL; + os_free(iface->current_rates); + iface->current_rates = NULL; + os_free(iface->basic_rates); + iface->basic_rates = NULL; + iface->cac_started = 0; + ap_list_deinit(iface); + sta_track_deinit(iface); + airtime_policy_update_deinit(iface); +} + + +/** + * hostapd_cleanup_iface - Complete per-interface cleanup + * @iface: Pointer to interface data + * + * This function is called after per-BSS data structures are deinitialized + * with hostapd_cleanup(). + */ +static void hostapd_cleanup_iface(struct hostapd_iface *iface) +{ + wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface); + eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface, + NULL); + + hostapd_cleanup_iface_partial(iface); + hostapd_config_free(iface->conf); + iface->conf = NULL; + + os_free(iface->config_fname); + os_free(iface->bss); + wpa_printf(MSG_DEBUG, "%s: free iface=%p", __func__, iface); + os_free(iface); +} + + +#ifdef CONFIG_WEP + +static void hostapd_clear_wep(struct hostapd_data *hapd) +{ + if (hapd->drv_priv && !hapd->iface->driver_ap_teardown && hapd->conf) { + hostapd_set_privacy(hapd, 0); + hostapd_broadcast_wep_clear(hapd); + } +} + + +static int hostapd_setup_encryption(char *iface, struct hostapd_data *hapd) +{ + int i; + + hostapd_broadcast_wep_set(hapd); + + if (hapd->conf->ssid.wep.default_len) { + hostapd_set_privacy(hapd, 1); + return 0; + } + + /* + * When IEEE 802.1X is not enabled, the driver may need to know how to + * set authentication algorithms for static WEP. + */ + hostapd_drv_set_authmode(hapd, hapd->conf->auth_algs); + + for (i = 0; i < 4; i++) { + if (hapd->conf->ssid.wep.key[i] && + hostapd_drv_set_key(iface, hapd, WPA_ALG_WEP, NULL, i, 0, + i == hapd->conf->ssid.wep.idx, NULL, 0, + hapd->conf->ssid.wep.key[i], + hapd->conf->ssid.wep.len[i], + i == hapd->conf->ssid.wep.idx ? + KEY_FLAG_GROUP_RX_TX_DEFAULT : + KEY_FLAG_GROUP_RX_TX)) { + wpa_printf(MSG_WARNING, "Could not set WEP " + "encryption."); + return -1; + } + if (hapd->conf->ssid.wep.key[i] && + i == hapd->conf->ssid.wep.idx) + hostapd_set_privacy(hapd, 1); + } + + return 0; +} + +#endif /* CONFIG_WEP */ + + +static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason) +{ + int ret = 0; + u8 addr[ETH_ALEN]; + + if (hostapd_drv_none(hapd) || hapd->drv_priv == NULL) + return 0; + + if (!hapd->iface->driver_ap_teardown) { + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, + "Flushing old station entries"); + + if (hostapd_flush(hapd)) { + wpa_msg(hapd->msg_ctx, MSG_WARNING, + "Could not connect to kernel driver"); + ret = -1; + } + } + if (hapd->conf && hapd->conf->broadcast_deauth) { + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, + "Deauthenticate all stations"); + os_memset(addr, 0xff, ETH_ALEN); + hostapd_drv_sta_deauth(hapd, addr, reason); + } + hostapd_free_stas(hapd); + + return ret; +} + + +void hostapd_bss_deinit_no_free(struct hostapd_data *hapd) +{ + hostapd_free_stas(hapd); + hostapd_flush_old_stations(hapd, WLAN_REASON_DEAUTH_LEAVING); +#ifdef CONFIG_WEP + hostapd_clear_wep(hapd); +#endif /* CONFIG_WEP */ +} + + +/** + * hostapd_validate_bssid_configuration - Validate BSSID configuration + * @iface: Pointer to interface data + * Returns: 0 on success, -1 on failure + * + * This function is used to validate that the configured BSSIDs are valid. + */ +static int hostapd_validate_bssid_configuration(struct hostapd_iface *iface) +{ + u8 mask[ETH_ALEN] = { 0 }; + struct hostapd_data *hapd = iface->bss[0]; + unsigned int i = iface->conf->num_bss, bits = 0, j; + int auto_addr = 0; + + if (hostapd_drv_none(hapd)) + return 0; + + if (iface->conf->use_driver_iface_addr) + return 0; + + /* Generate BSSID mask that is large enough to cover the BSSIDs. */ + + /* Determine the bits necessary to cover the number of BSSIDs. */ + for (i--; i; i >>= 1) + bits++; + + /* Determine the bits necessary to any configured BSSIDs, + if they are higher than the number of BSSIDs. */ + for (j = 0; j < iface->conf->num_bss; j++) { + if (is_zero_ether_addr(iface->conf->bss[j]->bssid)) { + if (j) + auto_addr++; + continue; + } + + for (i = 0; i < ETH_ALEN; i++) { + mask[i] |= + iface->conf->bss[j]->bssid[i] ^ + hapd->own_addr[i]; + } + } + + if (!auto_addr) + goto skip_mask_ext; + + for (i = 0; i < ETH_ALEN && mask[i] == 0; i++) + ; + j = 0; + if (i < ETH_ALEN) { + j = (5 - i) * 8; + + while (mask[i] != 0) { + mask[i] >>= 1; + j++; + } + } + + if (bits < j) + bits = j; + + if (bits > 40) { + wpa_printf(MSG_ERROR, "Too many bits in the BSSID mask (%u)", + bits); + return -1; + } + + os_memset(mask, 0xff, ETH_ALEN); + j = bits / 8; + for (i = 5; i > 5 - j; i--) + mask[i] = 0; + j = bits % 8; + while (j) { + j--; + mask[i] <<= 1; + } + +skip_mask_ext: + wpa_printf(MSG_DEBUG, "BSS count %lu, BSSID mask " MACSTR " (%d bits)", + (unsigned long) iface->conf->num_bss, MAC2STR(mask), bits); + + if (!auto_addr) + return 0; + + for (i = 0; i < ETH_ALEN; i++) { + if ((hapd->own_addr[i] & mask[i]) != hapd->own_addr[i]) { + wpa_printf(MSG_ERROR, "Invalid BSSID mask " MACSTR + " for start address " MACSTR ".", + MAC2STR(mask), MAC2STR(hapd->own_addr)); + wpa_printf(MSG_ERROR, "Start address must be the " + "first address in the block (i.e., addr " + "AND mask == addr)."); + return -1; + } + } + + return 0; +} + + +static int mac_in_conf(struct hostapd_config *conf, const void *a) +{ + size_t i; + + for (i = 0; i < conf->num_bss; i++) { + if (hostapd_mac_comp(conf->bss[i]->bssid, a) == 0) { + return 1; + } + } + + return 0; +} + + +#ifndef CONFIG_NO_RADIUS + +static int hostapd_das_nas_mismatch(struct hostapd_data *hapd, + struct radius_das_attrs *attr) +{ + if (attr->nas_identifier && + (!hapd->conf->nas_identifier || + os_strlen(hapd->conf->nas_identifier) != + attr->nas_identifier_len || + os_memcmp(hapd->conf->nas_identifier, attr->nas_identifier, + attr->nas_identifier_len) != 0)) { + wpa_printf(MSG_DEBUG, "RADIUS DAS: NAS-Identifier mismatch"); + return 1; + } + + if (attr->nas_ip_addr && + (hapd->conf->own_ip_addr.af != AF_INET || + os_memcmp(&hapd->conf->own_ip_addr.u.v4, attr->nas_ip_addr, 4) != + 0)) { + wpa_printf(MSG_DEBUG, "RADIUS DAS: NAS-IP-Address mismatch"); + return 1; + } + +#ifdef CONFIG_IPV6 + if (attr->nas_ipv6_addr && + (hapd->conf->own_ip_addr.af != AF_INET6 || + os_memcmp(&hapd->conf->own_ip_addr.u.v6, attr->nas_ipv6_addr, 16) + != 0)) { + wpa_printf(MSG_DEBUG, "RADIUS DAS: NAS-IPv6-Address mismatch"); + return 1; + } +#endif /* CONFIG_IPV6 */ + + return 0; +} + + +static struct sta_info * hostapd_das_find_sta(struct hostapd_data *hapd, + struct radius_das_attrs *attr, + int *multi) +{ + struct sta_info *selected, *sta; + char buf[128]; + int num_attr = 0; + int count; + + *multi = 0; + + for (sta = hapd->sta_list; sta; sta = sta->next) + sta->radius_das_match = 1; + + if (attr->sta_addr) { + num_attr++; + sta = ap_get_sta(hapd, attr->sta_addr); + if (!sta) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No Calling-Station-Id match"); + return NULL; + } + + selected = sta; + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (sta != selected) + sta->radius_das_match = 0; + } + wpa_printf(MSG_DEBUG, "RADIUS DAS: Calling-Station-Id match"); + } + + if (attr->acct_session_id) { + num_attr++; + if (attr->acct_session_id_len != 16) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: Acct-Session-Id cannot match"); + return NULL; + } + count = 0; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (!sta->radius_das_match) + continue; + os_snprintf(buf, sizeof(buf), "%016llX", + (unsigned long long) sta->acct_session_id); + if (os_memcmp(attr->acct_session_id, buf, 16) != 0) + sta->radius_das_match = 0; + else + count++; + } + + if (count == 0) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No matches remaining after Acct-Session-Id check"); + return NULL; + } + wpa_printf(MSG_DEBUG, "RADIUS DAS: Acct-Session-Id match"); + } + + if (attr->acct_multi_session_id) { + num_attr++; + if (attr->acct_multi_session_id_len != 16) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: Acct-Multi-Session-Id cannot match"); + return NULL; + } + count = 0; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (!sta->radius_das_match) + continue; + if (!sta->eapol_sm || + !sta->eapol_sm->acct_multi_session_id) { + sta->radius_das_match = 0; + continue; + } + os_snprintf(buf, sizeof(buf), "%016llX", + (unsigned long long) + sta->eapol_sm->acct_multi_session_id); + if (os_memcmp(attr->acct_multi_session_id, buf, 16) != + 0) + sta->radius_das_match = 0; + else + count++; + } + + if (count == 0) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No matches remaining after Acct-Multi-Session-Id check"); + return NULL; + } + wpa_printf(MSG_DEBUG, + "RADIUS DAS: Acct-Multi-Session-Id match"); + } + + if (attr->cui) { + num_attr++; + count = 0; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + struct wpabuf *cui; + + if (!sta->radius_das_match) + continue; + cui = ieee802_1x_get_radius_cui(sta->eapol_sm); + if (!cui || wpabuf_len(cui) != attr->cui_len || + os_memcmp(wpabuf_head(cui), attr->cui, + attr->cui_len) != 0) + sta->radius_das_match = 0; + else + count++; + } + + if (count == 0) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No matches remaining after Chargeable-User-Identity check"); + return NULL; + } + wpa_printf(MSG_DEBUG, + "RADIUS DAS: Chargeable-User-Identity match"); + } + + if (attr->user_name) { + num_attr++; + count = 0; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + u8 *identity; + size_t identity_len; + + if (!sta->radius_das_match) + continue; + identity = ieee802_1x_get_identity(sta->eapol_sm, + &identity_len); + if (!identity || + identity_len != attr->user_name_len || + os_memcmp(identity, attr->user_name, identity_len) + != 0) + sta->radius_das_match = 0; + else + count++; + } + + if (count == 0) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No matches remaining after User-Name check"); + return NULL; + } + wpa_printf(MSG_DEBUG, + "RADIUS DAS: User-Name match"); + } + + if (num_attr == 0) { + /* + * In theory, we could match all current associations, but it + * seems safer to just reject requests that do not include any + * session identification attributes. + */ + wpa_printf(MSG_DEBUG, + "RADIUS DAS: No session identification attributes included"); + return NULL; + } + + selected = NULL; + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (sta->radius_das_match) { + if (selected) { + *multi = 1; + return NULL; + } + selected = sta; + } + } + + return selected; +} + + +static int hostapd_das_disconnect_pmksa(struct hostapd_data *hapd, + struct radius_das_attrs *attr) +{ + if (!hapd->wpa_auth) + return -1; + return wpa_auth_radius_das_disconnect_pmksa(hapd->wpa_auth, attr); +} + + +static enum radius_das_res +hostapd_das_disconnect(void *ctx, struct radius_das_attrs *attr) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + int multi; + + if (hostapd_das_nas_mismatch(hapd, attr)) + return RADIUS_DAS_NAS_MISMATCH; + + sta = hostapd_das_find_sta(hapd, attr, &multi); + if (sta == NULL) { + if (multi) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: Multiple sessions match - not supported"); + return RADIUS_DAS_MULTI_SESSION_MATCH; + } + if (hostapd_das_disconnect_pmksa(hapd, attr) == 0) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: PMKSA cache entry matched"); + return RADIUS_DAS_SUCCESS; + } + wpa_printf(MSG_DEBUG, "RADIUS DAS: No matching session found"); + return RADIUS_DAS_SESSION_NOT_FOUND; + } + + wpa_printf(MSG_DEBUG, "RADIUS DAS: Found a matching session " MACSTR + " - disconnecting", MAC2STR(sta->addr)); + wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr); + + hostapd_drv_sta_deauth(hapd, sta->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + ap_sta_deauthenticate(hapd, sta, WLAN_REASON_PREV_AUTH_NOT_VALID); + + return RADIUS_DAS_SUCCESS; +} + + +#ifdef CONFIG_HS20 +static enum radius_das_res +hostapd_das_coa(void *ctx, struct radius_das_attrs *attr) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + int multi; + + if (hostapd_das_nas_mismatch(hapd, attr)) + return RADIUS_DAS_NAS_MISMATCH; + + sta = hostapd_das_find_sta(hapd, attr, &multi); + if (!sta) { + if (multi) { + wpa_printf(MSG_DEBUG, + "RADIUS DAS: Multiple sessions match - not supported"); + return RADIUS_DAS_MULTI_SESSION_MATCH; + } + wpa_printf(MSG_DEBUG, "RADIUS DAS: No matching session found"); + return RADIUS_DAS_SESSION_NOT_FOUND; + } + + wpa_printf(MSG_DEBUG, "RADIUS DAS: Found a matching session " MACSTR + " - CoA", MAC2STR(sta->addr)); + + if (attr->hs20_t_c_filtering) { + if (attr->hs20_t_c_filtering[0] & BIT(0)) { + wpa_printf(MSG_DEBUG, + "HS 2.0: Unexpected Terms and Conditions filtering required in CoA-Request"); + return RADIUS_DAS_COA_FAILED; + } + + hs20_t_c_filtering(hapd, sta, 0); + } + + return RADIUS_DAS_SUCCESS; +} +#else /* CONFIG_HS20 */ +#define hostapd_das_coa NULL +#endif /* CONFIG_HS20 */ + + +#ifdef CONFIG_SQLITE + +static int db_table_exists(sqlite3 *db, const char *name) +{ + char cmd[128]; + + os_snprintf(cmd, sizeof(cmd), "SELECT 1 FROM %s;", name); + return sqlite3_exec(db, cmd, NULL, NULL, NULL) == SQLITE_OK; +} + + +static int db_table_create_radius_attributes(sqlite3 *db) +{ + char *err = NULL; + const char *sql = + "CREATE TABLE radius_attributes(" + " id INTEGER PRIMARY KEY," + " sta TEXT," + " reqtype TEXT," + " attr TEXT" + ");" + "CREATE INDEX idx_sta_reqtype ON radius_attributes(sta,reqtype);"; + + wpa_printf(MSG_DEBUG, + "Adding database table for RADIUS attribute information"); + if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) { + wpa_printf(MSG_ERROR, "SQLite error: %s", err); + sqlite3_free(err); + return -1; + } + + return 0; +} + +#endif /* CONFIG_SQLITE */ + +#endif /* CONFIG_NO_RADIUS */ + + +static int hostapd_start_beacon(struct hostapd_data *hapd, + bool flush_old_stations) +{ + struct hostapd_bss_config *conf = hapd->conf; + + if (!conf->start_disabled && ieee802_11_set_beacon(hapd) < 0) + return -1; + + if (flush_old_stations && !conf->start_disabled && + conf->broadcast_deauth) { + u8 addr[ETH_ALEN]; + + /* Should any previously associated STA not have noticed that + * the AP had stopped and restarted, send one more + * deauthentication notification now that the AP is ready to + * operate. */ + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, + "Deauthenticate all stations at BSS start"); + os_memset(addr, 0xff, ETH_ALEN); + hostapd_drv_sta_deauth(hapd, addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + } + + if (hapd->driver && hapd->driver->set_operstate) + hapd->driver->set_operstate(hapd->drv_priv, 1); + + return 0; +} + + +#ifndef CONFIG_NO_RADIUS +static int hostapd_bss_radius_init(struct hostapd_data *hapd) +{ + struct hostapd_bss_config *conf; + + if (!hapd) + return -1; + + conf = hapd->conf; + + if (hapd->radius) { + wpa_printf(MSG_DEBUG, + "Skipping RADIUS client init (already done)"); + return 0; + } + + hapd->radius = radius_client_init(hapd, conf->radius); + if (!hapd->radius) { + wpa_printf(MSG_ERROR, + "RADIUS client initialization failed."); + return -1; + } + + if (conf->radius_das_port) { + struct radius_das_conf das_conf; + + os_memset(&das_conf, 0, sizeof(das_conf)); + das_conf.port = conf->radius_das_port; + das_conf.shared_secret = conf->radius_das_shared_secret; + das_conf.shared_secret_len = + conf->radius_das_shared_secret_len; + das_conf.client_addr = &conf->radius_das_client_addr; + das_conf.time_window = conf->radius_das_time_window; + das_conf.require_event_timestamp = + conf->radius_das_require_event_timestamp; + das_conf.require_message_authenticator = + conf->radius_das_require_message_authenticator; + das_conf.ctx = hapd; + das_conf.disconnect = hostapd_das_disconnect; + das_conf.coa = hostapd_das_coa; + hapd->radius_das = radius_das_init(&das_conf); + if (!hapd->radius_das) { + wpa_printf(MSG_ERROR, + "RADIUS DAS initialization failed."); + return -1; + } + } + + return 0; +} +#endif /* CONFIG_NO_RADIUS */ + + +/** + * hostapd_setup_bss - Per-BSS setup (initialization) + * @hapd: Pointer to BSS data + * @first: Whether this BSS is the first BSS of an interface; -1 = not first, + * but interface may exist + * @start_beacon: Whether Beacon frame template should be configured and + * transmission of Beaconf rames started at this time. This is used when + * MBSSID element is enabled where the information regarding all BSSes + * should be retrieved before configuring the Beacon frame template. The + * calling functions are responsible for configuring the Beacon frame + * explicitly if this is set to false. + * + * This function is used to initialize all per-BSS data structures and + * resources. This gets called in a loop for each BSS when an interface is + * initialized. Most of the modules that are initialized here will be + * deinitialized in hostapd_cleanup(). + */ +static int hostapd_setup_bss(struct hostapd_data *hapd, int first, + bool start_beacon) +{ + struct hostapd_bss_config *conf = hapd->conf; + u8 ssid[SSID_MAX_LEN + 1]; + int ssid_len, set_ssid; + char force_ifname[IFNAMSIZ]; + u8 if_addr[ETH_ALEN]; + int flush_old_stations = 1; + + if (!hostapd_mld_is_first_bss(hapd)) + wpa_printf(MSG_DEBUG, + "MLD: %s: Setting non-first BSS", __func__); + + wpa_printf(MSG_DEBUG, "%s(hapd=%p (%s), first=%d)", + __func__, hapd, conf->iface, first); + +#ifdef EAP_SERVER_TNC + if (conf->tnc && tncs_global_init() < 0) { + wpa_printf(MSG_ERROR, "Failed to initialize TNCS"); + return -1; + } +#endif /* EAP_SERVER_TNC */ + + if (hapd->started) { + wpa_printf(MSG_ERROR, "%s: Interface %s was already started", + __func__, conf->iface); + return -1; + } + hapd->started = 1; + + if (!first || first == -1) { + u8 *addr = hapd->own_addr; + + if (!is_zero_ether_addr(conf->bssid)) { + /* Allocate the configured BSSID. */ + os_memcpy(hapd->own_addr, conf->bssid, ETH_ALEN); + + if (hostapd_mac_comp(hapd->own_addr, + hapd->iface->bss[0]->own_addr) == + 0) { + wpa_printf(MSG_ERROR, "BSS '%s' may not have " + "BSSID set to the MAC address of " + "the radio", conf->iface); + return -1; + } + } else if (hapd->iconf->use_driver_iface_addr) { + addr = NULL; + } else { + /* Allocate the next available BSSID. */ + do { + inc_byte_array(hapd->own_addr, ETH_ALEN); + } while (mac_in_conf(hapd->iconf, hapd->own_addr)); + } + +#ifdef CONFIG_IEEE80211BE + if (conf->mld_ap) { + struct hostapd_data *h_hapd; + + h_hapd = hostapd_mld_get_first_bss(hapd); + if (h_hapd) { + hapd->drv_priv = h_hapd->drv_priv; + hapd->interface_added = h_hapd->interface_added; + hostapd_mld_add_link(hapd); + wpa_printf(MSG_DEBUG, + "Setup of non first link (%d) BSS of MLD %s", + hapd->mld_link_id, hapd->conf->iface); + goto setup_mld; + } + } +#endif /* CONFIG_IEEE80211BE */ + + hapd->interface_added = 1; + if (hostapd_if_add(hapd->iface->bss[0], WPA_IF_AP_BSS, + conf->iface, addr, hapd, + &hapd->drv_priv, force_ifname, if_addr, + conf->bridge[0] ? conf->bridge : NULL, + first == -1)) { + wpa_printf(MSG_ERROR, "Failed to add BSS (BSSID=" + MACSTR ")", MAC2STR(hapd->own_addr)); + hapd->interface_added = 0; + return -1; + } + + if (!addr) + os_memcpy(hapd->own_addr, if_addr, ETH_ALEN); + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) { + wpa_printf(MSG_DEBUG, + "Setup of first link (%d) BSS of MLD %s", + hapd->mld_link_id, hapd->conf->iface); + os_memcpy(hapd->mld->mld_addr, hapd->own_addr, + ETH_ALEN); + hostapd_mld_add_link(hapd); + } +#endif /* CONFIG_IEEE80211BE */ + } + +#ifdef CONFIG_IEEE80211BE +setup_mld: + if (hapd->conf->mld_ap && !first) { + wpa_printf(MSG_DEBUG, + "MLD: Set link_id=%u, mld_addr=" MACSTR + ", own_addr=" MACSTR, + hapd->mld_link_id, MAC2STR(hapd->mld->mld_addr), + MAC2STR(hapd->own_addr)); + + if (hostapd_drv_link_add(hapd, hapd->mld_link_id, + hapd->own_addr)) + return -1; + } +#endif /* CONFIG_IEEE80211BE */ + + if (conf->wmm_enabled < 0) + conf->wmm_enabled = hapd->iconf->ieee80211n | + hapd->iconf->ieee80211ax; + +#ifdef CONFIG_IEEE80211R_AP + if (is_zero_ether_addr(conf->r1_key_holder)) + os_memcpy(conf->r1_key_holder, hapd->own_addr, ETH_ALEN); +#endif /* CONFIG_IEEE80211R_AP */ + +#ifdef CONFIG_MESH + if ((hapd->conf->mesh & MESH_ENABLED) && hapd->iface->mconf == NULL) + flush_old_stations = 0; +#endif /* CONFIG_MESH */ + + if (flush_old_stations) + hostapd_flush(hapd); + hostapd_set_privacy(hapd, 0); + +#ifdef CONFIG_WEP + if (!hostapd_drv_nl80211(hapd)) + hostapd_broadcast_wep_clear(hapd); + if (hostapd_setup_encryption(conf->iface, hapd)) + return -1; +#endif /* CONFIG_WEP */ + + /* + * Fetch the SSID from the system and use it or, + * if one was specified in the config file, verify they + * match. + */ + ssid_len = hostapd_get_ssid(hapd, ssid, sizeof(ssid)); + if (ssid_len < 0) { + wpa_printf(MSG_ERROR, "Could not read SSID from system"); + return -1; + } + if (conf->ssid.ssid_set) { + /* + * If SSID is specified in the config file and it differs + * from what is being used then force installation of the + * new SSID. + */ + set_ssid = (conf->ssid.ssid_len != (size_t) ssid_len || + os_memcmp(conf->ssid.ssid, ssid, ssid_len) != 0); + } else { + /* + * No SSID in the config file; just use the one we got + * from the system. + */ + set_ssid = 0; + conf->ssid.ssid_len = ssid_len; + os_memcpy(conf->ssid.ssid, ssid, conf->ssid.ssid_len); + } + + /* + * Short SSID calculation is identical to FCS and it is defined in + * IEEE P802.11-REVmd/D3.0, 9.4.2.170.3 (Calculating the Short-SSID). + */ + conf->ssid.short_ssid = ieee80211_crc32(conf->ssid.ssid, + conf->ssid.ssid_len); + + if (!hostapd_drv_none(hapd)) { + wpa_printf(MSG_DEBUG, "Using interface %s with hwaddr " MACSTR + " and ssid \"%s\"", + conf->iface, MAC2STR(hapd->own_addr), + wpa_ssid_txt(conf->ssid.ssid, conf->ssid.ssid_len)); + } + + if (hostapd_setup_wpa_psk(conf)) { + wpa_printf(MSG_ERROR, "WPA-PSK setup failed."); + return -1; + } + + /* Set SSID for the kernel driver (to be used in beacon and probe + * response frames) */ + if (set_ssid && hostapd_set_ssid(hapd, conf->ssid.ssid, + conf->ssid.ssid_len)) { + wpa_printf(MSG_ERROR, "Could not set SSID for kernel driver"); + return -1; + } + + if (wpa_debug_level <= MSG_MSGDUMP) + conf->radius->msg_dumps = 1; +#ifndef CONFIG_NO_RADIUS + +#ifdef CONFIG_SQLITE + if (conf->radius_req_attr_sqlite) { + if (sqlite3_open(conf->radius_req_attr_sqlite, + &hapd->rad_attr_db)) { + wpa_printf(MSG_ERROR, "Could not open SQLite file '%s'", + conf->radius_req_attr_sqlite); + return -1; + } + + wpa_printf(MSG_DEBUG, "Opening RADIUS attribute database: %s", + conf->radius_req_attr_sqlite); + if (!db_table_exists(hapd->rad_attr_db, "radius_attributes") && + db_table_create_radius_attributes(hapd->rad_attr_db) < 0) + return -1; + } +#endif /* CONFIG_SQLITE */ + + if (hostapd_mld_is_first_bss(hapd)) { + if (hostapd_bss_radius_init(hapd)) + return -1; + } else { +#ifdef CONFIG_IEEE80211BE + struct hostapd_data *f_bss; + + f_bss = hostapd_mld_get_first_bss(hapd); + if (!f_bss) + return -1; + + if (!f_bss->radius) { + wpa_printf(MSG_DEBUG, + "MLD: First BSS RADIUS client does not exist. Init on its behalf"); + + if (hostapd_bss_radius_init(f_bss)) + return -1; + } + + wpa_printf(MSG_DEBUG, + "MLD: Using RADIUS client of the first BSS"); + hapd->radius = f_bss->radius; + hapd->radius_das = f_bss->radius_das; +#endif /* CONFIG_IEEE80211BE */ + } +#endif /* CONFIG_NO_RADIUS */ + + if (hostapd_acl_init(hapd)) { + wpa_printf(MSG_ERROR, "ACL initialization failed."); + return -1; + } + if (hostapd_init_wps(hapd, conf)) + return -1; + +#ifdef CONFIG_DPP + hapd->gas = gas_query_ap_init(hapd, hapd->msg_ctx); + if (!hapd->gas) + return -1; + if (hostapd_dpp_init(hapd)) + return -1; +#endif /* CONFIG_DPP */ + +#ifdef CONFIG_NAN_USD + if (hostapd_nan_usd_init(hapd) < 0) + return -1; +#endif /* CONFIG_NAN_USD */ + + if (authsrv_init(hapd) < 0) + return -1; + + if (ieee802_1x_init(hapd)) { + wpa_printf(MSG_ERROR, "IEEE 802.1X initialization failed."); + return -1; + } + + if ((conf->wpa || conf->osen) && hostapd_setup_wpa(hapd)) + return -1; + + if (accounting_init(hapd)) { + wpa_printf(MSG_ERROR, "Accounting initialization failed."); + return -1; + } + +#ifdef CONFIG_INTERWORKING + if (gas_serv_init(hapd)) { + wpa_printf(MSG_ERROR, "GAS server initialization failed"); + return -1; + } +#endif /* CONFIG_INTERWORKING */ + + if (conf->qos_map_set_len && + hostapd_drv_set_qos_map(hapd, conf->qos_map_set, + conf->qos_map_set_len)) { + wpa_printf(MSG_ERROR, "Failed to initialize QoS Map"); + return -1; + } + + if (conf->bss_load_update_period && bss_load_update_init(hapd)) { + wpa_printf(MSG_ERROR, "BSS Load initialization failed"); + return -1; + } + + if (conf->bridge[0]) { + /* Set explicitly configured bridge parameters that might have + * been lost if the interface has been removed out of the + * bridge. */ + + /* multicast to unicast on bridge ports */ + if (conf->bridge_multicast_to_unicast) + hostapd_drv_br_port_set_attr( + hapd, DRV_BR_PORT_ATTR_MCAST2UCAST, 1); + + /* hairpin mode */ + if (conf->bridge_hairpin) + hostapd_drv_br_port_set_attr( + hapd, DRV_BR_PORT_ATTR_HAIRPIN_MODE, 1); + } + + if (conf->proxy_arp) { + if (x_snoop_init(hapd)) { + wpa_printf(MSG_ERROR, + "Generic snooping infrastructure initialization failed"); + return -1; + } + + if (dhcp_snoop_init(hapd)) { + wpa_printf(MSG_ERROR, + "DHCP snooping initialization failed"); + return -1; + } + + if (ndisc_snoop_init(hapd)) { + wpa_printf(MSG_ERROR, + "Neighbor Discovery snooping initialization failed"); + return -1; + } + } + + if (!hostapd_drv_none(hapd) && vlan_init(hapd)) { + wpa_printf(MSG_ERROR, "VLAN initialization failed."); + return -1; + } + + if (start_beacon && hostapd_start_beacon(hapd, flush_old_stations) < 0) + return -1; + + if (hapd->wpa_auth && wpa_init_keys(hapd->wpa_auth) < 0) + return -1; + + return 0; +} + + +static void hostapd_tx_queue_params(struct hostapd_iface *iface) +{ + struct hostapd_data *hapd = iface->bss[0]; + int i; + struct hostapd_tx_queue_params *p; + +#ifdef CONFIG_MESH + if ((hapd->conf->mesh & MESH_ENABLED) && iface->mconf == NULL) + return; +#endif /* CONFIG_MESH */ + + for (i = 0; i < NUM_TX_QUEUES; i++) { + p = &iface->conf->tx_queue[i]; + + if (hostapd_set_tx_queue_params(hapd, i, p->aifs, p->cwmin, + p->cwmax, p->burst)) { + wpa_printf(MSG_DEBUG, "Failed to set TX queue " + "parameters for queue %d.", i); + /* Continue anyway */ + } + } +} + + +static int hostapd_set_acl_list(struct hostapd_data *hapd, + struct mac_acl_entry *mac_acl, + int n_entries, u8 accept_acl) +{ + struct hostapd_acl_params *acl_params; + int i, err; + + acl_params = os_zalloc(sizeof(*acl_params) + + (n_entries * sizeof(acl_params->mac_acl[0]))); + if (!acl_params) + return -ENOMEM; + + for (i = 0; i < n_entries; i++) + os_memcpy(acl_params->mac_acl[i].addr, mac_acl[i].addr, + ETH_ALEN); + + acl_params->acl_policy = accept_acl; + acl_params->num_mac_acl = n_entries; + + err = hostapd_drv_set_acl(hapd, acl_params); + + os_free(acl_params); + + return err; +} + + +int hostapd_set_acl(struct hostapd_data *hapd) +{ + struct hostapd_config *conf = hapd->iconf; + int err = 0; + u8 accept_acl; + + if (hapd->iface->drv_max_acl_mac_addrs == 0) + return 0; + + if (conf->bss[0]->macaddr_acl == DENY_UNLESS_ACCEPTED) { + accept_acl = 1; + err = hostapd_set_acl_list(hapd, conf->bss[0]->accept_mac, + conf->bss[0]->num_accept_mac, + accept_acl); + if (err) { + wpa_printf(MSG_DEBUG, "Failed to set accept acl"); + return -1; + } + } else if (conf->bss[0]->macaddr_acl == ACCEPT_UNLESS_DENIED) { + accept_acl = 0; + err = hostapd_set_acl_list(hapd, conf->bss[0]->deny_mac, + conf->bss[0]->num_deny_mac, + accept_acl); + if (err) { + wpa_printf(MSG_DEBUG, "Failed to set deny acl"); + return -1; + } + } + return err; +} + + +static int start_ctrl_iface_bss(struct hostapd_data *hapd) +{ + if (!hapd->iface->interfaces || + !hapd->iface->interfaces->ctrl_iface_init) + return 0; + + if (hapd->iface->interfaces->ctrl_iface_init(hapd)) { + wpa_printf(MSG_ERROR, + "Failed to setup control interface for %s", + hapd->conf->iface); + return -1; + } + + return 0; +} + + +static int start_ctrl_iface(struct hostapd_iface *iface) +{ + size_t i; + + if (!iface->interfaces || !iface->interfaces->ctrl_iface_init) + return 0; + + for (i = 0; i < iface->num_bss; i++) { + struct hostapd_data *hapd = iface->bss[i]; + if (iface->interfaces->ctrl_iface_init(hapd)) { + wpa_printf(MSG_ERROR, + "Failed to setup control interface for %s", + hapd->conf->iface); + return -1; + } + } + + return 0; +} + + +/* When NO_IR flag is set and AP is stopped, clean up BSS parameters without + * deinitializing the driver and the control interfaces. A subsequent + * REG_CHANGE event can bring the AP back up. + */ +static void hostapd_no_ir_cleanup(struct hostapd_data *bss) +{ + hostapd_bss_deinit_no_free(bss); + hostapd_bss_link_deinit(bss); + hostapd_free_hapd_data(bss); + hostapd_cleanup_iface_partial(bss->iface); +} + + +static int hostapd_no_ir_channel_list_updated(struct hostapd_iface *iface, + void *ctx) +{ + bool all_no_ir, is_6ghz; + int i, j; + struct hostapd_hw_modes *mode = NULL; + + if (hostapd_get_hw_features(iface)) + return 0; + + all_no_ir = true; + is_6ghz = false; + + for (i = 0; i < iface->num_hw_features; i++) { + mode = &iface->hw_features[i]; + + if (mode->mode == iface->conf->hw_mode) { + if (iface->freq > 0 && + !hw_mode_get_channel(mode, iface->freq, NULL)) { + mode = NULL; + continue; + } + + for (j = 0; j < mode->num_channels; j++) { + if (!(mode->channels[j].flag & + HOSTAPD_CHAN_NO_IR)) + all_no_ir = false; + + if (is_6ghz_freq(mode->channels[j].freq)) + is_6ghz = true; + } + break; + } + } + + if (!mode || !is_6ghz) + return 0; + iface->current_mode = mode; + + if (iface->state == HAPD_IFACE_ENABLED) { + if (!all_no_ir) { + struct hostapd_channel_data *chan; + + chan = hw_get_channel_freq(iface->current_mode->mode, + iface->freq, NULL, + iface->hw_features, + iface->num_hw_features); + + if (!chan) { + wpa_printf(MSG_ERROR, + "NO_IR: Could not derive chan from freq"); + return 0; + } + + if (!(chan->flag & HOSTAPD_CHAN_NO_IR)) + return 0; + wpa_printf(MSG_DEBUG, + "NO_IR: The current channel has NO_IR flag now, stop AP."); + } else { + wpa_printf(MSG_DEBUG, + "NO_IR: All chan in new chanlist are NO_IR, stop AP."); + } + + hostapd_set_state(iface, HAPD_IFACE_NO_IR); + iface->is_no_ir = true; + hostapd_drv_stop_ap(iface->bss[0]); + hostapd_no_ir_cleanup(iface->bss[0]); + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_NO_IR); + } else if (iface->state == HAPD_IFACE_NO_IR) { + if (all_no_ir) { + wpa_printf(MSG_DEBUG, + "NO_IR: AP in NO_IR and all chan in the new chanlist are NO_IR. Ignore"); + return 0; + } + + if (!iface->conf->acs) { + struct hostapd_channel_data *chan; + + chan = hw_get_channel_freq(iface->current_mode->mode, + iface->freq, NULL, + iface->hw_features, + iface->num_hw_features); + if (!chan) { + wpa_printf(MSG_ERROR, + "NO_IR: Could not derive chan from freq"); + return 0; + } + + /* If the last operating channel is NO_IR, trigger ACS. + */ + if (chan->flag & HOSTAPD_CHAN_NO_IR) { + iface->freq = 0; + iface->conf->channel = 0; + if (acs_init(iface) != HOSTAPD_CHAN_ACS) + wpa_printf(MSG_ERROR, + "NO_IR: Could not start ACS"); + return 0; + } + } + + setup_interface2(iface); + } + + return 0; +} + + +static void channel_list_update_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_iface *iface = eloop_ctx; + + if (!iface->wait_channel_update) { + wpa_printf(MSG_INFO, "Channel list update timeout, but interface was not waiting for it"); + return; + } + + /* + * It is possible that the existing channel list is acceptable, so try + * to proceed. + */ + wpa_printf(MSG_DEBUG, "Channel list update timeout - try to continue anyway"); + setup_interface2(iface); +} + + +void hostapd_channel_list_updated(struct hostapd_iface *iface, int initiator) +{ + if (initiator == REGDOM_SET_BY_DRIVER) { + hostapd_for_each_interface(iface->interfaces, + hostapd_no_ir_channel_list_updated, + NULL); + return; + } + + if (!iface->wait_channel_update || initiator != REGDOM_SET_BY_USER) + return; + + wpa_printf(MSG_DEBUG, "Channel list updated - continue setup"); + eloop_cancel_timeout(channel_list_update_timeout, iface, NULL); + setup_interface2(iface); +} + + +static int setup_interface(struct hostapd_iface *iface) +{ + struct hostapd_data *hapd = iface->bss[0]; + size_t i; + + /* + * It is possible that setup_interface() is called after the interface + * was disabled etc., in which case driver_ap_teardown is possibly set + * to 1. Clear it here so any other key/station deletion, which is not + * part of a teardown flow, would also call the relevant driver + * callbacks. + */ + iface->driver_ap_teardown = 0; + + if (!iface->phy[0]) { + const char *phy = hostapd_drv_get_radio_name(hapd); + if (phy) { + wpa_printf(MSG_DEBUG, "phy: %s", phy); + os_strlcpy(iface->phy, phy, sizeof(iface->phy)); + } + } + + /* + * Make sure that all BSSes get configured with a pointer to the same + * driver interface. + */ + for (i = 1; i < iface->num_bss; i++) { + iface->bss[i]->driver = hapd->driver; + iface->bss[i]->drv_priv = hapd->drv_priv; + } + + if (hostapd_validate_bssid_configuration(iface)) + return -1; + + /* + * Initialize control interfaces early to allow external monitoring of + * channel setup operations that may take considerable amount of time + * especially for DFS cases. + */ + if (start_ctrl_iface(iface)) + return -1; + + if (hapd->iconf->country[0] && hapd->iconf->country[1]) { + char country[4], previous_country[4]; + + hostapd_set_state(iface, HAPD_IFACE_COUNTRY_UPDATE); + if (hostapd_get_country(hapd, previous_country) < 0) + previous_country[0] = '\0'; + + os_memcpy(country, hapd->iconf->country, 3); + country[3] = '\0'; + if (hostapd_set_country(hapd, country) < 0) { + wpa_printf(MSG_ERROR, "Failed to set country code"); + return -1; + } + + wpa_printf(MSG_DEBUG, "Previous country code %s, new country code %s", + previous_country, country); + + if (os_strncmp(previous_country, country, 2) != 0) { + wpa_printf(MSG_DEBUG, "Continue interface setup after channel list update"); + iface->wait_channel_update = 1; + eloop_register_timeout(5, 0, + channel_list_update_timeout, + iface, NULL); + return 0; + } + } + + return setup_interface2(iface); +} + + +static int configured_fixed_chan_to_freq(struct hostapd_iface *iface) +{ + int freq, i, j; + + if (!iface->conf->channel) + return 0; + if (iface->conf->op_class) { + freq = ieee80211_chan_to_freq(NULL, iface->conf->op_class, + iface->conf->channel); + if (freq < 0) { + wpa_printf(MSG_INFO, + "Could not convert op_class %u channel %u to operating frequency", + iface->conf->op_class, iface->conf->channel); + return -1; + } + iface->freq = freq; + return 0; + } + + /* Old configurations using only 2.4/5/60 GHz bands may not specify the + * op_class parameter. Select a matching channel from the configured + * mode using the channel parameter for these cases. + */ + for (j = 0; j < iface->num_hw_features; j++) { + struct hostapd_hw_modes *mode = &iface->hw_features[j]; + + if (iface->conf->hw_mode != HOSTAPD_MODE_IEEE80211ANY && + iface->conf->hw_mode != mode->mode) + continue; + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + + if (chan->chan == iface->conf->channel && + !is_6ghz_freq(chan->freq)) { + iface->freq = chan->freq; + return 0; + } + } + } + + wpa_printf(MSG_INFO, "Could not determine operating frequency"); + return -1; +} + + +static void hostapd_set_6ghz_sec_chan(struct hostapd_iface *iface) +{ + int bw; + + if (!is_6ghz_op_class(iface->conf->op_class)) + return; + + bw = op_class_to_bandwidth(iface->conf->op_class); + /* Assign the secondary channel if absent in config for + * bandwidths > 20 MHz */ + if (bw >= 40 && !iface->conf->secondary_channel) { + if (((iface->conf->channel - 1) / 4) % 2) + iface->conf->secondary_channel = -1; + else + iface->conf->secondary_channel = 1; + } +} + + +static int setup_interface2(struct hostapd_iface *iface) +{ + iface->wait_channel_update = 0; + iface->is_no_ir = false; + + if (hostapd_get_hw_features(iface)) { + /* Not all drivers support this yet, so continue without hw + * feature data. */ + } else { + int ret; + + if (iface->conf->acs && !iface->is_ch_switch_dfs) { + iface->freq = 0; + iface->conf->channel = 0; + } + iface->is_ch_switch_dfs = false; + + ret = configured_fixed_chan_to_freq(iface); + if (ret < 0) + goto fail; + + if (iface->conf->op_class) { + enum oper_chan_width ch_width; + + ch_width = op_class_to_ch_width(iface->conf->op_class); + hostapd_set_oper_chwidth(iface->conf, ch_width); + hostapd_set_6ghz_sec_chan(iface); + } + + ret = hostapd_select_hw_mode(iface); + if (ret < 0) { + wpa_printf(MSG_ERROR, "Could not select hw_mode and " + "channel. (%d)", ret); + goto fail; + } + if (ret == 1) { + wpa_printf(MSG_DEBUG, "Interface initialization will be completed in a callback (ACS)"); + return 0; + } + ret = hostapd_check_edmg_capab(iface); + if (ret < 0) + goto fail; + ret = hostapd_check_he_6ghz_capab(iface); + if (ret < 0) + goto fail; + ret = hostapd_check_ht_capab(iface); + if (ret < 0) + goto fail; + if (ret == 1) { + wpa_printf(MSG_DEBUG, "Interface initialization will " + "be completed in a callback"); + return 0; + } + + if (iface->conf->ieee80211h) + wpa_printf(MSG_DEBUG, "DFS support is enabled"); + } + return hostapd_setup_interface_complete(iface, 0); + +fail: + if (iface->is_no_ir) { + /* If AP is in NO_IR state, it can be reenabled by the driver + * regulatory update and EVENT_CHANNEL_LIST_CHANGED. */ + hostapd_set_state(iface, HAPD_IFACE_NO_IR); + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_NO_IR); + return 0; + } + + hostapd_set_state(iface, HAPD_IFACE_DISABLED); + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_DISABLED); + if (iface->interfaces && iface->interfaces->terminate_on_error) + eloop_terminate(); + return -1; +} + + +#ifdef CONFIG_FST + +static const u8 * fst_hostapd_get_bssid_cb(void *ctx) +{ + struct hostapd_data *hapd = ctx; + + return hapd->own_addr; +} + + +static void fst_hostapd_get_channel_info_cb(void *ctx, + enum hostapd_hw_mode *hw_mode, + u8 *channel) +{ + struct hostapd_data *hapd = ctx; + + *hw_mode = ieee80211_freq_to_chan(hapd->iface->freq, channel); +} + + +static int fst_hostapd_get_hw_modes_cb(void *ctx, + struct hostapd_hw_modes **modes) +{ + struct hostapd_data *hapd = ctx; + + *modes = hapd->iface->hw_features; + return hapd->iface->num_hw_features; +} + + +static void fst_hostapd_set_ies_cb(void *ctx, const struct wpabuf *fst_ies) +{ + struct hostapd_data *hapd = ctx; + + if (hapd->iface->fst_ies != fst_ies) { + hapd->iface->fst_ies = fst_ies; + if (ieee802_11_set_beacon(hapd)) + wpa_printf(MSG_WARNING, "FST: Cannot set beacon"); + } +} + + +static int fst_hostapd_send_action_cb(void *ctx, const u8 *da, + struct wpabuf *buf) +{ + struct hostapd_data *hapd = ctx; + + return hostapd_drv_send_action(hapd, hapd->iface->freq, 0, da, + wpabuf_head(buf), wpabuf_len(buf)); +} + + +static const struct wpabuf * fst_hostapd_get_mb_ie_cb(void *ctx, const u8 *addr) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta = ap_get_sta(hapd, addr); + + return sta ? sta->mb_ies : NULL; +} + + +static void fst_hostapd_update_mb_ie_cb(void *ctx, const u8 *addr, + const u8 *buf, size_t size) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta = ap_get_sta(hapd, addr); + + if (sta) { + struct mb_ies_info info; + + if (!mb_ies_info_by_ies(&info, buf, size)) { + wpabuf_free(sta->mb_ies); + sta->mb_ies = mb_ies_by_info(&info); + } + } +} + + +static const u8 * fst_hostapd_get_sta(struct fst_get_peer_ctx **get_ctx, + bool mb_only) +{ + struct sta_info *s = (struct sta_info *) *get_ctx; + + if (mb_only) { + for (; s && !s->mb_ies; s = s->next) + ; + } + + if (s) { + *get_ctx = (struct fst_get_peer_ctx *) s->next; + + return s->addr; + } + + *get_ctx = NULL; + return NULL; +} + + +static const u8 * fst_hostapd_get_peer_first(void *ctx, + struct fst_get_peer_ctx **get_ctx, + bool mb_only) +{ + struct hostapd_data *hapd = ctx; + + *get_ctx = (struct fst_get_peer_ctx *) hapd->sta_list; + + return fst_hostapd_get_sta(get_ctx, mb_only); +} + + +static const u8 * fst_hostapd_get_peer_next(void *ctx, + struct fst_get_peer_ctx **get_ctx, + bool mb_only) +{ + return fst_hostapd_get_sta(get_ctx, mb_only); +} + + +void fst_hostapd_fill_iface_obj(struct hostapd_data *hapd, + struct fst_wpa_obj *iface_obj) +{ + os_memset(iface_obj, 0, sizeof(*iface_obj)); + iface_obj->ctx = hapd; + iface_obj->get_bssid = fst_hostapd_get_bssid_cb; + iface_obj->get_channel_info = fst_hostapd_get_channel_info_cb; + iface_obj->get_hw_modes = fst_hostapd_get_hw_modes_cb; + iface_obj->set_ies = fst_hostapd_set_ies_cb; + iface_obj->send_action = fst_hostapd_send_action_cb; + iface_obj->get_mb_ie = fst_hostapd_get_mb_ie_cb; + iface_obj->update_mb_ie = fst_hostapd_update_mb_ie_cb; + iface_obj->get_peer_first = fst_hostapd_get_peer_first; + iface_obj->get_peer_next = fst_hostapd_get_peer_next; +} + +#endif /* CONFIG_FST */ + +#ifdef CONFIG_OWE + +static int hostapd_owe_iface_iter(struct hostapd_iface *iface, void *ctx) +{ + struct hostapd_data *hapd = ctx; + size_t i; + + for (i = 0; i < iface->num_bss; i++) { + struct hostapd_data *bss = iface->bss[i]; + + if (os_strcmp(hapd->conf->owe_transition_ifname, + bss->conf->iface) != 0) + continue; + + wpa_printf(MSG_DEBUG, + "OWE: ifname=%s found transition mode ifname=%s BSSID " + MACSTR " SSID %s", + hapd->conf->iface, bss->conf->iface, + MAC2STR(bss->own_addr), + wpa_ssid_txt(bss->conf->ssid.ssid, + bss->conf->ssid.ssid_len)); + if (!bss->conf->ssid.ssid_set || !bss->conf->ssid.ssid_len || + is_zero_ether_addr(bss->own_addr)) + continue; + + os_memcpy(hapd->conf->owe_transition_bssid, bss->own_addr, + ETH_ALEN); + os_memcpy(hapd->conf->owe_transition_ssid, + bss->conf->ssid.ssid, bss->conf->ssid.ssid_len); + hapd->conf->owe_transition_ssid_len = bss->conf->ssid.ssid_len; + wpa_printf(MSG_DEBUG, + "OWE: Copied transition mode information"); + return 1; + } + + return 0; +} + + +int hostapd_owe_trans_get_info(struct hostapd_data *hapd) +{ + if (hapd->conf->owe_transition_ssid_len > 0 && + !is_zero_ether_addr(hapd->conf->owe_transition_bssid)) + return 0; + + /* Find transition mode SSID/BSSID information from a BSS operated by + * this hostapd instance. */ + if (!hapd->iface->interfaces || + !hapd->iface->interfaces->for_each_interface) + return hostapd_owe_iface_iter(hapd->iface, hapd); + else + return hapd->iface->interfaces->for_each_interface( + hapd->iface->interfaces, hostapd_owe_iface_iter, hapd); +} + + +static int hostapd_owe_iface_iter2(struct hostapd_iface *iface, void *ctx) +{ + size_t i; + + for (i = 0; i < iface->num_bss; i++) { + struct hostapd_data *bss = iface->bss[i]; + int res; + + if (!bss->conf->owe_transition_ifname[0]) + continue; + if (bss->iface->state != HAPD_IFACE_ENABLED) { + wpa_printf(MSG_DEBUG, + "OWE: Interface %s state %s - defer beacon update", + bss->conf->iface, + hostapd_state_text(bss->iface->state)); + continue; + } + res = hostapd_owe_trans_get_info(bss); + if (res == 0) + continue; + wpa_printf(MSG_DEBUG, + "OWE: Matching transition mode interface enabled - update beacon data for %s", + bss->conf->iface); + ieee802_11_set_beacon(bss); + } + + return 0; +} + +#endif /* CONFIG_OWE */ + + +static void hostapd_owe_update_trans(struct hostapd_iface *iface) +{ +#ifdef CONFIG_OWE + /* Check whether the enabled BSS can complete OWE transition mode + * configuration for any pending interface. */ + if (!iface->interfaces || + !iface->interfaces->for_each_interface) + hostapd_owe_iface_iter2(iface, NULL); + else + iface->interfaces->for_each_interface( + iface->interfaces, hostapd_owe_iface_iter2, NULL); +#endif /* CONFIG_OWE */ +} + + +static void hostapd_interface_setup_failure_handler(void *eloop_ctx, + void *timeout_ctx) +{ + struct hostapd_iface *iface = eloop_ctx; + struct hostapd_data *hapd; + + if (iface->num_bss < 1 || !iface->bss || !iface->bss[0]) + return; + hapd = iface->bss[0]; + if (hapd->setup_complete_cb) + hapd->setup_complete_cb(hapd->setup_complete_cb_ctx); +} + + +static int hostapd_setup_interface_complete_sync(struct hostapd_iface *iface, + int err) +{ + struct hostapd_data *hapd = iface->bss[0]; + size_t j; + u8 *prev_addr; + int delay_apply_cfg = 0; + int res_dfs_offload = 0; + + if (err) + goto fail; + + wpa_printf(MSG_DEBUG, "Completing interface initialization"); + if (iface->freq) { +#ifdef NEED_AP_MLME + int res; +#endif /* NEED_AP_MLME */ + + wpa_printf(MSG_DEBUG, "Mode: %s Channel: %d " + "Frequency: %d MHz", + hostapd_hw_mode_txt(iface->conf->hw_mode), + iface->conf->channel, iface->freq); + +#ifdef NEED_AP_MLME + /* Handle DFS only if it is not offloaded to the driver */ + if (!(iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD)) { + /* Check DFS */ + res = hostapd_handle_dfs(iface); + if (res <= 0) { + if (res < 0) + goto fail; + return res; + } + } else { + /* If DFS is offloaded to the driver */ + res_dfs_offload = hostapd_handle_dfs_offload(iface); + if (res_dfs_offload <= 0) { + if (res_dfs_offload < 0) + goto fail; + } else { + wpa_printf(MSG_DEBUG, + "Proceed with AP/channel setup"); + /* + * If this is a DFS channel, move to completing + * AP setup. + */ + if (res_dfs_offload == 1) + goto dfs_offload; + /* Otherwise fall through. */ + } + } +#endif /* NEED_AP_MLME */ + +#ifdef CONFIG_MESH + if (iface->mconf != NULL) { + wpa_printf(MSG_DEBUG, + "%s: Mesh configuration will be applied while joining the mesh network", + iface->bss[0]->conf->iface); + delay_apply_cfg = 1; + } +#endif /* CONFIG_MESH */ + + if (!delay_apply_cfg && + hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq, + hapd->iconf->channel, + hapd->iconf->enable_edmg, + hapd->iconf->edmg_channel, + hapd->iconf->ieee80211n, + hapd->iconf->ieee80211ac, + hapd->iconf->ieee80211ax, + hapd->iconf->ieee80211be, + hapd->iconf->secondary_channel, + hostapd_get_oper_chwidth(hapd->iconf), + hostapd_get_oper_centr_freq_seg0_idx( + hapd->iconf), + hostapd_get_oper_centr_freq_seg1_idx( + hapd->iconf))) { + wpa_printf(MSG_ERROR, "Could not set channel for " + "kernel driver"); + goto fail; + } + } + + if (iface->current_mode) { + if (hostapd_prepare_rates(iface, iface->current_mode)) { + wpa_printf(MSG_ERROR, "Failed to prepare rates " + "table."); + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_WARNING, + "Failed to prepare rates table."); + goto fail; + } + } + + if (hapd->iconf->rts_threshold >= -1 && + hostapd_set_rts(hapd, hapd->iconf->rts_threshold) && + hapd->iconf->rts_threshold >= -1) { + wpa_printf(MSG_ERROR, "Could not set RTS threshold for " + "kernel driver"); + goto fail; + } + + if (hapd->iconf->fragm_threshold >= -1 && + hostapd_set_frag(hapd, hapd->iconf->fragm_threshold) && + hapd->iconf->fragm_threshold != -1) { + wpa_printf(MSG_ERROR, "Could not set fragmentation threshold " + "for kernel driver"); + goto fail; + } + + prev_addr = hapd->own_addr; + + for (j = 0; j < iface->num_bss; j++) { + hapd = iface->bss[j]; + if (j) + os_memcpy(hapd->own_addr, prev_addr, ETH_ALEN); + if (hostapd_setup_bss(hapd, j == 0, !iface->conf->mbssid)) { + for (;;) { + hapd = iface->bss[j]; + hostapd_bss_deinit_no_free(hapd); + hostapd_free_hapd_data(hapd); + if (j == 0) + break; + j--; + } + goto fail; + } + if (is_zero_ether_addr(hapd->conf->bssid)) + prev_addr = hapd->own_addr; + } + + if (hapd->iconf->mbssid) { + for (j = 0; hapd->iconf->mbssid && j < iface->num_bss; j++) { + hapd = iface->bss[j]; + if (hostapd_start_beacon(hapd, true)) { + for (;;) { + hapd = iface->bss[j]; + hostapd_bss_deinit_no_free(hapd); + hostapd_free_hapd_data(hapd); + if (j == 0) + break; + j--; + } + goto fail; + } + } + } + + hapd = iface->bss[0]; + + hostapd_tx_queue_params(iface); + + ap_list_init(iface); + + hostapd_set_acl(hapd); + + if (hostapd_driver_commit(hapd) < 0) { + wpa_printf(MSG_ERROR, "%s: Failed to commit driver " + "configuration", __func__); + goto fail; + } + + /* + * WPS UPnP module can be initialized only when the "upnp_iface" is up. + * If "interface" and "upnp_iface" are the same (e.g., non-bridge + * mode), the interface is up only after driver_commit, so initialize + * WPS after driver_commit. + */ + for (j = 0; j < iface->num_bss; j++) { + if (hostapd_init_wps_complete(iface->bss[j])) + goto fail; + } + + if ((iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) && + !res_dfs_offload) { + /* + * If freq is DFS, and DFS is offloaded to the driver, then wait + * for CAC to complete. + */ + wpa_printf(MSG_DEBUG, "%s: Wait for CAC to complete", __func__); + return res_dfs_offload; + } + +#ifdef NEED_AP_MLME +dfs_offload: +#endif /* NEED_AP_MLME */ + +#ifdef CONFIG_FST + if (hapd->iconf->fst_cfg.group_id[0]) { + struct fst_wpa_obj iface_obj; + + fst_hostapd_fill_iface_obj(hapd, &iface_obj); + iface->fst = fst_attach(hapd->conf->iface, hapd->own_addr, + &iface_obj, &hapd->iconf->fst_cfg); + if (!iface->fst) { + wpa_printf(MSG_ERROR, "Could not attach to FST %s", + hapd->iconf->fst_cfg.group_id); + goto fail; + } + } +#endif /* CONFIG_FST */ + + hostapd_set_state(iface, HAPD_IFACE_ENABLED); + hostapd_owe_update_trans(iface); + airtime_policy_update_init(iface); + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_ENABLED); + if (hapd->setup_complete_cb) + hapd->setup_complete_cb(hapd->setup_complete_cb_ctx); + +#ifdef CONFIG_MESH + if (delay_apply_cfg && !iface->mconf) { + wpa_printf(MSG_ERROR, "Error while completing mesh init"); + goto fail; + } +#endif /* CONFIG_MESH */ + + wpa_printf(MSG_DEBUG, "%s: Setup of interface done.", + iface->bss[0]->conf->iface); + if (iface->interfaces && iface->interfaces->terminate_on_error > 0) + iface->interfaces->terminate_on_error--; + + for (j = 0; j < iface->num_bss; j++) + hostapd_neighbor_set_own_report(iface->bss[j]); + + if (iface->interfaces && iface->interfaces->count > 1) + ieee802_11_set_beacons(iface); + + return 0; + +fail: + wpa_printf(MSG_ERROR, "Interface initialization failed"); + + if (iface->is_no_ir) { + hostapd_set_state(iface, HAPD_IFACE_NO_IR); + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_NO_IR); + return 0; + } + + hostapd_set_state(iface, HAPD_IFACE_DISABLED); + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_DISABLED); +#ifdef CONFIG_FST + if (iface->fst) { + fst_detach(iface->fst); + iface->fst = NULL; + } +#endif /* CONFIG_FST */ + + if (iface->interfaces && iface->interfaces->terminate_on_error) { + eloop_terminate(); + } else if (hapd->setup_complete_cb) { + /* + * Calling hapd->setup_complete_cb directly may cause iface + * deinitialization which may be accessed later by the caller. + */ + eloop_register_timeout(0, 0, + hostapd_interface_setup_failure_handler, + iface, NULL); + } + + return -1; +} + + +/** + * hostapd_setup_interface_complete - Complete interface setup + * + * This function is called when previous steps in the interface setup has been + * completed. This can also start operations, e.g., DFS, that will require + * additional processing before interface is ready to be enabled. Such + * operations will call this function from eloop callbacks when finished. + */ +int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) +{ + struct hapd_interfaces *interfaces = iface->interfaces; + struct hostapd_data *hapd = iface->bss[0]; + unsigned int i; + int not_ready_in_sync_ifaces = 0; + + if (!iface->need_to_start_in_sync) + return hostapd_setup_interface_complete_sync(iface, err); + + if (err) { + wpa_printf(MSG_ERROR, "Interface initialization failed"); + iface->need_to_start_in_sync = 0; + + if (iface->is_no_ir) { + hostapd_set_state(iface, HAPD_IFACE_NO_IR); + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_NO_IR); + return 0; + } + + hostapd_set_state(iface, HAPD_IFACE_DISABLED); + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_DISABLED); + if (interfaces && interfaces->terminate_on_error) + eloop_terminate(); + return -1; + } + + if (iface->ready_to_start_in_sync) { + /* Already in ready and waiting. should never happpen */ + return 0; + } + + for (i = 0; i < interfaces->count; i++) { + if (interfaces->iface[i]->need_to_start_in_sync && + !interfaces->iface[i]->ready_to_start_in_sync) + not_ready_in_sync_ifaces++; + } + + /* + * Check if this is the last interface, if yes then start all the other + * waiting interfaces. If not, add this interface to the waiting list. + */ + if (not_ready_in_sync_ifaces > 1 && iface->state == HAPD_IFACE_DFS) { + /* + * If this interface went through CAC, do not synchronize, just + * start immediately. + */ + iface->need_to_start_in_sync = 0; + wpa_printf(MSG_INFO, + "%s: Finished CAC - bypass sync and start interface", + iface->bss[0]->conf->iface); + return hostapd_setup_interface_complete_sync(iface, err); + } + + if (not_ready_in_sync_ifaces > 1) { + /* need to wait as there are other interfaces still coming up */ + iface->ready_to_start_in_sync = 1; + wpa_printf(MSG_INFO, + "%s: Interface waiting to sync with other interfaces", + iface->bss[0]->conf->iface); + return 0; + } + + wpa_printf(MSG_INFO, + "%s: Last interface to sync - starting all interfaces", + iface->bss[0]->conf->iface); + iface->need_to_start_in_sync = 0; + hostapd_setup_interface_complete_sync(iface, err); + for (i = 0; i < interfaces->count; i++) { + if (interfaces->iface[i]->need_to_start_in_sync && + interfaces->iface[i]->ready_to_start_in_sync) { + hostapd_setup_interface_complete_sync( + interfaces->iface[i], 0); + /* Only once the interfaces are sync started */ + interfaces->iface[i]->need_to_start_in_sync = 0; + } + } + + return 0; +} + + +/** + * hostapd_setup_interface - Setup of an interface + * @iface: Pointer to interface data. + * Returns: 0 on success, -1 on failure + * + * Initializes the driver interface, validates the configuration, + * and sets driver parameters based on the configuration. + * Flushes old stations, sets the channel, encryption, + * beacons, and WDS links based on the configuration. + * + * If interface setup requires more time, e.g., to perform HT co-ex scans, ACS, + * or DFS operations, this function returns 0 before such operations have been + * completed. The pending operations are registered into eloop and will be + * completed from eloop callbacks. Those callbacks end up calling + * hostapd_setup_interface_complete() once setup has been completed. + */ +int hostapd_setup_interface(struct hostapd_iface *iface) +{ + int ret; + + if (!iface->conf) + return -1; + ret = setup_interface(iface); + if (ret) { + wpa_printf(MSG_ERROR, "%s: Unable to setup interface.", + iface->conf->bss[0]->iface); + return -1; + } + + return 0; +} + + +/** + * hostapd_alloc_bss_data - Allocate and initialize per-BSS data + * @hapd_iface: Pointer to interface data + * @conf: Pointer to per-interface configuration + * @bss: Pointer to per-BSS configuration for this BSS + * Returns: Pointer to allocated BSS data + * + * This function is used to allocate per-BSS data structure. This data will be + * freed after hostapd_cleanup() is called for it during interface + * deinitialization. + */ +struct hostapd_data * +hostapd_alloc_bss_data(struct hostapd_iface *hapd_iface, + struct hostapd_config *conf, + struct hostapd_bss_config *bss) +{ + struct hostapd_data *hapd; + + hapd = os_zalloc(sizeof(*hapd)); + if (hapd == NULL) + return NULL; + + hapd->new_assoc_sta_cb = hostapd_new_assoc_sta; + hapd->iconf = conf; + hapd->conf = bss; + hapd->iface = hapd_iface; + if (conf) + hapd->driver = conf->driver; + hapd->ctrl_sock = -1; + dl_list_init(&hapd->ctrl_dst); + dl_list_init(&hapd->nr_db); + hapd->dhcp_sock = -1; +#ifdef CONFIG_IEEE80211R_AP + dl_list_init(&hapd->l2_queue); + dl_list_init(&hapd->l2_oui_queue); +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_SAE + dl_list_init(&hapd->sae_commit_queue); +#endif /* CONFIG_SAE */ + + return hapd; +} + + +static void hostapd_bss_deinit(struct hostapd_data *hapd) +{ + if (!hapd) + return; + wpa_printf(MSG_DEBUG, "%s: deinit bss %s", __func__, + hapd->conf ? hapd->conf->iface : "N/A"); + hostapd_bss_deinit_no_free(hapd); + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_DISABLED); +#ifdef CONFIG_SQLITE + if (hapd->rad_attr_db) { + sqlite3_close(hapd->rad_attr_db); + hapd->rad_attr_db = NULL; + } +#endif /* CONFIG_SQLITE */ + + hostapd_bss_link_deinit(hapd); + hostapd_cleanup(hapd); +} + + +void hostapd_interface_deinit(struct hostapd_iface *iface) +{ + int j; + + wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface); + if (iface == NULL) + return; + + hostapd_set_state(iface, HAPD_IFACE_DISABLED); + + eloop_cancel_timeout(channel_list_update_timeout, iface, NULL); + iface->wait_channel_update = 0; + iface->is_no_ir = false; + +#ifdef CONFIG_FST + if (iface->fst) { + fst_detach(iface->fst); + iface->fst = NULL; + } +#endif /* CONFIG_FST */ + + for (j = (int) iface->num_bss - 1; j >= 0; j--) { + if (!iface->bss) + break; + hostapd_bss_deinit(iface->bss[j]); + } + +#ifdef NEED_AP_MLME + hostapd_stop_setup_timers(iface); + eloop_cancel_timeout(ap_ht2040_timeout, iface, NULL); +#endif /* NEED_AP_MLME */ +} + + +#ifdef CONFIG_IEEE80211BE + +static void hostapd_mld_ref_inc(struct hostapd_mld *mld) +{ + if (!mld) + return; + + if (mld->refcount == HOSTAPD_MLD_MAX_REF_COUNT) { + wpa_printf(MSG_ERROR, "AP MLD %s: Ref count overflow", + mld->name); + return; + } + + mld->refcount++; +} + + +static void hostapd_mld_ref_dec(struct hostapd_mld *mld) +{ + if (!mld) + return; + + if (!mld->refcount) { + wpa_printf(MSG_ERROR, "AP MLD %s: Ref count underflow", + mld->name); + return; + } + + mld->refcount--; +} + +#endif /* CONFIG_IEEE80211BE */ + + +void hostapd_interface_free(struct hostapd_iface *iface) +{ + size_t j; + wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface); + for (j = 0; j < iface->num_bss; j++) { + if (!iface->bss) + break; +#ifdef CONFIG_IEEE80211BE + if (iface->bss[j]) + hostapd_mld_ref_dec(iface->bss[j]->mld); +#endif /* CONFIG_IEEE80211BE */ + wpa_printf(MSG_DEBUG, "%s: free hapd %p", + __func__, iface->bss[j]); + os_free(iface->bss[j]); + } + hostapd_cleanup_iface(iface); +} + + +struct hostapd_iface * hostapd_alloc_iface(void) +{ + struct hostapd_iface *hapd_iface; + + hapd_iface = os_zalloc(sizeof(*hapd_iface)); + if (!hapd_iface) + return NULL; + + dl_list_init(&hapd_iface->sta_seen); + + return hapd_iface; +} + + +#ifdef CONFIG_IEEE80211BE +static void hostapd_bss_alloc_link_id(struct hostapd_data *hapd) +{ + hapd->mld_link_id = hapd->mld->next_link_id++; + wpa_printf(MSG_DEBUG, "AP MLD: %s: Link ID %d assigned.", + hapd->mld->name, hapd->mld_link_id); +} +#endif /* CONFIG_IEEE80211BE */ + + +static void hostapd_bss_setup_multi_link(struct hostapd_data *hapd, + struct hapd_interfaces *interfaces) +{ +#ifdef CONFIG_IEEE80211BE + struct hostapd_mld *mld, **all_mld; + struct hostapd_bss_config *conf; + size_t i; + + conf = hapd->conf; + + if (!hapd->iconf || !hapd->iconf->ieee80211be || !conf->mld_ap || + conf->disable_11be) + return; + + for (i = 0; i < interfaces->mld_count; i++) { + mld = interfaces->mld[i]; + + if (!mld || os_strcmp(conf->iface, mld->name) != 0) + continue; + + hapd->mld = mld; + hostapd_mld_ref_inc(mld); + hostapd_bss_alloc_link_id(hapd); + break; + } + + if (hapd->mld) + return; + + mld = os_zalloc(sizeof(struct hostapd_mld)); + if (!mld) + goto fail; + + os_strlcpy(mld->name, conf->iface, sizeof(conf->iface)); + dl_list_init(&mld->links); + + wpa_printf(MSG_DEBUG, "AP MLD %s created", mld->name); + + hapd->mld = mld; + hostapd_mld_ref_inc(mld); + hostapd_bss_alloc_link_id(hapd); + + all_mld = os_realloc_array(interfaces->mld, interfaces->mld_count + 1, + sizeof(struct hostapd_mld *)); + if (!all_mld) + goto fail; + + interfaces->mld = all_mld; + interfaces->mld[interfaces->mld_count] = mld; + interfaces->mld_count++; + + return; +fail: + if (!mld) + return; + + wpa_printf(MSG_DEBUG, "AP MLD %s: free mld %p", mld->name, mld); + os_free(mld); + hapd->mld = NULL; +#endif /* CONFIG_IEEE80211BE */ +} + + +static void hostapd_cleanup_unused_mlds(struct hapd_interfaces *interfaces) +{ +#ifdef CONFIG_IEEE80211BE + struct hostapd_mld *mld, **all_mld; + size_t i, j, num_mlds; + bool forced_remove, remove; + + if (!interfaces->mld) + return; + + num_mlds = interfaces->mld_count; + + for (i = 0; i < interfaces->mld_count; i++) { + mld = interfaces->mld[i]; + if (!mld) + continue; + + remove = false; + forced_remove = false; + + if (!mld->refcount) + remove = true; + + /* If MLD is still being referenced but the number of interfaces + * is zero, it is safe to force its deletion. Normally, this + * should not happen but even if it does, let us free the + * memory. + */ + if (!remove && !interfaces->count) + forced_remove = true; + + if (!remove && !forced_remove) + continue; + + wpa_printf(MSG_DEBUG, "AP MLD %s: Freed%s", mld->name, + forced_remove ? " (forced)" : ""); + os_free(mld); + interfaces->mld[i] = NULL; + num_mlds--; + } + + if (!num_mlds) { + interfaces->mld_count = 0; + os_free(interfaces->mld); + interfaces->mld = NULL; + return; + } + + all_mld = os_zalloc(num_mlds * sizeof(struct hostapd_mld *)); + if (!all_mld) { + wpa_printf(MSG_ERROR, + "AP MLD: Failed to re-allocate the MLDs. Expect issues"); + return; + } + + for (i = 0, j = 0; i < interfaces->mld_count; i++) { + mld = interfaces->mld[i]; + if (!mld) + continue; + + all_mld[j++] = mld; + } + + /* This should not happen */ + if (j != num_mlds) { + wpa_printf(MSG_DEBUG, + "AP MLD: Some error occurred while reallocating MLDs. Expect issues."); + os_free(all_mld); + return; + } + + os_free(interfaces->mld); + interfaces->mld = all_mld; + interfaces->mld_count = num_mlds; +#endif /* CONFIG_IEEE80211BE */ +} + + +/** + * hostapd_init - Allocate and initialize per-interface data + * @config_file: Path to the configuration file + * Returns: Pointer to the allocated interface data or %NULL on failure + * + * This function is used to allocate main data structures for per-interface + * data. The allocated data buffer will be freed by calling + * hostapd_cleanup_iface(). + */ +struct hostapd_iface * hostapd_init(struct hapd_interfaces *interfaces, + const char *config_file) +{ + struct hostapd_iface *hapd_iface = NULL; + struct hostapd_config *conf = NULL; + struct hostapd_data *hapd; + size_t i; + + hapd_iface = hostapd_alloc_iface(); + if (hapd_iface == NULL) + goto fail; + + hapd_iface->config_fname = os_strdup(config_file); + if (hapd_iface->config_fname == NULL) + goto fail; + + conf = interfaces->config_read_cb(hapd_iface->config_fname); + if (conf == NULL) + goto fail; + hapd_iface->conf = conf; + + hapd_iface->num_bss = conf->num_bss; + hapd_iface->bss = os_calloc(conf->num_bss, + sizeof(struct hostapd_data *)); + if (hapd_iface->bss == NULL) + goto fail; + + for (i = 0; i < conf->num_bss; i++) { + hapd = hapd_iface->bss[i] = + hostapd_alloc_bss_data(hapd_iface, conf, + conf->bss[i]); + if (hapd == NULL) + goto fail; + hapd->msg_ctx = hapd; + hostapd_bss_setup_multi_link(hapd, interfaces); + } + + hapd_iface->is_ch_switch_dfs = false; + return hapd_iface; + +fail: + wpa_printf(MSG_ERROR, "Failed to set up interface with %s", + config_file); + if (conf) + hostapd_config_free(conf); + if (hapd_iface) { + os_free(hapd_iface->config_fname); + os_free(hapd_iface->bss); + wpa_printf(MSG_DEBUG, "%s: free iface %p", + __func__, hapd_iface); + os_free(hapd_iface); + } + return NULL; +} + + +static int ifname_in_use(struct hapd_interfaces *interfaces, const char *ifname) +{ + size_t i, j; + + for (i = 0; i < interfaces->count; i++) { + struct hostapd_iface *iface = interfaces->iface[i]; + for (j = 0; j < iface->num_bss; j++) { + struct hostapd_data *hapd = iface->bss[j]; + if (os_strcmp(ifname, hapd->conf->iface) == 0) + return 1; + } + } + + return 0; +} + + +/** + * hostapd_interface_init_bss - Read configuration file and init BSS data + * + * This function is used to parse configuration file for a BSS. This BSS is + * added to an existing interface sharing the same radio (if any) or a new + * interface is created if this is the first interface on a radio. This + * allocate memory for the BSS. No actual driver operations are started. + * + * This is similar to hostapd_interface_init(), but for a case where the + * configuration is used to add a single BSS instead of all BSSes for a radio. + */ +struct hostapd_iface * +hostapd_interface_init_bss(struct hapd_interfaces *interfaces, const char *phy, + const char *config_fname, int debug) +{ + struct hostapd_iface *new_iface = NULL, *iface = NULL; + struct hostapd_data *hapd; + int k; + size_t i, bss_idx; + + if (!phy || !*phy) + return NULL; + + for (i = 0; i < interfaces->count; i++) { + if (os_strcmp(interfaces->iface[i]->phy, phy) == 0) { + iface = interfaces->iface[i]; + break; + } + } + + wpa_printf(MSG_INFO, "Configuration file: %s (phy %s)%s", + config_fname, phy, iface ? "" : " --> new PHY"); + if (iface) { + struct hostapd_config *conf; + struct hostapd_bss_config **tmp_conf; + struct hostapd_data **tmp_bss; + struct hostapd_bss_config *bss; + const char *ifname; + + /* Add new BSS to existing iface */ + conf = interfaces->config_read_cb(config_fname); + if (conf == NULL) + return NULL; + if (conf->num_bss > 1) { + wpa_printf(MSG_ERROR, "Multiple BSSes specified in BSS-config"); + hostapd_config_free(conf); + return NULL; + } + + ifname = conf->bss[0]->iface; + if (ifname[0] != '\0' && ifname_in_use(interfaces, ifname)) { + wpa_printf(MSG_ERROR, + "Interface name %s already in use", ifname); + hostapd_config_free(conf); + return NULL; + } + + tmp_conf = os_realloc_array( + iface->conf->bss, iface->conf->num_bss + 1, + sizeof(struct hostapd_bss_config *)); + tmp_bss = os_realloc_array(iface->bss, iface->num_bss + 1, + sizeof(struct hostapd_data *)); + if (tmp_bss) + iface->bss = tmp_bss; + if (tmp_conf) { + iface->conf->bss = tmp_conf; + iface->conf->last_bss = tmp_conf[0]; + } + if (tmp_bss == NULL || tmp_conf == NULL) { + hostapd_config_free(conf); + return NULL; + } + bss = iface->conf->bss[iface->conf->num_bss] = conf->bss[0]; + iface->conf->num_bss++; + + hapd = hostapd_alloc_bss_data(iface, iface->conf, bss); + if (hapd == NULL) { + iface->conf->num_bss--; + hostapd_config_free(conf); + return NULL; + } + iface->conf->last_bss = bss; + iface->bss[iface->num_bss] = hapd; + hapd->msg_ctx = hapd; + hostapd_bss_setup_multi_link(hapd, interfaces); + + + bss_idx = iface->num_bss++; + conf->num_bss--; + conf->bss[0] = NULL; + hostapd_config_free(conf); + } else { + /* Add a new iface with the first BSS */ + new_iface = iface = hostapd_init(interfaces, config_fname); + if (!iface) + return NULL; + os_strlcpy(iface->phy, phy, sizeof(iface->phy)); + iface->interfaces = interfaces; + bss_idx = 0; + } + + for (k = 0; k < debug; k++) { + if (iface->bss[bss_idx]->conf->logger_stdout_level > 0) + iface->bss[bss_idx]->conf->logger_stdout_level--; + } + + if (iface->conf->bss[bss_idx]->iface[0] == '\0' && + !hostapd_drv_none(iface->bss[bss_idx])) { + wpa_printf(MSG_ERROR, "Interface name not specified in %s", + config_fname); + if (new_iface) + hostapd_interface_deinit_free(new_iface); + return NULL; + } + + return iface; +} + + +static void hostapd_cleanup_driver(const struct wpa_driver_ops *driver, + void *drv_priv, struct hostapd_iface *iface) +{ + if (!driver || !driver->hapd_deinit || !drv_priv) + return; + +#ifdef CONFIG_IEEE80211BE + /* In case of non-ML operation, de-init. But if ML operation exist, + * even if that's the last BSS in the interface, the driver (drv) could + * be in use for a different AP MLD. Hence, need to check if drv is + * still being used by some other BSS before de-initiallizing. */ + if (!iface->bss[0]->conf->mld_ap) { + driver->hapd_deinit(drv_priv); + } else if (hostapd_mld_is_first_bss(iface->bss[0]) && + driver->is_drv_shared && + !driver->is_drv_shared(drv_priv, iface->bss[0])) { + driver->hapd_deinit(drv_priv); + } else if (hostapd_if_link_remove(iface->bss[0], + WPA_IF_AP_BSS, + iface->bss[0]->conf->iface, + iface->bss[0]->mld_link_id)) { + wpa_printf(MSG_WARNING, "Failed to remove BSS interface %s", + iface->bss[0]->conf->iface); + } +#else /* CONFIG_IEEE80211BE */ + driver->hapd_deinit(drv_priv); +#endif /* CONFIG_IEEE80211BE */ + iface->bss[0]->drv_priv = NULL; +} + + +void hostapd_interface_deinit_free(struct hostapd_iface *iface) +{ + const struct wpa_driver_ops *driver; + void *drv_priv; + + wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface); + if (iface == NULL) + return; + wpa_printf(MSG_DEBUG, "%s: num_bss=%u conf->num_bss=%u", + __func__, (unsigned int) iface->num_bss, + (unsigned int) iface->conf->num_bss); + driver = iface->bss[0]->driver; + drv_priv = iface->bss[0]->drv_priv; + hostapd_interface_deinit(iface); + wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit", + __func__, driver, drv_priv); + hostapd_cleanup_driver(driver, drv_priv, iface); + hostapd_interface_free(iface); +} + + +static void hostapd_deinit_driver(const struct wpa_driver_ops *driver, + void *drv_priv, + struct hostapd_iface *hapd_iface) +{ + size_t j; + + wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit", + __func__, driver, drv_priv); + + hostapd_cleanup_driver(driver, drv_priv, hapd_iface); + + if (driver && driver->hapd_deinit && drv_priv) { + for (j = 0; j < hapd_iface->num_bss; j++) { + wpa_printf(MSG_DEBUG, "%s:bss[%d]->drv_priv=%p", + __func__, (int) j, + hapd_iface->bss[j]->drv_priv); + if (hapd_iface->bss[j]->drv_priv == drv_priv) { + hapd_iface->bss[j]->drv_priv = NULL; + hapd_iface->extended_capa = NULL; + hapd_iface->extended_capa_mask = NULL; + hapd_iface->extended_capa_len = 0; + } + } + } +} + + +static void hostapd_refresh_all_iface_beacons(struct hostapd_iface *hapd_iface) +{ + size_t j; + + if (!hapd_iface->interfaces || hapd_iface->interfaces->count <= 1) + return; + + for (j = 0; j < hapd_iface->interfaces->count; j++) { + if (hapd_iface->interfaces->iface[j] == hapd_iface) + continue; + + ieee802_11_update_beacons(hapd_iface->interfaces->iface[j]); + } +} + + +int hostapd_enable_iface(struct hostapd_iface *hapd_iface) +{ + size_t j; + + if (!hapd_iface) + return -1; + + if (hapd_iface->enable_iface_cb) + return hapd_iface->enable_iface_cb(hapd_iface); + + if (hapd_iface->bss[0]->drv_priv != NULL) { + wpa_printf(MSG_ERROR, "Interface %s already enabled", + hapd_iface->conf->bss[0]->iface); + return -1; + } + + wpa_printf(MSG_DEBUG, "Enable interface %s", + hapd_iface->conf->bss[0]->iface); + + for (j = 0; j < hapd_iface->num_bss; j++) + hostapd_set_security_params(hapd_iface->conf->bss[j], 1); + if (hostapd_config_check(hapd_iface->conf, 1) < 0) { + wpa_printf(MSG_INFO, "Invalid configuration - cannot enable"); + return -1; + } + + if (hapd_iface->interfaces == NULL || + hapd_iface->interfaces->driver_init == NULL || + hapd_iface->interfaces->driver_init(hapd_iface)) + return -1; + + if (hostapd_setup_interface(hapd_iface)) { + hostapd_deinit_driver(hapd_iface->bss[0]->driver, + hapd_iface->bss[0]->drv_priv, + hapd_iface); + return -1; + } + + hostapd_refresh_all_iface_beacons(hapd_iface); + + return 0; +} + + +int hostapd_reload_iface(struct hostapd_iface *hapd_iface) +{ + size_t j; + + wpa_printf(MSG_DEBUG, "Reload interface %s", + hapd_iface->conf->bss[0]->iface); + for (j = 0; j < hapd_iface->num_bss; j++) + hostapd_set_security_params(hapd_iface->conf->bss[j], 1); + if (hostapd_config_check(hapd_iface->conf, 1) < 0) { + wpa_printf(MSG_ERROR, "Updated configuration is invalid"); + return -1; + } + hostapd_clear_old(hapd_iface); + for (j = 0; j < hapd_iface->num_bss; j++) + hostapd_reload_bss(hapd_iface->bss[j]); + + return 0; +} + + +int hostapd_reload_bss_only(struct hostapd_data *bss) +{ + + wpa_printf(MSG_DEBUG, "Reload BSS %s", bss->conf->iface); + hostapd_set_security_params(bss->conf, 1); + if (hostapd_config_check(bss->iconf, 1) < 0) { + wpa_printf(MSG_ERROR, "Updated BSS configuration is invalid"); + return -1; + } + hostapd_clear_old_bss(bss); + hostapd_reload_bss(bss); + return 0; +} + + +int hostapd_disable_iface(struct hostapd_iface *hapd_iface) +{ + size_t j; + const struct wpa_driver_ops *driver; + void *drv_priv; + + if (hapd_iface == NULL) + return -1; + + if (hapd_iface->disable_iface_cb) + return hapd_iface->disable_iface_cb(hapd_iface); + + if (hapd_iface->bss[0]->drv_priv == NULL) { + wpa_printf(MSG_INFO, "Interface %s already disabled", + hapd_iface->conf->bss[0]->iface); + return -1; + } + + wpa_msg(hapd_iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_DISABLED); + driver = hapd_iface->bss[0]->driver; + drv_priv = hapd_iface->bss[0]->drv_priv; + + hapd_iface->driver_ap_teardown = + !!(hapd_iface->drv_flags & + WPA_DRIVER_FLAGS_AP_TEARDOWN_SUPPORT); + +#ifdef NEED_AP_MLME + for (j = 0; j < hapd_iface->num_bss; j++) + hostapd_cleanup_cs_params(hapd_iface->bss[j]); +#endif /* NEED_AP_MLME */ + + /* same as hostapd_interface_deinit without deinitializing ctrl-iface */ + for (j = 0; j < hapd_iface->num_bss; j++) { + struct hostapd_data *hapd = hapd_iface->bss[j]; + hostapd_bss_deinit_no_free(hapd); + hostapd_bss_link_deinit(hapd); + hostapd_free_hapd_data(hapd); + } + + hostapd_deinit_driver(driver, drv_priv, hapd_iface); + + /* From hostapd_cleanup_iface: These were initialized in + * hostapd_setup_interface and hostapd_setup_interface_complete + */ + hostapd_cleanup_iface_partial(hapd_iface); + + wpa_printf(MSG_DEBUG, "Interface %s disabled", + hapd_iface->bss[0]->conf->iface); + hostapd_set_state(hapd_iface, HAPD_IFACE_DISABLED); + hostapd_refresh_all_iface_beacons(hapd_iface); + return 0; +} + + +static struct hostapd_iface * +hostapd_iface_alloc(struct hapd_interfaces *interfaces) +{ + struct hostapd_iface **iface, *hapd_iface; + + iface = os_realloc_array(interfaces->iface, interfaces->count + 1, + sizeof(struct hostapd_iface *)); + if (iface == NULL) + return NULL; + interfaces->iface = iface; + hapd_iface = interfaces->iface[interfaces->count] = + hostapd_alloc_iface(); + if (hapd_iface == NULL) { + wpa_printf(MSG_ERROR, "%s: Failed to allocate memory for " + "the interface", __func__); + return NULL; + } + interfaces->count++; + hapd_iface->interfaces = interfaces; + + return hapd_iface; +} + + +static struct hostapd_config * +hostapd_config_alloc(struct hapd_interfaces *interfaces, const char *ifname, + const char *ctrl_iface, const char *driver) +{ + struct hostapd_bss_config *bss; + struct hostapd_config *conf; + + /* Allocates memory for bss and conf */ + conf = hostapd_config_defaults(); + if (conf == NULL) { + wpa_printf(MSG_ERROR, "%s: Failed to allocate memory for " + "configuration", __func__); + return NULL; + } + + if (driver) { + int j; + + for (j = 0; wpa_drivers[j]; j++) { + if (os_strcmp(driver, wpa_drivers[j]->name) == 0) { + conf->driver = wpa_drivers[j]; + goto skip; + } + } + + wpa_printf(MSG_ERROR, + "Invalid/unknown driver '%s' - registering the default driver", + driver); + } + + conf->driver = wpa_drivers[0]; + if (conf->driver == NULL) { + wpa_printf(MSG_ERROR, "No driver wrappers registered!"); + hostapd_config_free(conf); + return NULL; + } + +skip: + bss = conf->last_bss = conf->bss[0]; + + os_strlcpy(bss->iface, ifname, sizeof(bss->iface)); + bss->ctrl_interface = os_strdup(ctrl_iface); + if (bss->ctrl_interface == NULL) { + hostapd_config_free(conf); + return NULL; + } + + /* Reading configuration file skipped, will be done in SET! + * From reading the configuration till the end has to be done in + * SET + */ + return conf; +} + + +static int hostapd_data_alloc(struct hostapd_iface *hapd_iface, + struct hostapd_config *conf) +{ + size_t i; + struct hostapd_data *hapd; + + hapd_iface->bss = os_calloc(conf->num_bss, + sizeof(struct hostapd_data *)); + if (hapd_iface->bss == NULL) + return -1; + + for (i = 0; i < conf->num_bss; i++) { + hapd = hapd_iface->bss[i] = + hostapd_alloc_bss_data(hapd_iface, conf, conf->bss[i]); + if (hapd == NULL) { + while (i > 0) { + i--; + os_free(hapd_iface->bss[i]); + hapd_iface->bss[i] = NULL; + } + os_free(hapd_iface->bss); + hapd_iface->bss = NULL; + return -1; + } + hapd->msg_ctx = hapd; + hostapd_bss_setup_multi_link(hapd, hapd_iface->interfaces); + } + + hapd_iface->conf = conf; + hapd_iface->num_bss = conf->num_bss; + + return 0; +} + + +int hostapd_add_iface(struct hapd_interfaces *interfaces, char *buf) +{ + struct hostapd_config *conf = NULL; + struct hostapd_iface *hapd_iface = NULL, *new_iface = NULL; + struct hostapd_data *hapd; + char *ptr; + size_t i, j; + const char *conf_file = NULL, *phy_name = NULL; + + if (os_strncmp(buf, "bss_config=", 11) == 0) { + char *pos; + phy_name = buf + 11; + pos = os_strchr(phy_name, ':'); + if (!pos) + return -1; + *pos++ = '\0'; + conf_file = pos; + if (!os_strlen(conf_file)) + return -1; + + hapd_iface = hostapd_interface_init_bss(interfaces, phy_name, + conf_file, 0); + if (!hapd_iface) + return -1; + for (j = 0; j < interfaces->count; j++) { + if (interfaces->iface[j] == hapd_iface) + break; + } + if (j == interfaces->count) { + struct hostapd_iface **tmp; + tmp = os_realloc_array(interfaces->iface, + interfaces->count + 1, + sizeof(struct hostapd_iface *)); + if (!tmp) { + hostapd_interface_deinit_free(hapd_iface); + return -1; + } + interfaces->iface = tmp; + interfaces->iface[interfaces->count++] = hapd_iface; + new_iface = hapd_iface; + } + + if (new_iface) { + if (interfaces->driver_init(hapd_iface)) + goto fail; + + if (hostapd_setup_interface(hapd_iface)) { + hostapd_deinit_driver( + hapd_iface->bss[0]->driver, + hapd_iface->bss[0]->drv_priv, + hapd_iface); + goto fail; + } + } else { + /* Assign new BSS with bss[0]'s driver info */ + hapd = hapd_iface->bss[hapd_iface->num_bss - 1]; + hapd->driver = hapd_iface->bss[0]->driver; + hapd->drv_priv = hapd_iface->bss[0]->drv_priv; + os_memcpy(hapd->own_addr, hapd_iface->bss[0]->own_addr, + ETH_ALEN); + + if (start_ctrl_iface_bss(hapd) < 0 || + (hapd_iface->state == HAPD_IFACE_ENABLED && + hostapd_setup_bss(hapd, -1, true))) { + hostapd_bss_link_deinit(hapd); + hostapd_cleanup(hapd); + hapd_iface->bss[hapd_iface->num_bss - 1] = NULL; + hapd_iface->conf->num_bss--; + hapd_iface->num_bss--; + wpa_printf(MSG_DEBUG, "%s: free hapd %p %s", + __func__, hapd, hapd->conf->iface); + hostapd_config_free_bss(hapd->conf); + hapd->conf = NULL; +#ifdef CONFIG_IEEE80211BE + hostapd_mld_ref_dec(hapd->mld); +#endif /* CONFIG_IEEE80211BE */ + os_free(hapd); + return -1; + } + } + hostapd_owe_update_trans(hapd_iface); + return 0; + } + + ptr = os_strchr(buf, ' '); + if (ptr == NULL) + return -1; + *ptr++ = '\0'; + + if (os_strncmp(ptr, "config=", 7) == 0) + conf_file = ptr + 7; + + for (i = 0; i < interfaces->count; i++) { + bool mld_ap = false; + +#ifdef CONFIG_IEEE80211BE + mld_ap = interfaces->iface[i]->conf->bss[0]->mld_ap; +#endif /* CONFIG_IEEE80211BE */ + + if (!os_strcmp(interfaces->iface[i]->conf->bss[0]->iface, + buf) && !mld_ap) { + wpa_printf(MSG_INFO, "Cannot add interface - it " + "already exists"); + return -1; + } + } + + hapd_iface = hostapd_iface_alloc(interfaces); + if (hapd_iface == NULL) { + wpa_printf(MSG_ERROR, "%s: Failed to allocate memory " + "for interface", __func__); + goto fail; + } + new_iface = hapd_iface; + + if (conf_file && interfaces->config_read_cb) { + conf = interfaces->config_read_cb(conf_file); + if (conf && conf->bss) + os_strlcpy(conf->bss[0]->iface, buf, + sizeof(conf->bss[0]->iface)); + } else { + char *driver = os_strchr(ptr, ' '); + + if (driver) + *driver++ = '\0'; + conf = hostapd_config_alloc(interfaces, buf, ptr, driver); + } + + if (conf == NULL || conf->bss == NULL) { + wpa_printf(MSG_ERROR, "%s: Failed to allocate memory " + "for configuration", __func__); + goto fail; + } + + if (hostapd_data_alloc(hapd_iface, conf) < 0) { + wpa_printf(MSG_ERROR, "%s: Failed to allocate memory " + "for hostapd", __func__); + goto fail; + } + conf = NULL; + + if (start_ctrl_iface(hapd_iface) < 0) + goto fail; + + wpa_printf(MSG_INFO, "Add interface '%s'", + hapd_iface->conf->bss[0]->iface); + + return 0; + +fail: + if (conf) + hostapd_config_free(conf); + if (hapd_iface) { + if (hapd_iface->bss) { + for (i = 0; i < hapd_iface->num_bss; i++) { + hapd = hapd_iface->bss[i]; + if (!hapd) + continue; + if (hapd_iface->interfaces && + hapd_iface->interfaces->ctrl_iface_deinit) + hapd_iface->interfaces-> + ctrl_iface_deinit(hapd); + wpa_printf(MSG_DEBUG, "%s: free hapd %p (%s)", + __func__, hapd_iface->bss[i], + hapd->conf->iface); + hostapd_bss_link_deinit(hapd); + hostapd_cleanup(hapd); +#ifdef CONFIG_IEEE80211BE + hostapd_mld_ref_dec(hapd->mld); +#endif /* CONFIG_IEEE80211BE */ + os_free(hapd); + hapd_iface->bss[i] = NULL; + } + os_free(hapd_iface->bss); + hapd_iface->bss = NULL; + } + if (new_iface) { + interfaces->count--; + interfaces->iface[interfaces->count] = NULL; + hostapd_cleanup_unused_mlds(interfaces); + } + hostapd_cleanup_iface(hapd_iface); + } + return -1; +} + + +static int hostapd_remove_bss(struct hostapd_iface *iface, unsigned int idx) +{ + size_t i; + + wpa_printf(MSG_INFO, "Remove BSS '%s'", iface->conf->bss[idx]->iface); + + /* Remove hostapd_data only if it has already been initialized */ + if (idx < iface->num_bss) { + struct hostapd_data *hapd = iface->bss[idx]; + + hostapd_bss_deinit(hapd); + wpa_printf(MSG_DEBUG, "%s: free hapd %p (%s)", + __func__, hapd, hapd->conf->iface); + hostapd_config_free_bss(hapd->conf); + hapd->conf = NULL; +#ifdef CONFIG_IEEE80211BE + hostapd_mld_ref_dec(hapd->mld); +#endif /* CONFIG_IEEE80211BE */ + os_free(hapd); + + iface->num_bss--; + + for (i = idx; i < iface->num_bss; i++) + iface->bss[i] = iface->bss[i + 1]; + } else { + hostapd_config_free_bss(iface->conf->bss[idx]); + iface->conf->bss[idx] = NULL; + } + + iface->conf->num_bss--; + for (i = idx; i < iface->conf->num_bss; i++) + iface->conf->bss[i] = iface->conf->bss[i + 1]; + + return 0; +} + + +int hostapd_remove_iface(struct hapd_interfaces *interfaces, char *buf) +{ + struct hostapd_iface *hapd_iface; + size_t i, j, k = 0; + + for (i = 0; i < interfaces->count; i++) { + hapd_iface = interfaces->iface[i]; + if (hapd_iface == NULL) + return -1; + if (!os_strcmp(hapd_iface->conf->bss[0]->iface, buf)) { + wpa_printf(MSG_INFO, "Remove interface '%s'", buf); + hapd_iface->driver_ap_teardown = + !!(hapd_iface->drv_flags & + WPA_DRIVER_FLAGS_AP_TEARDOWN_SUPPORT); + + hostapd_interface_deinit_free(hapd_iface); + k = i; + while (k < (interfaces->count - 1)) { + interfaces->iface[k] = + interfaces->iface[k + 1]; + k++; + } + interfaces->count--; + hostapd_cleanup_unused_mlds(interfaces); + + return 0; + } + + for (j = 0; j < hapd_iface->conf->num_bss; j++) { + if (!os_strcmp(hapd_iface->conf->bss[j]->iface, buf)) { + hapd_iface->driver_ap_teardown = + !(hapd_iface->drv_flags & + WPA_DRIVER_FLAGS_AP_TEARDOWN_SUPPORT); + return hostapd_remove_bss(hapd_iface, j); + } + } + } + return -1; +} + + +/** + * hostapd_new_assoc_sta - Notify that a new station associated with the AP + * @hapd: Pointer to BSS data + * @sta: Pointer to the associated STA data + * @reassoc: 1 to indicate this was a re-association; 0 = first association + * + * This function will be called whenever a station associates with the AP. It + * can be called from ieee802_11.c for drivers that export MLME to hostapd and + * from drv_callbacks.c based on driver events for drivers that take care of + * management frames (IEEE 802.11 authentication and association) internally. + */ +void hostapd_new_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta, + int reassoc) +{ + if (hapd->tkip_countermeasures) { + hostapd_drv_sta_deauth(hapd, sta->addr, + WLAN_REASON_MICHAEL_MIC_FAILURE); + return; + } + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta) && + sta->mld_assoc_link_id != hapd->mld_link_id) + return; +#endif /* CONFIG_IEEE80211BE */ + + ap_sta_clear_disconnect_timeouts(hapd, sta); + sta->post_csa_sa_query = 0; + +#ifdef CONFIG_P2P + if (sta->p2p_ie == NULL && !sta->no_p2p_set) { + sta->no_p2p_set = 1; + hapd->num_sta_no_p2p++; + if (hapd->num_sta_no_p2p == 1) + hostapd_p2p_non_p2p_sta_connected(hapd); + } +#endif /* CONFIG_P2P */ + + airtime_policy_new_sta(hapd, sta); + + /* Start accounting here, if IEEE 802.1X and WPA are not used. + * IEEE 802.1X/WPA code will start accounting after the station has + * been authorized. */ + if (!hapd->conf->ieee802_1x && !hapd->conf->wpa && !hapd->conf->osen) { + ap_sta_set_authorized(hapd, sta, 1); + os_get_reltime(&sta->connected_time); + accounting_sta_start(hapd, sta); + } + + /* Start IEEE 802.1X authentication process for new stations */ + ieee802_1x_new_station(hapd, sta); + if (reassoc) { + if (sta->auth_alg != WLAN_AUTH_FT && + sta->auth_alg != WLAN_AUTH_FILS_SK && + sta->auth_alg != WLAN_AUTH_FILS_SK_PFS && + sta->auth_alg != WLAN_AUTH_FILS_PK && + !(sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS))) + wpa_auth_sm_event(sta->wpa_sm, WPA_REAUTH); + } else if (!(hapd->iface->drv_flags2 & + WPA_DRIVER_FLAGS2_4WAY_HANDSHAKE_AP_PSK)) { + /* The 4-way handshake offloaded case will have this handled + * based on the port authorized event. */ + wpa_auth_sta_associated(hapd->wpa_auth, sta->wpa_sm); + } + + if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_WIRED) { + if (eloop_cancel_timeout(ap_handle_timer, hapd, sta) > 0) { + wpa_printf(MSG_DEBUG, + "%s: %s: canceled wired ap_handle_timer timeout for " + MACSTR, + hapd->conf->iface, __func__, + MAC2STR(sta->addr)); + } + } else if (!(hapd->iface->drv_flags & + WPA_DRIVER_FLAGS_INACTIVITY_TIMER)) { + wpa_printf(MSG_DEBUG, + "%s: %s: reschedule ap_handle_timer timeout for " + MACSTR " (%d seconds - ap_max_inactivity)", + hapd->conf->iface, __func__, MAC2STR(sta->addr), + hapd->conf->ap_max_inactivity); + eloop_cancel_timeout(ap_handle_timer, hapd, sta); + eloop_register_timeout(hapd->conf->ap_max_inactivity, 0, + ap_handle_timer, hapd, sta); + } + +#ifdef CONFIG_MACSEC + if (hapd->conf->wpa_key_mgmt == WPA_KEY_MGMT_NONE && + hapd->conf->mka_psk_set) + ieee802_1x_create_preshared_mka_hapd(hapd, sta); + else + ieee802_1x_alloc_kay_sm_hapd(hapd, sta); +#endif /* CONFIG_MACSEC */ +} + + +const char * hostapd_state_text(enum hostapd_iface_state s) +{ + switch (s) { + case HAPD_IFACE_UNINITIALIZED: + return "UNINITIALIZED"; + case HAPD_IFACE_DISABLED: + return "DISABLED"; + case HAPD_IFACE_COUNTRY_UPDATE: + return "COUNTRY_UPDATE"; + case HAPD_IFACE_ACS: + return "ACS"; + case HAPD_IFACE_HT_SCAN: + return "HT_SCAN"; + case HAPD_IFACE_DFS: + return "DFS"; + case HAPD_IFACE_ENABLED: + return "ENABLED"; + case HAPD_IFACE_NO_IR: + return "NO_IR"; + } + + return "UNKNOWN"; +} + + +void hostapd_set_state(struct hostapd_iface *iface, enum hostapd_iface_state s) +{ + wpa_printf(MSG_INFO, "%s: interface state %s->%s", + iface->conf ? iface->conf->bss[0]->iface : "N/A", + hostapd_state_text(iface->state), hostapd_state_text(s)); + iface->state = s; +} + + +int hostapd_csa_in_progress(struct hostapd_iface *iface) +{ + unsigned int i; + + for (i = 0; i < iface->num_bss; i++) + if (iface->bss[i]->csa_in_progress) + return 1; + return 0; +} + + +#ifdef NEED_AP_MLME + +void free_beacon_data(struct beacon_data *beacon) +{ + os_free(beacon->head); + beacon->head = NULL; + os_free(beacon->tail); + beacon->tail = NULL; + os_free(beacon->probe_resp); + beacon->probe_resp = NULL; + os_free(beacon->beacon_ies); + beacon->beacon_ies = NULL; + os_free(beacon->proberesp_ies); + beacon->proberesp_ies = NULL; + os_free(beacon->assocresp_ies); + beacon->assocresp_ies = NULL; +} + + +static int hostapd_build_beacon_data(struct hostapd_data *hapd, + struct beacon_data *beacon) +{ + struct wpabuf *beacon_extra, *proberesp_extra, *assocresp_extra; + struct wpa_driver_ap_params params; + int ret; + + os_memset(beacon, 0, sizeof(*beacon)); + ret = ieee802_11_build_ap_params(hapd, ¶ms); + if (ret < 0) + return ret; + + ret = hostapd_build_ap_extra_ies(hapd, &beacon_extra, + &proberesp_extra, + &assocresp_extra); + if (ret) + goto free_ap_params; + + ret = -1; + beacon->head = os_memdup(params.head, params.head_len); + if (!beacon->head) + goto free_ap_extra_ies; + + beacon->head_len = params.head_len; + + beacon->tail = os_memdup(params.tail, params.tail_len); + if (!beacon->tail) + goto free_beacon; + + beacon->tail_len = params.tail_len; + + if (params.proberesp != NULL) { + beacon->probe_resp = os_memdup(params.proberesp, + params.proberesp_len); + if (!beacon->probe_resp) + goto free_beacon; + + beacon->probe_resp_len = params.proberesp_len; + } + + /* copy the extra ies */ + if (beacon_extra) { + beacon->beacon_ies = os_memdup(beacon_extra->buf, + wpabuf_len(beacon_extra)); + if (!beacon->beacon_ies) + goto free_beacon; + + beacon->beacon_ies_len = wpabuf_len(beacon_extra); + } + + if (proberesp_extra) { + beacon->proberesp_ies = os_memdup(proberesp_extra->buf, + wpabuf_len(proberesp_extra)); + if (!beacon->proberesp_ies) + goto free_beacon; + + beacon->proberesp_ies_len = wpabuf_len(proberesp_extra); + } + + if (assocresp_extra) { + beacon->assocresp_ies = os_memdup(assocresp_extra->buf, + wpabuf_len(assocresp_extra)); + if (!beacon->assocresp_ies) + goto free_beacon; + + beacon->assocresp_ies_len = wpabuf_len(assocresp_extra); + } + + ret = 0; +free_beacon: + /* if the function fails, the caller should not free beacon data */ + if (ret) + free_beacon_data(beacon); + +free_ap_extra_ies: + hostapd_free_ap_extra_ies(hapd, beacon_extra, proberesp_extra, + assocresp_extra); +free_ap_params: + ieee802_11_free_ap_params(¶ms); + return ret; +} + + +/* + * TODO: This flow currently supports only changing channel and width within + * the same hw_mode. Any other changes to MAC parameters or provided settings + * are not supported. + */ +static int hostapd_change_config_freq(struct hostapd_data *hapd, + struct hostapd_config *conf, + struct hostapd_freq_params *params, + struct hostapd_freq_params *old_params) +{ + int channel; + u8 seg0 = 0, seg1 = 0; + struct hostapd_hw_modes *mode; + + if (!params->channel) { + /* check if the new channel is supported by hw */ + params->channel = hostapd_hw_get_channel(hapd, params->freq); + } + + channel = params->channel; + if (!channel) + return -1; + + hostapd_determine_mode(hapd->iface); + mode = hapd->iface->current_mode; + + /* if a pointer to old_params is provided we save previous state */ + if (old_params && + hostapd_set_freq_params(old_params, conf->hw_mode, + hostapd_hw_get_freq(hapd, conf->channel), + conf->channel, conf->enable_edmg, + conf->edmg_channel, conf->ieee80211n, + conf->ieee80211ac, conf->ieee80211ax, + conf->ieee80211be, conf->secondary_channel, + hostapd_get_oper_chwidth(conf), + hostapd_get_oper_centr_freq_seg0_idx(conf), + hostapd_get_oper_centr_freq_seg1_idx(conf), + conf->vht_capab, + mode ? &mode->he_capab[IEEE80211_MODE_AP] : + NULL, + mode ? &mode->eht_capab[IEEE80211_MODE_AP] : + NULL, + hostapd_get_punct_bitmap(hapd))) + return -1; + + switch (params->bandwidth) { + case 0: + case 20: + conf->ht_capab &= ~HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET; + break; + case 40: + case 80: + case 160: + case 320: + conf->ht_capab |= HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET; + break; + default: + return -1; + } + + switch (params->bandwidth) { + case 0: + case 20: + case 40: + hostapd_set_oper_chwidth(conf, CONF_OPER_CHWIDTH_USE_HT); + break; + case 80: + if (params->center_freq2) + hostapd_set_oper_chwidth(conf, + CONF_OPER_CHWIDTH_80P80MHZ); + else + hostapd_set_oper_chwidth(conf, + CONF_OPER_CHWIDTH_80MHZ); + break; + case 160: + hostapd_set_oper_chwidth(conf, CONF_OPER_CHWIDTH_160MHZ); + break; + case 320: + hostapd_set_oper_chwidth(conf, CONF_OPER_CHWIDTH_320MHZ); + break; + default: + return -1; + } + + conf->channel = channel; + conf->ieee80211n = params->ht_enabled; + conf->ieee80211ac = params->vht_enabled; + conf->secondary_channel = params->sec_channel_offset; + if (params->center_freq1 && + ieee80211_freq_to_chan(params->center_freq1, &seg0) == + NUM_HOSTAPD_MODES) + return -1; + if (params->center_freq2 && + ieee80211_freq_to_chan(params->center_freq2, + &seg1) == NUM_HOSTAPD_MODES) + return -1; + hostapd_set_oper_centr_freq_seg0_idx(conf, seg0); + hostapd_set_oper_centr_freq_seg1_idx(conf, seg1); + + /* TODO: maybe call here hostapd_config_check here? */ + + return 0; +} + + +static int hostapd_fill_csa_settings(struct hostapd_data *hapd, + struct csa_settings *settings) +{ + struct hostapd_iface *iface = hapd->iface; + struct hostapd_freq_params old_freq; + int ret; +#ifdef CONFIG_IEEE80211BE + u16 old_punct_bitmap; +#endif /* CONFIG_IEEE80211BE */ + u8 chan, bandwidth; + + os_memset(&old_freq, 0, sizeof(old_freq)); + if (!iface || !iface->freq || hapd->csa_in_progress) + return -1; + + switch (settings->freq_params.bandwidth) { + case 80: + if (settings->freq_params.center_freq2) + bandwidth = CONF_OPER_CHWIDTH_80P80MHZ; + else + bandwidth = CONF_OPER_CHWIDTH_80MHZ; + break; + case 160: + bandwidth = CONF_OPER_CHWIDTH_160MHZ; + break; + case 320: + bandwidth = CONF_OPER_CHWIDTH_320MHZ; + break; + default: + bandwidth = CONF_OPER_CHWIDTH_USE_HT; + break; + } + + if (ieee80211_freq_to_channel_ext( + settings->freq_params.freq, + settings->freq_params.sec_channel_offset, + bandwidth, + &hapd->iface->cs_oper_class, + &chan) == NUM_HOSTAPD_MODES) { + wpa_printf(MSG_DEBUG, + "invalid frequency for channel switch (freq=%d, sec_channel_offset=%d, vht_enabled=%d, he_enabled=%d, eht_enabled=%d)", + settings->freq_params.freq, + settings->freq_params.sec_channel_offset, + settings->freq_params.vht_enabled, + settings->freq_params.he_enabled, + settings->freq_params.eht_enabled); + return -1; + } + + settings->freq_params.channel = chan; + + ret = hostapd_change_config_freq(iface->bss[0], iface->conf, + &settings->freq_params, + &old_freq); + if (ret) + return ret; + +#ifdef CONFIG_IEEE80211BE + old_punct_bitmap = iface->conf->punct_bitmap; + iface->conf->punct_bitmap = settings->punct_bitmap; +#endif /* CONFIG_IEEE80211BE */ + ret = hostapd_build_beacon_data(hapd, &settings->beacon_after); + + /* change back the configuration */ +#ifdef CONFIG_IEEE80211BE + iface->conf->punct_bitmap = old_punct_bitmap; +#endif /* CONFIG_IEEE80211BE */ + hostapd_change_config_freq(iface->bss[0], iface->conf, + &old_freq, NULL); + + if (ret) + return ret; + + /* set channel switch parameters for csa ie */ + hapd->cs_freq_params = settings->freq_params; + hapd->cs_count = settings->cs_count; + hapd->cs_block_tx = settings->block_tx; + + ret = hostapd_build_beacon_data(hapd, &settings->beacon_csa); + if (ret) { + free_beacon_data(&settings->beacon_after); + return ret; + } + + settings->counter_offset_beacon[0] = hapd->cs_c_off_beacon; + settings->counter_offset_presp[0] = hapd->cs_c_off_proberesp; + settings->counter_offset_beacon[1] = hapd->cs_c_off_ecsa_beacon; + settings->counter_offset_presp[1] = hapd->cs_c_off_ecsa_proberesp; + settings->link_id = -1; +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) + settings->link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_IEEE80211AX + settings->ubpr.unsol_bcast_probe_resp_tmpl = + hostapd_unsol_bcast_probe_resp(hapd, &settings->ubpr); +#endif /* CONFIG_IEEE80211AX */ + + return 0; +} + + +void hostapd_cleanup_cs_params(struct hostapd_data *hapd) +{ + os_memset(&hapd->cs_freq_params, 0, sizeof(hapd->cs_freq_params)); + hapd->cs_count = 0; + hapd->cs_block_tx = 0; + hapd->cs_c_off_beacon = 0; + hapd->cs_c_off_proberesp = 0; + hapd->csa_in_progress = 0; + hapd->cs_c_off_ecsa_beacon = 0; + hapd->cs_c_off_ecsa_proberesp = 0; +} + + +void hostapd_chan_switch_config(struct hostapd_data *hapd, + struct hostapd_freq_params *freq_params) +{ + if (freq_params->eht_enabled) + hapd->iconf->ch_switch_eht_config |= CH_SWITCH_EHT_ENABLED; + else + hapd->iconf->ch_switch_eht_config |= CH_SWITCH_EHT_DISABLED; + + if (freq_params->he_enabled) + hapd->iconf->ch_switch_he_config |= CH_SWITCH_HE_ENABLED; + else + hapd->iconf->ch_switch_he_config |= CH_SWITCH_HE_DISABLED; + + if (freq_params->vht_enabled) + hapd->iconf->ch_switch_vht_config |= CH_SWITCH_VHT_ENABLED; + else + hapd->iconf->ch_switch_vht_config |= CH_SWITCH_VHT_DISABLED; + + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "CHAN_SWITCH EHT config 0x%x HE config 0x%x VHT config 0x%x", + hapd->iconf->ch_switch_eht_config, + hapd->iconf->ch_switch_he_config, + hapd->iconf->ch_switch_vht_config); +} + + +int hostapd_switch_channel(struct hostapd_data *hapd, + struct csa_settings *settings) +{ + int ret; + + if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_CSA)) { + wpa_printf(MSG_INFO, "CSA is not supported"); + return -1; + } + + ret = hostapd_fill_csa_settings(hapd, settings); + if (ret) + return ret; + + ret = hostapd_drv_switch_channel(hapd, settings); + free_beacon_data(&settings->beacon_csa); + free_beacon_data(&settings->beacon_after); +#ifdef CONFIG_IEEE80211AX + os_free(settings->ubpr.unsol_bcast_probe_resp_tmpl); +#endif /* CONFIG_IEEE80211AX */ + + if (ret) { + /* if we failed, clean cs parameters */ + hostapd_cleanup_cs_params(hapd); + return ret; + } + + hapd->csa_in_progress = 1; + return 0; +} + + +void +hostapd_switch_channel_fallback(struct hostapd_iface *iface, + const struct hostapd_freq_params *freq_params) +{ + u8 seg0_idx = 0, seg1_idx = 0; + enum oper_chan_width bw = CONF_OPER_CHWIDTH_USE_HT; + u8 op_class, chan = 0; + + wpa_printf(MSG_DEBUG, "Restarting all CSA-related BSSes"); + + if (freq_params->center_freq1) + ieee80211_freq_to_chan(freq_params->center_freq1, &seg0_idx); + if (freq_params->center_freq2) + ieee80211_freq_to_chan(freq_params->center_freq2, &seg1_idx); + + switch (freq_params->bandwidth) { + case 0: + case 20: + case 40: + bw = CONF_OPER_CHWIDTH_USE_HT; + break; + case 80: + if (freq_params->center_freq2) { + bw = CONF_OPER_CHWIDTH_80P80MHZ; + iface->conf->vht_capab |= + VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ; + } else { + bw = CONF_OPER_CHWIDTH_80MHZ; + } + break; + case 160: + bw = CONF_OPER_CHWIDTH_160MHZ; + iface->conf->vht_capab |= VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; + break; + case 320: + bw = CONF_OPER_CHWIDTH_320MHZ; + break; + default: + wpa_printf(MSG_WARNING, "Unknown CSA bandwidth: %d", + freq_params->bandwidth); + break; + } + + iface->freq = freq_params->freq; + iface->conf->channel = freq_params->channel; + iface->conf->secondary_channel = freq_params->sec_channel_offset; + if (ieee80211_freq_to_channel_ext(freq_params->freq, + freq_params->sec_channel_offset, bw, + &op_class, &chan) == + NUM_HOSTAPD_MODES || + chan != freq_params->channel) + wpa_printf(MSG_INFO, "CSA: Channel mismatch: %d -> %d", + freq_params->channel, chan); + + iface->conf->op_class = op_class; + hostapd_set_oper_centr_freq_seg0_idx(iface->conf, seg0_idx); + hostapd_set_oper_centr_freq_seg1_idx(iface->conf, seg1_idx); + hostapd_set_oper_chwidth(iface->conf, bw); + iface->conf->ieee80211n = freq_params->ht_enabled; + iface->conf->ieee80211ac = freq_params->vht_enabled; + iface->conf->ieee80211ax = freq_params->he_enabled; + iface->conf->ieee80211be = freq_params->eht_enabled; + + /* + * cs_params must not be cleared earlier because the freq_params + * argument may actually point to one of these. + * These params will be cleared during interface disable below. + */ + hostapd_disable_iface(iface); + hostapd_enable_iface(iface); +} + + +#ifdef CONFIG_IEEE80211AX + +void hostapd_cleanup_cca_params(struct hostapd_data *hapd) +{ + hapd->cca_count = 0; + hapd->cca_color = 0; + hapd->cca_c_off_beacon = 0; + hapd->cca_c_off_proberesp = 0; + hapd->cca_in_progress = false; +} + + +int hostapd_fill_cca_settings(struct hostapd_data *hapd, + struct cca_settings *settings) +{ + struct hostapd_iface *iface = hapd->iface; + u8 old_color; + int ret; + + if (!iface || iface->conf->he_op.he_bss_color_disabled) + return -1; + + settings->link_id = -1; +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) + settings->link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + + old_color = iface->conf->he_op.he_bss_color; + iface->conf->he_op.he_bss_color = hapd->cca_color; + ret = hostapd_build_beacon_data(hapd, &settings->beacon_after); + if (ret) + return ret; + + iface->conf->he_op.he_bss_color = old_color; + + settings->cca_count = hapd->cca_count; + settings->cca_color = hapd->cca_color, + hapd->cca_in_progress = true; + + ret = hostapd_build_beacon_data(hapd, &settings->beacon_cca); + if (ret) { + free_beacon_data(&settings->beacon_after); + return ret; + } + + settings->ubpr.unsol_bcast_probe_resp_tmpl = + hostapd_unsol_bcast_probe_resp(hapd, &settings->ubpr); + + settings->counter_offset_beacon = hapd->cca_c_off_beacon; + settings->counter_offset_presp = hapd->cca_c_off_proberesp; + + return 0; +} + + +static void hostapd_switch_color_timeout_handler(void *eloop_data, + void *user_ctx) +{ + struct hostapd_data *hapd = (struct hostapd_data *) eloop_data; + os_time_t delta_t; + unsigned int b; + int i, r; + + /* CCA can be triggered once the handler constantly receives + * color collision events to for at least + * DOT11BSS_COLOR_COLLISION_AP_PERIOD (50 s by default). */ + delta_t = hapd->last_color_collision.sec - + hapd->first_color_collision.sec; + if (delta_t < DOT11BSS_COLOR_COLLISION_AP_PERIOD) + return; + + r = os_random() % HE_OPERATION_BSS_COLOR_MAX; + for (i = 0; i < HE_OPERATION_BSS_COLOR_MAX; i++) { + if (r && !(hapd->color_collision_bitmap & (1ULL << r))) + break; + + r = (r + 1) % HE_OPERATION_BSS_COLOR_MAX; + } + + if (i == HE_OPERATION_BSS_COLOR_MAX) { + /* There are no free colors so turn BSS coloring off */ + wpa_printf(MSG_INFO, + "No free colors left, turning off BSS coloring"); + hapd->iface->conf->he_op.he_bss_color_disabled = 1; + hapd->iface->conf->he_op.he_bss_color = os_random() % 63 + 1; + for (b = 0; b < hapd->iface->num_bss; b++) + ieee802_11_set_beacon(hapd->iface->bss[b]); + return; + } + + for (b = 0; b < hapd->iface->num_bss; b++) { + struct hostapd_data *bss = hapd->iface->bss[b]; + struct cca_settings settings; + int ret; + + hostapd_cleanup_cca_params(bss); + bss->cca_color = r; + bss->cca_count = 10; + + if (hostapd_fill_cca_settings(bss, &settings)) { + hostapd_cleanup_cca_params(bss); + continue; + } + + ret = hostapd_drv_switch_color(bss, &settings); + if (ret) + hostapd_cleanup_cca_params(bss); + + free_beacon_data(&settings.beacon_cca); + free_beacon_data(&settings.beacon_after); + os_free(settings.ubpr.unsol_bcast_probe_resp_tmpl); + } +} + + +void hostapd_switch_color(struct hostapd_data *hapd, u64 bitmap) +{ + struct os_reltime now; + + if (hapd->cca_in_progress) + return; + + if (os_get_reltime(&now)) + return; + + hapd->color_collision_bitmap = bitmap; + hapd->last_color_collision = now; + + if (eloop_is_timeout_registered(hostapd_switch_color_timeout_handler, + hapd, NULL)) + return; + + hapd->first_color_collision = now; + /* 10 s window as margin for persistent color collision reporting */ + eloop_register_timeout(DOT11BSS_COLOR_COLLISION_AP_PERIOD + 10, 0, + hostapd_switch_color_timeout_handler, + hapd, NULL); +} + +#endif /* CONFIG_IEEE80211AX */ + +#endif /* NEED_AP_MLME */ + + +struct hostapd_data * hostapd_get_iface(struct hapd_interfaces *interfaces, + const char *ifname) +{ + size_t i, j; + + for (i = 0; i < interfaces->count; i++) { + struct hostapd_iface *iface = interfaces->iface[i]; + + for (j = 0; j < iface->num_bss; j++) { + struct hostapd_data *hapd = iface->bss[j]; + + if (os_strcmp(ifname, hapd->conf->iface) == 0) + return hapd; + } + } + + return NULL; +} + + +void hostapd_periodic_iface(struct hostapd_iface *iface) +{ + size_t i; + + ap_list_timer(iface); + + for (i = 0; i < iface->num_bss; i++) { + struct hostapd_data *hapd = iface->bss[i]; + + if (!hapd->started) + continue; + +#ifndef CONFIG_NO_RADIUS + hostapd_acl_expire(hapd); +#endif /* CONFIG_NO_RADIUS */ + } +} + + +#ifdef CONFIG_OCV +void hostapd_ocv_check_csa_sa_query(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta; + + wpa_printf(MSG_DEBUG, "OCV: Post-CSA SA Query initiation check"); + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (!sta->post_csa_sa_query) + continue; + + wpa_printf(MSG_DEBUG, "OCV: OCVC STA " MACSTR + " did not start SA Query after CSA - disconnect", + MAC2STR(sta->addr)); + ap_sta_disconnect(hapd, sta, sta->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + } +} +#endif /* CONFIG_OCV */ + + +#ifdef CONFIG_IEEE80211BE + +struct hostapd_data * hostapd_mld_get_link_bss(struct hostapd_data *hapd, + u8 link_id) +{ + struct hostapd_iface *iface; + struct hostapd_data *bss; + unsigned int i, j; + + for (i = 0; i < hapd->iface->interfaces->count; i++) { + iface = hapd->iface->interfaces->iface[i]; + if (!iface) + continue; + + for (j = 0; j < iface->num_bss; j++) { + bss = iface->bss[j]; + + if (!bss->conf->mld_ap || + !hostapd_is_ml_partner(hapd, bss)) + continue; + + if (!bss->drv_priv) + continue; + + if (bss->mld_link_id == link_id) + return bss; + } + } + + return NULL; +} + + +bool hostapd_is_ml_partner(struct hostapd_data *hapd1, + struct hostapd_data *hapd2) +{ + if (!hapd1->conf->mld_ap || !hapd2->conf->mld_ap) + return false; + + return !os_strcmp(hapd1->conf->iface, hapd2->conf->iface); +} + + +u8 hostapd_get_mld_id(struct hostapd_data *hapd) +{ + if (!hapd->conf->mld_ap) + return 255; + + /* MLD ID 0 represents self */ + return 0; + + /* TODO: MLD ID for Multiple BSS cases */ +} + + +int hostapd_mld_add_link(struct hostapd_data *hapd) +{ + struct hostapd_mld *mld = hapd->mld; + + if (!hapd->conf->mld_ap) + return 0; + + /* Should not happen */ + if (!mld) + return -1; + + dl_list_add_tail(&mld->links, &hapd->link); + mld->num_links++; + + wpa_printf(MSG_DEBUG, "AP MLD %s: Link ID %d added. num_links: %d", + mld->name, hapd->mld_link_id, mld->num_links); + + if (mld->fbss) + return 0; + + mld->fbss = hapd; + wpa_printf(MSG_DEBUG, "AP MLD %s: First link BSS set to %p", + mld->name, mld->fbss); + return 0; +} + + +int hostapd_mld_remove_link(struct hostapd_data *hapd) +{ + struct hostapd_mld *mld = hapd->mld; + struct hostapd_data *next_fbss; + + if (!hapd->conf->mld_ap) + return 0; + + /* Should not happen */ + if (!mld) + return -1; + + dl_list_del(&hapd->link); + mld->num_links--; + + wpa_printf(MSG_DEBUG, "AP MLD %s: Link ID %d removed. num_links: %d", + mld->name, hapd->mld_link_id, mld->num_links); + + if (mld->fbss != hapd) + return 0; + + /* If the list is empty, all links are removed */ + if (dl_list_empty(&mld->links)) { + mld->fbss = NULL; + } else { + next_fbss = dl_list_entry(mld->links.next, struct hostapd_data, + link); + mld->fbss = next_fbss; + } + + wpa_printf(MSG_DEBUG, "AP MLD %s: First link BSS set to %p", + mld->name, mld->fbss); + return 0; +} + + +bool hostapd_mld_is_first_bss(struct hostapd_data *hapd) +{ + struct hostapd_mld *mld = hapd->mld; + + if (!hapd->conf->mld_ap) + return true; + + /* Should not happen */ + if (!mld) + return false; + + /* If fbss is not set, it is safe to assume the caller is the first BSS. + */ + if (!mld->fbss) + return true; + + return hapd == mld->fbss; +} + + +struct hostapd_data * hostapd_mld_get_first_bss(struct hostapd_data *hapd) +{ + struct hostapd_mld *mld = hapd->mld; + + if (!hapd->conf->mld_ap) + return NULL; + + /* Should not happen */ + if (!mld) + return NULL; + + return mld->fbss; +} + +#endif /* CONFIG_IEEE80211BE */ + + +u16 hostapd_get_punct_bitmap(struct hostapd_data *hapd) +{ + u16 punct_bitmap = 0; + +#ifdef CONFIG_IEEE80211BE + punct_bitmap = hapd->iconf->punct_bitmap; +#ifdef CONFIG_TESTING_OPTIONS + if (!punct_bitmap) + punct_bitmap = hapd->conf->eht_oper_puncturing_override; +#endif /* CONFIG_TESTING_OPTIONS */ +#endif /* CONFIG_IEEE80211BE */ + + return punct_bitmap; +} diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h new file mode 100644 index 0000000..dcf395c --- /dev/null +++ b/src/ap/hostapd.h @@ -0,0 +1,852 @@ +/* + * hostapd / Initialization and configuration + * Copyright (c) 2002-2014, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef HOSTAPD_H +#define HOSTAPD_H + +#ifdef CONFIG_SQLITE +#include +#endif /* CONFIG_SQLITE */ + +#include "common/defs.h" +#include "common/dpp.h" +#include "utils/list.h" +#include "ap_config.h" +#include "drivers/driver.h" + +#define OCE_STA_CFON_ENABLED(hapd) \ + ((hapd->conf->oce & OCE_STA_CFON) && \ + (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_OCE_STA_CFON)) +#define OCE_AP_ENABLED(hapd) \ + ((hapd->conf->oce & OCE_AP) && \ + (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_OCE_AP)) + +struct wpa_ctrl_dst; +struct radius_server_data; +struct upnp_wps_device_sm; +struct hostapd_data; +struct sta_info; +struct ieee80211_ht_capabilities; +struct full_dynamic_vlan; +enum wps_event; +union wps_event_data; +#ifdef CONFIG_MESH +struct mesh_conf; +#endif /* CONFIG_MESH */ + +#ifdef CONFIG_CTRL_IFACE_UDP +#define CTRL_IFACE_COOKIE_LEN 8 +#endif /* CONFIG_CTRL_IFACE_UDP */ + +struct hostapd_iface; +struct hostapd_mld; + +struct hapd_interfaces { + int (*reload_config)(struct hostapd_iface *iface); + struct hostapd_config * (*config_read_cb)(const char *config_fname); + int (*ctrl_iface_init)(struct hostapd_data *hapd); + void (*ctrl_iface_deinit)(struct hostapd_data *hapd); + int (*for_each_interface)(struct hapd_interfaces *interfaces, + int (*cb)(struct hostapd_iface *iface, + void *ctx), void *ctx); + int (*driver_init)(struct hostapd_iface *iface); + + size_t count; + int global_ctrl_sock; + struct dl_list global_ctrl_dst; + char *global_iface_path; + char *global_iface_name; +#ifndef CONFIG_NATIVE_WINDOWS + gid_t ctrl_iface_group; +#endif /* CONFIG_NATIVE_WINDOWS */ + struct hostapd_iface **iface; + + size_t terminate_on_error; +#ifndef CONFIG_NO_VLAN + struct dynamic_iface *vlan_priv; +#endif /* CONFIG_NO_VLAN */ +#ifdef CONFIG_ETH_P_OUI + struct dl_list eth_p_oui; /* OUI Extended EtherType handlers */ +#endif /* CONFIG_ETH_P_OUI */ + int eloop_initialized; + +#ifdef CONFIG_DPP + struct dpp_global *dpp; +#ifdef CONFIG_DPP3 + struct os_reltime dpp_pb_time; + struct os_reltime dpp_pb_announce_time; + struct dpp_pb_info dpp_pb[DPP_PB_INFO_COUNT]; + struct dpp_bootstrap_info *dpp_pb_bi; + u8 dpp_pb_c_nonce[DPP_MAX_NONCE_LEN]; + u8 dpp_pb_resp_hash[SHA256_MAC_LEN]; + struct os_reltime dpp_pb_last_resp; + bool dpp_pb_result_indicated; + char *dpp_pb_cmd; +#endif /* CONFIG_DPP3 */ +#endif /* CONFIG_DPP */ + +#ifdef CONFIG_CTRL_IFACE_UDP + unsigned char ctrl_iface_cookie[CTRL_IFACE_COOKIE_LEN]; +#endif /* CONFIG_CTRL_IFACE_UDP */ + +#ifdef CONFIG_IEEE80211BE + struct hostapd_mld **mld; + size_t mld_count; +#endif /* CONFIG_IEEE80211BE */ +}; + +enum hostapd_chan_status { + HOSTAPD_CHAN_VALID = 0, /* channel is ready */ + HOSTAPD_CHAN_INVALID = 1, /* no usable channel found */ + HOSTAPD_CHAN_ACS = 2, /* ACS work being performed */ + HOSTAPD_CHAN_INVALID_NO_IR = 3, /* channel invalid due to AFC NO IR */ +}; + +struct hostapd_probereq_cb { + int (*cb)(void *ctx, const u8 *sa, const u8 *da, const u8 *bssid, + const u8 *ie, size_t ie_len, int ssi_signal); + void *ctx; +}; + +#define HOSTAPD_RATE_BASIC 0x00000001 + +struct hostapd_rate_data { + int rate; /* rate in 100 kbps */ + int flags; /* HOSTAPD_RATE_ flags */ +}; + +struct hostapd_frame_info { + unsigned int freq; + u32 channel; + u32 datarate; + int ssi_signal; /* dBm */ +}; + +enum wps_status { + WPS_STATUS_SUCCESS = 1, + WPS_STATUS_FAILURE +}; + +enum pbc_status { + WPS_PBC_STATUS_DISABLE, + WPS_PBC_STATUS_ACTIVE, + WPS_PBC_STATUS_TIMEOUT, + WPS_PBC_STATUS_OVERLAP +}; + +struct wps_stat { + enum wps_status status; + enum wps_error_indication failure_reason; + enum pbc_status pbc_status; + u8 peer_addr[ETH_ALEN]; +}; + +struct hostapd_neighbor_entry { + struct dl_list list; + u8 bssid[ETH_ALEN]; + struct wpa_ssid_value ssid; + struct wpabuf *nr; + struct wpabuf *lci; + struct wpabuf *civic; + /* LCI update time */ + struct os_time lci_date; + int stationary; + u32 short_ssid; + u8 bss_parameters; +}; + +struct hostapd_sae_commit_queue { + struct dl_list list; + int rssi; + size_t len; + u8 msg[]; +}; + +/** + * struct hostapd_data - hostapd per-BSS data structure + */ +struct hostapd_data { + struct hostapd_iface *iface; + struct hostapd_config *iconf; + struct hostapd_bss_config *conf; + int interface_added; /* virtual interface added for this BSS */ + unsigned int started:1; + unsigned int disabled:1; + unsigned int reenable_beacon:1; + + u8 own_addr[ETH_ALEN]; + + int num_sta; /* number of entries in sta_list */ + struct sta_info *sta_list; /* STA info list head */ +#define STA_HASH_SIZE 256 +#define STA_HASH(sta) (sta[5]) + struct sta_info *sta_hash[STA_HASH_SIZE]; + + /* + * Bitfield for indicating which AIDs are allocated. Only AID values + * 1-2007 are used and as such, the bit at index 0 corresponds to AID + * 1. + */ +#define AID_WORDS ((2008 + 31) / 32) + u32 sta_aid[AID_WORDS]; + + const struct wpa_driver_ops *driver; + void *drv_priv; + + void (*new_assoc_sta_cb)(struct hostapd_data *hapd, + struct sta_info *sta, int reassoc); + + void *msg_ctx; /* ctx for wpa_msg() calls */ + void *msg_ctx_parent; /* parent interface ctx for wpa_msg() calls */ + + struct radius_client_data *radius; + u64 acct_session_id; + struct radius_das_data *radius_das; + + struct hostapd_cached_radius_acl *acl_cache; + struct hostapd_acl_query_data *acl_queries; + + struct wpa_authenticator *wpa_auth; + struct eapol_authenticator *eapol_auth; + struct eap_config *eap_cfg; + + struct rsn_preauth_interface *preauth_iface; + struct os_reltime michael_mic_failure; + int michael_mic_failures; + int tkip_countermeasures; + + int ctrl_sock; + struct dl_list ctrl_dst; + + void *ssl_ctx; + void *eap_sim_db_priv; + struct crypto_rsa_key *imsi_privacy_key; + struct radius_server_data *radius_srv; + struct dl_list erp_keys; /* struct eap_server_erp_key */ + + int parameter_set_count; + + /* Time Advertisement */ + u8 time_update_counter; + struct wpabuf *time_adv; + +#ifdef CONFIG_FULL_DYNAMIC_VLAN + struct full_dynamic_vlan *full_dynamic_vlan; +#endif /* CONFIG_FULL_DYNAMIC_VLAN */ + + struct l2_packet_data *l2; + +#ifdef CONFIG_IEEE80211R_AP + struct dl_list l2_queue; + struct dl_list l2_oui_queue; + struct eth_p_oui_ctx *oui_pull; + struct eth_p_oui_ctx *oui_resp; + struct eth_p_oui_ctx *oui_push; + struct eth_p_oui_ctx *oui_sreq; + struct eth_p_oui_ctx *oui_sresp; +#endif /* CONFIG_IEEE80211R_AP */ + + struct wps_context *wps; + + int beacon_set_done; + struct wpabuf *wps_beacon_ie; + struct wpabuf *wps_probe_resp_ie; +#ifdef CONFIG_WPS + unsigned int ap_pin_failures; + unsigned int ap_pin_failures_consecutive; + struct upnp_wps_device_sm *wps_upnp; + unsigned int ap_pin_lockout_time; + + struct wps_stat wps_stats; +#endif /* CONFIG_WPS */ + +#ifdef CONFIG_MACSEC + struct ieee802_1x_kay *kay; +#endif /* CONFIG_MACSEC */ + + struct hostapd_probereq_cb *probereq_cb; + size_t num_probereq_cb; + + void (*public_action_cb)(void *ctx, const u8 *buf, size_t len, + int freq); + void *public_action_cb_ctx; + void (*public_action_cb2)(void *ctx, const u8 *buf, size_t len, + int freq); + void *public_action_cb2_ctx; + + int (*vendor_action_cb)(void *ctx, const u8 *buf, size_t len, + int freq); + void *vendor_action_cb_ctx; + + void (*wps_reg_success_cb)(void *ctx, const u8 *mac_addr, + const u8 *uuid_e); + void *wps_reg_success_cb_ctx; + + void (*wps_event_cb)(void *ctx, enum wps_event event, + union wps_event_data *data); + void *wps_event_cb_ctx; + + void (*sta_authorized_cb)(void *ctx, const u8 *mac_addr, + int authorized, const u8 *p2p_dev_addr, + const u8 *ip); + void *sta_authorized_cb_ctx; + + void (*setup_complete_cb)(void *ctx); + void *setup_complete_cb_ctx; + + void (*new_psk_cb)(void *ctx, const u8 *mac_addr, + const u8 *p2p_dev_addr, const u8 *psk, + size_t psk_len); + void *new_psk_cb_ctx; + + /* channel switch parameters */ + struct hostapd_freq_params cs_freq_params; + u8 cs_count; + int cs_block_tx; + unsigned int cs_c_off_beacon; + unsigned int cs_c_off_proberesp; + int csa_in_progress; + unsigned int cs_c_off_ecsa_beacon; + unsigned int cs_c_off_ecsa_proberesp; + +#ifdef CONFIG_IEEE80211AX + bool cca_in_progress; + u8 cca_count; + u8 cca_color; + unsigned int cca_c_off_beacon; + unsigned int cca_c_off_proberesp; + struct os_reltime first_color_collision; + struct os_reltime last_color_collision; + u64 color_collision_bitmap; +#endif /* CONFIG_IEEE80211AX */ + +#ifdef CONFIG_P2P + struct p2p_data *p2p; + struct p2p_group *p2p_group; + struct wpabuf *p2p_beacon_ie; + struct wpabuf *p2p_probe_resp_ie; + + /* Number of non-P2P association stations */ + int num_sta_no_p2p; + + /* Periodic NoA (used only when no non-P2P clients in the group) */ + int noa_enabled; + int noa_start; + int noa_duration; +#endif /* CONFIG_P2P */ +#ifdef CONFIG_PROXYARP + struct l2_packet_data *sock_dhcp; + struct l2_packet_data *sock_ndisc; + bool x_snoop_initialized; +#endif /* CONFIG_PROXYARP */ +#ifdef CONFIG_MESH + int num_plinks; + int max_plinks; + void (*mesh_sta_free_cb)(struct hostapd_data *hapd, + struct sta_info *sta); + struct wpabuf *mesh_pending_auth; + struct os_reltime mesh_pending_auth_time; + u8 mesh_required_peer[ETH_ALEN]; +#endif /* CONFIG_MESH */ + +#ifdef CONFIG_SQLITE + struct hostapd_eap_user tmp_eap_user; +#endif /* CONFIG_SQLITE */ + +#ifdef CONFIG_SAE + +#define COMEBACK_KEY_SIZE 8 +#define COMEBACK_PENDING_IDX_SIZE 256 + + /** Key used for generating SAE anti-clogging tokens */ + u8 comeback_key[COMEBACK_KEY_SIZE]; + struct os_reltime last_comeback_key_update; + u16 comeback_idx; + u16 comeback_pending_idx[COMEBACK_PENDING_IDX_SIZE]; + int dot11RSNASAERetransPeriod; /* msec */ + struct dl_list sae_commit_queue; /* struct hostapd_sae_commit_queue */ +#endif /* CONFIG_SAE */ + +#ifdef CONFIG_TESTING_OPTIONS + unsigned int ext_mgmt_frame_handling:1; + unsigned int ext_eapol_frame_io:1; + + struct l2_packet_data *l2_test; + + enum wpa_alg last_gtk_alg; + int last_gtk_key_idx; + u8 last_gtk[WPA_GTK_MAX_LEN]; + size_t last_gtk_len; + + enum wpa_alg last_igtk_alg; + int last_igtk_key_idx; + u8 last_igtk[WPA_IGTK_MAX_LEN]; + size_t last_igtk_len; + + enum wpa_alg last_bigtk_alg; + int last_bigtk_key_idx; + u8 last_bigtk[WPA_BIGTK_MAX_LEN]; + size_t last_bigtk_len; + + bool force_backlog_bytes; +#endif /* CONFIG_TESTING_OPTIONS */ + +#ifdef CONFIG_MBO + unsigned int mbo_assoc_disallow; +#endif /* CONFIG_MBO */ + + struct dl_list nr_db; + + u8 beacon_req_token; + u8 lci_req_token; + u8 range_req_token; + u8 link_measurement_req_token; + unsigned int lci_req_active:1; + unsigned int range_req_active:1; + unsigned int link_mesr_req_active:1; + + int dhcp_sock; /* UDP socket used with the DHCP server */ + + struct ptksa_cache *ptksa; + +#ifdef CONFIG_DPP + int dpp_init_done; + struct dpp_authentication *dpp_auth; + u8 dpp_allowed_roles; + int dpp_qr_mutual; + int dpp_auth_ok_on_ack; + int dpp_in_response_listen; + struct gas_query_ap *gas; + struct dpp_pkex *dpp_pkex; + struct dpp_bootstrap_info *dpp_pkex_bi; + char *dpp_pkex_code; + size_t dpp_pkex_code_len; + char *dpp_pkex_identifier; + enum dpp_pkex_ver dpp_pkex_ver; + char *dpp_pkex_auth_cmd; + char *dpp_configurator_params; + struct os_reltime dpp_last_init; + struct os_reltime dpp_init_iter_start; + unsigned int dpp_init_max_tries; + unsigned int dpp_init_retry_time; + unsigned int dpp_resp_wait_time; + unsigned int dpp_resp_max_tries; + unsigned int dpp_resp_retry_time; +#ifdef CONFIG_DPP2 + struct wpabuf *dpp_presence_announcement; + struct dpp_bootstrap_info *dpp_chirp_bi; + int dpp_chirp_freq; + int *dpp_chirp_freqs; + int dpp_chirp_iter; + int dpp_chirp_round; + int dpp_chirp_scan_done; + int dpp_chirp_listen; + struct os_reltime dpp_relay_last_needs_ctrl; +#endif /* CONFIG_DPP2 */ +#ifdef CONFIG_TESTING_OPTIONS + char *dpp_config_obj_override; + char *dpp_discovery_override; + char *dpp_groups_override; + unsigned int dpp_ignore_netaccesskey_mismatch:1; +#endif /* CONFIG_TESTING_OPTIONS */ +#endif /* CONFIG_DPP */ + +#ifdef CONFIG_AIRTIME_POLICY + unsigned int num_backlogged_sta; + unsigned int airtime_weight; +#endif /* CONFIG_AIRTIME_POLICY */ + + u8 last_1x_eapol_key_replay_counter[8]; + +#ifdef CONFIG_SQLITE + sqlite3 *rad_attr_db; +#endif /* CONFIG_SQLITE */ + +#ifdef CONFIG_CTRL_IFACE_UDP + unsigned char ctrl_iface_cookie[CTRL_IFACE_COOKIE_LEN]; +#endif /* CONFIG_CTRL_IFACE_UDP */ + +#ifdef CONFIG_IEEE80211BE + u8 eht_mld_bss_param_change; + struct hostapd_mld *mld; + struct dl_list link; + u8 mld_link_id; +#ifdef CONFIG_TESTING_OPTIONS + u8 eht_mld_link_removal_count; +#endif /* CONFIG_TESTING_OPTIONS */ +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_NAN_USD + struct nan_de *nan_de; +#endif /* CONFIG_NAN_USD */ + + u64 scan_cookie; /* Scan instance identifier for the ongoing HT40 scan + */ +}; + + +struct hostapd_sta_info { + struct dl_list list; + u8 addr[ETH_ALEN]; + struct os_reltime last_seen; + int ssi_signal; +#ifdef CONFIG_TAXONOMY + struct wpabuf *probe_ie_taxonomy; +#endif /* CONFIG_TAXONOMY */ +}; + +#ifdef CONFIG_IEEE80211BE +/** + * struct hostapd_mld - hostapd per-mld data structure + */ +struct hostapd_mld { + char name[IFNAMSIZ + 1]; + u8 mld_addr[ETH_ALEN]; + u8 next_link_id; + u8 num_links; + /* Number of hostapd_data (hapd) referencing this. num_links cannot be + * used since num_links can go to 0 even when a BSS is disabled and + * when it is re-enabled, the MLD should exist and hence it cannot be + * freed when num_links is 0. + */ + u8 refcount; + + struct hostapd_data *fbss; + struct dl_list links; /* List head of all affiliated links */ +}; + +#define HOSTAPD_MLD_MAX_REF_COUNT 0xFF +#endif /* CONFIG_IEEE80211BE */ + +/** + * struct hostapd_iface - hostapd per-interface data structure + */ +struct hostapd_iface { + struct hapd_interfaces *interfaces; + void *owner; + char *config_fname; + struct hostapd_config *conf; + char phy[16]; /* Name of the PHY (radio) */ + + enum hostapd_iface_state { + HAPD_IFACE_UNINITIALIZED, + HAPD_IFACE_DISABLED, + HAPD_IFACE_COUNTRY_UPDATE, + HAPD_IFACE_ACS, + HAPD_IFACE_HT_SCAN, + HAPD_IFACE_DFS, + HAPD_IFACE_NO_IR, + HAPD_IFACE_ENABLED + } state; + +#ifdef CONFIG_MESH + struct mesh_conf *mconf; +#endif /* CONFIG_MESH */ + + size_t num_bss; + struct hostapd_data **bss; + + unsigned int wait_channel_update:1; + unsigned int cac_started:1; +#ifdef CONFIG_FST + struct fst_iface *fst; + const struct wpabuf *fst_ies; +#endif /* CONFIG_FST */ + + /* + * When set, indicates that the driver will handle the AP + * teardown: delete global keys, station keys, and stations. + */ + unsigned int driver_ap_teardown:1; + + /* + * When set, indicates that this interface is part of list of + * interfaces that need to be started together (synchronously). + */ + unsigned int need_to_start_in_sync:1; + + /* Ready to start but waiting for other interfaces to become ready. */ + unsigned int ready_to_start_in_sync:1; + + int num_ap; /* number of entries in ap_list */ + struct ap_info *ap_list; /* AP info list head */ + struct ap_info *ap_hash[STA_HASH_SIZE]; + + u64 drv_flags; + u64 drv_flags2; + unsigned int drv_rrm_flags; + + /* + * A bitmap of supported protocols for probe response offload. See + * struct wpa_driver_capa in driver.h + */ + unsigned int probe_resp_offloads; + + /* extended capabilities supported by the driver */ + const u8 *extended_capa, *extended_capa_mask; + unsigned int extended_capa_len; + + u16 mld_eml_capa, mld_mld_capa; + + unsigned int drv_max_acl_mac_addrs; + + struct hostapd_hw_modes *hw_features; + int num_hw_features; + struct hostapd_hw_modes *current_mode; + /* Rates that are currently used (i.e., filtered copy of + * current_mode->channels */ + int num_rates; + struct hostapd_rate_data *current_rates; + int *basic_rates; + int freq; + + bool radar_detected; + + /* Background radar configuration */ + struct { + int channel; + int secondary_channel; + int freq; + int centr_freq_seg0_idx; + int centr_freq_seg1_idx; + /* Main chain is on temporary channel during + * CAC detection on radar offchain. + */ + unsigned int temp_ch:1; + /* CAC started on radar offchain */ + unsigned int cac_started:1; + } radar_background; + + u16 hw_flags; + + /* Number of associated Non-ERP stations (i.e., stations using 802.11b + * in 802.11g BSS) */ + int num_sta_non_erp; + + /* Number of associated stations that do not support Short Slot Time */ + int num_sta_no_short_slot_time; + + /* Number of associated stations that do not support Short Preamble */ + int num_sta_no_short_preamble; + + int olbc; /* Overlapping Legacy BSS Condition */ + + /* Number of HT associated stations that do not support greenfield */ + int num_sta_ht_no_gf; + + /* Number of associated non-HT stations */ + int num_sta_no_ht; + + /* Number of HT associated stations 20 MHz */ + int num_sta_ht_20mhz; + + /* Number of HT40 intolerant stations */ + int num_sta_ht40_intolerant; + + /* Overlapping BSS information */ + int olbc_ht; + + u16 ht_op_mode; + + /* surveying helpers */ + + /* number of channels surveyed */ + unsigned int chans_surveyed; + + /* lowest observed noise floor in dBm */ + s8 lowest_nf; + + /* channel utilization calculation */ + u64 last_channel_time; + u64 last_channel_time_busy; + u8 channel_utilization; + + unsigned int chan_util_samples_sum; + unsigned int chan_util_num_sample_periods; + unsigned int chan_util_average; + + /* eCSA IE will be added only if operating class is specified */ + u8 cs_oper_class; + + unsigned int dfs_cac_ms; + struct os_reltime dfs_cac_start; + + /* Latched with the actual secondary channel information and will be + * used while juggling between HT20 and HT40 modes. */ + int secondary_ch; + +#ifdef CONFIG_ACS + unsigned int acs_num_completed_scans; + unsigned int acs_num_retries; +#endif /* CONFIG_ACS */ + + void (*scan_cb)(struct hostapd_iface *iface); + int num_ht40_scan_tries; + + struct dl_list sta_seen; /* struct hostapd_sta_info */ + unsigned int num_sta_seen; + + u8 dfs_domain; +#ifdef CONFIG_AIRTIME_POLICY + unsigned int airtime_quantum; +#endif /* CONFIG_AIRTIME_POLICY */ + + /* Previous WMM element information */ + struct hostapd_wmm_ac_params prev_wmm[WMM_AC_NUM]; + + /* Maximum number of interfaces supported for MBSSID advertisement */ + unsigned int mbssid_max_interfaces; + /* Maximum profile periodicity for enhanced MBSSID advertisement */ + unsigned int ema_max_periodicity; + + int (*enable_iface_cb)(struct hostapd_iface *iface); + int (*disable_iface_cb)(struct hostapd_iface *iface); + + /* Configured freq of interface is NO_IR */ + bool is_no_ir; + + bool is_ch_switch_dfs; /* Channel switch from ACS to DFS */ +}; + +/* hostapd.c */ +int hostapd_for_each_interface(struct hapd_interfaces *interfaces, + int (*cb)(struct hostapd_iface *iface, + void *ctx), void *ctx); +int hostapd_reload_config(struct hostapd_iface *iface); +void hostapd_reconfig_encryption(struct hostapd_data *hapd); +struct hostapd_data * +hostapd_alloc_bss_data(struct hostapd_iface *hapd_iface, + struct hostapd_config *conf, + struct hostapd_bss_config *bss); +int hostapd_setup_interface(struct hostapd_iface *iface); +int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err); +void hostapd_interface_deinit(struct hostapd_iface *iface); +void hostapd_interface_free(struct hostapd_iface *iface); +struct hostapd_iface * hostapd_alloc_iface(void); +struct hostapd_iface * hostapd_init(struct hapd_interfaces *interfaces, + const char *config_file); +struct hostapd_iface * +hostapd_interface_init_bss(struct hapd_interfaces *interfaces, const char *phy, + const char *config_fname, int debug); +void hostapd_new_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta, + int reassoc); +void hostapd_interface_deinit_free(struct hostapd_iface *iface); +int hostapd_enable_iface(struct hostapd_iface *hapd_iface); +int hostapd_reload_iface(struct hostapd_iface *hapd_iface); +int hostapd_reload_bss_only(struct hostapd_data *bss); +int hostapd_disable_iface(struct hostapd_iface *hapd_iface); +void hostapd_bss_deinit_no_free(struct hostapd_data *hapd); +void hostapd_free_hapd_data(struct hostapd_data *hapd); +void hostapd_cleanup_iface_partial(struct hostapd_iface *iface); +int hostapd_add_iface(struct hapd_interfaces *ifaces, char *buf); +int hostapd_remove_iface(struct hapd_interfaces *ifaces, char *buf); +void hostapd_channel_list_updated(struct hostapd_iface *iface, int initiator); +void hostapd_set_state(struct hostapd_iface *iface, enum hostapd_iface_state s); +const char * hostapd_state_text(enum hostapd_iface_state s); +int hostapd_csa_in_progress(struct hostapd_iface *iface); +void hostapd_chan_switch_config(struct hostapd_data *hapd, + struct hostapd_freq_params *freq_params); +int hostapd_switch_channel(struct hostapd_data *hapd, + struct csa_settings *settings); +void +hostapd_switch_channel_fallback(struct hostapd_iface *iface, + const struct hostapd_freq_params *freq_params); +void hostapd_cleanup_cs_params(struct hostapd_data *hapd); +void hostapd_periodic_iface(struct hostapd_iface *iface); +int hostapd_owe_trans_get_info(struct hostapd_data *hapd); +void hostapd_ocv_check_csa_sa_query(void *eloop_ctx, void *timeout_ctx); + +void hostapd_switch_color(struct hostapd_data *hapd, u64 bitmap); +void hostapd_cleanup_cca_params(struct hostapd_data *hapd); + +/* utils.c */ +int hostapd_register_probereq_cb(struct hostapd_data *hapd, + int (*cb)(void *ctx, const u8 *sa, + const u8 *da, const u8 *bssid, + const u8 *ie, size_t ie_len, + int ssi_signal), + void *ctx); +void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr, + int mld_assoc_link_id); + +/* drv_callbacks.c (TODO: move to somewhere else?) */ +void hostapd_notify_assoc_fils_finish(struct hostapd_data *hapd, + struct sta_info *sta); +int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, + const u8 *req_ie, size_t req_ielen, const u8 *resp_ie, + size_t resp_ielen, const u8 *link_addr, int reassoc); +void hostapd_notif_disassoc(struct hostapd_data *hapd, const u8 *addr); +void hostapd_event_sta_low_ack(struct hostapd_data *hapd, const u8 *addr); +void hostapd_event_connect_failed_reason(struct hostapd_data *hapd, + const u8 *addr, int reason_code); +int hostapd_probe_req_rx(struct hostapd_data *hapd, const u8 *sa, const u8 *da, + const u8 *bssid, const u8 *ie, size_t ie_len, + int ssi_signal); +void hostapd_event_ch_switch(struct hostapd_data *hapd, int freq, int ht, + int offset, int width, int cf1, int cf2, + u16 punct_bitmap, int finished); +struct survey_results; +void hostapd_event_get_survey(struct hostapd_iface *iface, + struct survey_results *survey_results); +void hostapd_acs_channel_selected(struct hostapd_data *hapd, + struct acs_selected_channels *acs_res); + +const struct hostapd_eap_user * +hostapd_get_eap_user(struct hostapd_data *hapd, const u8 *identity, + size_t identity_len, int phase2); + +struct hostapd_data * hostapd_get_iface(struct hapd_interfaces *interfaces, + const char *ifname); +void hostapd_event_sta_opmode_changed(struct hostapd_data *hapd, const u8 *addr, + enum smps_mode smps_mode, + enum chan_width chan_width, u8 rx_nss); + +#ifdef CONFIG_FST +void fst_hostapd_fill_iface_obj(struct hostapd_data *hapd, + struct fst_wpa_obj *iface_obj); +#endif /* CONFIG_FST */ + +int hostapd_set_acl(struct hostapd_data *hapd); +struct hostapd_data * hostapd_mbssid_get_tx_bss(struct hostapd_data *hapd); +int hostapd_mbssid_get_bss_index(struct hostapd_data *hapd); +struct hostapd_data * hostapd_mld_get_link_bss(struct hostapd_data *hapd, + u8 link_id); +int hostapd_link_remove(struct hostapd_data *hapd, u32 count); +bool hostapd_is_ml_partner(struct hostapd_data *hapd1, + struct hostapd_data *hapd2); +u8 hostapd_get_mld_id(struct hostapd_data *hapd); +int hostapd_mld_add_link(struct hostapd_data *hapd); +int hostapd_mld_remove_link(struct hostapd_data *hapd); +struct hostapd_data * hostapd_mld_get_first_bss(struct hostapd_data *hapd); + +void free_beacon_data(struct beacon_data *beacon); +int hostapd_fill_cca_settings(struct hostapd_data *hapd, + struct cca_settings *settings); + +#ifdef CONFIG_IEEE80211BE + +bool hostapd_mld_is_first_bss(struct hostapd_data *hapd); + +#define for_each_mld_link(partner, self) \ + dl_list_for_each(partner, &self->mld->links, struct hostapd_data, link) + +#else /* CONFIG_IEEE80211BE */ + +static inline bool hostapd_mld_is_first_bss(struct hostapd_data *hapd) +{ + return true; +} + +#define for_each_mld_link(partner, self) \ + if (false) + +#endif /* CONFIG_IEEE80211BE */ + +u16 hostapd_get_punct_bitmap(struct hostapd_data *hapd); + +#endif /* HOSTAPD_H */ diff --git a/src/ap/hs20.c b/src/ap/hs20.c new file mode 100644 index 0000000..05e9b9d --- /dev/null +++ b/src/ap/hs20.c @@ -0,0 +1,255 @@ +/* + * Hotspot 2.0 AP ANQP processing + * Copyright (c) 2009, Atheros Communications, Inc. + * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "common/ieee802_11_defs.h" +#include "common/wpa_ctrl.h" +#include "hostapd.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "sta_info.h" +#include "hs20.h" + + +u8 * hostapd_eid_hs20_indication(struct hostapd_data *hapd, u8 *eid) +{ + u8 conf; + if (!hapd->conf->hs20) + return eid; + *eid++ = WLAN_EID_VENDOR_SPECIFIC; + *eid++ = hapd->conf->hs20_release < 2 ? 5 : 7; + WPA_PUT_BE24(eid, OUI_WFA); + eid += 3; + *eid++ = HS20_INDICATION_OUI_TYPE; + conf = (hapd->conf->hs20_release - 1) << 4; /* Release Number */ + if (hapd->conf->hs20_release >= 2) + conf |= HS20_ANQP_DOMAIN_ID_PRESENT; + if (hapd->conf->disable_dgaf) + conf |= HS20_DGAF_DISABLED; + *eid++ = conf; + if (hapd->conf->hs20_release >= 2) { + WPA_PUT_LE16(eid, hapd->conf->anqp_domain_id); + eid += 2; + } + + return eid; +} + + +u8 * hostapd_eid_osen(struct hostapd_data *hapd, u8 *eid) +{ + u8 *len; + u16 capab; + + if (!hapd->conf->osen) + return eid; + + *eid++ = WLAN_EID_VENDOR_SPECIFIC; + len = eid++; /* to be filled */ + WPA_PUT_BE24(eid, OUI_WFA); + eid += 3; + *eid++ = HS20_OSEN_OUI_TYPE; + + /* Group Data Cipher Suite */ + RSN_SELECTOR_PUT(eid, RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED); + eid += RSN_SELECTOR_LEN; + + /* Pairwise Cipher Suite Count and List */ + WPA_PUT_LE16(eid, 1); + eid += 2; + RSN_SELECTOR_PUT(eid, RSN_CIPHER_SUITE_CCMP); + eid += RSN_SELECTOR_LEN; + + /* AKM Suite Count and List */ + WPA_PUT_LE16(eid, 1); + eid += 2; + RSN_SELECTOR_PUT(eid, RSN_AUTH_KEY_MGMT_OSEN); + eid += RSN_SELECTOR_LEN; + + /* RSN Capabilities */ + capab = 0; + if (hapd->conf->wmm_enabled) { + /* 4 PTKSA replay counters when using WMM */ + capab |= (RSN_NUM_REPLAY_COUNTERS_16 << 2); + } + if (hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + capab |= WPA_CAPABILITY_MFPC; + if (hapd->conf->ieee80211w == MGMT_FRAME_PROTECTION_REQUIRED) + capab |= WPA_CAPABILITY_MFPR; + } +#ifdef CONFIG_OCV + if (hapd->conf->ocv && + (hapd->iface->drv_flags2 & + (WPA_DRIVER_FLAGS2_AP_SME | WPA_DRIVER_FLAGS2_OCV))) + capab |= WPA_CAPABILITY_OCVC; +#endif /* CONFIG_OCV */ + WPA_PUT_LE16(eid, capab); + eid += 2; + + *len = eid - len - 1; + + return eid; +} + + +int hs20_send_wnm_notification(struct hostapd_data *hapd, const u8 *addr, + u8 osu_method, const char *url) +{ + struct wpabuf *buf; + size_t len = 0; + int ret; + + /* TODO: should refuse to send notification if the STA is not associated + * or if the STA did not indicate support for WNM-Notification */ + + if (url) { + len = 1 + os_strlen(url); + if (5 + len > 255) { + wpa_printf(MSG_INFO, "HS 2.0: Too long URL for " + "WNM-Notification: '%s'", url); + return -1; + } + } + + buf = wpabuf_alloc(4 + 7 + len); + if (buf == NULL) + return -1; + + wpabuf_put_u8(buf, WLAN_ACTION_WNM); + wpabuf_put_u8(buf, WNM_NOTIFICATION_REQ); + wpabuf_put_u8(buf, 1); /* Dialog token */ + wpabuf_put_u8(buf, 1); /* Type - 1 reserved for WFA */ + + /* Subscription Remediation subelement */ + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + wpabuf_put_u8(buf, 5 + len); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_WNM_SUB_REM_NEEDED); + if (url) { + wpabuf_put_u8(buf, len - 1); + wpabuf_put_data(buf, url, len - 1); + wpabuf_put_u8(buf, osu_method); + } else { + /* Server URL and Server Method fields not included */ + wpabuf_put_u8(buf, 0); + } + + ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr, + wpabuf_head(buf), wpabuf_len(buf)); + + wpabuf_free(buf); + + return ret; +} + + +int hs20_send_wnm_notification_deauth_req(struct hostapd_data *hapd, + const u8 *addr, + const struct wpabuf *payload) +{ + struct wpabuf *buf; + int ret; + + /* TODO: should refuse to send notification if the STA is not associated + * or if the STA did not indicate support for WNM-Notification */ + + buf = wpabuf_alloc(4 + 6 + wpabuf_len(payload)); + if (buf == NULL) + return -1; + + wpabuf_put_u8(buf, WLAN_ACTION_WNM); + wpabuf_put_u8(buf, WNM_NOTIFICATION_REQ); + wpabuf_put_u8(buf, 1); /* Dialog token */ + wpabuf_put_u8(buf, 1); /* Type - 1 reserved for WFA */ + + /* Deauthentication Imminent Notice subelement */ + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + wpabuf_put_u8(buf, 4 + wpabuf_len(payload)); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_WNM_DEAUTH_IMMINENT_NOTICE); + wpabuf_put_buf(buf, payload); + + ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr, + wpabuf_head(buf), wpabuf_len(buf)); + + wpabuf_free(buf); + + return ret; +} + + +int hs20_send_wnm_notification_t_c(struct hostapd_data *hapd, + const u8 *addr, const char *url) +{ + struct wpabuf *buf; + int ret; + size_t url_len; + + if (!url) { + wpa_printf(MSG_INFO, "HS 2.0: No T&C Server URL available"); + return -1; + } + + url_len = os_strlen(url); + if (5 + url_len > 255) { + wpa_printf(MSG_INFO, + "HS 2.0: Too long T&C Server URL for WNM-Notification: '%s'", + url); + return -1; + } + + buf = wpabuf_alloc(4 + 7 + url_len); + if (!buf) + return -1; + + wpabuf_put_u8(buf, WLAN_ACTION_WNM); + wpabuf_put_u8(buf, WNM_NOTIFICATION_REQ); + wpabuf_put_u8(buf, 1); /* Dialog token */ + wpabuf_put_u8(buf, 1); /* Type - 1 reserved for WFA */ + + /* Terms and Conditions Acceptance subelement */ + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + wpabuf_put_u8(buf, 4 + 1 + url_len); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_WNM_T_C_ACCEPTANCE); + wpabuf_put_u8(buf, url_len); + wpabuf_put_str(buf, url); + + ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr, + wpabuf_head(buf), wpabuf_len(buf)); + + wpabuf_free(buf); + + return ret; +} + + +void hs20_t_c_filtering(struct hostapd_data *hapd, struct sta_info *sta, + int enabled) +{ + if (enabled) { + wpa_printf(MSG_DEBUG, + "HS 2.0: Terms and Conditions filtering required for " + MACSTR, MAC2STR(sta->addr)); + sta->hs20_t_c_filtering = 1; + /* TODO: Enable firewall filtering for the STA */ + wpa_msg(hapd->msg_ctx, MSG_INFO, HS20_T_C_FILTERING_ADD MACSTR, + MAC2STR(sta->addr)); + } else { + wpa_printf(MSG_DEBUG, + "HS 2.0: Terms and Conditions filtering not required for " + MACSTR, MAC2STR(sta->addr)); + sta->hs20_t_c_filtering = 0; + /* TODO: Disable firewall filtering for the STA */ + wpa_msg(hapd->msg_ctx, MSG_INFO, + HS20_T_C_FILTERING_REMOVE MACSTR, MAC2STR(sta->addr)); + } +} diff --git a/src/ap/hs20.h b/src/ap/hs20.h new file mode 100644 index 0000000..e99e26e --- /dev/null +++ b/src/ap/hs20.h @@ -0,0 +1,26 @@ +/* + * Hotspot 2.0 AP ANQP processing + * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef HS20_H +#define HS20_H + +struct hostapd_data; + +u8 * hostapd_eid_hs20_indication(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_osen(struct hostapd_data *hapd, u8 *eid); +int hs20_send_wnm_notification(struct hostapd_data *hapd, const u8 *addr, + u8 osu_method, const char *url); +int hs20_send_wnm_notification_deauth_req(struct hostapd_data *hapd, + const u8 *addr, + const struct wpabuf *payload); +int hs20_send_wnm_notification_t_c(struct hostapd_data *hapd, + const u8 *addr, const char *url); +void hs20_t_c_filtering(struct hostapd_data *hapd, struct sta_info *sta, + int enabled); + +#endif /* HS20_H */ diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c new file mode 100644 index 0000000..c455660 --- /dev/null +++ b/src/ap/hw_features.c @@ -0,0 +1,1393 @@ +/* + * hostapd / Hardware feature query and different modes + * Copyright 2002-2003, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * Copyright (c) 2008-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "common/wpa_ctrl.h" +#include "common/hw_features_common.h" +#include "hostapd.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "acs.h" +#include "ieee802_11.h" +#include "beacon.h" +#include "hw_features.h" + + +void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, + size_t num_hw_features) +{ + size_t i; + + if (hw_features == NULL) + return; + + for (i = 0; i < num_hw_features; i++) { + os_free(hw_features[i].channels); + os_free(hw_features[i].rates); + } + + os_free(hw_features); +} + + +#ifndef CONFIG_NO_STDOUT_DEBUG +static char * dfs_info(struct hostapd_channel_data *chan) +{ + static char info[256]; + char *state; + + switch (chan->flag & HOSTAPD_CHAN_DFS_MASK) { + case HOSTAPD_CHAN_DFS_UNKNOWN: + state = "unknown"; + break; + case HOSTAPD_CHAN_DFS_USABLE: + state = "usable"; + break; + case HOSTAPD_CHAN_DFS_UNAVAILABLE: + state = "unavailable"; + break; + case HOSTAPD_CHAN_DFS_AVAILABLE: + state = "available"; + break; + default: + return ""; + } + os_snprintf(info, sizeof(info), " (DFS state = %s)", state); + info[sizeof(info) - 1] = '\0'; + + return info; +} +#endif /* CONFIG_NO_STDOUT_DEBUG */ + + +int hostapd_get_hw_features(struct hostapd_iface *iface) +{ + struct hostapd_data *hapd = iface->bss[0]; + int i, j; + u16 num_modes, flags; + struct hostapd_hw_modes *modes; + u8 dfs_domain; + enum hostapd_hw_mode mode = HOSTAPD_MODE_IEEE80211ANY; + bool is_6ghz = false; + bool orig_mode_valid = false; + + if (hostapd_drv_none(hapd)) + return -1; + modes = hostapd_get_hw_feature_data(hapd, &num_modes, &flags, + &dfs_domain); + if (modes == NULL) { + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "Fetching hardware channel/rate support not " + "supported."); + return -1; + } + + iface->hw_flags = flags; + iface->dfs_domain = dfs_domain; + + if (iface->current_mode) { + /* + * Received driver event CHANNEL_LIST_CHANGED when the current + * hw mode is valid. Clear iface->current_mode temporarily as + * the mode instance will be replaced with a new instance and + * the current pointer would be pointing to freed memory. + */ + orig_mode_valid = true; + mode = iface->current_mode->mode; + is_6ghz = iface->current_mode->is_6ghz; + iface->current_mode = NULL; + } + hostapd_free_hw_features(iface->hw_features, iface->num_hw_features); + iface->hw_features = modes; + iface->num_hw_features = num_modes; + + for (i = 0; i < num_modes; i++) { + struct hostapd_hw_modes *feature = &modes[i]; + int dfs_enabled = hapd->iconf->ieee80211h && + (iface->drv_flags & WPA_DRIVER_FLAGS_RADAR); + + /* Restore orignal mode if possible */ + if (orig_mode_valid && feature->mode == mode && + feature->num_channels > 0 && + is_6ghz == is_6ghz_freq(feature->channels[0].freq)) + iface->current_mode = feature; + + /* set flag for channels we can use in current regulatory + * domain */ + for (j = 0; j < feature->num_channels; j++) { + int dfs = 0; + + /* + * Disable all channels that are marked not to allow + * to initiate radiation (a.k.a. passive scan and no + * IBSS). + * Use radar channels only if the driver supports DFS. + */ + if ((feature->channels[j].flag & + HOSTAPD_CHAN_RADAR) && dfs_enabled) { + dfs = 1; + } else if (((feature->channels[j].flag & + HOSTAPD_CHAN_RADAR) && + !(iface->drv_flags & + WPA_DRIVER_FLAGS_DFS_OFFLOAD)) || + (feature->channels[j].flag & + HOSTAPD_CHAN_NO_IR)) { + feature->channels[j].flag |= + HOSTAPD_CHAN_DISABLED; + } + + if (feature->channels[j].flag & HOSTAPD_CHAN_DISABLED) + continue; + + wpa_printf(MSG_MSGDUMP, "Allowed channel: mode=%d " + "chan=%d freq=%d MHz max_tx_power=%d dBm%s", + feature->mode, + feature->channels[j].chan, + feature->channels[j].freq, + feature->channels[j].max_tx_power, + dfs ? dfs_info(&feature->channels[j]) : ""); + } + } + + if (orig_mode_valid && !iface->current_mode) { + wpa_printf(MSG_ERROR, + "%s: Could not update iface->current_mode", + __func__); + } + + return 0; +} + + +int hostapd_prepare_rates(struct hostapd_iface *iface, + struct hostapd_hw_modes *mode) +{ + int i, num_basic_rates = 0; + int basic_rates_a[] = { 60, 120, 240, -1 }; + int basic_rates_b[] = { 10, 20, -1 }; + int basic_rates_g[] = { 10, 20, 55, 110, -1 }; + int *basic_rates; + + if (iface->conf->basic_rates) + basic_rates = iface->conf->basic_rates; + else switch (mode->mode) { + case HOSTAPD_MODE_IEEE80211A: + basic_rates = basic_rates_a; + break; + case HOSTAPD_MODE_IEEE80211B: + basic_rates = basic_rates_b; + break; + case HOSTAPD_MODE_IEEE80211G: + basic_rates = basic_rates_g; + break; + case HOSTAPD_MODE_IEEE80211AD: + return 0; /* No basic rates for 11ad */ + default: + return -1; + } + + i = 0; + while (basic_rates[i] >= 0) + i++; + if (i) + i++; /* -1 termination */ + os_free(iface->basic_rates); + iface->basic_rates = os_malloc(i * sizeof(int)); + if (iface->basic_rates) + os_memcpy(iface->basic_rates, basic_rates, i * sizeof(int)); + + os_free(iface->current_rates); + iface->num_rates = 0; + + iface->current_rates = + os_calloc(mode->num_rates, sizeof(struct hostapd_rate_data)); + if (!iface->current_rates) { + wpa_printf(MSG_ERROR, "Failed to allocate memory for rate " + "table."); + return -1; + } + + for (i = 0; i < mode->num_rates; i++) { + struct hostapd_rate_data *rate; + + if (iface->conf->supported_rates && + !hostapd_rate_found(iface->conf->supported_rates, + mode->rates[i])) + continue; + + rate = &iface->current_rates[iface->num_rates]; + rate->rate = mode->rates[i]; + if (hostapd_rate_found(basic_rates, rate->rate)) { + rate->flags |= HOSTAPD_RATE_BASIC; + num_basic_rates++; + } + wpa_printf(MSG_DEBUG, "RATE[%d] rate=%d flags=0x%x", + iface->num_rates, rate->rate, rate->flags); + iface->num_rates++; + } + + if ((iface->num_rates == 0 || num_basic_rates == 0) && + (!iface->conf->ieee80211n || !iface->conf->require_ht)) { + wpa_printf(MSG_ERROR, "No rates remaining in supported/basic " + "rate sets (%d,%d).", + iface->num_rates, num_basic_rates); + return -1; + } + + return 0; +} + + +static int ieee80211n_allowed_ht40_channel_pair(struct hostapd_iface *iface) +{ + int pri_freq, sec_freq; + struct hostapd_channel_data *p_chan, *s_chan; + + pri_freq = iface->freq; + sec_freq = pri_freq + iface->conf->secondary_channel * 20; + + if (!iface->current_mode) + return 0; + + p_chan = hw_get_channel_freq(iface->current_mode->mode, pri_freq, NULL, + iface->hw_features, + iface->num_hw_features); + + s_chan = hw_get_channel_freq(iface->current_mode->mode, sec_freq, NULL, + iface->hw_features, + iface->num_hw_features); + + return allowed_ht40_channel_pair(iface->current_mode->mode, + p_chan, s_chan); +} + + +static void ieee80211n_switch_pri_sec(struct hostapd_iface *iface) +{ + if (iface->conf->secondary_channel > 0) { + iface->conf->channel += 4; + iface->freq += 20; + iface->conf->secondary_channel = -1; + } else { + iface->conf->channel -= 4; + iface->freq -= 20; + iface->conf->secondary_channel = 1; + } +} + + +static int ieee80211n_check_40mhz_5g(struct hostapd_iface *iface, + struct wpa_scan_results *scan_res) +{ + unsigned int pri_freq, sec_freq; + int res; + struct hostapd_channel_data *pri_chan, *sec_chan; + + pri_freq = iface->freq; + sec_freq = pri_freq + iface->conf->secondary_channel * 20; + + if (!iface->current_mode) + return 0; + pri_chan = hw_get_channel_freq(iface->current_mode->mode, pri_freq, + NULL, iface->hw_features, + iface->num_hw_features); + sec_chan = hw_get_channel_freq(iface->current_mode->mode, sec_freq, + NULL, iface->hw_features, + iface->num_hw_features); + + res = check_40mhz_5g(scan_res, pri_chan, sec_chan); + + if (res == 2) { + if (iface->conf->no_pri_sec_switch) { + wpa_printf(MSG_DEBUG, + "Cannot switch PRI/SEC channels due to local constraint"); + } else { + ieee80211n_switch_pri_sec(iface); + } + } + + return !!res; +} + + +static int ieee80211n_check_40mhz_2g4(struct hostapd_iface *iface, + struct wpa_scan_results *scan_res) +{ + int pri_chan, sec_chan; + + pri_chan = iface->conf->channel; + sec_chan = pri_chan + iface->conf->secondary_channel * 4; + + return check_40mhz_2g4(iface->current_mode, scan_res, pri_chan, + sec_chan); +} + + +static void ieee80211n_check_scan(struct hostapd_iface *iface) +{ + struct wpa_scan_results *scan_res; + int oper40; + int res = 0; + + /* Check list of neighboring BSSes (from scan) to see whether 40 MHz is + * allowed per IEEE Std 802.11-2012, 10.15.3.2 */ + + iface->scan_cb = NULL; + + scan_res = hostapd_driver_get_scan_results(iface->bss[0]); + if (scan_res == NULL) { + hostapd_setup_interface_complete(iface, 1); + return; + } + + if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A) + oper40 = ieee80211n_check_40mhz_5g(iface, scan_res); + else + oper40 = ieee80211n_check_40mhz_2g4(iface, scan_res); + wpa_scan_results_free(scan_res); + + iface->secondary_ch = iface->conf->secondary_channel; + if (!oper40) { + wpa_printf(MSG_INFO, "20/40 MHz operation not permitted on " + "channel pri=%d sec=%d based on overlapping BSSes", + iface->conf->channel, + iface->conf->channel + + iface->conf->secondary_channel * 4); + iface->conf->secondary_channel = 0; + if (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX) { + /* + * TODO: Could consider scheduling another scan to check + * if channel width can be changed if no coex reports + * are received from associating stations. + */ + } + } + +#ifdef CONFIG_IEEE80211AX + if (iface->conf->secondary_channel && + iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G && + iface->conf->ieee80211ax) { + struct he_capabilities *he_cap; + + he_cap = &iface->current_mode->he_capab[IEEE80211_MODE_AP]; + if (!(he_cap->phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX] & + HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G)) { + wpa_printf(MSG_DEBUG, + "HE: 40 MHz channel width is not supported in 2.4 GHz; clear secondary channel configuration"); + iface->conf->secondary_channel = 0; + } + } +#endif /* CONFIG_IEEE80211AX */ + + if (iface->conf->secondary_channel) + res = ieee80211n_allowed_ht40_channel_pair(iface); + if (!res) { + iface->conf->secondary_channel = 0; + hostapd_set_oper_centr_freq_seg0_idx(iface->conf, 0); + hostapd_set_oper_centr_freq_seg1_idx(iface->conf, 0); + hostapd_set_oper_chwidth(iface->conf, CONF_OPER_CHWIDTH_USE_HT); + res = 1; + wpa_printf(MSG_INFO, "Fallback to 20 MHz"); + } + + hostapd_setup_interface_complete(iface, !res); +} + + +static void ieee80211n_scan_channels_2g4(struct hostapd_iface *iface, + struct wpa_driver_scan_params *params) +{ + /* Scan only the affected frequency range */ + int pri_freq, sec_freq; + int affected_start, affected_end; + int i, pos; + struct hostapd_hw_modes *mode; + + if (iface->current_mode == NULL) + return; + + pri_freq = iface->freq; + if (iface->conf->secondary_channel > 0) + sec_freq = pri_freq + 20; + else + sec_freq = pri_freq - 20; + /* + * Note: Need to find the PRI channel also in cases where the affected + * channel is the SEC channel of a 40 MHz BSS, so need to include the + * scanning coverage here to be 40 MHz from the center frequency. + */ + affected_start = (pri_freq + sec_freq) / 2 - 40; + affected_end = (pri_freq + sec_freq) / 2 + 40; + wpa_printf(MSG_DEBUG, "40 MHz affected channel range: [%d,%d] MHz", + affected_start, affected_end); + + mode = iface->current_mode; + params->freqs = os_calloc(mode->num_channels + 1, sizeof(int)); + if (params->freqs == NULL) + return; + pos = 0; + + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + if (chan->flag & HOSTAPD_CHAN_DISABLED) + continue; + if (chan->freq < affected_start || + chan->freq > affected_end) + continue; + params->freqs[pos++] = chan->freq; + } +} + + +static void ieee80211n_scan_channels_5g(struct hostapd_iface *iface, + struct wpa_driver_scan_params *params) +{ + /* Scan only the affected frequency range */ + int pri_freq; + int affected_start, affected_end; + int i, pos; + struct hostapd_hw_modes *mode; + + if (iface->current_mode == NULL) + return; + + pri_freq = iface->freq; + if (iface->conf->secondary_channel > 0) { + affected_start = pri_freq - 10; + affected_end = pri_freq + 30; + } else { + affected_start = pri_freq - 30; + affected_end = pri_freq + 10; + } + wpa_printf(MSG_DEBUG, "40 MHz affected channel range: [%d,%d] MHz", + affected_start, affected_end); + + mode = iface->current_mode; + params->freqs = os_calloc(mode->num_channels + 1, sizeof(int)); + if (params->freqs == NULL) + return; + pos = 0; + + for (i = 0; i < mode->num_channels; i++) { + struct hostapd_channel_data *chan = &mode->channels[i]; + if (chan->flag & HOSTAPD_CHAN_DISABLED) + continue; + if (chan->freq < affected_start || + chan->freq > affected_end) + continue; + params->freqs[pos++] = chan->freq; + } +} + + +static void ap_ht40_scan_retry(void *eloop_data, void *user_data) +{ +#define HT2040_COEX_SCAN_RETRY 15 + struct hostapd_iface *iface = eloop_data; + struct wpa_driver_scan_params params; + int ret; + + os_memset(¶ms, 0, sizeof(params)); + if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G) + ieee80211n_scan_channels_2g4(iface, ¶ms); + else + ieee80211n_scan_channels_5g(iface, ¶ms); + + params.link_id = -1; +#ifdef CONFIG_IEEE80211BE + if (iface->bss[0]->conf->mld_ap) + params.link_id = iface->bss[0]->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + + ret = hostapd_driver_scan(iface->bss[0], ¶ms); + iface->num_ht40_scan_tries++; + os_free(params.freqs); + + if (ret == -EBUSY && + iface->num_ht40_scan_tries < HT2040_COEX_SCAN_RETRY) { + wpa_printf(MSG_ERROR, + "Failed to request a scan of neighboring BSSes ret=%d (%s) - try to scan again (attempt %d)", + ret, strerror(-ret), iface->num_ht40_scan_tries); + eloop_register_timeout(1, 0, ap_ht40_scan_retry, iface, NULL); + return; + } + + if (ret == 0) { + iface->scan_cb = ieee80211n_check_scan; + iface->bss[0]->scan_cookie = params.scan_cookie; + return; + } + + wpa_printf(MSG_DEBUG, + "Failed to request a scan in device, bringing up in HT20 mode"); + iface->conf->secondary_channel = 0; + iface->conf->ht_capab &= ~HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET; + hostapd_setup_interface_complete(iface, 0); +} + + +void hostapd_stop_setup_timers(struct hostapd_iface *iface) +{ + eloop_cancel_timeout(ap_ht40_scan_retry, iface, NULL); +} + + +static int ieee80211n_check_40mhz(struct hostapd_iface *iface) +{ + struct wpa_driver_scan_params params; + int ret; + + /* Check that HT40 is used and PRI / SEC switch is allowed */ + if (!iface->conf->secondary_channel || iface->conf->no_pri_sec_switch) + return 0; + + hostapd_set_state(iface, HAPD_IFACE_HT_SCAN); + wpa_printf(MSG_DEBUG, "Scan for neighboring BSSes prior to enabling " + "40 MHz channel"); + os_memset(¶ms, 0, sizeof(params)); + if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G) + ieee80211n_scan_channels_2g4(iface, ¶ms); + else + ieee80211n_scan_channels_5g(iface, ¶ms); + + params.link_id = -1; +#ifdef CONFIG_IEEE80211BE + if (iface->bss[0]->conf->mld_ap) + params.link_id = iface->bss[0]->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + ret = hostapd_driver_scan(iface->bss[0], ¶ms); + os_free(params.freqs); + + if (ret == -EBUSY) { + wpa_printf(MSG_ERROR, + "Failed to request a scan of neighboring BSSes ret=%d (%s) - try to scan again", + ret, strerror(-ret)); + iface->num_ht40_scan_tries = 1; + eloop_cancel_timeout(ap_ht40_scan_retry, iface, NULL); + eloop_register_timeout(1, 0, ap_ht40_scan_retry, iface, NULL); + return 1; + } + + if (ret < 0) { + wpa_printf(MSG_ERROR, + "Failed to request a scan of neighboring BSSes ret=%d (%s)", + ret, strerror(-ret)); + return -1; + } + + iface->scan_cb = ieee80211n_check_scan; + iface->bss[0]->scan_cookie = params.scan_cookie; + return 1; +} + + +static int ieee80211n_supported_ht_capab(struct hostapd_iface *iface) +{ + u16 hw = iface->current_mode->ht_capab; + u16 conf = iface->conf->ht_capab; + + if ((conf & HT_CAP_INFO_LDPC_CODING_CAP) && + !(hw & HT_CAP_INFO_LDPC_CODING_CAP)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [LDPC]"); + return 0; + } + + /* + * Driver ACS chosen channel may not be HT40 due to internal driver + * restrictions. + */ + if (!iface->conf->acs && (conf & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET) && + !(hw & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [HT40*]"); + return 0; + } + + if ((conf & HT_CAP_INFO_GREEN_FIELD) && + !(hw & HT_CAP_INFO_GREEN_FIELD)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [GF]"); + return 0; + } + + if ((conf & HT_CAP_INFO_SHORT_GI20MHZ) && + !(hw & HT_CAP_INFO_SHORT_GI20MHZ)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [SHORT-GI-20]"); + return 0; + } + + if ((conf & HT_CAP_INFO_SHORT_GI40MHZ) && + !(hw & HT_CAP_INFO_SHORT_GI40MHZ)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [SHORT-GI-40]"); + return 0; + } + + if ((conf & HT_CAP_INFO_TX_STBC) && !(hw & HT_CAP_INFO_TX_STBC)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [TX-STBC]"); + return 0; + } + + if ((conf & HT_CAP_INFO_RX_STBC_MASK) > + (hw & HT_CAP_INFO_RX_STBC_MASK)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [RX-STBC*]"); + return 0; + } + + if ((conf & HT_CAP_INFO_DELAYED_BA) && + !(hw & HT_CAP_INFO_DELAYED_BA)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [DELAYED-BA]"); + return 0; + } + + if ((conf & HT_CAP_INFO_MAX_AMSDU_SIZE) && + !(hw & HT_CAP_INFO_MAX_AMSDU_SIZE)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [MAX-AMSDU-7935]"); + return 0; + } + + if ((conf & HT_CAP_INFO_DSSS_CCK40MHZ) && + !(hw & HT_CAP_INFO_DSSS_CCK40MHZ)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [DSSS_CCK-40]"); + return 0; + } + + if ((conf & HT_CAP_INFO_LSIG_TXOP_PROTECT_SUPPORT) && + !(hw & HT_CAP_INFO_LSIG_TXOP_PROTECT_SUPPORT)) { + wpa_printf(MSG_ERROR, "Driver does not support configured " + "HT capability [LSIG-TXOP-PROT]"); + return 0; + } + + return 1; +} + + +#ifdef CONFIG_IEEE80211AC +static int ieee80211ac_supported_vht_capab(struct hostapd_iface *iface) +{ + struct hostapd_hw_modes *mode = iface->current_mode; + u32 hw = mode->vht_capab; + u32 conf = iface->conf->vht_capab; + + wpa_printf(MSG_DEBUG, "hw vht capab: 0x%x, conf vht capab: 0x%x", + hw, conf); + + if (mode->mode == HOSTAPD_MODE_IEEE80211G && + iface->conf->bss[0]->vendor_vht && + mode->vht_capab == 0 && iface->hw_features) { + int i; + + for (i = 0; i < iface->num_hw_features; i++) { + if (iface->hw_features[i].mode == + HOSTAPD_MODE_IEEE80211A) { + mode = &iface->hw_features[i]; + hw = mode->vht_capab; + wpa_printf(MSG_DEBUG, + "update hw vht capab based on 5 GHz band: 0x%x", + hw); + break; + } + } + } + + return ieee80211ac_cap_check(hw, conf); +} +#endif /* CONFIG_IEEE80211AC */ + + +#ifdef CONFIG_IEEE80211AX +static int ieee80211ax_supported_he_capab(struct hostapd_iface *iface) +{ + return 1; +} +#endif /* CONFIG_IEEE80211AX */ + + +int hostapd_check_ht_capab(struct hostapd_iface *iface) +{ + int ret; + + if (is_6ghz_freq(iface->freq)) + return 0; + if (!iface->conf->ieee80211n) + return 0; + + if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211B && + iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G && + (iface->conf->ht_capab & HT_CAP_INFO_DSSS_CCK40MHZ)) { + wpa_printf(MSG_DEBUG, + "Disable HT capability [DSSS_CCK-40] on 5 GHz band"); + iface->conf->ht_capab &= ~HT_CAP_INFO_DSSS_CCK40MHZ; + } + + if (!ieee80211n_supported_ht_capab(iface)) + return -1; +#ifdef CONFIG_IEEE80211AX + if (iface->conf->ieee80211ax && + !ieee80211ax_supported_he_capab(iface)) + return -1; +#endif /* CONFIG_IEEE80211AX */ +#ifdef CONFIG_IEEE80211AC + if (iface->conf->ieee80211ac && + !ieee80211ac_supported_vht_capab(iface)) + return -1; +#endif /* CONFIG_IEEE80211AC */ + ret = ieee80211n_check_40mhz(iface); + if (ret) + return ret; + if (!ieee80211n_allowed_ht40_channel_pair(iface)) + return -1; + + return 0; +} + + +int hostapd_check_edmg_capab(struct hostapd_iface *iface) +{ + struct hostapd_hw_modes *mode = iface->hw_features; + struct ieee80211_edmg_config edmg; + + if (!iface->conf->enable_edmg) + return 0; + + hostapd_encode_edmg_chan(iface->conf->enable_edmg, + iface->conf->edmg_channel, + iface->conf->channel, + &edmg); + + if (mode->edmg.channels && ieee802_edmg_is_allowed(mode->edmg, edmg)) + return 0; + + wpa_printf(MSG_WARNING, "Requested EDMG configuration is not valid"); + wpa_printf(MSG_INFO, "EDMG capab: channels 0x%x, bw_config %d", + mode->edmg.channels, mode->edmg.bw_config); + wpa_printf(MSG_INFO, + "Requested EDMG configuration: channels 0x%x, bw_config %d", + edmg.channels, edmg.bw_config); + return -1; +} + + +int hostapd_check_he_6ghz_capab(struct hostapd_iface *iface) +{ +#ifdef CONFIG_IEEE80211AX + struct he_capabilities *he_cap; + u16 hw; + + if (!iface->current_mode || !is_6ghz_freq(iface->freq)) + return 0; + + he_cap = &iface->current_mode->he_capab[IEEE80211_MODE_AP]; + hw = he_cap->he_6ghz_capa; + if (iface->conf->he_6ghz_max_mpdu > + ((hw & HE_6GHZ_BAND_CAP_MAX_MPDU_LEN_MASK) >> + HE_6GHZ_BAND_CAP_MAX_MPDU_LEN_SHIFT)) { + wpa_printf(MSG_ERROR, + "The driver does not support the configured HE 6 GHz Max MPDU length"); + return -1; + } + + if (iface->conf->he_6ghz_max_ampdu_len_exp > + ((hw & HE_6GHZ_BAND_CAP_MAX_AMPDU_LEN_EXP_MASK) >> + HE_6GHZ_BAND_CAP_MAX_AMPDU_LEN_EXP_SHIFT)) { + wpa_printf(MSG_ERROR, + "The driver does not support the configured HE 6 GHz Max AMPDU Length Exponent"); + return -1; + } + + if (iface->conf->he_6ghz_rx_ant_pat && + !(hw & HE_6GHZ_BAND_CAP_RX_ANTPAT_CONS)) { + wpa_printf(MSG_ERROR, + "The driver does not support the configured HE 6 GHz Rx Antenna Pattern"); + return -1; + } + + if (iface->conf->he_6ghz_tx_ant_pat && + !(hw & HE_6GHZ_BAND_CAP_TX_ANTPAT_CONS)) { + wpa_printf(MSG_ERROR, + "The driver does not support the configured HE 6 GHz Tx Antenna Pattern"); + return -1; + } +#endif /* CONFIG_IEEE80211AX */ + return 0; +} + + +/* Returns: + * 1 = usable + * 0 = not usable + * -1 = not currently usable due to 6 GHz NO-IR + */ +static int hostapd_is_usable_chan(struct hostapd_iface *iface, + int frequency, int primary) +{ + struct hostapd_channel_data *chan; + + if (!iface->current_mode) + return 0; + + chan = hw_get_channel_freq(iface->current_mode->mode, frequency, NULL, + iface->hw_features, iface->num_hw_features); + if (!chan) + return 0; + + if ((primary && chan_pri_allowed(chan)) || + (!primary && !(chan->flag & HOSTAPD_CHAN_DISABLED))) + return 1; + + wpa_printf(MSG_INFO, + "Frequency %d (%s) not allowed for AP mode, flags: 0x%x%s%s", + frequency, primary ? "primary" : "secondary", + chan->flag, + chan->flag & HOSTAPD_CHAN_NO_IR ? " NO-IR" : "", + chan->flag & HOSTAPD_CHAN_RADAR ? " RADAR" : ""); + + if (is_6ghz_freq(chan->freq) && (chan->flag & HOSTAPD_CHAN_NO_IR)) + return -1; + + return 0; +} + + +static int hostapd_is_usable_edmg(struct hostapd_iface *iface) +{ + int i, contiguous = 0; + int num_of_enabled = 0; + int max_contiguous = 0; + int err; + struct ieee80211_edmg_config edmg; + struct hostapd_channel_data *pri_chan; + + if (!iface->conf->enable_edmg) + return 1; + + if (!iface->current_mode) + return 0; + pri_chan = hw_get_channel_freq(iface->current_mode->mode, + iface->freq, NULL, + iface->hw_features, + iface->num_hw_features); + if (!pri_chan) + return 0; + hostapd_encode_edmg_chan(iface->conf->enable_edmg, + iface->conf->edmg_channel, + pri_chan->chan, + &edmg); + if (!(edmg.channels & BIT(pri_chan->chan - 1))) + return 0; + + /* 60 GHz channels 1..6 */ + for (i = 0; i < 6; i++) { + int freq = 56160 + 2160 * (i + 1); + + if (edmg.channels & BIT(i)) { + contiguous++; + num_of_enabled++; + } else { + contiguous = 0; + continue; + } + + /* P802.11ay defines that the total number of subfields + * set to one does not exceed 4. + */ + if (num_of_enabled > 4) + return 0; + + err = hostapd_is_usable_chan(iface, freq, 1); + if (err <= 0) + return err; + + if (contiguous > max_contiguous) + max_contiguous = contiguous; + } + + /* Check if the EDMG configuration is valid under the limitations + * of P802.11ay. + */ + /* check bw_config against contiguous EDMG channels */ + switch (edmg.bw_config) { + case EDMG_BW_CONFIG_4: + if (!max_contiguous) + return 0; + break; + case EDMG_BW_CONFIG_5: + if (max_contiguous < 2) + return 0; + break; + default: + return 0; + } + + return 1; +} + + +static bool hostapd_is_usable_punct_bitmap(struct hostapd_iface *iface) +{ +#ifdef CONFIG_IEEE80211BE + struct hostapd_config *conf = iface->conf; + u16 bw; + u8 start_chan; + + if (!conf->punct_bitmap) + return true; + + if (!conf->ieee80211be) { + wpa_printf(MSG_ERROR, + "Currently RU puncturing is supported only if ieee80211be is enabled"); + return false; + } + + if (iface->freq >= 2412 && iface->freq <= 2484) { + wpa_printf(MSG_ERROR, + "RU puncturing not supported in 2.4 GHz"); + return false; + } + + /* + * In the 6 GHz band, eht_oper_chwidth is ignored. Use operating class + * to determine channel width. + */ + if (conf->op_class == 137) { + bw = 320; + start_chan = conf->eht_oper_centr_freq_seg0_idx - 30; + } else { + switch (conf->eht_oper_chwidth) { + case 0: + wpa_printf(MSG_ERROR, + "RU puncturing is supported only in 80 MHz and 160 MHz"); + return false; + case 1: + bw = 80; + start_chan = conf->eht_oper_centr_freq_seg0_idx - 6; + break; + case 2: + bw = 160; + start_chan = conf->eht_oper_centr_freq_seg0_idx - 14; + break; + default: + return false; + } + } + + if (!is_punct_bitmap_valid(bw, (conf->channel - start_chan) / 4, + conf->punct_bitmap)) { + wpa_printf(MSG_ERROR, "Invalid puncturing bitmap"); + return false; + } +#endif /* CONFIG_IEEE80211BE */ + + return true; +} + + +/* Returns: + * 1 = usable + * 0 = not usable + * -1 = not currently usable due to 6 GHz NO-IR + */ +static int hostapd_is_usable_chans(struct hostapd_iface *iface) +{ + int secondary_freq; + struct hostapd_channel_data *pri_chan; + int err, err2; + + if (!iface->current_mode) + return 0; + pri_chan = hw_get_channel_freq(iface->current_mode->mode, + iface->freq, NULL, + iface->hw_features, + iface->num_hw_features); + if (!pri_chan) { + wpa_printf(MSG_ERROR, "Primary frequency not present"); + return 0; + } + + err = hostapd_is_usable_chan(iface, pri_chan->freq, 1); + if (err <= 0) { + wpa_printf(MSG_ERROR, "Primary frequency not allowed"); + return err; + } + err = hostapd_is_usable_edmg(iface); + if (err <= 0) + return err; + + if (!hostapd_is_usable_punct_bitmap(iface)) + return 0; + + if (!iface->conf->secondary_channel) + return 1; + + err = hostapd_is_usable_chan(iface, iface->freq + + iface->conf->secondary_channel * 20, 0); + if (err > 0) { + if (iface->conf->secondary_channel == 1 && + (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) + return 1; + if (iface->conf->secondary_channel == -1 && + (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) + return 1; + } + if (!iface->conf->ht40_plus_minus_allowed) + return err; + + /* Both HT40+ and HT40- are set, pick a valid secondary channel */ + secondary_freq = iface->freq + 20; + err2 = hostapd_is_usable_chan(iface, secondary_freq, 0); + if (err2 > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) { + iface->conf->secondary_channel = 1; + return 1; + } + + secondary_freq = iface->freq - 20; + err2 = hostapd_is_usable_chan(iface, secondary_freq, 0); + if (err2 > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) { + iface->conf->secondary_channel = -1; + return 1; + } + + return err; +} + + +static bool skip_mode(struct hostapd_iface *iface, + struct hostapd_hw_modes *mode) +{ + int chan; + + if (iface->freq > 0 && !hw_mode_get_channel(mode, iface->freq, &chan)) + return true; + + if (is_6ghz_op_class(iface->conf->op_class) && iface->freq == 0 && + !mode->is_6ghz) + return true; + + return false; +} + + +int hostapd_determine_mode(struct hostapd_iface *iface) +{ + int i; + enum hostapd_hw_mode target_mode; + + if (iface->current_mode || + iface->conf->hw_mode != HOSTAPD_MODE_IEEE80211ANY) + return 0; + + if (iface->freq < 4000) + target_mode = HOSTAPD_MODE_IEEE80211G; + else if (iface->freq > 50000) + target_mode = HOSTAPD_MODE_IEEE80211AD; + else + target_mode = HOSTAPD_MODE_IEEE80211A; + + for (i = 0; i < iface->num_hw_features; i++) { + struct hostapd_hw_modes *mode; + + mode = &iface->hw_features[i]; + if (mode->mode == target_mode) { + if (skip_mode(iface, mode)) + continue; + + iface->current_mode = mode; + iface->conf->hw_mode = mode->mode; + break; + } + } + + if (!iface->current_mode) { + wpa_printf(MSG_ERROR, "ACS/CSA: Cannot decide mode"); + return -1; + } + return 0; +} + + +static enum hostapd_chan_status +hostapd_check_chans(struct hostapd_iface *iface) +{ + if (iface->freq) { + int err; + + hostapd_determine_mode(iface); + + err = hostapd_is_usable_chans(iface); + if (err <= 0) { + if (!err) + return HOSTAPD_CHAN_INVALID; + return HOSTAPD_CHAN_INVALID_NO_IR; + } + return HOSTAPD_CHAN_VALID; + } + + /* + * The user set channel=0 or channel=acs_survey + * which is used to trigger ACS. + */ + + switch (acs_init(iface)) { + case HOSTAPD_CHAN_ACS: + return HOSTAPD_CHAN_ACS; + case HOSTAPD_CHAN_INVALID_NO_IR: + return HOSTAPD_CHAN_INVALID_NO_IR; + case HOSTAPD_CHAN_VALID: + case HOSTAPD_CHAN_INVALID: + default: + return HOSTAPD_CHAN_INVALID; + } +} + + +static void hostapd_notify_bad_chans(struct hostapd_iface *iface) +{ + if (!iface->current_mode) { + hostapd_logger(iface->bss[0], NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_WARNING, + "Hardware does not support configured mode"); + return; + } + hostapd_logger(iface->bss[0], NULL, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_WARNING, + "Configured channel (%d) or frequency (%d) (secondary_channel=%d) not found from the channel list of the current mode (%d) %s", + iface->conf->channel, + iface->freq, iface->conf->secondary_channel, + iface->current_mode->mode, + hostapd_hw_mode_txt(iface->current_mode->mode)); + hostapd_logger(iface->bss[0], NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_WARNING, + "Hardware does not support configured channel"); +} + + +int hostapd_acs_completed(struct hostapd_iface *iface, int err) +{ + int ret = -1; + + if (err) + goto out; + + switch (hostapd_check_chans(iface)) { + case HOSTAPD_CHAN_VALID: + iface->is_no_ir = false; + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, + ACS_EVENT_COMPLETED "freq=%d channel=%d", + iface->freq, iface->conf->channel); + break; + case HOSTAPD_CHAN_ACS: + wpa_printf(MSG_ERROR, "ACS error - reported complete, but no result available"); + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, ACS_EVENT_FAILED); + hostapd_notify_bad_chans(iface); + goto out; + case HOSTAPD_CHAN_INVALID_NO_IR: + iface->is_no_ir = true; + /* fall through */ + case HOSTAPD_CHAN_INVALID: + default: + wpa_printf(MSG_ERROR, "ACS picked unusable channels"); + wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, ACS_EVENT_FAILED); + hostapd_notify_bad_chans(iface); + goto out; + } + + ret = hostapd_check_ht_capab(iface); + if (ret < 0) + goto out; + if (ret == 1) { + wpa_printf(MSG_DEBUG, "Interface initialization will be completed in a callback"); + return 0; + } + + ret = 0; +out: + return hostapd_setup_interface_complete(iface, ret); +} + + +/** + * hostapd_csa_update_hwmode - Update hardware mode + * @iface: Pointer to interface data. + * Returns: 0 on success, < 0 on failure + * + * Update hardware mode when the operating channel changed because of CSA. + */ +int hostapd_csa_update_hwmode(struct hostapd_iface *iface) +{ + if (!iface || !iface->conf) + return -1; + + iface->current_mode = NULL; + iface->conf->hw_mode = HOSTAPD_MODE_IEEE80211ANY; + + return hostapd_determine_mode(iface); +} + + +/** + * hostapd_select_hw_mode - Select the hardware mode + * @iface: Pointer to interface data. + * Returns: 0 on success, < 0 on failure + * + * Sets up the hardware mode, channel, rates, and passive scanning + * based on the configuration. + */ +int hostapd_select_hw_mode(struct hostapd_iface *iface) +{ + int i; + + if (iface->num_hw_features < 1) + return -1; + + if ((iface->conf->hw_mode == HOSTAPD_MODE_IEEE80211G || + iface->conf->ieee80211n || iface->conf->ieee80211ac || + iface->conf->ieee80211ax || iface->conf->ieee80211be) && + iface->conf->channel == 14) { + wpa_printf(MSG_INFO, "Disable OFDM/HT/VHT/HE/EHT on channel 14"); + iface->conf->hw_mode = HOSTAPD_MODE_IEEE80211B; + iface->conf->ieee80211n = 0; + iface->conf->ieee80211ac = 0; + iface->conf->ieee80211ax = 0; + iface->conf->ieee80211be = 0; + } + + iface->current_mode = NULL; + for (i = 0; i < iface->num_hw_features; i++) { + struct hostapd_hw_modes *mode = &iface->hw_features[i]; + + if (mode->mode == iface->conf->hw_mode) { + if (skip_mode(iface, mode)) + continue; + + iface->current_mode = mode; + break; + } + } + + if (iface->current_mode == NULL) { + if ((iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) && + (iface->drv_flags & WPA_DRIVER_FLAGS_SUPPORT_HW_MODE_ANY)) { + wpa_printf(MSG_DEBUG, + "Using offloaded hw_mode=any ACS"); + } else if (!(iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) && + iface->conf->hw_mode == HOSTAPD_MODE_IEEE80211ANY) { + wpa_printf(MSG_DEBUG, + "Using internal ACS for hw_mode=any"); + } else { + wpa_printf(MSG_ERROR, + "Hardware does not support configured mode"); + hostapd_logger(iface->bss[0], NULL, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_WARNING, + "Hardware does not support configured mode (%d) (hw_mode in hostapd.conf)", + (int) iface->conf->hw_mode); + return -2; + } + } + + switch (hostapd_check_chans(iface)) { + case HOSTAPD_CHAN_VALID: + iface->is_no_ir = false; + return 0; + case HOSTAPD_CHAN_ACS: /* ACS will run and later complete */ + return 1; + case HOSTAPD_CHAN_INVALID_NO_IR: + iface->is_no_ir = true; + /* fall through */ + case HOSTAPD_CHAN_INVALID: + default: + hostapd_notify_bad_chans(iface); + return -3; + } +} + + +const char * hostapd_hw_mode_txt(int mode) +{ + switch (mode) { + case HOSTAPD_MODE_IEEE80211A: + return "IEEE 802.11a"; + case HOSTAPD_MODE_IEEE80211B: + return "IEEE 802.11b"; + case HOSTAPD_MODE_IEEE80211G: + return "IEEE 802.11g"; + case HOSTAPD_MODE_IEEE80211AD: + return "IEEE 802.11ad"; + default: + return "UNKNOWN"; + } +} + + +int hostapd_hw_get_freq(struct hostapd_data *hapd, int chan) +{ + return hw_get_freq(hapd->iface->current_mode, chan); +} + + +int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq) +{ + int i, channel; + struct hostapd_hw_modes *mode; + + if (hapd->iface->current_mode) { + channel = hw_get_chan(hapd->iface->current_mode->mode, freq, + hapd->iface->hw_features, + hapd->iface->num_hw_features); + if (channel) + return channel; + } + + /* Check other available modes since the channel list for the current + * mode did not include the specified frequency. */ + if (!hapd->iface->hw_features) + return 0; + for (i = 0; i < hapd->iface->num_hw_features; i++) { + mode = &hapd->iface->hw_features[i]; + channel = hw_get_chan(mode->mode, freq, + hapd->iface->hw_features, + hapd->iface->num_hw_features); + if (channel) + return channel; + } + return 0; +} + + +int hostapd_hw_skip_mode(struct hostapd_iface *iface, + struct hostapd_hw_modes *mode) +{ + int i; + + if (iface->current_mode) + return mode != iface->current_mode; + if (mode->mode != HOSTAPD_MODE_IEEE80211B) + return 0; + for (i = 0; i < iface->num_hw_features; i++) { + if (iface->hw_features[i].mode == HOSTAPD_MODE_IEEE80211G) + return 1; + } + return 0; +} diff --git a/src/ap/hw_features.h b/src/ap/hw_features.h new file mode 100644 index 0000000..c682c6d --- /dev/null +++ b/src/ap/hw_features.h @@ -0,0 +1,108 @@ +/* + * hostapd / Hardware feature query and different modes + * Copyright 2002-2003, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * Copyright (c) 2008-2011, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef HW_FEATURES_H +#define HW_FEATURES_H + +#ifdef NEED_AP_MLME +void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, + size_t num_hw_features); +int hostapd_get_hw_features(struct hostapd_iface *iface); +int hostapd_csa_update_hwmode(struct hostapd_iface *iface); +int hostapd_acs_completed(struct hostapd_iface *iface, int err); +int hostapd_select_hw_mode(struct hostapd_iface *iface); +const char * hostapd_hw_mode_txt(int mode); +int hostapd_hw_get_freq(struct hostapd_data *hapd, int chan); +int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq); +int hostapd_check_ht_capab(struct hostapd_iface *iface); +int hostapd_check_edmg_capab(struct hostapd_iface *iface); +int hostapd_check_he_6ghz_capab(struct hostapd_iface *iface); +int hostapd_prepare_rates(struct hostapd_iface *iface, + struct hostapd_hw_modes *mode); +void hostapd_stop_setup_timers(struct hostapd_iface *iface); +int hostapd_hw_skip_mode(struct hostapd_iface *iface, + struct hostapd_hw_modes *mode); +int hostapd_determine_mode(struct hostapd_iface *iface); +#else /* NEED_AP_MLME */ +static inline void +hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, + size_t num_hw_features) +{ +} + +static inline int hostapd_get_hw_features(struct hostapd_iface *iface) +{ + return -1; +} + +static inline int hostapd_csa_update_hwmode(struct hostapd_iface *iface) +{ + return 0; +} + +static inline int hostapd_acs_completed(struct hostapd_iface *iface, int err) +{ + return -1; +} + +static inline int hostapd_select_hw_mode(struct hostapd_iface *iface) +{ + return -100; +} + +static inline const char * hostapd_hw_mode_txt(int mode) +{ + return "UNKNOWN"; +} + +static inline int hostapd_hw_get_freq(struct hostapd_data *hapd, int chan) +{ + return -1; +} + +static inline int hostapd_check_ht_capab(struct hostapd_iface *iface) +{ + return 0; +} + +static inline int hostapd_check_edmg_capab(struct hostapd_iface *iface) +{ + return 0; +} + +static inline int hostapd_prepare_rates(struct hostapd_iface *iface, + struct hostapd_hw_modes *mode) +{ + return 0; +} + +static inline void hostapd_stop_setup_timers(struct hostapd_iface *iface) +{ +} + +static inline int hostapd_hw_skip_mode(struct hostapd_iface *iface, + struct hostapd_hw_modes *mode) +{ + return 0; +} + +static inline int hostapd_check_he_6ghz_capab(struct hostapd_iface *iface) +{ + return 0; +} + +static inline int hostapd_determine_mode(struct hostapd_iface *iface) +{ + return 0; +} + +#endif /* NEED_AP_MLME */ + +#endif /* HW_FEATURES_H */ diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c new file mode 100644 index 0000000..6c516bc --- /dev/null +++ b/src/ap/ieee802_11.c @@ -0,0 +1,8278 @@ +/* + * hostapd / IEEE 802.11 Management + * Copyright (c) 2002-2017, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#ifndef CONFIG_NATIVE_WINDOWS + +#include "utils/common.h" +#include "utils/eloop.h" +#include "crypto/crypto.h" +#include "crypto/sha256.h" +#include "crypto/sha384.h" +#include "crypto/sha512.h" +#include "crypto/random.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "common/wpa_ctrl.h" +#include "common/sae.h" +#include "common/dpp.h" +#include "common/ocv.h" +#include "common/wpa_common.h" +#include "common/wpa_ctrl.h" +#include "common/ptksa_cache.h" +#include "radius/radius.h" +#include "radius/radius_client.h" +#include "p2p/p2p.h" +#include "wps/wps.h" +#include "fst/fst.h" +#include "hostapd.h" +#include "beacon.h" +#include "ieee802_11_auth.h" +#include "sta_info.h" +#include "ieee802_1x.h" +#include "wpa_auth.h" +#include "pmksa_cache_auth.h" +#include "wmm.h" +#include "ap_list.h" +#include "accounting.h" +#include "ap_config.h" +#include "ap_mlme.h" +#include "p2p_hostapd.h" +#include "ap_drv_ops.h" +#include "wnm_ap.h" +#include "hw_features.h" +#include "ieee802_11.h" +#include "dfs.h" +#include "mbo_ap.h" +#include "rrm.h" +#include "taxonomy.h" +#include "fils_hlp.h" +#include "dpp_hostapd.h" +#include "gas_query_ap.h" +#include "comeback_token.h" +#include "nan_usd_ap.h" +#include "pasn/pasn_common.h" + + +#ifdef CONFIG_FILS +static struct wpabuf * +prepare_auth_resp_fils(struct hostapd_data *hapd, + struct sta_info *sta, u16 *resp, + struct rsn_pmksa_cache_entry *pmksa, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len, + int *is_pub); +#endif /* CONFIG_FILS */ + +#ifdef CONFIG_PASN +#ifdef CONFIG_FILS + +static void pasn_fils_auth_resp(struct hostapd_data *hapd, + struct sta_info *sta, u16 status, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len); + +#endif /* CONFIG_FILS */ +#endif /* CONFIG_PASN */ + +static void handle_auth(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len, + int rssi, int from_queue); +static int add_associated_sta(struct hostapd_data *hapd, + struct sta_info *sta, int reassoc); + + +static u8 * hostapd_eid_multi_ap(struct hostapd_data *hapd, u8 *eid, size_t len) +{ + struct multi_ap_params multi_ap = { 0 }; + + if (!hapd->conf->multi_ap) + return eid; + + if (hapd->conf->multi_ap & BACKHAUL_BSS) + multi_ap.capability |= MULTI_AP_BACKHAUL_BSS; + if (hapd->conf->multi_ap & FRONTHAUL_BSS) + multi_ap.capability |= MULTI_AP_FRONTHAUL_BSS; + + if (hapd->conf->multi_ap_client_disallow & + PROFILE1_CLIENT_ASSOC_DISALLOW) + multi_ap.capability |= + MULTI_AP_PROFILE1_BACKHAUL_STA_DISALLOWED; + if (hapd->conf->multi_ap_client_disallow & + PROFILE2_CLIENT_ASSOC_DISALLOW) + multi_ap.capability |= + MULTI_AP_PROFILE2_BACKHAUL_STA_DISALLOWED; + + multi_ap.profile = hapd->conf->multi_ap_profile; + multi_ap.vlanid = hapd->conf->multi_ap_vlanid; + + return eid + add_multi_ap_ie(eid, len, &multi_ap); +} + + +u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; + int i, num, count; + int h2e_required; + + if (hapd->iface->current_rates == NULL) + return eid; + + *pos++ = WLAN_EID_SUPP_RATES; + num = hapd->iface->num_rates; + if (hapd->iconf->ieee80211n && hapd->iconf->require_ht) + num++; + if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht) + num++; +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && hapd->iconf->require_he) + num++; +#endif /* CONFIG_IEEE80211AX */ + h2e_required = (hapd->conf->sae_pwe == SAE_PWE_HASH_TO_ELEMENT || + hostapd_sae_pw_id_in_use(hapd->conf) == 2) && + hapd->conf->sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK && + wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt); + if (h2e_required) + num++; + if (num > 8) { + /* rest of the rates are encoded in Extended supported + * rates element */ + num = 8; + } + + *pos++ = num; + for (i = 0, count = 0; i < hapd->iface->num_rates && count < num; + i++) { + count++; + *pos = hapd->iface->current_rates[i].rate / 5; + if (hapd->iface->current_rates[i].flags & HOSTAPD_RATE_BASIC) + *pos |= 0x80; + pos++; + } + + if (hapd->iconf->ieee80211n && hapd->iconf->require_ht && count < 8) { + count++; + *pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_HT_PHY; + } + + if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht && count < 8) { + count++; + *pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_VHT_PHY; + } + +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && hapd->iconf->require_he && count < 8) { + count++; + *pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_HE_PHY; + } +#endif /* CONFIG_IEEE80211AX */ + + if (h2e_required && count < 8) { + count++; + *pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_SAE_H2E_ONLY; + } + + return pos; +} + + +u8 * hostapd_eid_ext_supp_rates(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; + int i, num, count; + int h2e_required; + + hapd->conf->xrates_supported = false; + if (hapd->iface->current_rates == NULL) + return eid; + + num = hapd->iface->num_rates; + if (hapd->iconf->ieee80211n && hapd->iconf->require_ht) + num++; + if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht) + num++; +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && hapd->iconf->require_he) + num++; +#endif /* CONFIG_IEEE80211AX */ + h2e_required = (hapd->conf->sae_pwe == SAE_PWE_HASH_TO_ELEMENT || + hostapd_sae_pw_id_in_use(hapd->conf) == 2) && + hapd->conf->sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK && + wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt); + if (h2e_required) + num++; + if (num <= 8) + return eid; + num -= 8; + + *pos++ = WLAN_EID_EXT_SUPP_RATES; + *pos++ = num; + for (i = 0, count = 0; i < hapd->iface->num_rates && count < num + 8; + i++) { + count++; + if (count <= 8) + continue; /* already in SuppRates IE */ + *pos = hapd->iface->current_rates[i].rate / 5; + if (hapd->iface->current_rates[i].flags & HOSTAPD_RATE_BASIC) + *pos |= 0x80; + pos++; + } + + if (hapd->iconf->ieee80211n && hapd->iconf->require_ht) { + count++; + if (count > 8) + *pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_HT_PHY; + } + + if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht) { + count++; + if (count > 8) + *pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_VHT_PHY; + } + +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && hapd->iconf->require_he) { + count++; + if (count > 8) + *pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_HE_PHY; + } +#endif /* CONFIG_IEEE80211AX */ + + if (h2e_required) { + count++; + if (count > 8) + *pos++ = 0x80 | BSS_MEMBERSHIP_SELECTOR_SAE_H2E_ONLY; + } + + hapd->conf->xrates_supported = true; + return pos; +} + + +u8 * hostapd_eid_rm_enabled_capab(struct hostapd_data *hapd, u8 *eid, + size_t len) +{ + size_t i; + + for (i = 0; i < RRM_CAPABILITIES_IE_LEN; i++) { + if (hapd->conf->radio_measurements[i]) + break; + } + + if (i == RRM_CAPABILITIES_IE_LEN || len < 2 + RRM_CAPABILITIES_IE_LEN) + return eid; + + *eid++ = WLAN_EID_RRM_ENABLED_CAPABILITIES; + *eid++ = RRM_CAPABILITIES_IE_LEN; + os_memcpy(eid, hapd->conf->radio_measurements, RRM_CAPABILITIES_IE_LEN); + + return eid + RRM_CAPABILITIES_IE_LEN; +} + + +u16 hostapd_own_capab_info(struct hostapd_data *hapd) +{ + int capab = WLAN_CAPABILITY_ESS; + int privacy = 0; + int dfs; + int i; + + /* Check if any of configured channels require DFS */ + dfs = hostapd_is_dfs_required(hapd->iface); + if (dfs < 0) { + wpa_printf(MSG_WARNING, "Failed to check if DFS is required; ret=%d", + dfs); + dfs = 0; + } + + if (hapd->iface->num_sta_no_short_preamble == 0 && + hapd->iconf->preamble == SHORT_PREAMBLE) + capab |= WLAN_CAPABILITY_SHORT_PREAMBLE; + +#ifdef CONFIG_WEP + privacy = hapd->conf->ssid.wep.keys_set; + + if (hapd->conf->ieee802_1x && + (hapd->conf->default_wep_key_len || + hapd->conf->individual_wep_key_len)) + privacy = 1; +#endif /* CONFIG_WEP */ + + if (hapd->conf->wpa) + privacy = 1; + +#ifdef CONFIG_HS20 + if (hapd->conf->osen) + privacy = 1; +#endif /* CONFIG_HS20 */ + + if (privacy) + capab |= WLAN_CAPABILITY_PRIVACY; + + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G && + hapd->iface->num_sta_no_short_slot_time == 0) + capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME; + + /* + * Currently, Spectrum Management capability bit is set when directly + * requested in configuration by spectrum_mgmt_required or when AP is + * running on DFS channel. + * TODO: Also consider driver support for TPC to set Spectrum Mgmt bit + */ + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A && + (hapd->iconf->spectrum_mgmt_required || dfs)) + capab |= WLAN_CAPABILITY_SPECTRUM_MGMT; + + for (i = 0; i < RRM_CAPABILITIES_IE_LEN; i++) { + if (hapd->conf->radio_measurements[i]) { + capab |= IEEE80211_CAP_RRM; + break; + } + } + + return capab; +} + + +#ifdef CONFIG_WEP +#ifndef CONFIG_NO_RC4 +static u16 auth_shared_key(struct hostapd_data *hapd, struct sta_info *sta, + u16 auth_transaction, const u8 *challenge, + int iswep) +{ + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "authentication (shared key, transaction %d)", + auth_transaction); + + if (auth_transaction == 1) { + if (!sta->challenge) { + /* Generate a pseudo-random challenge */ + u8 key[8]; + + sta->challenge = os_zalloc(WLAN_AUTH_CHALLENGE_LEN); + if (sta->challenge == NULL) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + if (os_get_random(key, sizeof(key)) < 0) { + os_free(sta->challenge); + sta->challenge = NULL; + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + rc4_skip(key, sizeof(key), 0, + sta->challenge, WLAN_AUTH_CHALLENGE_LEN); + } + return 0; + } + + if (auth_transaction != 3) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + /* Transaction 3 */ + if (!iswep || !sta->challenge || !challenge || + os_memcmp_const(sta->challenge, challenge, + WLAN_AUTH_CHALLENGE_LEN)) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "shared key authentication - invalid " + "challenge-response"); + return WLAN_STATUS_CHALLENGE_FAIL; + } + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "authentication OK (shared key)"); + sta->flags |= WLAN_STA_AUTH; + wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH); + os_free(sta->challenge); + sta->challenge = NULL; + + return 0; +} +#endif /* CONFIG_NO_RC4 */ +#endif /* CONFIG_WEP */ + + +static int send_auth_reply(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *dst, + u16 auth_alg, u16 auth_transaction, u16 resp, + const u8 *ies, size_t ies_len, const char *dbg) +{ + struct ieee80211_mgmt *reply; + u8 *buf; + size_t rlen; + int reply_res = WLAN_STATUS_UNSPECIFIED_FAILURE; + const u8 *sa = hapd->own_addr; + struct wpabuf *ml_resp = NULL; + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) { + ml_resp = hostapd_ml_auth_resp(hapd); + if (!ml_resp) + return -1; + } +#endif /* CONFIG_IEEE80211BE */ + + rlen = IEEE80211_HDRLEN + sizeof(reply->u.auth) + ies_len; + if (ml_resp) + rlen += wpabuf_len(ml_resp); + buf = os_zalloc(rlen); + if (!buf) { + wpabuf_free(ml_resp); + return -1; + } + + reply = (struct ieee80211_mgmt *) buf; + reply->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_AUTH); + os_memcpy(reply->da, dst, ETH_ALEN); + os_memcpy(reply->sa, sa, ETH_ALEN); + os_memcpy(reply->bssid, sa, ETH_ALEN); + + reply->u.auth.auth_alg = host_to_le16(auth_alg); + reply->u.auth.auth_transaction = host_to_le16(auth_transaction); + reply->u.auth.status_code = host_to_le16(resp); + + if (ies && ies_len) + os_memcpy(reply->u.auth.variable, ies, ies_len); + +#ifdef CONFIG_IEEE80211BE + if (ml_resp) + os_memcpy(reply->u.auth.variable + ies_len, + wpabuf_head(ml_resp), wpabuf_len(ml_resp)); + + wpabuf_free(ml_resp); +#endif /* CONFIG_IEEE80211BE */ + + wpa_printf(MSG_DEBUG, "authentication reply: STA=" MACSTR + " auth_alg=%d auth_transaction=%d resp=%d (IE len=%lu) (dbg=%s)", + MAC2STR(dst), auth_alg, auth_transaction, + resp, (unsigned long) ies_len, dbg); +#ifdef CONFIG_TESTING_OPTIONS +#ifdef CONFIG_SAE + if (hapd->conf->sae_confirm_immediate == 2 && + auth_alg == WLAN_AUTH_SAE) { + if (auth_transaction == 1 && sta && + (resp == WLAN_STATUS_SUCCESS || + resp == WLAN_STATUS_SAE_HASH_TO_ELEMENT || + resp == WLAN_STATUS_SAE_PK)) { + wpa_printf(MSG_DEBUG, + "TESTING: Postpone SAE Commit transmission until Confirm is ready"); + os_free(sta->sae_postponed_commit); + sta->sae_postponed_commit = buf; + sta->sae_postponed_commit_len = rlen; + return WLAN_STATUS_SUCCESS; + } + + if (auth_transaction == 2 && sta && sta->sae_postponed_commit) { + wpa_printf(MSG_DEBUG, + "TESTING: Send postponed SAE Commit first, immediately followed by SAE Confirm"); + if (hostapd_drv_send_mlme(hapd, + sta->sae_postponed_commit, + sta->sae_postponed_commit_len, + 0, NULL, 0, 0) < 0) + wpa_printf(MSG_INFO, "send_auth_reply: send failed"); + os_free(sta->sae_postponed_commit); + sta->sae_postponed_commit = NULL; + sta->sae_postponed_commit_len = 0; + } + } +#endif /* CONFIG_SAE */ +#endif /* CONFIG_TESTING_OPTIONS */ + if (hostapd_drv_send_mlme(hapd, reply, rlen, 0, NULL, 0, 0) < 0) + wpa_printf(MSG_INFO, "send_auth_reply: send failed"); + else + reply_res = WLAN_STATUS_SUCCESS; + + os_free(buf); + + return reply_res; +} + + +#ifdef CONFIG_IEEE80211R_AP +static void handle_auth_ft_finish(void *ctx, const u8 *dst, + u16 auth_transaction, u16 status, + const u8 *ies, size_t ies_len) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + int reply_res; + + reply_res = send_auth_reply(hapd, NULL, dst, WLAN_AUTH_FT, + auth_transaction, status, ies, ies_len, + "auth-ft-finish"); + + sta = ap_get_sta(hapd, dst); + if (sta == NULL) + return; + + if (sta->added_unassoc && (reply_res != WLAN_STATUS_SUCCESS || + status != WLAN_STATUS_SUCCESS)) { + hostapd_drv_sta_remove(hapd, sta->addr); + sta->added_unassoc = 0; + return; + } + + if (status != WLAN_STATUS_SUCCESS) + return; + + hostapd_logger(hapd, dst, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "authentication OK (FT)"); + sta->flags |= WLAN_STA_AUTH; + mlme_authenticate_indication(hapd, sta); +} +#endif /* CONFIG_IEEE80211R_AP */ + + +#ifdef CONFIG_SAE + +static void sae_set_state(struct sta_info *sta, enum sae_state state, + const char *reason) +{ + wpa_printf(MSG_DEBUG, "SAE: State %s -> %s for peer " MACSTR " (%s)", + sae_state_txt(sta->sae->state), sae_state_txt(state), + MAC2STR(sta->addr), reason); + sta->sae->state = state; +} + + +const char * sae_get_password(struct hostapd_data *hapd, + struct sta_info *sta, + const char *rx_id, + struct sae_password_entry **pw_entry, + struct sae_pt **s_pt, + const struct sae_pk **s_pk) +{ + const char *password = NULL; + struct sae_password_entry *pw; + struct sae_pt *pt = NULL; + const struct sae_pk *pk = NULL; + struct hostapd_sta_wpa_psk_short *psk = NULL; + + for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) { + if (!is_broadcast_ether_addr(pw->peer_addr) && + (!sta || + !ether_addr_equal(pw->peer_addr, sta->addr))) + continue; + if ((rx_id && !pw->identifier) || (!rx_id && pw->identifier)) + continue; + if (rx_id && pw->identifier && + os_strcmp(rx_id, pw->identifier) != 0) + continue; + password = pw->password; + pt = pw->pt; + if (!(hapd->conf->mesh & MESH_ENABLED)) + pk = pw->pk; + break; + } + if (!password) { + password = hapd->conf->ssid.wpa_passphrase; + pt = hapd->conf->ssid.pt; + } + + if (!password && sta) { + for (psk = sta->psk; psk; psk = psk->next) { + if (psk->is_passphrase) { + password = psk->passphrase; + break; + } + } + } + + if (pw_entry) + *pw_entry = pw; + if (s_pt) + *s_pt = pt; + if (s_pk) + *s_pk = pk; + + return password; +} + + +static struct wpabuf * auth_build_sae_commit(struct hostapd_data *hapd, + struct sta_info *sta, int update, + int status_code) +{ + struct wpabuf *buf; + const char *password = NULL; + struct sae_password_entry *pw; + const char *rx_id = NULL; + int use_pt = 0; + struct sae_pt *pt = NULL; + const struct sae_pk *pk = NULL; + const u8 *own_addr = hapd->own_addr; + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) + own_addr = hapd->mld->mld_addr; +#endif /* CONFIG_IEEE80211BE */ + + if (sta->sae->tmp) { + rx_id = sta->sae->tmp->pw_id; + use_pt = sta->sae->h2e; +#ifdef CONFIG_SAE_PK + os_memcpy(sta->sae->tmp->own_addr, own_addr, ETH_ALEN); + os_memcpy(sta->sae->tmp->peer_addr, sta->addr, ETH_ALEN); +#endif /* CONFIG_SAE_PK */ + } + + if (rx_id && hapd->conf->sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK) + use_pt = 1; + else if (status_code == WLAN_STATUS_SUCCESS) + use_pt = 0; + else if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT || + status_code == WLAN_STATUS_SAE_PK) + use_pt = 1; + + password = sae_get_password(hapd, sta, rx_id, &pw, &pt, &pk); + if (!password || (use_pt && !pt)) { + wpa_printf(MSG_DEBUG, "SAE: No password available"); + return NULL; + } + + if (update && use_pt && + sae_prepare_commit_pt(sta->sae, pt, own_addr, sta->addr, + NULL, pk) < 0) + return NULL; + + if (update && !use_pt && + sae_prepare_commit(own_addr, sta->addr, + (u8 *) password, os_strlen(password), + sta->sae) < 0) { + wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE"); + return NULL; + } + + if (pw && pw->vlan_id) { + if (!sta->sae->tmp) { + wpa_printf(MSG_INFO, + "SAE: No temporary data allocated - cannot store VLAN ID"); + return NULL; + } + sta->sae->tmp->vlan_id = pw->vlan_id; + } + + buf = wpabuf_alloc(SAE_COMMIT_MAX_LEN + + (rx_id ? 3 + os_strlen(rx_id) : 0)); + if (buf && + sae_write_commit(sta->sae, buf, sta->sae->tmp ? + sta->sae->tmp->anti_clogging_token : NULL, + rx_id) < 0) { + wpabuf_free(buf); + buf = NULL; + } + + return buf; +} + + +static struct wpabuf * auth_build_sae_confirm(struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct wpabuf *buf; + + buf = wpabuf_alloc(SAE_CONFIRM_MAX_LEN); + if (buf == NULL) + return NULL; + +#ifdef CONFIG_SAE_PK +#ifdef CONFIG_TESTING_OPTIONS + if (sta->sae->tmp) + sta->sae->tmp->omit_pk_elem = hapd->conf->sae_pk_omit; +#endif /* CONFIG_TESTING_OPTIONS */ +#endif /* CONFIG_SAE_PK */ + + if (sae_write_confirm(sta->sae, buf) < 0) { + wpabuf_free(buf); + return NULL; + } + + return buf; +} + + +static int auth_sae_send_commit(struct hostapd_data *hapd, + struct sta_info *sta, + int update, int status_code) +{ + struct wpabuf *data; + int reply_res; + u16 status; + + data = auth_build_sae_commit(hapd, sta, update, status_code); + if (!data && sta->sae->tmp && sta->sae->tmp->pw_id) + return WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER; + if (data == NULL) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + if (sta->sae->tmp && sta->sae->pk) + status = WLAN_STATUS_SAE_PK; + else if (sta->sae->tmp && sta->sae->h2e) + status = WLAN_STATUS_SAE_HASH_TO_ELEMENT; + else + status = WLAN_STATUS_SUCCESS; +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->sae_commit_status >= 0 && + hapd->conf->sae_commit_status != status) { + wpa_printf(MSG_INFO, + "TESTING: Override SAE commit status code %u --> %d", + status, hapd->conf->sae_commit_status); + status = hapd->conf->sae_commit_status; + } +#endif /* CONFIG_TESTING_OPTIONS */ + reply_res = send_auth_reply(hapd, sta, sta->addr, + WLAN_AUTH_SAE, 1, + status, wpabuf_head(data), + wpabuf_len(data), "sae-send-commit"); + + wpabuf_free(data); + + return reply_res; +} + + +static int auth_sae_send_confirm(struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct wpabuf *data; + int reply_res; + + data = auth_build_sae_confirm(hapd, sta); + if (data == NULL) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + reply_res = send_auth_reply(hapd, sta, sta->addr, + WLAN_AUTH_SAE, 2, + WLAN_STATUS_SUCCESS, wpabuf_head(data), + wpabuf_len(data), "sae-send-confirm"); + + wpabuf_free(data); + + return reply_res; +} + +#endif /* CONFIG_SAE */ + + +#if defined(CONFIG_SAE) || defined(CONFIG_PASN) + +static int use_anti_clogging(struct hostapd_data *hapd) +{ + struct sta_info *sta; + unsigned int open = 0; + + if (hapd->conf->anti_clogging_threshold == 0) + return 1; + + for (sta = hapd->sta_list; sta; sta = sta->next) { +#ifdef CONFIG_SAE + if (sta->sae && + (sta->sae->state == SAE_COMMITTED || + sta->sae->state == SAE_CONFIRMED)) + open++; +#endif /* CONFIG_SAE */ +#ifdef CONFIG_PASN + if (sta->pasn && sta->pasn->ecdh) + open++; +#endif /* CONFIG_PASN */ + if (open >= hapd->conf->anti_clogging_threshold) + return 1; + } + +#ifdef CONFIG_SAE + /* In addition to already existing open SAE sessions, check whether + * there are enough pending commit messages in the processing queue to + * potentially result in too many open sessions. */ + if (open + dl_list_len(&hapd->sae_commit_queue) >= + hapd->conf->anti_clogging_threshold) + return 1; +#endif /* CONFIG_SAE */ + + return 0; +} + +#endif /* defined(CONFIG_SAE) || defined(CONFIG_PASN) */ + + +#ifdef CONFIG_SAE + +static int sae_check_big_sync(struct hostapd_data *hapd, struct sta_info *sta) +{ + if (sta->sae->sync > hapd->conf->sae_sync) { + sae_set_state(sta, SAE_NOTHING, "Sync > dot11RSNASAESync"); + sta->sae->sync = 0; + if (sta->sae->tmp) { + /* Disable this SAE instance for 10 seconds to avoid + * unnecessary flood of multiple SAE commits in + * unexpected mesh cases. */ + if (os_get_reltime(&sta->sae->tmp->disabled_until) == 0) + sta->sae->tmp->disabled_until.sec += 10; + } + return -1; + } + return 0; +} + + +static bool sae_proto_instance_disabled(struct sta_info *sta) +{ + struct sae_temporary_data *tmp; + + if (!sta->sae) + return false; + tmp = sta->sae->tmp; + if (!tmp) + return false; + + if (os_reltime_initialized(&tmp->disabled_until)) { + struct os_reltime now; + + os_get_reltime(&now); + if (os_reltime_before(&now, &tmp->disabled_until)) + return true; + } + + return false; +} + + +static void auth_sae_retransmit_timer(void *eloop_ctx, void *eloop_data) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = eloop_data; + int ret; + + if (sae_check_big_sync(hapd, sta)) + return; + sta->sae->sync++; + wpa_printf(MSG_DEBUG, "SAE: Auth SAE retransmit timer for " MACSTR + " (sync=%d state=%s)", + MAC2STR(sta->addr), sta->sae->sync, + sae_state_txt(sta->sae->state)); + + switch (sta->sae->state) { + case SAE_COMMITTED: + ret = auth_sae_send_commit(hapd, sta, 0, -1); + eloop_register_timeout(0, + hapd->dot11RSNASAERetransPeriod * 1000, + auth_sae_retransmit_timer, hapd, sta); + break; + case SAE_CONFIRMED: + ret = auth_sae_send_confirm(hapd, sta); + eloop_register_timeout(0, + hapd->dot11RSNASAERetransPeriod * 1000, + auth_sae_retransmit_timer, hapd, sta); + break; + default: + ret = -1; + break; + } + + if (ret != WLAN_STATUS_SUCCESS) + wpa_printf(MSG_INFO, "SAE: Failed to retransmit: ret=%d", ret); +} + + +void sae_clear_retransmit_timer(struct hostapd_data *hapd, struct sta_info *sta) +{ + eloop_cancel_timeout(auth_sae_retransmit_timer, hapd, sta); +} + + +static void sae_set_retransmit_timer(struct hostapd_data *hapd, + struct sta_info *sta) +{ + if (!(hapd->conf->mesh & MESH_ENABLED)) + return; + + eloop_cancel_timeout(auth_sae_retransmit_timer, hapd, sta); + eloop_register_timeout(0, hapd->dot11RSNASAERetransPeriod * 1000, + auth_sae_retransmit_timer, hapd, sta); +} + + +static void sae_sme_send_external_auth_status(struct hostapd_data *hapd, + struct sta_info *sta, u16 status) +{ + struct external_auth params; + + os_memset(¶ms, 0, sizeof(params)); + params.status = status; + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) + params.bssid = + sta->mld_info.links[sta->mld_assoc_link_id].peer_addr; +#endif /* CONFIG_IEEE80211BE */ + if (!params.bssid) + params.bssid = sta->addr; + + if (status == WLAN_STATUS_SUCCESS && sta->sae && + !hapd->conf->disable_pmksa_caching) + params.pmkid = sta->sae->pmkid; + + hostapd_drv_send_external_auth_status(hapd, ¶ms); +} + + +void sae_accept_sta(struct hostapd_data *hapd, struct sta_info *sta) +{ +#ifndef CONFIG_NO_VLAN + struct vlan_description vlan_desc; + + if (sta->sae->tmp && sta->sae->tmp->vlan_id > 0) { + wpa_printf(MSG_DEBUG, "SAE: Assign STA " MACSTR + " to VLAN ID %d", + MAC2STR(sta->addr), sta->sae->tmp->vlan_id); + + if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD)) { + os_memset(&vlan_desc, 0, sizeof(vlan_desc)); + vlan_desc.notempty = 1; + vlan_desc.untagged = sta->sae->tmp->vlan_id; + if (!hostapd_vlan_valid(hapd->conf->vlan, &vlan_desc)) { + wpa_printf(MSG_INFO, + "Invalid VLAN ID %d in sae_password", + sta->sae->tmp->vlan_id); + return; + } + + if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0 || + ap_sta_bind_vlan(hapd, sta) < 0) { + wpa_printf(MSG_INFO, + "Failed to assign VLAN ID %d from sae_password to " + MACSTR, sta->sae->tmp->vlan_id, + MAC2STR(sta->addr)); + return; + } + } else { + sta->vlan_id = sta->sae->tmp->vlan_id; + } + } +#endif /* CONFIG_NO_VLAN */ + + sta->flags |= WLAN_STA_AUTH; + sta->auth_alg = WLAN_AUTH_SAE; + mlme_authenticate_indication(hapd, sta); + wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH); + sae_set_state(sta, SAE_ACCEPTED, "Accept Confirm"); + crypto_bignum_deinit(sta->sae->peer_commit_scalar_accepted, 0); + sta->sae->peer_commit_scalar_accepted = sta->sae->peer_commit_scalar; + sta->sae->peer_commit_scalar = NULL; + wpa_auth_pmksa_add_sae(hapd->wpa_auth, sta->addr, + sta->sae->pmk, sta->sae->pmk_len, + sta->sae->pmkid, sta->sae->akmp); + sae_sme_send_external_auth_status(hapd, sta, WLAN_STATUS_SUCCESS); +} + + +static int sae_sm_step(struct hostapd_data *hapd, struct sta_info *sta, + u16 auth_transaction, u16 status_code, + int allow_reuse, int *sta_removed) +{ + int ret; + + *sta_removed = 0; + + if (auth_transaction != 1 && auth_transaction != 2) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + wpa_printf(MSG_DEBUG, "SAE: Peer " MACSTR " state=%s auth_trans=%u", + MAC2STR(sta->addr), sae_state_txt(sta->sae->state), + auth_transaction); + + if (auth_transaction == 1 && sae_proto_instance_disabled(sta)) { + wpa_printf(MSG_DEBUG, + "SAE: Protocol instance temporarily disabled - discard received SAE commit"); + return WLAN_STATUS_SUCCESS; + } + + switch (sta->sae->state) { + case SAE_NOTHING: + if (auth_transaction == 1) { + if (sta->sae->tmp) { + sta->sae->h2e = + (status_code == + WLAN_STATUS_SAE_HASH_TO_ELEMENT || + status_code == WLAN_STATUS_SAE_PK); + sta->sae->pk = + status_code == WLAN_STATUS_SAE_PK; + } + ret = auth_sae_send_commit(hapd, sta, + !allow_reuse, status_code); + if (ret) + return ret; + sae_set_state(sta, SAE_COMMITTED, "Sent Commit"); + + if (sae_process_commit(sta->sae) < 0) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + /* + * In mesh case, both Commit and Confirm are sent + * immediately. In infrastructure BSS, by default, only + * a single Authentication frame (Commit) is expected + * from the AP here and the second one (Confirm) will + * be sent once the STA has sent its second + * Authentication frame (Confirm). This behavior can be + * overridden with explicit configuration so that the + * infrastructure BSS case sends both frames together. + */ + if ((hapd->conf->mesh & MESH_ENABLED) || + hapd->conf->sae_confirm_immediate) { + /* + * Send both Commit and Confirm immediately + * based on SAE finite state machine + * Nothing -> Confirm transition. + */ + ret = auth_sae_send_confirm(hapd, sta); + if (ret) + return ret; + sae_set_state(sta, SAE_CONFIRMED, + "Sent Confirm (mesh)"); + } else { + /* + * For infrastructure BSS, send only the Commit + * message now to get alternating sequence of + * Authentication frames between the AP and STA. + * Confirm will be sent in + * Committed -> Confirmed/Accepted transition + * when receiving Confirm from STA. + */ + } + sta->sae->sync = 0; + sae_set_retransmit_timer(hapd, sta); + } else { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "SAE confirm before commit"); + } + break; + case SAE_COMMITTED: + sae_clear_retransmit_timer(hapd, sta); + if (auth_transaction == 1) { + if (sae_process_commit(sta->sae) < 0) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + ret = auth_sae_send_confirm(hapd, sta); + if (ret) + return ret; + sae_set_state(sta, SAE_CONFIRMED, "Sent Confirm"); + sta->sae->sync = 0; + sae_set_retransmit_timer(hapd, sta); + } else if (hapd->conf->mesh & MESH_ENABLED) { + /* + * In mesh case, follow SAE finite state machine and + * send Commit now, if sync count allows. + */ + if (sae_check_big_sync(hapd, sta)) + return WLAN_STATUS_SUCCESS; + sta->sae->sync++; + + ret = auth_sae_send_commit(hapd, sta, 0, status_code); + if (ret) + return ret; + + sae_set_retransmit_timer(hapd, sta); + } else { + /* + * For instructure BSS, send the postponed Confirm from + * Nothing -> Confirmed transition that was reduced to + * Nothing -> Committed above. + */ + ret = auth_sae_send_confirm(hapd, sta); + if (ret) + return ret; + + sae_set_state(sta, SAE_CONFIRMED, "Sent Confirm"); + + /* + * Since this was triggered on Confirm RX, run another + * step to get to Accepted without waiting for + * additional events. + */ + return sae_sm_step(hapd, sta, auth_transaction, + WLAN_STATUS_SUCCESS, 0, sta_removed); + } + break; + case SAE_CONFIRMED: + sae_clear_retransmit_timer(hapd, sta); + if (auth_transaction == 1) { + if (sae_check_big_sync(hapd, sta)) + return WLAN_STATUS_SUCCESS; + sta->sae->sync++; + + ret = auth_sae_send_commit(hapd, sta, 1, status_code); + if (ret) + return ret; + + if (sae_process_commit(sta->sae) < 0) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + ret = auth_sae_send_confirm(hapd, sta); + if (ret) + return ret; + + sae_set_retransmit_timer(hapd, sta); + } else { + sta->sae->send_confirm = 0xffff; + sae_accept_sta(hapd, sta); + } + break; + case SAE_ACCEPTED: + if (auth_transaction == 1 && + (hapd->conf->mesh & MESH_ENABLED)) { + wpa_printf(MSG_DEBUG, "SAE: remove the STA (" MACSTR + ") doing reauthentication", + MAC2STR(sta->addr)); + wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr); + ap_free_sta(hapd, sta); + *sta_removed = 1; + } else if (auth_transaction == 1) { + wpa_printf(MSG_DEBUG, "SAE: Start reauthentication"); + ret = auth_sae_send_commit(hapd, sta, 1, status_code); + if (ret) + return ret; + sae_set_state(sta, SAE_COMMITTED, "Sent Commit"); + + if (sae_process_commit(sta->sae) < 0) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + sta->sae->sync = 0; + sae_set_retransmit_timer(hapd, sta); + } else { + if (sae_check_big_sync(hapd, sta)) + return WLAN_STATUS_SUCCESS; + sta->sae->sync++; + + ret = auth_sae_send_confirm(hapd, sta); + sae_clear_temp_data(sta->sae); + if (ret) + return ret; + } + break; + default: + wpa_printf(MSG_ERROR, "SAE: invalid state %d", + sta->sae->state); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + return WLAN_STATUS_SUCCESS; +} + + +static void sae_pick_next_group(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct sae_data *sae = sta->sae; + int i, *groups = hapd->conf->sae_groups; + int default_groups[] = { 19, 0 }; + + if (sae->state != SAE_COMMITTED) + return; + + wpa_printf(MSG_DEBUG, "SAE: Previously selected group: %d", sae->group); + + if (!groups) + groups = default_groups; + for (i = 0; groups[i] > 0; i++) { + if (sae->group == groups[i]) + break; + } + + if (groups[i] <= 0) { + wpa_printf(MSG_DEBUG, + "SAE: Previously selected group not found from the current configuration"); + return; + } + + for (;;) { + i++; + if (groups[i] <= 0) { + wpa_printf(MSG_DEBUG, + "SAE: No alternative group enabled"); + return; + } + + if (sae_set_group(sae, groups[i]) < 0) + continue; + + break; + } + wpa_printf(MSG_DEBUG, "SAE: Selected new group: %d", groups[i]); +} + + +static int sae_status_success(struct hostapd_data *hapd, u16 status_code) +{ + enum sae_pwe sae_pwe = hapd->conf->sae_pwe; + int id_in_use; + bool sae_pk = false; + + id_in_use = hostapd_sae_pw_id_in_use(hapd->conf); + if (id_in_use == 2 && sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK) + sae_pwe = SAE_PWE_HASH_TO_ELEMENT; + else if (id_in_use == 1 && sae_pwe == SAE_PWE_HUNT_AND_PECK) + sae_pwe = SAE_PWE_BOTH; +#ifdef CONFIG_SAE_PK + sae_pk = hostapd_sae_pk_in_use(hapd->conf); + if (sae_pwe == SAE_PWE_HUNT_AND_PECK && sae_pk) + sae_pwe = SAE_PWE_BOTH; +#endif /* CONFIG_SAE_PK */ + if (sae_pwe == SAE_PWE_HUNT_AND_PECK && + (hapd->conf->wpa_key_mgmt & + (WPA_KEY_MGMT_SAE_EXT_KEY | WPA_KEY_MGMT_FT_SAE_EXT_KEY))) + sae_pwe = SAE_PWE_BOTH; + + return ((sae_pwe == SAE_PWE_HUNT_AND_PECK || + sae_pwe == SAE_PWE_FORCE_HUNT_AND_PECK) && + status_code == WLAN_STATUS_SUCCESS) || + (sae_pwe == SAE_PWE_HASH_TO_ELEMENT && + (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT || + (sae_pk && status_code == WLAN_STATUS_SAE_PK))) || + (sae_pwe == SAE_PWE_BOTH && + (status_code == WLAN_STATUS_SUCCESS || + status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT || + (sae_pk && status_code == WLAN_STATUS_SAE_PK))); +} + + +static int sae_is_group_enabled(struct hostapd_data *hapd, int group) +{ + int *groups = hapd->conf->sae_groups; + int default_groups[] = { 19, 0 }; + int i; + + if (!groups) + groups = default_groups; + + for (i = 0; groups[i] > 0; i++) { + if (groups[i] == group) + return 1; + } + + return 0; +} + + +static int check_sae_rejected_groups(struct hostapd_data *hapd, + struct sae_data *sae) +{ + const struct wpabuf *groups; + size_t i, count, len; + const u8 *pos; + + if (!sae->tmp) + return 0; + groups = sae->tmp->peer_rejected_groups; + if (!groups) + return 0; + + pos = wpabuf_head(groups); + len = wpabuf_len(groups); + if (len & 1) { + wpa_printf(MSG_DEBUG, + "SAE: Invalid length of the Rejected Groups element payload: %zu", + len); + return 1; + } + + count = len / 2; + for (i = 0; i < count; i++) { + int enabled; + u16 group; + + group = WPA_GET_LE16(pos); + pos += 2; + enabled = sae_is_group_enabled(hapd, group); + wpa_printf(MSG_DEBUG, "SAE: Rejected group %u is %s", + group, enabled ? "enabled" : "disabled"); + if (enabled) + return 1; + } + + return 0; +} + + +static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta, + const struct ieee80211_mgmt *mgmt, size_t len, + u16 auth_transaction, u16 status_code) +{ + int resp = WLAN_STATUS_SUCCESS; + struct wpabuf *data = NULL; + int *groups = hapd->conf->sae_groups; + int default_groups[] = { 19, 0 }; + const u8 *pos, *end; + int sta_removed = 0; + bool success_status; + + if (!groups) + groups = default_groups; + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->sae_reflection_attack && auth_transaction == 1) { + wpa_printf(MSG_DEBUG, "SAE: TESTING - reflection attack"); + pos = mgmt->u.auth.variable; + end = ((const u8 *) mgmt) + len; + resp = status_code; + send_auth_reply(hapd, sta, sta->addr, + WLAN_AUTH_SAE, + auth_transaction, resp, pos, end - pos, + "auth-sae-reflection-attack"); + goto remove_sta; + } + + if (hapd->conf->sae_commit_override && auth_transaction == 1) { + wpa_printf(MSG_DEBUG, "SAE: TESTING - commit override"); + send_auth_reply(hapd, sta, sta->addr, + WLAN_AUTH_SAE, + auth_transaction, resp, + wpabuf_head(hapd->conf->sae_commit_override), + wpabuf_len(hapd->conf->sae_commit_override), + "sae-commit-override"); + goto remove_sta; + } +#endif /* CONFIG_TESTING_OPTIONS */ + if (!sta->sae) { + if (auth_transaction != 1 || + !sae_status_success(hapd, status_code)) { + wpa_printf(MSG_DEBUG, "SAE: Unexpected Status Code %u", + status_code); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto reply; + } + sta->sae = os_zalloc(sizeof(*sta->sae)); + if (!sta->sae) { + resp = -1; + goto remove_sta; + } + sae_set_state(sta, SAE_NOTHING, "Init"); + sta->sae->sync = 0; + } + + if (sta->mesh_sae_pmksa_caching) { + wpa_printf(MSG_DEBUG, + "SAE: Cancel use of mesh PMKSA caching because peer starts SAE authentication"); + wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr); + sta->mesh_sae_pmksa_caching = 0; + } + + if (auth_transaction == 1) { + const u8 *token = NULL; + size_t token_len = 0; + int allow_reuse = 0; + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "start SAE authentication (RX commit, status=%u (%s))", + status_code, status2str(status_code)); + + if ((hapd->conf->mesh & MESH_ENABLED) && + status_code == WLAN_STATUS_ANTI_CLOGGING_TOKEN_REQ && + sta->sae->tmp) { + pos = mgmt->u.auth.variable; + end = ((const u8 *) mgmt) + len; + if (pos + sizeof(le16) > end) { + wpa_printf(MSG_ERROR, + "SAE: Too short anti-clogging token request"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto reply; + } + resp = sae_group_allowed(sta->sae, groups, + WPA_GET_LE16(pos)); + if (resp != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_ERROR, + "SAE: Invalid group in anti-clogging token request"); + goto reply; + } + pos += sizeof(le16); + + wpabuf_free(sta->sae->tmp->anti_clogging_token); + sta->sae->tmp->anti_clogging_token = + wpabuf_alloc_copy(pos, end - pos); + if (sta->sae->tmp->anti_clogging_token == NULL) { + wpa_printf(MSG_ERROR, + "SAE: Failed to alloc for anti-clogging token"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto remove_sta; + } + + /* + * IEEE Std 802.11-2012, 11.3.8.6.4: If the Status code + * is 76, a new Commit Message shall be constructed + * with the Anti-Clogging Token from the received + * Authentication frame, and the commit-scalar and + * COMMIT-ELEMENT previously sent. + */ + resp = auth_sae_send_commit(hapd, sta, 0, status_code); + if (resp != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_ERROR, + "SAE: Failed to send commit message"); + goto remove_sta; + } + sae_set_state(sta, SAE_COMMITTED, + "Sent Commit (anti-clogging token case in mesh)"); + sta->sae->sync = 0; + sae_set_retransmit_timer(hapd, sta); + return; + } + + if ((hapd->conf->mesh & MESH_ENABLED) && + status_code == + WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED && + sta->sae->tmp) { + wpa_printf(MSG_DEBUG, + "SAE: Peer did not accept our SAE group"); + sae_pick_next_group(hapd, sta); + goto remove_sta; + } + + if (!sae_status_success(hapd, status_code)) + goto remove_sta; + + if (sae_proto_instance_disabled(sta)) { + wpa_printf(MSG_DEBUG, + "SAE: Protocol instance temporarily disabled - discard received SAE commit"); + return; + } + + if (!(hapd->conf->mesh & MESH_ENABLED) && + sta->sae->state == SAE_COMMITTED) { + /* This is needed in the infrastructure BSS case to + * address a sequence where a STA entry may remain in + * hostapd across two attempts to do SAE authentication + * by the same STA. The second attempt may end up trying + * to use a different group and that would not be + * allowed if we remain in Committed state with the + * previously set parameters. */ + pos = mgmt->u.auth.variable; + end = ((const u8 *) mgmt) + len; + if (end - pos >= (int) sizeof(le16) && + sae_group_allowed(sta->sae, groups, + WPA_GET_LE16(pos)) == + WLAN_STATUS_SUCCESS) { + /* Do not waste resources deriving the same PWE + * again since the same group is reused. */ + sae_set_state(sta, SAE_NOTHING, + "Allow previous PWE to be reused"); + allow_reuse = 1; + } else { + sae_set_state(sta, SAE_NOTHING, + "Clear existing state to allow restart"); + sae_clear_data(sta->sae); + } + } + + resp = sae_parse_commit(sta->sae, mgmt->u.auth.variable, + ((const u8 *) mgmt) + len - + mgmt->u.auth.variable, &token, + &token_len, groups, status_code == + WLAN_STATUS_SAE_HASH_TO_ELEMENT || + status_code == WLAN_STATUS_SAE_PK, + NULL); + if (resp == SAE_SILENTLY_DISCARD) { + wpa_printf(MSG_DEBUG, + "SAE: Drop commit message from " MACSTR " due to reflection attack", + MAC2STR(sta->addr)); + goto remove_sta; + } + + if (resp == WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER) { + wpa_msg(hapd->msg_ctx, MSG_INFO, + WPA_EVENT_SAE_UNKNOWN_PASSWORD_IDENTIFIER + MACSTR, MAC2STR(sta->addr)); + sae_clear_retransmit_timer(hapd, sta); + sae_set_state(sta, SAE_NOTHING, + "Unknown Password Identifier"); + goto remove_sta; + } + + if (token && + check_comeback_token(hapd->comeback_key, + hapd->comeback_pending_idx, sta->addr, + token, token_len) + < 0) { + wpa_printf(MSG_DEBUG, "SAE: Drop commit message with " + "incorrect token from " MACSTR, + MAC2STR(sta->addr)); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto remove_sta; + } + + if (resp != WLAN_STATUS_SUCCESS) + goto reply; + + if (check_sae_rejected_groups(hapd, sta->sae)) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto reply; + } + + if (!token && use_anti_clogging(hapd) && !allow_reuse) { + int h2e = 0; + + wpa_printf(MSG_DEBUG, + "SAE: Request anti-clogging token from " + MACSTR, MAC2STR(sta->addr)); + if (sta->sae->tmp) + h2e = sta->sae->h2e; + if (status_code == WLAN_STATUS_SAE_HASH_TO_ELEMENT || + status_code == WLAN_STATUS_SAE_PK) + h2e = 1; + data = auth_build_token_req( + &hapd->last_comeback_key_update, + hapd->comeback_key, + hapd->comeback_idx, + hapd->comeback_pending_idx, + sizeof(hapd->comeback_pending_idx), + sta->sae->group, + sta->addr, h2e); + resp = WLAN_STATUS_ANTI_CLOGGING_TOKEN_REQ; + if (hapd->conf->mesh & MESH_ENABLED) + sae_set_state(sta, SAE_NOTHING, + "Request anti-clogging token case in mesh"); + goto reply; + } + + resp = sae_sm_step(hapd, sta, auth_transaction, + status_code, allow_reuse, &sta_removed); + } else if (auth_transaction == 2) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "SAE authentication (RX confirm, status=%u (%s))", + status_code, status2str(status_code)); + if (status_code != WLAN_STATUS_SUCCESS) + goto remove_sta; + if (sta->sae->state >= SAE_CONFIRMED || + !(hapd->conf->mesh & MESH_ENABLED)) { + const u8 *var; + size_t var_len; + u16 peer_send_confirm; + + var = mgmt->u.auth.variable; + var_len = ((u8 *) mgmt) + len - mgmt->u.auth.variable; + if (var_len < 2) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto reply; + } + + peer_send_confirm = WPA_GET_LE16(var); + + if (sta->sae->state == SAE_ACCEPTED && + (peer_send_confirm <= sta->sae->rc || + peer_send_confirm == 0xffff)) { + wpa_printf(MSG_DEBUG, + "SAE: Silently ignore unexpected Confirm from peer " + MACSTR + " (peer-send-confirm=%u Rc=%u)", + MAC2STR(sta->addr), + peer_send_confirm, sta->sae->rc); + return; + } + + if (sae_check_confirm(sta->sae, var, var_len, + NULL) < 0) { + resp = WLAN_STATUS_CHALLENGE_FAIL; + goto reply; + } + sta->sae->rc = peer_send_confirm; + } + resp = sae_sm_step(hapd, sta, auth_transaction, + status_code, 0, &sta_removed); + } else { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "unexpected SAE authentication transaction %u (status=%u (%s))", + auth_transaction, status_code, + status2str(status_code)); + if (status_code != WLAN_STATUS_SUCCESS) + goto remove_sta; + resp = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION; + } + +reply: + if (!sta_removed && resp != WLAN_STATUS_SUCCESS) { + pos = mgmt->u.auth.variable; + end = ((const u8 *) mgmt) + len; + + /* Copy the Finite Cyclic Group field from the request if we + * rejected it as unsupported group. */ + if (resp == WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED && + !data && end - pos >= 2) + data = wpabuf_alloc_copy(pos, 2); + + sae_sme_send_external_auth_status(hapd, sta, resp); + send_auth_reply(hapd, sta, sta->addr, + WLAN_AUTH_SAE, + auth_transaction, resp, + data ? wpabuf_head(data) : (u8 *) "", + data ? wpabuf_len(data) : 0, "auth-sae"); + if (sta->sae && sta->sae->tmp && sta->sae->tmp->pw_id && + resp == WLAN_STATUS_UNKNOWN_PASSWORD_IDENTIFIER && + auth_transaction == 1) { + wpa_printf(MSG_DEBUG, + "SAE: Clear stored password identifier since this SAE commit was not accepted"); + os_free(sta->sae->tmp->pw_id); + sta->sae->tmp->pw_id = NULL; + } + } + +remove_sta: + if (auth_transaction == 1) + success_status = sae_status_success(hapd, status_code); + else + success_status = status_code == WLAN_STATUS_SUCCESS; + if (!sta_removed && sta->added_unassoc && + (resp != WLAN_STATUS_SUCCESS || !success_status)) { + hostapd_drv_sta_remove(hapd, sta->addr); + sta->added_unassoc = 0; + } + wpabuf_free(data); +} + + +/** + * auth_sae_init_committed - Send COMMIT and start SAE in committed state + * @hapd: BSS data for the device initiating the authentication + * @sta: the peer to which commit authentication frame is sent + * + * This function implements Init event handling (IEEE Std 802.11-2012, + * 11.3.8.6.3) in which initial COMMIT message is sent. Prior to calling, the + * sta->sae structure should be initialized appropriately via a call to + * sae_prepare_commit(). + */ +int auth_sae_init_committed(struct hostapd_data *hapd, struct sta_info *sta) +{ + int ret; + + if (!sta->sae || !sta->sae->tmp) + return -1; + + if (sta->sae->state != SAE_NOTHING) + return -1; + + ret = auth_sae_send_commit(hapd, sta, 0, -1); + if (ret) + return -1; + + sae_set_state(sta, SAE_COMMITTED, "Init and sent commit"); + sta->sae->sync = 0; + sae_set_retransmit_timer(hapd, sta); + + return 0; +} + + +void auth_sae_process_commit(void *eloop_ctx, void *user_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct hostapd_sae_commit_queue *q; + unsigned int queue_len; + + q = dl_list_first(&hapd->sae_commit_queue, + struct hostapd_sae_commit_queue, list); + if (!q) + return; + wpa_printf(MSG_DEBUG, + "SAE: Process next available message from queue"); + dl_list_del(&q->list); + handle_auth(hapd, (const struct ieee80211_mgmt *) q->msg, q->len, + q->rssi, 1); + os_free(q); + + if (eloop_is_timeout_registered(auth_sae_process_commit, hapd, NULL)) + return; + queue_len = dl_list_len(&hapd->sae_commit_queue); + eloop_register_timeout(0, queue_len * 10000, auth_sae_process_commit, + hapd, NULL); +} + + +static void auth_sae_queue(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len, + int rssi) +{ + struct hostapd_sae_commit_queue *q, *q2; + unsigned int queue_len; + const struct ieee80211_mgmt *mgmt2; + + queue_len = dl_list_len(&hapd->sae_commit_queue); + if (queue_len >= 15) { + wpa_printf(MSG_DEBUG, + "SAE: No more room in message queue - drop the new frame from " + MACSTR, MAC2STR(mgmt->sa)); + return; + } + + wpa_printf(MSG_DEBUG, "SAE: Queue Authentication message from " + MACSTR " for processing (queue_len %u)", MAC2STR(mgmt->sa), + queue_len); + q = os_zalloc(sizeof(*q) + len); + if (!q) + return; + q->rssi = rssi; + q->len = len; + os_memcpy(q->msg, mgmt, len); + + /* Check whether there is already a queued Authentication frame from the + * same station with the same transaction number and if so, replace that + * queue entry with the new one. This avoids issues with a peer that + * sends multiple times (e.g., due to frequent SAE retries). There is no + * point in us trying to process the old attempts after a new one has + * obsoleted them. */ + dl_list_for_each(q2, &hapd->sae_commit_queue, + struct hostapd_sae_commit_queue, list) { + mgmt2 = (const struct ieee80211_mgmt *) q2->msg; + if (ether_addr_equal(mgmt->sa, mgmt2->sa) && + mgmt->u.auth.auth_transaction == + mgmt2->u.auth.auth_transaction) { + wpa_printf(MSG_DEBUG, + "SAE: Replace queued message from same STA with same transaction number"); + dl_list_add(&q2->list, &q->list); + dl_list_del(&q2->list); + os_free(q2); + goto queued; + } + } + + /* No pending identical entry, so add to the end of the queue */ + dl_list_add_tail(&hapd->sae_commit_queue, &q->list); + +queued: + if (eloop_is_timeout_registered(auth_sae_process_commit, hapd, NULL)) + return; + eloop_register_timeout(0, queue_len * 10000, auth_sae_process_commit, + hapd, NULL); +} + + +static int auth_sae_queued_addr(struct hostapd_data *hapd, const u8 *addr) +{ + struct hostapd_sae_commit_queue *q; + const struct ieee80211_mgmt *mgmt; + + dl_list_for_each(q, &hapd->sae_commit_queue, + struct hostapd_sae_commit_queue, list) { + mgmt = (const struct ieee80211_mgmt *) q->msg; + if (ether_addr_equal(addr, mgmt->sa)) + return 1; + } + + return 0; +} + +#endif /* CONFIG_SAE */ + + +static u16 wpa_res_to_status_code(enum wpa_validate_result res) +{ + switch (res) { + case WPA_IE_OK: + return WLAN_STATUS_SUCCESS; + case WPA_INVALID_IE: + return WLAN_STATUS_INVALID_IE; + case WPA_INVALID_GROUP: + return WLAN_STATUS_GROUP_CIPHER_NOT_VALID; + case WPA_INVALID_PAIRWISE: + return WLAN_STATUS_PAIRWISE_CIPHER_NOT_VALID; + case WPA_INVALID_AKMP: + return WLAN_STATUS_AKMP_NOT_VALID; + case WPA_NOT_ENABLED: + return WLAN_STATUS_INVALID_IE; + case WPA_ALLOC_FAIL: + return WLAN_STATUS_UNSPECIFIED_FAILURE; + case WPA_MGMT_FRAME_PROTECTION_VIOLATION: + return WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION; + case WPA_INVALID_MGMT_GROUP_CIPHER: + return WLAN_STATUS_CIPHER_REJECTED_PER_POLICY; + case WPA_INVALID_MDIE: + return WLAN_STATUS_INVALID_MDIE; + case WPA_INVALID_PROTO: + return WLAN_STATUS_INVALID_IE; + case WPA_INVALID_PMKID: + return WLAN_STATUS_INVALID_PMKID; + case WPA_DENIED_OTHER_REASON: + return WLAN_STATUS_ASSOC_DENIED_UNSPEC; + } + return WLAN_STATUS_INVALID_IE; +} + + +#ifdef CONFIG_FILS + +static void handle_auth_fils_finish(struct hostapd_data *hapd, + struct sta_info *sta, u16 resp, + struct wpabuf *data, int pub); + +void handle_auth_fils(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *pos, size_t len, u16 auth_alg, + u16 auth_transaction, u16 status_code, + void (*cb)(struct hostapd_data *hapd, + struct sta_info *sta, u16 resp, + struct wpabuf *data, int pub)) +{ + u16 resp = WLAN_STATUS_SUCCESS; + const u8 *end; + struct ieee802_11_elems elems; + enum wpa_validate_result res; + struct wpa_ie_data rsn; + struct rsn_pmksa_cache_entry *pmksa = NULL; + + if (auth_transaction != 1 || status_code != WLAN_STATUS_SUCCESS) + return; + + end = pos + len; + + wpa_hexdump(MSG_DEBUG, "FILS: Authentication frame fields", + pos, end - pos); + + /* TODO: FILS PK */ +#ifdef CONFIG_FILS_SK_PFS + if (auth_alg == WLAN_AUTH_FILS_SK_PFS) { + u16 group; + struct wpabuf *pub; + size_t elem_len; + + /* Using FILS PFS */ + + /* Finite Cyclic Group */ + if (end - pos < 2) { + wpa_printf(MSG_DEBUG, + "FILS: No room for Finite Cyclic Group"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + group = WPA_GET_LE16(pos); + pos += 2; + if (group != hapd->conf->fils_dh_group) { + wpa_printf(MSG_DEBUG, + "FILS: Unsupported Finite Cyclic Group: %u (expected %u)", + group, hapd->conf->fils_dh_group); + resp = WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED; + goto fail; + } + + crypto_ecdh_deinit(sta->fils_ecdh); + sta->fils_ecdh = crypto_ecdh_init(group); + if (!sta->fils_ecdh) { + wpa_printf(MSG_INFO, + "FILS: Could not initialize ECDH with group %d", + group); + resp = WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED; + goto fail; + } + + pub = crypto_ecdh_get_pubkey(sta->fils_ecdh, 1); + if (!pub) { + wpa_printf(MSG_DEBUG, + "FILS: Failed to derive ECDH public key"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + elem_len = wpabuf_len(pub); + wpabuf_free(pub); + + /* Element */ + if ((size_t) (end - pos) < elem_len) { + wpa_printf(MSG_DEBUG, "FILS: No room for Element"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + wpabuf_free(sta->fils_g_sta); + sta->fils_g_sta = wpabuf_alloc_copy(pos, elem_len); + wpabuf_clear_free(sta->fils_dh_ss); + sta->fils_dh_ss = crypto_ecdh_set_peerkey(sta->fils_ecdh, 1, + pos, elem_len); + if (!sta->fils_dh_ss) { + wpa_printf(MSG_DEBUG, "FILS: ECDH operation failed"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + wpa_hexdump_buf_key(MSG_DEBUG, "FILS: DH_SS", sta->fils_dh_ss); + pos += elem_len; + } else { + crypto_ecdh_deinit(sta->fils_ecdh); + sta->fils_ecdh = NULL; + wpabuf_clear_free(sta->fils_dh_ss); + sta->fils_dh_ss = NULL; + } +#endif /* CONFIG_FILS_SK_PFS */ + + wpa_hexdump(MSG_DEBUG, "FILS: Remaining IEs", pos, end - pos); + if (ieee802_11_parse_elems(pos, end - pos, &elems, 1) == ParseFailed) { + wpa_printf(MSG_DEBUG, "FILS: Could not parse elements"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + /* RSNE */ + wpa_hexdump(MSG_DEBUG, "FILS: RSN element", + elems.rsn_ie, elems.rsn_ie_len); + if (!elems.rsn_ie || + wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2, elems.rsn_ie_len + 2, + &rsn) < 0) { + wpa_printf(MSG_DEBUG, "FILS: No valid RSN element"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + if (!sta->wpa_sm) + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, sta->addr, + NULL); + if (!sta->wpa_sm) { + wpa_printf(MSG_DEBUG, + "FILS: Failed to initialize RSN state machine"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm, + hapd->iface->freq, + elems.rsn_ie - 2, elems.rsn_ie_len + 2, + elems.rsnxe ? elems.rsnxe - 2 : NULL, + elems.rsnxe ? elems.rsnxe_len + 2 : 0, + elems.mdie, elems.mdie_len, NULL, 0, NULL); + resp = wpa_res_to_status_code(res); + if (resp != WLAN_STATUS_SUCCESS) + goto fail; + + if (!elems.fils_nonce) { + wpa_printf(MSG_DEBUG, "FILS: No FILS Nonce field"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + wpa_hexdump(MSG_DEBUG, "FILS: SNonce", elems.fils_nonce, + FILS_NONCE_LEN); + os_memcpy(sta->fils_snonce, elems.fils_nonce, FILS_NONCE_LEN); + + /* PMKID List */ + if (rsn.pmkid && rsn.num_pmkid > 0) { + u8 num; + const u8 *pmkid; + + wpa_hexdump(MSG_DEBUG, "FILS: PMKID List", + rsn.pmkid, rsn.num_pmkid * PMKID_LEN); + + pmkid = rsn.pmkid; + num = rsn.num_pmkid; + while (num) { + wpa_hexdump(MSG_DEBUG, "FILS: PMKID", pmkid, PMKID_LEN); + pmksa = wpa_auth_pmksa_get(hapd->wpa_auth, sta->addr, + pmkid); + if (pmksa) + break; + pmksa = wpa_auth_pmksa_get_fils_cache_id(hapd->wpa_auth, + sta->addr, + pmkid); + if (pmksa) + break; + pmkid += PMKID_LEN; + num--; + } + } + if (pmksa && wpa_auth_sta_key_mgmt(sta->wpa_sm) != pmksa->akmp) { + wpa_printf(MSG_DEBUG, + "FILS: Matching PMKSA cache entry has different AKMP (0x%x != 0x%x) - ignore", + wpa_auth_sta_key_mgmt(sta->wpa_sm), pmksa->akmp); + pmksa = NULL; + } + if (pmksa) + wpa_printf(MSG_DEBUG, "FILS: Found matching PMKSA cache entry"); + + /* FILS Session */ + if (!elems.fils_session) { + wpa_printf(MSG_DEBUG, "FILS: No FILS Session element"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + wpa_hexdump(MSG_DEBUG, "FILS: FILS Session", elems.fils_session, + FILS_SESSION_LEN); + os_memcpy(sta->fils_session, elems.fils_session, FILS_SESSION_LEN); + + /* Wrapped Data */ + if (elems.wrapped_data) { + wpa_hexdump(MSG_DEBUG, "FILS: Wrapped Data", + elems.wrapped_data, + elems.wrapped_data_len); + if (!pmksa) { +#ifndef CONFIG_NO_RADIUS + if (!sta->eapol_sm) { + sta->eapol_sm = + ieee802_1x_alloc_eapol_sm(hapd, sta); + } + wpa_printf(MSG_DEBUG, + "FILS: Forward EAP-Initiate/Re-auth to authentication server"); + ieee802_1x_encapsulate_radius( + hapd, sta, elems.wrapped_data, + elems.wrapped_data_len); + sta->fils_pending_cb = cb; + wpa_printf(MSG_DEBUG, + "FILS: Will send Authentication frame once the response from authentication server is available"); + sta->flags |= WLAN_STA_PENDING_FILS_ERP; + /* Calculate pending PMKID here so that we do not need + * to maintain a copy of the EAP-Initiate/Reauth + * message. */ + if (fils_pmkid_erp(wpa_auth_sta_key_mgmt(sta->wpa_sm), + elems.wrapped_data, + elems.wrapped_data_len, + sta->fils_erp_pmkid) == 0) + sta->fils_erp_pmkid_set = 1; + return; +#else /* CONFIG_NO_RADIUS */ + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; +#endif /* CONFIG_NO_RADIUS */ + } + } + +fail: + if (cb) { + struct wpabuf *data; + int pub = 0; + + data = prepare_auth_resp_fils(hapd, sta, &resp, pmksa, NULL, + NULL, 0, &pub); + if (!data) { + wpa_printf(MSG_DEBUG, + "%s: prepare_auth_resp_fils() returned failure", + __func__); + } + + cb(hapd, sta, resp, data, pub); + } +} + + +static struct wpabuf * +prepare_auth_resp_fils(struct hostapd_data *hapd, + struct sta_info *sta, u16 *resp, + struct rsn_pmksa_cache_entry *pmksa, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len, + int *is_pub) +{ + u8 fils_nonce[FILS_NONCE_LEN]; + size_t ielen; + struct wpabuf *data = NULL; + const u8 *ie; + u8 *ie_buf = NULL; + const u8 *pmk = NULL; + size_t pmk_len = 0; + u8 pmk_buf[PMK_LEN_MAX]; + struct wpabuf *pub = NULL; + + if (*resp != WLAN_STATUS_SUCCESS) + goto fail; + + ie = wpa_auth_get_wpa_ie(hapd->wpa_auth, &ielen); + if (!ie) { + *resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + if (pmksa) { + /* Add PMKID of the selected PMKSA into RSNE */ + ie_buf = os_malloc(ielen + 2 + 2 + PMKID_LEN); + if (!ie_buf) { + *resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + os_memcpy(ie_buf, ie, ielen); + if (wpa_insert_pmkid(ie_buf, &ielen, pmksa->pmkid, true) < 0) { + *resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + ie = ie_buf; + } + + if (random_get_bytes(fils_nonce, FILS_NONCE_LEN) < 0) { + *resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + wpa_hexdump(MSG_DEBUG, "RSN: Generated FILS Nonce", + fils_nonce, FILS_NONCE_LEN); + +#ifdef CONFIG_FILS_SK_PFS + if (sta->fils_dh_ss && sta->fils_ecdh) { + pub = crypto_ecdh_get_pubkey(sta->fils_ecdh, 1); + if (!pub) { + *resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + } +#endif /* CONFIG_FILS_SK_PFS */ + + data = wpabuf_alloc(1000 + ielen + (pub ? wpabuf_len(pub) : 0)); + if (!data) { + *resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + /* TODO: FILS PK */ +#ifdef CONFIG_FILS_SK_PFS + if (pub) { + /* Finite Cyclic Group */ + wpabuf_put_le16(data, hapd->conf->fils_dh_group); + + /* Element */ + wpabuf_put_buf(data, pub); + } +#endif /* CONFIG_FILS_SK_PFS */ + + /* RSNE */ + wpabuf_put_data(data, ie, ielen); + + /* MDE when using FILS+FT (already included in ie,ielen with RSNE) */ + +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(wpa_auth_sta_key_mgmt(sta->wpa_sm))) { + /* FTE[R1KH-ID,R0KH-ID] when using FILS+FT */ + int res; + + res = wpa_auth_write_fte(hapd->wpa_auth, sta->wpa_sm, + wpabuf_put(data, 0), + wpabuf_tailroom(data)); + if (res < 0) { + *resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + wpabuf_put(data, res); + } +#endif /* CONFIG_IEEE80211R_AP */ + + /* FILS Nonce */ + wpabuf_put_u8(data, WLAN_EID_EXTENSION); /* Element ID */ + wpabuf_put_u8(data, 1 + FILS_NONCE_LEN); /* Length */ + /* Element ID Extension */ + wpabuf_put_u8(data, WLAN_EID_EXT_FILS_NONCE); + wpabuf_put_data(data, fils_nonce, FILS_NONCE_LEN); + + /* FILS Session */ + wpabuf_put_u8(data, WLAN_EID_EXTENSION); /* Element ID */ + wpabuf_put_u8(data, 1 + FILS_SESSION_LEN); /* Length */ + /* Element ID Extension */ + wpabuf_put_u8(data, WLAN_EID_EXT_FILS_SESSION); + wpabuf_put_data(data, sta->fils_session, FILS_SESSION_LEN); + + /* Wrapped Data */ + if (!pmksa && erp_resp) { + wpabuf_put_u8(data, WLAN_EID_EXTENSION); /* Element ID */ + wpabuf_put_u8(data, 1 + wpabuf_len(erp_resp)); /* Length */ + /* Element ID Extension */ + wpabuf_put_u8(data, WLAN_EID_EXT_WRAPPED_DATA); + wpabuf_put_buf(data, erp_resp); + + if (fils_rmsk_to_pmk(wpa_auth_sta_key_mgmt(sta->wpa_sm), + msk, msk_len, sta->fils_snonce, fils_nonce, + sta->fils_dh_ss ? + wpabuf_head(sta->fils_dh_ss) : NULL, + sta->fils_dh_ss ? + wpabuf_len(sta->fils_dh_ss) : 0, + pmk_buf, &pmk_len)) { + wpa_printf(MSG_DEBUG, "FILS: Failed to derive PMK"); + *resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + wpabuf_free(data); + data = NULL; + goto fail; + } + pmk = pmk_buf; + + /* Don't use DHss in PTK derivation if PMKSA caching is not + * used. */ + wpabuf_clear_free(sta->fils_dh_ss); + sta->fils_dh_ss = NULL; + + if (sta->fils_erp_pmkid_set) { + /* TODO: get PMKLifetime from WPA parameters */ + unsigned int dot11RSNAConfigPMKLifetime = 43200; + int session_timeout; + + session_timeout = dot11RSNAConfigPMKLifetime; + if (sta->session_timeout_set) { + struct os_reltime now, diff; + + os_get_reltime(&now); + os_reltime_sub(&sta->session_timeout, &now, + &diff); + session_timeout = diff.sec; + } + + sta->fils_erp_pmkid_set = 0; + wpa_auth_add_fils_pmk_pmkid(sta->wpa_sm, pmk, pmk_len, + sta->fils_erp_pmkid); + if (!hapd->conf->disable_pmksa_caching && + wpa_auth_pmksa_add2( + hapd->wpa_auth, sta->addr, + pmk, pmk_len, + sta->fils_erp_pmkid, + session_timeout, + wpa_auth_sta_key_mgmt(sta->wpa_sm), + NULL) < 0) { + wpa_printf(MSG_ERROR, + "FILS: Failed to add PMKSA cache entry based on ERP"); + } + } + } else if (pmksa) { + pmk = pmksa->pmk; + pmk_len = pmksa->pmk_len; + } + + if (!pmk) { + wpa_printf(MSG_DEBUG, "FILS: No PMK available"); + *resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + wpabuf_free(data); + data = NULL; + goto fail; + } + + if (fils_auth_pmk_to_ptk(sta->wpa_sm, pmk, pmk_len, + sta->fils_snonce, fils_nonce, + sta->fils_dh_ss ? + wpabuf_head(sta->fils_dh_ss) : NULL, + sta->fils_dh_ss ? + wpabuf_len(sta->fils_dh_ss) : 0, + sta->fils_g_sta, pub) < 0) { + *resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + wpabuf_free(data); + data = NULL; + goto fail; + } + +fail: + if (is_pub) + *is_pub = pub != NULL; + os_free(ie_buf); + wpabuf_free(pub); + wpabuf_clear_free(sta->fils_dh_ss); + sta->fils_dh_ss = NULL; +#ifdef CONFIG_FILS_SK_PFS + crypto_ecdh_deinit(sta->fils_ecdh); + sta->fils_ecdh = NULL; +#endif /* CONFIG_FILS_SK_PFS */ + return data; +} + + +static void handle_auth_fils_finish(struct hostapd_data *hapd, + struct sta_info *sta, u16 resp, + struct wpabuf *data, int pub) +{ + u16 auth_alg; + + auth_alg = (pub || + resp == WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED) ? + WLAN_AUTH_FILS_SK_PFS : WLAN_AUTH_FILS_SK; + send_auth_reply(hapd, sta, sta->addr, auth_alg, 2, resp, + data ? wpabuf_head(data) : (u8 *) "", + data ? wpabuf_len(data) : 0, "auth-fils-finish"); + wpabuf_free(data); + + if (resp == WLAN_STATUS_SUCCESS) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "authentication OK (FILS)"); + sta->flags |= WLAN_STA_AUTH; + wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH); + sta->auth_alg = pub ? WLAN_AUTH_FILS_SK_PFS : WLAN_AUTH_FILS_SK; + mlme_authenticate_indication(hapd, sta); + } +} + + +void ieee802_11_finish_fils_auth(struct hostapd_data *hapd, + struct sta_info *sta, int success, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len) +{ + u16 resp; + u32 flags = sta->flags; + + sta->flags &= ~(WLAN_STA_PENDING_FILS_ERP | + WLAN_STA_PENDING_PASN_FILS_ERP); + + resp = success ? WLAN_STATUS_SUCCESS : WLAN_STATUS_UNSPECIFIED_FAILURE; + + if (flags & WLAN_STA_PENDING_FILS_ERP) { + struct wpabuf *data; + int pub = 0; + + if (!sta->fils_pending_cb) + return; + + data = prepare_auth_resp_fils(hapd, sta, &resp, NULL, erp_resp, + msk, msk_len, &pub); + if (!data) { + wpa_printf(MSG_DEBUG, + "%s: prepare_auth_resp_fils() failure", + __func__); + } + sta->fils_pending_cb(hapd, sta, resp, data, pub); +#ifdef CONFIG_PASN + } else if (flags & WLAN_STA_PENDING_PASN_FILS_ERP) { + pasn_fils_auth_resp(hapd, sta, resp, erp_resp, + msk, msk_len); +#endif /* CONFIG_PASN */ + } +} + +#endif /* CONFIG_FILS */ + + +static int ieee802_11_allowed_address(struct hostapd_data *hapd, const u8 *addr, + const u8 *msg, size_t len, + struct radius_sta *info) +{ + int res; + + res = hostapd_allowed_address(hapd, addr, msg, len, info, 0); + + if (res == HOSTAPD_ACL_REJECT) { + wpa_printf(MSG_DEBUG, "Station " MACSTR + " not allowed to authenticate", + MAC2STR(addr)); + return HOSTAPD_ACL_REJECT; + } + + if (res == HOSTAPD_ACL_PENDING) { + wpa_printf(MSG_DEBUG, "Authentication frame from " MACSTR + " waiting for an external authentication", + MAC2STR(addr)); + /* Authentication code will re-send the authentication frame + * after it has received (and cached) information from the + * external source. */ + return HOSTAPD_ACL_PENDING; + } + + return res; +} + + +int ieee802_11_set_radius_info(struct hostapd_data *hapd, struct sta_info *sta, + int res, struct radius_sta *info) +{ + u32 session_timeout = info->session_timeout; + u32 acct_interim_interval = info->acct_interim_interval; + struct vlan_description *vlan_id = &info->vlan_id; + struct hostapd_sta_wpa_psk_short *psk = info->psk; + char *identity = info->identity; + char *radius_cui = info->radius_cui; + + if (vlan_id->notempty && + !hostapd_vlan_valid(hapd->conf->vlan, vlan_id)) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS, + HOSTAPD_LEVEL_INFO, + "Invalid VLAN %d%s received from RADIUS server", + vlan_id->untagged, + vlan_id->tagged[0] ? "+" : ""); + return -1; + } + if (ap_sta_set_vlan(hapd, sta, vlan_id) < 0) + return -1; + if (sta->vlan_id) + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS, + HOSTAPD_LEVEL_INFO, "VLAN ID %d", sta->vlan_id); + + hostapd_free_psk_list(sta->psk); + if (hapd->conf->wpa_psk_radius != PSK_RADIUS_IGNORED) + hostapd_copy_psk_list(&sta->psk, psk); + else + sta->psk = NULL; + + os_free(sta->identity); + if (identity) + sta->identity = os_strdup(identity); + else + sta->identity = NULL; + + os_free(sta->radius_cui); + if (radius_cui) + sta->radius_cui = os_strdup(radius_cui); + else + sta->radius_cui = NULL; + + if (hapd->conf->acct_interim_interval == 0 && acct_interim_interval) + sta->acct_interim_interval = acct_interim_interval; + if (res == HOSTAPD_ACL_ACCEPT_TIMEOUT) { + sta->session_timeout_set = 1; + os_get_reltime(&sta->session_timeout); + sta->session_timeout.sec += session_timeout; + ap_sta_session_timeout(hapd, sta, session_timeout); + } else { + sta->session_timeout_set = 0; + ap_sta_no_session_timeout(hapd, sta); + } + + return 0; +} + + +#ifdef CONFIG_PASN +#ifdef CONFIG_FILS + +static void pasn_fils_auth_resp(struct hostapd_data *hapd, + struct sta_info *sta, u16 status, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len) +{ + struct pasn_data *pasn = sta->pasn; + struct pasn_fils *fils = &pasn->fils; + u8 pmk[PMK_LEN_MAX]; + size_t pmk_len; + int ret; + + wpa_printf(MSG_DEBUG, "PASN: FILS: Handle AS response - status=%u", + status); + + if (status != WLAN_STATUS_SUCCESS) + goto fail; + + if (!pasn->secret) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Missing secret"); + goto fail; + } + + if (random_get_bytes(fils->anonce, FILS_NONCE_LEN) < 0) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Failed to get ANonce"); + goto fail; + } + + wpa_hexdump(MSG_DEBUG, "RSN: Generated FILS ANonce", + fils->anonce, FILS_NONCE_LEN); + + ret = fils_rmsk_to_pmk(pasn_get_akmp(pasn), msk, msk_len, fils->nonce, + fils->anonce, NULL, 0, pmk, &pmk_len); + if (ret) { + wpa_printf(MSG_DEBUG, "FILS: Failed to derive PMK"); + goto fail; + } + + ret = pasn_pmk_to_ptk(pmk, pmk_len, sta->addr, hapd->own_addr, + wpabuf_head(pasn->secret), + wpabuf_len(pasn->secret), + pasn_get_ptk(sta->pasn), pasn_get_akmp(sta->pasn), + pasn_get_cipher(sta->pasn), sta->pasn->kdk_len); + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Failed to derive PTK"); + goto fail; + } + + if (pasn->secure_ltf) { + ret = wpa_ltf_keyseed(pasn_get_ptk(pasn), pasn_get_akmp(pasn), + pasn_get_cipher(pasn)); + if (ret) { + wpa_printf(MSG_DEBUG, + "PASN: FILS: Failed to derive LTF keyseed"); + goto fail; + } + } + + wpa_printf(MSG_DEBUG, "PASN: PTK successfully derived"); + + wpabuf_free(pasn->secret); + pasn->secret = NULL; + + fils->erp_resp = erp_resp; + ret = handle_auth_pasn_resp(sta->pasn, hapd->own_addr, sta->addr, NULL, + WLAN_STATUS_SUCCESS); + fils->erp_resp = NULL; + + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Failed to send response"); + goto fail; + } + + fils->state = PASN_FILS_STATE_COMPLETE; + return; +fail: + ap_free_sta(hapd, sta); +} + + +static int pasn_wd_handle_fils(struct hostapd_data *hapd, struct sta_info *sta, + struct wpabuf *wd) +{ +#ifdef CONFIG_NO_RADIUS + wpa_printf(MSG_DEBUG, "PASN: FILS: RADIUS is not configured. Fail"); + return -1; +#else /* CONFIG_NO_RADIUS */ + struct pasn_data *pasn = sta->pasn; + struct pasn_fils *fils = &pasn->fils; + struct ieee802_11_elems elems; + struct wpa_ie_data rsne_data; + struct wpabuf *fils_wd; + const u8 *data; + size_t buf_len; + u16 alg, seq, status; + int ret; + + if (fils->state != PASN_FILS_STATE_NONE) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Not expecting wrapped data"); + return -1; + } + + if (!wd) { + wpa_printf(MSG_DEBUG, "PASN: FILS: No wrapped data"); + return -1; + } + + data = wpabuf_head_u8(wd); + buf_len = wpabuf_len(wd); + + if (buf_len < 6) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Buffer too short. len=%zu", + buf_len); + return -1; + } + + alg = WPA_GET_LE16(data); + seq = WPA_GET_LE16(data + 2); + status = WPA_GET_LE16(data + 4); + + wpa_printf(MSG_DEBUG, "PASN: FILS: alg=%u, seq=%u, status=%u", + alg, seq, status); + + if (alg != WLAN_AUTH_FILS_SK || seq != 1 || + status != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_DEBUG, + "PASN: FILS: Dropping peer authentication"); + return -1; + } + + data += 6; + buf_len -= 6; + + if (ieee802_11_parse_elems(data, buf_len, &elems, 1) == ParseFailed) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Could not parse elements"); + return -1; + } + + if (!elems.rsn_ie || !elems.fils_nonce || !elems.fils_nonce || + !elems.wrapped_data || !elems.fils_session) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Missing IEs"); + return -1; + } + + ret = wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2, elems.rsn_ie_len + 2, + &rsne_data); + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Failed parsing RSNE"); + return -1; + } + + ret = wpa_pasn_validate_rsne(&rsne_data); + if (ret) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Failed validating RSNE"); + return -1; + } + + if (rsne_data.num_pmkid) { + wpa_printf(MSG_DEBUG, + "PASN: FILS: Not expecting PMKID in RSNE"); + return -1; + } + + wpa_hexdump(MSG_DEBUG, "PASN: FILS: Nonce", elems.fils_nonce, + FILS_NONCE_LEN); + os_memcpy(fils->nonce, elems.fils_nonce, FILS_NONCE_LEN); + + wpa_hexdump(MSG_DEBUG, "PASN: FILS: Session", elems.fils_session, + FILS_SESSION_LEN); + os_memcpy(fils->session, elems.fils_session, FILS_SESSION_LEN); + + fils_wd = ieee802_11_defrag(elems.wrapped_data, elems.wrapped_data_len, + true); + + if (!fils_wd) { + wpa_printf(MSG_DEBUG, "PASN: FILS: Missing wrapped data"); + return -1; + } + + if (!sta->eapol_sm) + sta->eapol_sm = ieee802_1x_alloc_eapol_sm(hapd, sta); + + wpa_printf(MSG_DEBUG, + "PASN: FILS: Forward EAP-Initiate/Re-auth to AS"); + + ieee802_1x_encapsulate_radius(hapd, sta, wpabuf_head(fils_wd), + wpabuf_len(fils_wd)); + + sta->flags |= WLAN_STA_PENDING_PASN_FILS_ERP; + + fils->state = PASN_FILS_STATE_PENDING_AS; + + /* + * Calculate pending PMKID here so that we do not need to maintain a + * copy of the EAP-Initiate/Reautt message. + */ + fils_pmkid_erp(pasn_get_akmp(pasn), + wpabuf_head(fils_wd), wpabuf_len(fils_wd), + fils->erp_pmkid); + + wpabuf_free(fils_wd); + return 0; +#endif /* CONFIG_NO_RADIUS */ +} + +#endif /* CONFIG_FILS */ + + +static int hapd_pasn_send_mlme(void *ctx, const u8 *data, size_t data_len, + int noack, unsigned int freq, unsigned int wait) +{ + struct hostapd_data *hapd = ctx; + + return hostapd_drv_send_mlme(hapd, data, data_len, 0, NULL, 0, 0); +} + + +static void hapd_initialize_pasn(struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct pasn_data *pasn = sta->pasn; + + pasn_register_callbacks(pasn, hapd, hapd_pasn_send_mlme, NULL); + pasn_set_bssid(pasn, hapd->own_addr); + pasn_set_own_addr(pasn, hapd->own_addr); + pasn_set_peer_addr(pasn, sta->addr); + pasn_set_wpa_key_mgmt(pasn, hapd->conf->wpa_key_mgmt); + pasn_set_rsn_pairwise(pasn, hapd->conf->rsn_pairwise); + pasn->pasn_groups = hapd->conf->pasn_groups; + pasn->noauth = hapd->conf->pasn_noauth; + if (hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_LTF_AP) + pasn_enable_kdk_derivation(pasn); + +#ifdef CONFIG_TESTING_OPTIONS + pasn->corrupt_mic = hapd->conf->pasn_corrupt_mic; + if (hapd->conf->force_kdk_derivation) + pasn_enable_kdk_derivation(pasn); +#endif /* CONFIG_TESTING_OPTIONS */ + pasn->use_anti_clogging = use_anti_clogging(hapd); + pasn_set_password(pasn, sae_get_password(hapd, sta, NULL, NULL, + &pasn->pt, NULL)); + pasn->rsn_ie = wpa_auth_get_wpa_ie(hapd->wpa_auth, &pasn->rsn_ie_len); + pasn_set_rsnxe_ie(pasn, hostapd_wpa_ie(hapd, WLAN_EID_RSNX)); + pasn->disable_pmksa_caching = hapd->conf->disable_pmksa_caching; + pasn_set_responder_pmksa(pasn, + wpa_auth_get_pmksa_cache(hapd->wpa_auth)); + + pasn->comeback_after = hapd->conf->pasn_comeback_after; + pasn->comeback_idx = hapd->comeback_idx; + pasn->comeback_key = hapd->comeback_key; + pasn->comeback_pending_idx = hapd->comeback_pending_idx; +} + + +static int pasn_set_keys_from_cache(struct hostapd_data *hapd, + const u8 *own_addr, const u8 *sta_addr, + int cipher, int akmp) +{ + struct ptksa_cache_entry *entry; + + entry = ptksa_cache_get(hapd->ptksa, sta_addr, cipher); + if (!entry) { + wpa_printf(MSG_DEBUG, "PASN: peer " MACSTR + " not present in PTKSA cache", MAC2STR(sta_addr)); + return -1; + } + + if (!ether_addr_equal(entry->own_addr, own_addr)) { + wpa_printf(MSG_DEBUG, + "PASN: own addr " MACSTR " and PTKSA entry own addr " + MACSTR " differ", + MAC2STR(own_addr), MAC2STR(entry->own_addr)); + return -1; + } + + wpa_printf(MSG_DEBUG, "PASN: " MACSTR " present in PTKSA cache", + MAC2STR(sta_addr)); + hostapd_drv_set_secure_ranging_ctx(hapd, own_addr, sta_addr, cipher, + entry->ptk.tk_len, entry->ptk.tk, + entry->ptk.ltf_keyseed_len, + entry->ptk.ltf_keyseed, 0); + + return 0; +} + + +static void hapd_pasn_update_params(struct hostapd_data *hapd, + struct sta_info *sta, + const struct ieee80211_mgmt *mgmt, + size_t len) +{ + struct pasn_data *pasn = sta->pasn; + struct ieee802_11_elems elems; + struct wpa_ie_data rsn_data; +#ifdef CONFIG_FILS + struct wpa_pasn_params_data pasn_params; + struct wpabuf *wrapped_data = NULL; +#endif /* CONFIG_FILS */ + int akmp; + + if (ieee802_11_parse_elems(mgmt->u.auth.variable, + len - offsetof(struct ieee80211_mgmt, + u.auth.variable), + &elems, 0) == ParseFailed) { + wpa_printf(MSG_DEBUG, + "PASN: Failed parsing Authentication frame"); + return; + } + + if (!elems.rsn_ie || + wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2, elems.rsn_ie_len + 2, + &rsn_data)) { + wpa_printf(MSG_DEBUG, "PASN: Failed parsing RSNE"); + return; + } + + if (!(rsn_data.key_mgmt & pasn->wpa_key_mgmt) || + !(rsn_data.pairwise_cipher & pasn->rsn_pairwise)) { + wpa_printf(MSG_DEBUG, "PASN: Mismatch in AKMP/cipher"); + return; + } + + pasn_set_akmp(pasn, rsn_data.key_mgmt); + pasn_set_cipher(pasn, rsn_data.pairwise_cipher); + + if (pasn->derive_kdk && + !ieee802_11_rsnx_capab_len(elems.rsnxe, elems.rsnxe_len, + WLAN_RSNX_CAPAB_SECURE_LTF)) + pasn_disable_kdk_derivation(pasn); +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->force_kdk_derivation) + pasn_enable_kdk_derivation(pasn); +#endif /* CONFIG_TESTING_OPTIONS */ + akmp = pasn_get_akmp(pasn); + + if (wpa_key_mgmt_ft(akmp) && rsn_data.num_pmkid) { +#ifdef CONFIG_IEEE80211R_AP + pasn->pmk_r1_len = 0; + wpa_ft_fetch_pmk_r1(hapd->wpa_auth, sta->addr, + rsn_data.pmkid, + pasn->pmk_r1, &pasn->pmk_r1_len, NULL, + NULL, NULL, NULL, + NULL, NULL, NULL); +#endif /* CONFIG_IEEE80211R_AP */ + } +#ifdef CONFIG_FILS + if (akmp != WPA_KEY_MGMT_FILS_SHA256 && + akmp != WPA_KEY_MGMT_FILS_SHA384) + return; + if (!elems.pasn_params || + wpa_pasn_parse_parameter_ie(elems.pasn_params - 3, + elems.pasn_params_len + 3, + false, &pasn_params)) { + wpa_printf(MSG_DEBUG, + "PASN: Failed validation of PASN Parameters element"); + return; + } + if (pasn_params.wrapped_data_format != WPA_PASN_WRAPPED_DATA_NO) { + wrapped_data = ieee802_11_defrag(elems.wrapped_data, + elems.wrapped_data_len, true); + if (!wrapped_data) { + wpa_printf(MSG_DEBUG, "PASN: Missing wrapped data"); + return; + } + if (pasn_wd_handle_fils(hapd, sta, wrapped_data)) + wpa_printf(MSG_DEBUG, + "PASN: Failed processing FILS wrapped data"); + else + pasn->fils_wd_valid = true; + } + wpabuf_free(wrapped_data); +#endif /* CONFIG_FILS */ +} + + +static void handle_auth_pasn(struct hostapd_data *hapd, struct sta_info *sta, + const struct ieee80211_mgmt *mgmt, size_t len, + u16 trans_seq, u16 status) +{ + if (hapd->conf->wpa != WPA_PROTO_RSN) { + wpa_printf(MSG_INFO, "PASN: RSN is not configured"); + return; + } + + wpa_printf(MSG_INFO, "PASN authentication: sta=" MACSTR, + MAC2STR(sta->addr)); + + if (trans_seq == 1) { + if (sta->pasn) { + wpa_printf(MSG_DEBUG, + "PASN: Not expecting transaction == 1"); + return; + } + + if (status != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_DEBUG, + "PASN: Failure status in transaction == 1"); + return; + } + + sta->pasn = pasn_data_init(); + if (!sta->pasn) { + wpa_printf(MSG_DEBUG, + "PASN: Failed to allocate PASN context"); + return; + } + + hapd_initialize_pasn(hapd, sta); + + hapd_pasn_update_params(hapd, sta, mgmt, len); + if (handle_auth_pasn_1(sta->pasn, hapd->own_addr, + sta->addr, mgmt, len) < 0) + ap_free_sta(hapd, sta); + } else if (trans_seq == 3) { + if (!sta->pasn) { + wpa_printf(MSG_DEBUG, + "PASN: Not expecting transaction == 3"); + return; + } + + if (status != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_DEBUG, + "PASN: Failure status in transaction == 3"); + ap_free_sta_pasn(hapd, sta); + return; + } + + if (handle_auth_pasn_3(sta->pasn, hapd->own_addr, + sta->addr, mgmt, len) == 0) { + ptksa_cache_add(hapd->ptksa, hapd->own_addr, sta->addr, + pasn_get_cipher(sta->pasn), 43200, + pasn_get_ptk(sta->pasn), NULL, NULL, + pasn_get_akmp(sta->pasn)); + + pasn_set_keys_from_cache(hapd, hapd->own_addr, + sta->addr, + pasn_get_cipher(sta->pasn), + pasn_get_akmp(sta->pasn)); + } + ap_free_sta(hapd, sta); + } else { + wpa_printf(MSG_DEBUG, + "PASN: Invalid transaction %u - ignore", trans_seq); + } +} + +#endif /* CONFIG_PASN */ + + +static void handle_auth(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len, + int rssi, int from_queue) +{ + u16 auth_alg, auth_transaction, status_code; + u16 resp = WLAN_STATUS_SUCCESS; + struct sta_info *sta = NULL; + int res, reply_res; + u16 fc; + const u8 *challenge = NULL; + u8 resp_ies[2 + WLAN_AUTH_CHALLENGE_LEN]; + size_t resp_ies_len = 0; + u16 seq_ctrl; + struct radius_sta rad_info; + const u8 *dst, *sa; +#ifdef CONFIG_IEEE80211BE + bool mld_sta = false; +#endif /* CONFIG_IEEE80211BE */ + + if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) { + wpa_printf(MSG_INFO, "handle_auth - too short payload (len=%lu)", + (unsigned long) len); + return; + } + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->iconf->ignore_auth_probability > 0.0 && + drand48() < hapd->iconf->ignore_auth_probability) { + wpa_printf(MSG_INFO, + "TESTING: ignoring auth frame from " MACSTR, + MAC2STR(mgmt->sa)); + return; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + sa = mgmt->sa; +#ifdef CONFIG_IEEE80211BE + /* + * Handle MLO authentication before the station is added to hostapd and + * the driver so that the station MLD MAC address would be used in both + * hostapd and the driver. + */ + sa = hostapd_process_ml_auth(hapd, mgmt, len); + if (sa) + mld_sta = true; + else + sa = mgmt->sa; +#endif /* CONFIG_IEEE80211BE */ + + auth_alg = le_to_host16(mgmt->u.auth.auth_alg); + auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction); + status_code = le_to_host16(mgmt->u.auth.status_code); + fc = le_to_host16(mgmt->frame_control); + seq_ctrl = le_to_host16(mgmt->seq_ctrl); + + if (len >= IEEE80211_HDRLEN + sizeof(mgmt->u.auth) + + 2 + WLAN_AUTH_CHALLENGE_LEN && + mgmt->u.auth.variable[0] == WLAN_EID_CHALLENGE && + mgmt->u.auth.variable[1] == WLAN_AUTH_CHALLENGE_LEN) + challenge = &mgmt->u.auth.variable[2]; + + wpa_printf(MSG_DEBUG, "authentication: STA=" MACSTR " auth_alg=%d " + "auth_transaction=%d status_code=%d wep=%d%s " + "seq_ctrl=0x%x%s%s", + MAC2STR(sa), auth_alg, auth_transaction, + status_code, !!(fc & WLAN_FC_ISWEP), + challenge ? " challenge" : "", + seq_ctrl, (fc & WLAN_FC_RETRY) ? " retry" : "", + from_queue ? " (from queue)" : ""); + +#ifdef CONFIG_NO_RC4 + if (auth_alg == WLAN_AUTH_SHARED_KEY) { + wpa_printf(MSG_INFO, + "Unsupported authentication algorithm (%d)", + auth_alg); + resp = WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG; + goto fail; + } +#endif /* CONFIG_NO_RC4 */ + + if (hapd->tkip_countermeasures) { + wpa_printf(MSG_DEBUG, + "Ongoing TKIP countermeasures (Michael MIC failure) - reject authentication"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + if (!(((hapd->conf->auth_algs & WPA_AUTH_ALG_OPEN) && + auth_alg == WLAN_AUTH_OPEN) || +#ifdef CONFIG_IEEE80211R_AP + (hapd->conf->wpa && wpa_key_mgmt_ft(hapd->conf->wpa_key_mgmt) && + auth_alg == WLAN_AUTH_FT) || +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_SAE + (hapd->conf->wpa && wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) && + auth_alg == WLAN_AUTH_SAE) || +#endif /* CONFIG_SAE */ +#ifdef CONFIG_FILS + (hapd->conf->wpa && wpa_key_mgmt_fils(hapd->conf->wpa_key_mgmt) && + auth_alg == WLAN_AUTH_FILS_SK) || + (hapd->conf->wpa && wpa_key_mgmt_fils(hapd->conf->wpa_key_mgmt) && + hapd->conf->fils_dh_group && + auth_alg == WLAN_AUTH_FILS_SK_PFS) || +#endif /* CONFIG_FILS */ +#ifdef CONFIG_PASN + (hapd->conf->wpa && + (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_PASN) && + auth_alg == WLAN_AUTH_PASN) || +#endif /* CONFIG_PASN */ + ((hapd->conf->auth_algs & WPA_AUTH_ALG_SHARED) && + auth_alg == WLAN_AUTH_SHARED_KEY))) { + wpa_printf(MSG_INFO, "Unsupported authentication algorithm (%d)", + auth_alg); + resp = WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG; + goto fail; + } + + if (!(auth_transaction == 1 || auth_alg == WLAN_AUTH_SAE || +#ifdef CONFIG_PASN + (auth_alg == WLAN_AUTH_PASN && auth_transaction == 3) || +#endif /* CONFIG_PASN */ + (auth_alg == WLAN_AUTH_SHARED_KEY && auth_transaction == 3))) { + wpa_printf(MSG_INFO, "Unknown authentication transaction number (%d)", + auth_transaction); + resp = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION; + goto fail; + } + + if (ether_addr_equal(mgmt->sa, hapd->own_addr)) { + wpa_printf(MSG_INFO, "Station " MACSTR " not allowed to authenticate", + MAC2STR(sa)); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + +#ifdef CONFIG_IEEE80211BE + if (mld_sta && + (ether_addr_equal(sa, hapd->own_addr) || + ether_addr_equal(sa, hapd->mld->mld_addr))) { + wpa_printf(MSG_INFO, + "Station " MACSTR " not allowed to authenticate", + MAC2STR(sa)); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } +#endif /* CONFIG_IEEE80211BE */ + + if (hapd->conf->no_auth_if_seen_on) { + struct hostapd_data *other; + + other = sta_track_seen_on(hapd->iface, sa, + hapd->conf->no_auth_if_seen_on); + if (other) { + u8 *pos; + u32 info; + u8 op_class, channel, phytype; + + wpa_printf(MSG_DEBUG, "%s: Reject authentication from " + MACSTR " since STA has been seen on %s", + hapd->conf->iface, MAC2STR(sa), + hapd->conf->no_auth_if_seen_on); + + resp = WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION; + pos = &resp_ies[0]; + *pos++ = WLAN_EID_NEIGHBOR_REPORT; + *pos++ = 13; + os_memcpy(pos, other->own_addr, ETH_ALEN); + pos += ETH_ALEN; + info = 0; /* TODO: BSSID Information */ + WPA_PUT_LE32(pos, info); + pos += 4; + if (other->iconf->hw_mode == HOSTAPD_MODE_IEEE80211AD) + phytype = 8; /* dmg */ + else if (other->iconf->ieee80211ac) + phytype = 9; /* vht */ + else if (other->iconf->ieee80211n) + phytype = 7; /* ht */ + else if (other->iconf->hw_mode == + HOSTAPD_MODE_IEEE80211A) + phytype = 4; /* ofdm */ + else if (other->iconf->hw_mode == + HOSTAPD_MODE_IEEE80211G) + phytype = 6; /* erp */ + else + phytype = 5; /* hrdsss */ + if (ieee80211_freq_to_channel_ext( + hostapd_hw_get_freq(other, + other->iconf->channel), + other->iconf->secondary_channel, + other->iconf->ieee80211ac, + &op_class, &channel) == NUM_HOSTAPD_MODES) { + op_class = 0; + channel = other->iconf->channel; + } + *pos++ = op_class; + *pos++ = channel; + *pos++ = phytype; + resp_ies_len = pos - &resp_ies[0]; + goto fail; + } + } + + res = ieee802_11_allowed_address(hapd, sa, (const u8 *) mgmt, len, + &rad_info); + if (res == HOSTAPD_ACL_REJECT) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "Ignore Authentication frame from " MACSTR + " due to ACL reject", MAC2STR(sa)); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + if (res == HOSTAPD_ACL_PENDING) + return; + +#ifdef CONFIG_SAE + if (auth_alg == WLAN_AUTH_SAE && !from_queue && + (auth_transaction == 1 || + (auth_transaction == 2 && auth_sae_queued_addr(hapd, sa)))) { + /* Handle SAE Authentication commit message through a queue to + * provide more control for postponing the needed heavy + * processing under a possible DoS attack scenario. In addition, + * queue SAE Authentication confirm message if there happens to + * be a queued commit message from the same peer. This is needed + * to avoid reordering Authentication frames within the same + * SAE exchange. */ + auth_sae_queue(hapd, mgmt, len, rssi); + return; + } +#endif /* CONFIG_SAE */ + + sta = ap_get_sta(hapd, sa); + if (sta) { + sta->flags &= ~WLAN_STA_PENDING_FILS_ERP; + sta->ft_over_ds = 0; + if ((fc & WLAN_FC_RETRY) && + sta->last_seq_ctrl != WLAN_INVALID_MGMT_SEQ && + sta->last_seq_ctrl == seq_ctrl && + sta->last_subtype == WLAN_FC_STYPE_AUTH) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "Drop repeated authentication frame seq_ctrl=0x%x", + seq_ctrl); + return; + } +#ifdef CONFIG_PASN + if (auth_alg == WLAN_AUTH_PASN && + (sta->flags & WLAN_STA_ASSOC)) { + wpa_printf(MSG_DEBUG, + "PASN: auth: Existing station: " MACSTR, + MAC2STR(sta->addr)); + return; + } +#endif /* CONFIG_PASN */ + } else { +#ifdef CONFIG_MESH + if (hapd->conf->mesh & MESH_ENABLED) { + /* if the mesh peer is not available, we don't do auth. + */ + wpa_printf(MSG_DEBUG, "Mesh peer " MACSTR + " not yet known - drop Authentication frame", + MAC2STR(sa)); + /* + * Save a copy of the frame so that it can be processed + * if a new peer entry is added shortly after this. + */ + wpabuf_free(hapd->mesh_pending_auth); + hapd->mesh_pending_auth = wpabuf_alloc_copy(mgmt, len); + os_get_reltime(&hapd->mesh_pending_auth_time); + return; + } +#endif /* CONFIG_MESH */ + + sta = ap_sta_add(hapd, sa); + if (!sta) { + wpa_printf(MSG_DEBUG, "ap_sta_add() failed"); + resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; + goto fail; + } + } + +#ifdef CONFIG_IEEE80211BE + /* Set the non-AP MLD information based on the initial Authentication + * frame. Once the STA entry has been added to the driver, the driver + * will translate addresses in the frame and we need to avoid overriding + * peer_addr based on mgmt->sa which would have been translated to the + * MLD MAC address. */ + if (!sta->added_unassoc && auth_transaction == 1) { + ap_sta_free_sta_profile(&sta->mld_info); + os_memset(&sta->mld_info, 0, sizeof(sta->mld_info)); + + if (mld_sta) { + u8 link_id = hapd->mld_link_id; + + ap_sta_set_mld(sta, true); + sta->mld_assoc_link_id = link_id; + + /* + * Set the MLD address as the station address and the + * station addresses. + */ + os_memcpy(sta->mld_info.common_info.mld_addr, sa, + ETH_ALEN); + os_memcpy(sta->mld_info.links[link_id].peer_addr, + mgmt->sa, ETH_ALEN); + os_memcpy(sta->mld_info.links[link_id].local_addr, + hapd->own_addr, ETH_ALEN); + } + } +#endif /* CONFIG_IEEE80211BE */ + + sta->last_seq_ctrl = seq_ctrl; + sta->last_subtype = WLAN_FC_STYPE_AUTH; +#ifdef CONFIG_MBO + sta->auth_rssi = rssi; +#endif /* CONFIG_MBO */ + + res = ieee802_11_set_radius_info(hapd, sta, res, &rad_info); + if (res) { + wpa_printf(MSG_DEBUG, "ieee802_11_set_radius_info() failed"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + sta->flags &= ~WLAN_STA_PREAUTH; + ieee802_1x_notify_pre_auth(sta->eapol_sm, 0); + + /* + * If the driver supports full AP client state, add a station to the + * driver before sending authentication reply to make sure the driver + * has resources, and not to go through the entire authentication and + * association handshake, and fail it at the end. + * + * If this is not the first transaction, in a multi-step authentication + * algorithm, the station already exists in the driver + * (sta->added_unassoc = 1) so skip it. + * + * In mesh mode, the station was already added to the driver when the + * NEW_PEER_CANDIDATE event is received. + * + * If PMF was negotiated for the existing association, skip this to + * avoid dropping the STA entry and the associated keys. This is needed + * to allow the original connection work until the attempt can complete + * (re)association, so that unprotected Authentication frame cannot be + * used to bypass PMF protection. + * + * PASN authentication does not require adding/removing station to the + * driver so skip this flow in case of PASN authentication. + */ + if (FULL_AP_CLIENT_STATE_SUPP(hapd->iface->drv_flags) && + (!(sta->flags & WLAN_STA_MFP) || !ap_sta_is_authorized(sta)) && + !(hapd->conf->mesh & MESH_ENABLED) && + !(sta->added_unassoc) && auth_alg != WLAN_AUTH_PASN) { + if (ap_sta_re_add(hapd, sta) < 0) { + resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; + goto fail; + } + } + + switch (auth_alg) { + case WLAN_AUTH_OPEN: + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "authentication OK (open system)"); + sta->flags |= WLAN_STA_AUTH; + wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH); + sta->auth_alg = WLAN_AUTH_OPEN; + mlme_authenticate_indication(hapd, sta); + break; +#ifdef CONFIG_WEP +#ifndef CONFIG_NO_RC4 + case WLAN_AUTH_SHARED_KEY: + resp = auth_shared_key(hapd, sta, auth_transaction, challenge, + fc & WLAN_FC_ISWEP); + if (resp != 0) + wpa_printf(MSG_DEBUG, + "auth_shared_key() failed: status=%d", resp); + sta->auth_alg = WLAN_AUTH_SHARED_KEY; + mlme_authenticate_indication(hapd, sta); + if (sta->challenge && auth_transaction == 1) { + resp_ies[0] = WLAN_EID_CHALLENGE; + resp_ies[1] = WLAN_AUTH_CHALLENGE_LEN; + os_memcpy(resp_ies + 2, sta->challenge, + WLAN_AUTH_CHALLENGE_LEN); + resp_ies_len = 2 + WLAN_AUTH_CHALLENGE_LEN; + } + break; +#endif /* CONFIG_NO_RC4 */ +#endif /* CONFIG_WEP */ +#ifdef CONFIG_IEEE80211R_AP + case WLAN_AUTH_FT: + sta->auth_alg = WLAN_AUTH_FT; + if (sta->wpa_sm == NULL) + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, + sta->addr, NULL); + if (sta->wpa_sm == NULL) { + wpa_printf(MSG_DEBUG, "FT: Failed to initialize WPA " + "state machine"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + wpa_ft_process_auth(sta->wpa_sm, + auth_transaction, mgmt->u.auth.variable, + len - IEEE80211_HDRLEN - + sizeof(mgmt->u.auth), + handle_auth_ft_finish, hapd); + /* handle_auth_ft_finish() callback will complete auth. */ + return; +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_SAE + case WLAN_AUTH_SAE: +#ifdef CONFIG_MESH + if (status_code == WLAN_STATUS_SUCCESS && + hapd->conf->mesh & MESH_ENABLED) { + if (sta->wpa_sm == NULL) + sta->wpa_sm = + wpa_auth_sta_init(hapd->wpa_auth, + sta->addr, NULL); + if (sta->wpa_sm == NULL) { + wpa_printf(MSG_DEBUG, + "SAE: Failed to initialize WPA state machine"); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + } +#endif /* CONFIG_MESH */ + handle_auth_sae(hapd, sta, mgmt, len, auth_transaction, + status_code); + return; +#endif /* CONFIG_SAE */ +#ifdef CONFIG_FILS + case WLAN_AUTH_FILS_SK: + case WLAN_AUTH_FILS_SK_PFS: + handle_auth_fils(hapd, sta, mgmt->u.auth.variable, + len - IEEE80211_HDRLEN - sizeof(mgmt->u.auth), + auth_alg, auth_transaction, status_code, + handle_auth_fils_finish); + return; +#endif /* CONFIG_FILS */ +#ifdef CONFIG_PASN + case WLAN_AUTH_PASN: + handle_auth_pasn(hapd, sta, mgmt, len, auth_transaction, + status_code); + return; +#endif /* CONFIG_PASN */ + } + + fail: + dst = mgmt->sa; + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) + dst = sta->addr; +#endif /* CONFIG_IEEE80211BE */ + + reply_res = send_auth_reply(hapd, sta, dst, auth_alg, + auth_alg == WLAN_AUTH_SAE ? + auth_transaction : auth_transaction + 1, + resp, resp_ies, resp_ies_len, + "handle-auth"); + + if (sta && sta->added_unassoc && (resp != WLAN_STATUS_SUCCESS || + reply_res != WLAN_STATUS_SUCCESS)) { + hostapd_drv_sta_remove(hapd, sta->addr); + sta->added_unassoc = 0; + } +} + + +static u8 hostapd_max_bssid_indicator(struct hostapd_data *hapd) +{ + size_t num_bss_nontx; + u8 max_bssid_ind = 0; + + if (!hapd->iconf->mbssid || hapd->iface->num_bss <= 1) + return 0; + + num_bss_nontx = hapd->iface->num_bss - 1; + while (num_bss_nontx > 0) { + max_bssid_ind++; + num_bss_nontx >>= 1; + } + return max_bssid_ind; +} + + +static u32 hostapd_get_aid_word(struct hostapd_data *hapd, + struct sta_info *sta, int i) +{ +#ifdef CONFIG_IEEE80211BE + u32 aid_word = 0; + + /* Do not assign an AID that is in use on any of the affiliated links + * when finding an AID for a non-AP MLD. */ + if (hapd->conf->mld_ap && sta->mld_info.mld_sta) { + int j; + + for (j = 0; j < MAX_NUM_MLD_LINKS; j++) { + struct hostapd_data *link_bss; + + if (!sta->mld_info.links[j].valid) + continue; + + link_bss = hostapd_mld_get_link_bss(hapd, j); + if (!link_bss) { + /* This shouldn't happen, just skip */ + wpa_printf(MSG_ERROR, + "MLD: Failed to get link BSS for AID"); + continue; + } + + aid_word |= link_bss->sta_aid[i]; + } + + return aid_word; + } +#endif /* CONFIG_IEEE80211BE */ + + return hapd->sta_aid[i]; +} + + +int hostapd_get_aid(struct hostapd_data *hapd, struct sta_info *sta) +{ + int i, j = 32, aid; + + /* Transmitted and non-transmitted BSSIDs share the same AID pool, so + * use the shared storage in the transmitted BSS to find the next + * available value. */ + hapd = hostapd_mbssid_get_tx_bss(hapd); + + /* get a unique AID */ + if (sta->aid > 0) { + wpa_printf(MSG_DEBUG, " old AID %d", sta->aid); + return 0; + } + + if (TEST_FAIL()) + return -1; + + for (i = 0; i < AID_WORDS; i++) { + u32 aid_word = hostapd_get_aid_word(hapd, sta, i); + + if (aid_word == (u32) -1) + continue; + for (j = 0; j < 32; j++) { + if (!(aid_word & BIT(j))) + break; + } + if (j < 32) + break; + } + if (j == 32) + return -1; + aid = i * 32 + j + (1 << hostapd_max_bssid_indicator(hapd)); + if (aid > 2007) + return -1; + + sta->aid = aid; + hapd->sta_aid[i] |= BIT(j); + wpa_printf(MSG_DEBUG, " new AID %d", sta->aid); + return 0; +} + + +static u16 check_ssid(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *ssid_ie, size_t ssid_ie_len) +{ + if (ssid_ie == NULL) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + if (ssid_ie_len != hapd->conf->ssid.ssid_len || + os_memcmp(ssid_ie, hapd->conf->ssid.ssid, ssid_ie_len) != 0) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Station tried to associate with unknown SSID " + "'%s'", wpa_ssid_txt(ssid_ie, ssid_ie_len)); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + return WLAN_STATUS_SUCCESS; +} + + +static u16 check_wmm(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *wmm_ie, size_t wmm_ie_len) +{ + sta->flags &= ~WLAN_STA_WMM; + sta->qosinfo = 0; + if (wmm_ie && hapd->conf->wmm_enabled) { + struct wmm_information_element *wmm; + + if (!hostapd_eid_wmm_valid(hapd, wmm_ie, wmm_ie_len)) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_WPA, + HOSTAPD_LEVEL_DEBUG, + "invalid WMM element in association " + "request"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + sta->flags |= WLAN_STA_WMM; + wmm = (struct wmm_information_element *) wmm_ie; + sta->qosinfo = wmm->qos_info; + } + return WLAN_STATUS_SUCCESS; +} + +static u16 check_multi_ap(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *multi_ap_ie, size_t multi_ap_len) +{ + struct multi_ap_params multi_ap; + u16 status; + + sta->flags &= ~WLAN_STA_MULTI_AP; + + if (!hapd->conf->multi_ap) + return WLAN_STATUS_SUCCESS; + + if (!multi_ap_ie) { + if (!(hapd->conf->multi_ap & FRONTHAUL_BSS)) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Non-Multi-AP STA tries to associate with backhaul-only BSS"); + return WLAN_STATUS_ASSOC_DENIED_UNSPEC; + } + + return WLAN_STATUS_SUCCESS; + } + + status = check_multi_ap_ie(multi_ap_ie + 4, multi_ap_len - 4, + &multi_ap); + if (status != WLAN_STATUS_SUCCESS) + return status; + + if (multi_ap.capability && multi_ap.capability != MULTI_AP_BACKHAUL_STA) + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Multi-AP IE with unexpected value 0x%02x", + multi_ap.capability); + + if (multi_ap.profile == MULTI_AP_PROFILE_1 && + (hapd->conf->multi_ap_client_disallow & + PROFILE1_CLIENT_ASSOC_DISALLOW)) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Multi-AP Profile-1 clients not allowed"); + return WLAN_STATUS_ASSOC_DENIED_UNSPEC; + } + + if (multi_ap.profile >= MULTI_AP_PROFILE_2 && + (hapd->conf->multi_ap_client_disallow & + PROFILE2_CLIENT_ASSOC_DISALLOW)) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Multi-AP Profile-2 clients not allowed"); + return WLAN_STATUS_ASSOC_DENIED_UNSPEC; + } + + if (!(multi_ap.capability & MULTI_AP_BACKHAUL_STA)) { + if (hapd->conf->multi_ap & FRONTHAUL_BSS) + return WLAN_STATUS_SUCCESS; + + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Non-Multi-AP STA tries to associate with backhaul-only BSS"); + return WLAN_STATUS_ASSOC_DENIED_UNSPEC; + } + + if (!(hapd->conf->multi_ap & BACKHAUL_BSS)) + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "Backhaul STA tries to associate with fronthaul-only BSS"); + + sta->flags |= WLAN_STA_MULTI_AP; + return WLAN_STATUS_SUCCESS; +} + + +static u16 copy_supp_rates(struct hostapd_data *hapd, struct sta_info *sta, + struct ieee802_11_elems *elems) +{ + /* Supported rates not used in IEEE 802.11ad/DMG */ + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) + return WLAN_STATUS_SUCCESS; + + if (!elems->supp_rates) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "No supported rates element in AssocReq"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + if (elems->supp_rates_len + elems->ext_supp_rates_len > + sizeof(sta->supported_rates)) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "Invalid supported rates element length %d+%d", + elems->supp_rates_len, + elems->ext_supp_rates_len); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + sta->supported_rates_len = merge_byte_arrays( + sta->supported_rates, sizeof(sta->supported_rates), + elems->supp_rates, elems->supp_rates_len, + elems->ext_supp_rates, elems->ext_supp_rates_len); + + return WLAN_STATUS_SUCCESS; +} + + +#ifdef CONFIG_OWE + +static int owe_group_supported(struct hostapd_data *hapd, u16 group) +{ + int i; + int *groups = hapd->conf->owe_groups; + + if (group != 19 && group != 20 && group != 21) + return 0; + + if (!groups) + return 1; + + for (i = 0; groups[i] > 0; i++) { + if (groups[i] == group) + return 1; + } + + return 0; +} + + +static u16 owe_process_assoc_req(struct hostapd_data *hapd, + struct sta_info *sta, const u8 *owe_dh, + u8 owe_dh_len) +{ + struct wpabuf *secret, *pub, *hkey; + int res; + u8 prk[SHA512_MAC_LEN], pmkid[SHA512_MAC_LEN]; + const char *info = "OWE Key Generation"; + const u8 *addr[2]; + size_t len[2]; + u16 group; + size_t hash_len, prime_len; + + if (wpa_auth_sta_get_pmksa(sta->wpa_sm)) { + wpa_printf(MSG_DEBUG, "OWE: Using PMKSA caching"); + return WLAN_STATUS_SUCCESS; + } + + group = WPA_GET_LE16(owe_dh); + if (!owe_group_supported(hapd, group)) { + wpa_printf(MSG_DEBUG, "OWE: Unsupported DH group %u", group); + return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED; + } + if (group == 19) + prime_len = 32; + else if (group == 20) + prime_len = 48; + else if (group == 21) + prime_len = 66; + else + return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED; + + if (sta->owe_group == group && sta->owe_ecdh) { + /* This is a workaround for mac80211 behavior of retransmitting + * the Association Request frames multiple times if the link + * layer retries (i.e., seq# remains same) fail. The mac80211 + * initiated retransmission will use a different seq# and as + * such, will go through duplicate detection. If we were to + * change our DH key for that attempt, there would be two + * different DH shared secrets and the STA would likely select + * the wrong one. */ + wpa_printf(MSG_DEBUG, + "OWE: Try to reuse own previous DH key since the STA tried to go through OWE association again"); + } else { + crypto_ecdh_deinit(sta->owe_ecdh); + sta->owe_ecdh = crypto_ecdh_init(group); + } + if (!sta->owe_ecdh) + return WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED; + sta->owe_group = group; + + secret = crypto_ecdh_set_peerkey(sta->owe_ecdh, 0, owe_dh + 2, + owe_dh_len - 2); + secret = wpabuf_zeropad(secret, prime_len); + if (!secret) { + wpa_printf(MSG_DEBUG, "OWE: Invalid peer DH public key"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + wpa_hexdump_buf_key(MSG_DEBUG, "OWE: DH shared secret", secret); + + /* prk = HKDF-extract(C | A | group, z) */ + + pub = crypto_ecdh_get_pubkey(sta->owe_ecdh, 0); + if (!pub) { + wpabuf_clear_free(secret); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + /* PMKID = Truncate-128(Hash(C | A)) */ + addr[0] = owe_dh + 2; + len[0] = owe_dh_len - 2; + addr[1] = wpabuf_head(pub); + len[1] = wpabuf_len(pub); + if (group == 19) { + res = sha256_vector(2, addr, len, pmkid); + hash_len = SHA256_MAC_LEN; + } else if (group == 20) { + res = sha384_vector(2, addr, len, pmkid); + hash_len = SHA384_MAC_LEN; + } else if (group == 21) { + res = sha512_vector(2, addr, len, pmkid); + hash_len = SHA512_MAC_LEN; + } else { + wpabuf_free(pub); + wpabuf_clear_free(secret); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + pub = wpabuf_zeropad(pub, prime_len); + if (res < 0 || !pub) { + wpabuf_free(pub); + wpabuf_clear_free(secret); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + hkey = wpabuf_alloc(owe_dh_len - 2 + wpabuf_len(pub) + 2); + if (!hkey) { + wpabuf_free(pub); + wpabuf_clear_free(secret); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + wpabuf_put_data(hkey, owe_dh + 2, owe_dh_len - 2); /* C */ + wpabuf_put_buf(hkey, pub); /* A */ + wpabuf_free(pub); + wpabuf_put_le16(hkey, group); /* group */ + if (group == 19) + res = hmac_sha256(wpabuf_head(hkey), wpabuf_len(hkey), + wpabuf_head(secret), wpabuf_len(secret), prk); + else if (group == 20) + res = hmac_sha384(wpabuf_head(hkey), wpabuf_len(hkey), + wpabuf_head(secret), wpabuf_len(secret), prk); + else if (group == 21) + res = hmac_sha512(wpabuf_head(hkey), wpabuf_len(hkey), + wpabuf_head(secret), wpabuf_len(secret), prk); + wpabuf_clear_free(hkey); + wpabuf_clear_free(secret); + if (res < 0) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + wpa_hexdump_key(MSG_DEBUG, "OWE: prk", prk, hash_len); + + /* PMK = HKDF-expand(prk, "OWE Key Generation", n) */ + + os_free(sta->owe_pmk); + sta->owe_pmk = os_malloc(hash_len); + if (!sta->owe_pmk) { + os_memset(prk, 0, SHA512_MAC_LEN); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + if (group == 19) + res = hmac_sha256_kdf(prk, hash_len, NULL, (const u8 *) info, + os_strlen(info), sta->owe_pmk, hash_len); + else if (group == 20) + res = hmac_sha384_kdf(prk, hash_len, NULL, (const u8 *) info, + os_strlen(info), sta->owe_pmk, hash_len); + else if (group == 21) + res = hmac_sha512_kdf(prk, hash_len, NULL, (const u8 *) info, + os_strlen(info), sta->owe_pmk, hash_len); + os_memset(prk, 0, SHA512_MAC_LEN); + if (res < 0) { + os_free(sta->owe_pmk); + sta->owe_pmk = NULL; + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + sta->owe_pmk_len = hash_len; + + wpa_hexdump_key(MSG_DEBUG, "OWE: PMK", sta->owe_pmk, sta->owe_pmk_len); + wpa_hexdump(MSG_DEBUG, "OWE: PMKID", pmkid, PMKID_LEN); + wpa_auth_pmksa_add2(hapd->wpa_auth, sta->addr, sta->owe_pmk, + sta->owe_pmk_len, pmkid, 0, WPA_KEY_MGMT_OWE, NULL); + + return WLAN_STATUS_SUCCESS; +} + + +u16 owe_validate_request(struct hostapd_data *hapd, const u8 *peer, + const u8 *rsn_ie, size_t rsn_ie_len, + const u8 *owe_dh, size_t owe_dh_len) +{ + struct wpa_ie_data data; + int res; + + if (!rsn_ie || rsn_ie_len < 2) { + wpa_printf(MSG_DEBUG, "OWE: Invalid RSNE from " MACSTR, + MAC2STR(peer)); + return WLAN_STATUS_INVALID_IE; + } + rsn_ie -= 2; + rsn_ie_len += 2; + + res = wpa_parse_wpa_ie_rsn(rsn_ie, rsn_ie_len, &data); + if (res) { + wpa_printf(MSG_DEBUG, "Failed to parse RSNE from " MACSTR + " (res=%d)", MAC2STR(peer), res); + wpa_hexdump(MSG_DEBUG, "RSNE", rsn_ie, rsn_ie_len); + return wpa_res_to_status_code(res); + } + if (!(data.key_mgmt & WPA_KEY_MGMT_OWE)) { + wpa_printf(MSG_DEBUG, + "OWE: Unexpected key mgmt 0x%x from " MACSTR, + (unsigned int) data.key_mgmt, MAC2STR(peer)); + return WLAN_STATUS_AKMP_NOT_VALID; + } + if (!owe_dh) { + wpa_printf(MSG_DEBUG, + "OWE: No Diffie-Hellman Parameter element from " + MACSTR, MAC2STR(peer)); + return WLAN_STATUS_AKMP_NOT_VALID; + } + + return WLAN_STATUS_SUCCESS; +} + + +u16 owe_process_rsn_ie(struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *rsn_ie, size_t rsn_ie_len, + const u8 *owe_dh, size_t owe_dh_len, + const u8 *link_addr) +{ + u16 status; + u8 *owe_buf, ie[256 * 2]; + size_t ie_len = 0; + enum wpa_validate_result res; + + if (!rsn_ie || rsn_ie_len < 2) { + wpa_printf(MSG_DEBUG, "OWE: No RSNE in (Re)AssocReq"); + status = WLAN_STATUS_INVALID_IE; + goto end; + } + + if (!sta->wpa_sm) + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, sta->addr, + NULL); + if (!sta->wpa_sm) { + wpa_printf(MSG_WARNING, + "OWE: Failed to initialize WPA state machine"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto end; + } +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) + wpa_auth_set_ml_info(sta->wpa_sm, + sta->mld_assoc_link_id, &sta->mld_info); +#endif /* CONFIG_IEEE80211BE */ + rsn_ie -= 2; + rsn_ie_len += 2; + res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm, + hapd->iface->freq, rsn_ie, rsn_ie_len, + NULL, 0, NULL, 0, owe_dh, owe_dh_len, NULL); + status = wpa_res_to_status_code(res); + if (status != WLAN_STATUS_SUCCESS) + goto end; + status = owe_process_assoc_req(hapd, sta, owe_dh, owe_dh_len); + if (status != WLAN_STATUS_SUCCESS) + goto end; + owe_buf = wpa_auth_write_assoc_resp_owe(sta->wpa_sm, ie, sizeof(ie), + NULL, 0); + if (!owe_buf) { + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto end; + } + + if (sta->owe_ecdh) { + struct wpabuf *pub; + + pub = crypto_ecdh_get_pubkey(sta->owe_ecdh, 0); + if (!pub) { + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto end; + } + + /* OWE Diffie-Hellman Parameter element */ + *owe_buf++ = WLAN_EID_EXTENSION; /* Element ID */ + *owe_buf++ = 1 + 2 + wpabuf_len(pub); /* Length */ + *owe_buf++ = WLAN_EID_EXT_OWE_DH_PARAM; /* Element ID Extension + */ + WPA_PUT_LE16(owe_buf, sta->owe_group); + owe_buf += 2; + os_memcpy(owe_buf, wpabuf_head(pub), wpabuf_len(pub)); + owe_buf += wpabuf_len(pub); + wpabuf_free(pub); + sta->external_dh_updated = 1; + } + ie_len = owe_buf - ie; + +end: + wpa_printf(MSG_DEBUG, "OWE: Update status %d, ie len %d for peer " + MACSTR, status, (unsigned int) ie_len, + MAC2STR(link_addr ? link_addr : sta->addr)); + hostapd_drv_update_dh_ie(hapd, link_addr ? link_addr : sta->addr, + status, + status == WLAN_STATUS_SUCCESS ? ie : NULL, + ie_len); + + return status; +} + +#endif /* CONFIG_OWE */ + + +static bool check_sa_query(struct hostapd_data *hapd, struct sta_info *sta, + int reassoc) +{ + if ((sta->flags & + (WLAN_STA_ASSOC | WLAN_STA_MFP | WLAN_STA_AUTHORIZED)) != + (WLAN_STA_ASSOC | WLAN_STA_MFP | WLAN_STA_AUTHORIZED)) + return false; + + if (!sta->sa_query_timed_out && sta->sa_query_count > 0) + ap_check_sa_query_timeout(hapd, sta); + + if (!sta->sa_query_timed_out && + (!reassoc || sta->auth_alg != WLAN_AUTH_FT)) { + /* + * STA has already been associated with MFP and SA Query timeout + * has not been reached. Reject the association attempt + * temporarily and start SA Query, if one is not pending. + */ + if (sta->sa_query_count == 0) + ap_sta_start_sa_query(hapd, sta); + + return true; + } + + return false; +} + + +static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *ies, size_t ies_len, + struct ieee802_11_elems *elems, int reassoc, + bool link) +{ + int resp; + const u8 *wpa_ie; + size_t wpa_ie_len; + const u8 *p2p_dev_addr = NULL; + struct hostapd_data *assoc_hapd; + struct sta_info *assoc_sta = NULL; + + resp = check_ssid(hapd, sta, elems->ssid, elems->ssid_len); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + resp = check_wmm(hapd, sta, elems->wmm, elems->wmm_len); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + resp = check_ext_capab(hapd, sta, elems->ext_capab, + elems->ext_capab_len); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + resp = copy_supp_rates(hapd, sta, elems); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + + resp = check_multi_ap(hapd, sta, elems->multi_ap, elems->multi_ap_len); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + + resp = copy_sta_ht_capab(hapd, sta, elems->ht_capabilities); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + if (hapd->iconf->ieee80211n && hapd->iconf->require_ht && + !(sta->flags & WLAN_STA_HT)) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "Station does not support " + "mandatory HT PHY - reject association"); + return WLAN_STATUS_ASSOC_DENIED_NO_HT; + } + +#ifdef CONFIG_IEEE80211AC + if (hapd->iconf->ieee80211ac) { + resp = copy_sta_vht_capab(hapd, sta, elems->vht_capabilities); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + + resp = set_sta_vht_opmode(hapd, sta, elems->opmode_notif); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + } + + if (hapd->iconf->ieee80211ac && hapd->iconf->require_vht && + !(sta->flags & WLAN_STA_VHT)) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "Station does not support " + "mandatory VHT PHY - reject association"); + return WLAN_STATUS_ASSOC_DENIED_NO_VHT; + } + + if (hapd->conf->vendor_vht && !elems->vht_capabilities) { + resp = copy_sta_vendor_vht(hapd, sta, elems->vendor_vht, + elems->vendor_vht_len); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + } +#endif /* CONFIG_IEEE80211AC */ +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) { + resp = copy_sta_he_capab(hapd, sta, IEEE80211_MODE_AP, + elems->he_capabilities, + elems->he_capabilities_len); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + + if (hapd->iconf->require_he && !(sta->flags & WLAN_STA_HE)) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Station does not support mandatory HE PHY - reject association"); + return WLAN_STATUS_DENIED_HE_NOT_SUPPORTED; + } + + if (is_6ghz_op_class(hapd->iconf->op_class)) { + if (!(sta->flags & WLAN_STA_HE)) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Station does not support mandatory HE PHY - reject association"); + return WLAN_STATUS_DENIED_HE_NOT_SUPPORTED; + } + resp = copy_sta_he_6ghz_capab(hapd, sta, + elems->he_6ghz_band_cap); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + } + } +#endif /* CONFIG_IEEE80211AX */ +#ifdef CONFIG_IEEE80211BE + if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { + resp = copy_sta_eht_capab(hapd, sta, IEEE80211_MODE_AP, + elems->he_capabilities, + elems->he_capabilities_len, + elems->eht_capabilities, + elems->eht_capabilities_len); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + + if (!link) { + resp = hostapd_process_ml_assoc_req(hapd, elems, sta); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + } + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_P2P + if (elems->p2p && ies && ies_len) { + wpabuf_free(sta->p2p_ie); + sta->p2p_ie = ieee802_11_vendor_ie_concat(ies, ies_len, + P2P_IE_VENDOR_TYPE); + if (sta->p2p_ie) + p2p_dev_addr = p2p_get_go_dev_addr(sta->p2p_ie); + } else { + wpabuf_free(sta->p2p_ie); + sta->p2p_ie = NULL; + } +#endif /* CONFIG_P2P */ + + if ((hapd->conf->wpa & WPA_PROTO_RSN) && elems->rsn_ie) { + wpa_ie = elems->rsn_ie; + wpa_ie_len = elems->rsn_ie_len; + } else if ((hapd->conf->wpa & WPA_PROTO_WPA) && + elems->wpa_ie) { + wpa_ie = elems->wpa_ie; + wpa_ie_len = elems->wpa_ie_len; + } else { + wpa_ie = NULL; + wpa_ie_len = 0; + } + +#ifdef CONFIG_WPS + sta->flags &= ~(WLAN_STA_WPS | WLAN_STA_MAYBE_WPS | WLAN_STA_WPS2); + if (hapd->conf->wps_state && elems->wps_ie && ies && ies_len) { + wpa_printf(MSG_DEBUG, "STA included WPS IE in (Re)Association " + "Request - assume WPS is used"); + sta->flags |= WLAN_STA_WPS; + wpabuf_free(sta->wps_ie); + sta->wps_ie = ieee802_11_vendor_ie_concat(ies, ies_len, + WPS_IE_VENDOR_TYPE); + if (sta->wps_ie && wps_is_20(sta->wps_ie)) { + wpa_printf(MSG_DEBUG, "WPS: STA supports WPS 2.0"); + sta->flags |= WLAN_STA_WPS2; + } + wpa_ie = NULL; + wpa_ie_len = 0; + if (sta->wps_ie && wps_validate_assoc_req(sta->wps_ie) < 0) { + wpa_printf(MSG_DEBUG, "WPS: Invalid WPS IE in " + "(Re)Association Request - reject"); + return WLAN_STATUS_INVALID_IE; + } + } else if (hapd->conf->wps_state && wpa_ie == NULL) { + wpa_printf(MSG_DEBUG, "STA did not include WPA/RSN IE in " + "(Re)Association Request - possible WPS use"); + sta->flags |= WLAN_STA_MAYBE_WPS; + } else +#endif /* CONFIG_WPS */ + if (hapd->conf->wpa && wpa_ie == NULL) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "No WPA/RSN IE in association request"); + return WLAN_STATUS_INVALID_IE; + } + + if (hapd->conf->wpa && wpa_ie) { + enum wpa_validate_result res; +#ifdef CONFIG_IEEE80211BE + struct mld_info *info = &sta->mld_info; + bool init = !sta->wpa_sm; +#endif /* CONFIG_IEEE80211BE */ + + wpa_ie -= 2; + wpa_ie_len += 2; + + if (!sta->wpa_sm) { + if (!link) + assoc_sta = hostapd_ml_get_assoc_sta( + hapd, sta, &assoc_hapd); + + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, + sta->addr, + p2p_dev_addr); + + if (!sta->wpa_sm) { + wpa_printf(MSG_WARNING, + "Failed to initialize RSN state machine"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + } + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) { + wpa_printf(MSG_DEBUG, + "MLD: %s ML info in RSN Authenticator", + init ? "Set" : "Reset"); + wpa_auth_set_ml_info(sta->wpa_sm, + sta->mld_assoc_link_id, + info); + } +#endif /* CONFIG_IEEE80211BE */ + + wpa_auth_set_auth_alg(sta->wpa_sm, sta->auth_alg); + res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm, + hapd->iface->freq, + wpa_ie, wpa_ie_len, + elems->rsnxe ? elems->rsnxe - 2 : + NULL, + elems->rsnxe ? elems->rsnxe_len + 2 : + 0, + elems->mdie, elems->mdie_len, + elems->owe_dh, elems->owe_dh_len, + assoc_sta ? assoc_sta->wpa_sm : NULL); + resp = wpa_res_to_status_code(res); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + + if (wpa_auth_uses_mfp(sta->wpa_sm)) + sta->flags |= WLAN_STA_MFP; + else + sta->flags &= ~WLAN_STA_MFP; + +#ifdef CONFIG_IEEE80211R_AP + if (sta->auth_alg == WLAN_AUTH_FT) { + if (!reassoc) { + wpa_printf(MSG_DEBUG, "FT: " MACSTR " tried " + "to use association (not " + "re-association) with FT auth_alg", + MAC2STR(sta->addr)); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + resp = wpa_ft_validate_reassoc(sta->wpa_sm, ies, + ies_len); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + } +#endif /* CONFIG_IEEE80211R_AP */ + + if (link) + goto skip_sae_owe; +#ifdef CONFIG_SAE + if (wpa_auth_uses_sae(sta->wpa_sm) && sta->sae && + sta->sae->state == SAE_ACCEPTED) + wpa_auth_add_sae_pmkid(sta->wpa_sm, sta->sae->pmkid); + + if (wpa_auth_uses_sae(sta->wpa_sm) && + sta->auth_alg == WLAN_AUTH_OPEN) { + struct rsn_pmksa_cache_entry *sa; + sa = wpa_auth_sta_get_pmksa(sta->wpa_sm); + if (!sa || !wpa_key_mgmt_sae(sa->akmp)) { + wpa_printf(MSG_DEBUG, + "SAE: No PMKSA cache entry found for " + MACSTR, MAC2STR(sta->addr)); + return WLAN_STATUS_INVALID_PMKID; + } + wpa_printf(MSG_DEBUG, "SAE: " MACSTR + " using PMKSA caching", MAC2STR(sta->addr)); + } else if (wpa_auth_uses_sae(sta->wpa_sm) && + sta->auth_alg != WLAN_AUTH_SAE && + !(sta->auth_alg == WLAN_AUTH_FT && + wpa_auth_uses_ft_sae(sta->wpa_sm))) { + wpa_printf(MSG_DEBUG, "SAE: " MACSTR " tried to use " + "SAE AKM after non-SAE auth_alg %u", + MAC2STR(sta->addr), sta->auth_alg); + return WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG; + } + + if (hapd->conf->sae_pwe == SAE_PWE_BOTH && + sta->auth_alg == WLAN_AUTH_SAE && + sta->sae && !sta->sae->h2e && + ieee802_11_rsnx_capab_len(elems->rsnxe, elems->rsnxe_len, + WLAN_RSNX_CAPAB_SAE_H2E)) { + wpa_printf(MSG_INFO, "SAE: " MACSTR + " indicates support for SAE H2E, but did not use it", + MAC2STR(sta->addr)); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } +#endif /* CONFIG_SAE */ + +#ifdef CONFIG_OWE + if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) && + wpa_auth_sta_key_mgmt(sta->wpa_sm) == WPA_KEY_MGMT_OWE && + elems->owe_dh) { + resp = owe_process_assoc_req(hapd, sta, elems->owe_dh, + elems->owe_dh_len); + if (resp != WLAN_STATUS_SUCCESS) + return resp; + } +#endif /* CONFIG_OWE */ + skip_sae_owe: + +#ifdef CONFIG_DPP2 + dpp_pfs_free(sta->dpp_pfs); + sta->dpp_pfs = NULL; + + if (DPP_VERSION > 1 && + (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_DPP) && + hapd->conf->dpp_netaccesskey && sta->wpa_sm && + wpa_auth_sta_key_mgmt(sta->wpa_sm) == WPA_KEY_MGMT_DPP && + elems->owe_dh) { + sta->dpp_pfs = dpp_pfs_init( + wpabuf_head(hapd->conf->dpp_netaccesskey), + wpabuf_len(hapd->conf->dpp_netaccesskey)); + if (!sta->dpp_pfs) { + wpa_printf(MSG_DEBUG, + "DPP: Could not initialize PFS"); + /* Try to continue without PFS */ + goto pfs_fail; + } + + if (dpp_pfs_process(sta->dpp_pfs, elems->owe_dh, + elems->owe_dh_len) < 0) { + dpp_pfs_free(sta->dpp_pfs); + sta->dpp_pfs = NULL; + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + } + + wpa_auth_set_dpp_z(sta->wpa_sm, sta->dpp_pfs ? + sta->dpp_pfs->secret : NULL); + pfs_fail: +#endif /* CONFIG_DPP2 */ + + if ((sta->flags & (WLAN_STA_HT | WLAN_STA_VHT)) && + wpa_auth_get_pairwise(sta->wpa_sm) == WPA_CIPHER_TKIP) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Station tried to use TKIP with HT " + "association"); + return WLAN_STATUS_CIPHER_REJECTED_PER_POLICY; + } + + wpa_auth_set_ssid_protection( + sta->wpa_sm, + hapd->conf->ssid_protection && + ieee802_11_rsnx_capab_len( + elems->rsnxe, elems->rsnxe_len, + WLAN_RSNX_CAPAB_SSID_PROTECTION)); +#ifdef CONFIG_HS20 + } else if (hapd->conf->osen) { + if (!elems->osen) { + hostapd_logger( + hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "No HS 2.0 OSEN element in association request"); + return WLAN_STATUS_INVALID_IE; + } + + wpa_printf(MSG_DEBUG, "HS 2.0: OSEN association"); + if (sta->wpa_sm == NULL) + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, + sta->addr, NULL); + if (sta->wpa_sm == NULL) { + wpa_printf(MSG_WARNING, "Failed to initialize WPA " + "state machine"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + if (wpa_validate_osen(hapd->wpa_auth, sta->wpa_sm, + elems->osen - 2, elems->osen_len + 2) < 0) + return WLAN_STATUS_INVALID_IE; +#endif /* CONFIG_HS20 */ + } else + wpa_auth_sta_no_wpa(sta->wpa_sm); + +#ifdef CONFIG_P2P + p2p_group_notif_assoc(hapd->p2p_group, sta->addr, ies, ies_len); +#endif /* CONFIG_P2P */ + +#ifdef CONFIG_HS20 + wpabuf_free(sta->hs20_ie); + if (elems->hs20 && elems->hs20_len > 4) { + int release; + + sta->hs20_ie = wpabuf_alloc_copy(elems->hs20 + 4, + elems->hs20_len - 4); + release = ((elems->hs20[4] >> 4) & 0x0f) + 1; + if (release >= 2 && !wpa_auth_uses_mfp(sta->wpa_sm) && + hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + wpa_printf(MSG_DEBUG, + "HS 2.0: PMF not negotiated by release %d station " + MACSTR, release, MAC2STR(sta->addr)); + return WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION; + } + } else { + sta->hs20_ie = NULL; + } + + wpabuf_free(sta->roaming_consortium); + if (elems->roaming_cons_sel) + sta->roaming_consortium = wpabuf_alloc_copy( + elems->roaming_cons_sel + 4, + elems->roaming_cons_sel_len - 4); + else + sta->roaming_consortium = NULL; +#endif /* CONFIG_HS20 */ + +#ifdef CONFIG_FST + wpabuf_free(sta->mb_ies); + if (hapd->iface->fst) + sta->mb_ies = mb_ies_by_info(&elems->mb_ies); + else + sta->mb_ies = NULL; +#endif /* CONFIG_FST */ + +#ifdef CONFIG_MBO + mbo_ap_check_sta_assoc(hapd, sta, elems); + + if (hapd->conf->mbo_enabled && (hapd->conf->wpa & 2) && + elems->mbo && sta->cell_capa && !(sta->flags & WLAN_STA_MFP) && + hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + wpa_printf(MSG_INFO, + "MBO: Reject WPA2 association without PMF"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } +#endif /* CONFIG_MBO */ + +#if defined(CONFIG_FILS) && defined(CONFIG_OCV) + if (wpa_auth_uses_ocv(sta->wpa_sm) && + (sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK)) { + struct wpa_channel_info ci; + int tx_chanwidth; + int tx_seg1_idx; + enum oci_verify_result res; + + if (hostapd_drv_channel_info(hapd, &ci) != 0) { + wpa_printf(MSG_WARNING, + "Failed to get channel info to validate received OCI in FILS (Re)Association Request frame"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + if (get_sta_tx_parameters(sta->wpa_sm, + channel_width_to_int(ci.chanwidth), + ci.seg1_idx, &tx_chanwidth, + &tx_seg1_idx) < 0) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + res = ocv_verify_tx_params(elems->oci, elems->oci_len, &ci, + tx_chanwidth, tx_seg1_idx); + if (wpa_auth_uses_ocv(sta->wpa_sm) == 2 && + res == OCI_NOT_FOUND) { + /* Work around misbehaving STAs */ + wpa_printf(MSG_INFO, + "FILS: Disable OCV with a STA that does not send OCI"); + wpa_auth_set_ocv(sta->wpa_sm, 0); + } else if (res != OCI_SUCCESS) { + wpa_printf(MSG_WARNING, "FILS: OCV failed: %s", + ocv_errorstr); + wpa_msg(hapd->msg_ctx, MSG_INFO, OCV_FAILURE "addr=" + MACSTR " frame=fils-reassoc-req error=%s", + MAC2STR(sta->addr), ocv_errorstr); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + } +#endif /* CONFIG_FILS && CONFIG_OCV */ + + ap_copy_sta_supp_op_classes(sta, elems->supp_op_classes, + elems->supp_op_classes_len); + + if ((sta->capability & WLAN_CAPABILITY_RADIO_MEASUREMENT) && + elems->rrm_enabled && + elems->rrm_enabled_len >= sizeof(sta->rrm_enabled_capa)) + os_memcpy(sta->rrm_enabled_capa, elems->rrm_enabled, + sizeof(sta->rrm_enabled_capa)); + + if (elems->power_capab) { + sta->min_tx_power = elems->power_capab[0]; + sta->max_tx_power = elems->power_capab[1]; + sta->power_capab = 1; + } else { + sta->power_capab = 0; + } + + if (elems->bss_max_idle_period && + hapd->conf->max_acceptable_idle_period) { + u16 req; + + req = WPA_GET_LE16(elems->bss_max_idle_period); + if (req <= hapd->conf->max_acceptable_idle_period) + sta->max_idle_period = req; + else if (hapd->conf->max_acceptable_idle_period > + hapd->conf->ap_max_inactivity) + sta->max_idle_period = + hapd->conf->max_acceptable_idle_period; + } + + return WLAN_STATUS_SUCCESS; +} + + +static int check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *ies, size_t ies_len, int reassoc) +{ + struct ieee802_11_elems elems; + + if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Station sent an invalid association request"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, reassoc, + false); +} + + +#ifdef CONFIG_IEEE80211BE + +static void ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd, + struct mld_link_info *link) +{ + u8 buf[EHT_ML_MAX_STA_PROF_LEN]; + u8 *p = buf; + size_t buflen = sizeof(buf); + + /* Capability Info */ + WPA_PUT_LE16(p, hostapd_own_capab_info(hapd)); + p += 2; + + /* Status Code */ + WPA_PUT_LE16(p, link->status); + p += 2; + + if (link->status != WLAN_STATUS_SUCCESS) + goto out; + + /* AID is not included */ + p = hostapd_eid_supp_rates(hapd, p); + p = hostapd_eid_ext_supp_rates(hapd, p); + p = hostapd_eid_rm_enabled_capab(hapd, p, buf + buflen - p); + p = hostapd_eid_ht_capabilities(hapd, p); + p = hostapd_eid_ht_operation(hapd, p); + + if (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac) { + p = hostapd_eid_vht_capabilities(hapd, p, 0); + p = hostapd_eid_vht_operation(hapd, p); + } + + if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) { + p = hostapd_eid_he_capab(hapd, p, IEEE80211_MODE_AP); + p = hostapd_eid_he_operation(hapd, p); + p = hostapd_eid_spatial_reuse(hapd, p); + p = hostapd_eid_he_mu_edca_parameter_set(hapd, p); + p = hostapd_eid_he_6ghz_band_cap(hapd, p); + if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { + p = hostapd_eid_eht_capab(hapd, p, IEEE80211_MODE_AP); + p = hostapd_eid_eht_operation(hapd, p); + } + } + + p = hostapd_eid_ext_capab(hapd, p, false); + p = hostapd_eid_mbo(hapd, p, buf + buflen - p); + p = hostapd_eid_wmm(hapd, p); + + if (hapd->conf->assocresp_elements && + (size_t) (buf + buflen - p) >= + wpabuf_len(hapd->conf->assocresp_elements)) { + os_memcpy(p, wpabuf_head(hapd->conf->assocresp_elements), + wpabuf_len(hapd->conf->assocresp_elements)); + p += wpabuf_len(hapd->conf->assocresp_elements); + } + +out: + os_free(link->resp_sta_profile); + link->resp_sta_profile = os_memdup(buf, p - buf); + link->resp_sta_profile_len = link->resp_sta_profile ? p - buf : 0; +} + + +static int ieee80211_ml_process_link(struct hostapd_data *hapd, + struct sta_info *origin_sta, + struct mld_link_info *link, + const u8 *ies, size_t ies_len, + bool reassoc, bool offload) +{ + struct ieee802_11_elems elems; + struct wpabuf *mlbuf = NULL; + struct sta_info *sta = NULL; + u16 status = WLAN_STATUS_SUCCESS; + int i; + + wpa_printf(MSG_DEBUG, "MLD: link: link_id=%u, peer=" MACSTR, + hapd->mld_link_id, MAC2STR(link->peer_addr)); + + if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) { + wpa_printf(MSG_DEBUG, "MLD: link: Element parsing failed"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto out; + } + + sta = ap_get_sta(hapd, origin_sta->addr); + if (sta) { + wpa_printf(MSG_INFO, "MLD: link: Station already exists"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + sta = NULL; + goto out; + } + + sta = ap_sta_add(hapd, origin_sta->addr); + if (!sta) { + wpa_printf(MSG_DEBUG, "MLD: link: ap_sta_add() failed"); + status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; + goto out; + } + + mlbuf = ieee802_11_defrag(elems.basic_mle, elems.basic_mle_len, true); + if (!mlbuf) + goto out; + + if (ieee802_11_parse_link_assoc_req(ies, ies_len, &elems, mlbuf, + hapd->mld_link_id, true) == + ParseFailed) { + wpa_printf(MSG_DEBUG, + "MLD: link: Failed to parse association request Multi-Link element"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto out; + } + + sta->flags |= origin_sta->flags | WLAN_STA_ASSOC_REQ_OK; + sta->mld_assoc_link_id = origin_sta->mld_assoc_link_id; + + status = __check_assoc_ies(hapd, sta, NULL, 0, &elems, reassoc, true); + if (status != WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_DEBUG, "MLD: link: Element check failed"); + goto out; + } + + ap_sta_set_mld(sta, true); + + os_memcpy(&sta->mld_info, &origin_sta->mld_info, sizeof(sta->mld_info)); + for (i = 0; i < MAX_NUM_MLD_LINKS; i++) { + struct mld_link_info *li = &sta->mld_info.links[i]; + + li->resp_sta_profile = NULL; + li->resp_sta_profile_len = 0; + } + + if (!offload) { + /* + * Get the AID from the station on which the association was + * performed, and mark it as used. + */ + sta->aid = origin_sta->aid; + if (sta->aid == 0) { + wpa_printf(MSG_DEBUG, "MLD: link: No AID assigned"); + status = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto out; + } + hapd->sta_aid[(sta->aid - 1) / 32] |= BIT((sta->aid - 1) % 32); + sta->listen_interval = origin_sta->listen_interval; + if (update_ht_state(hapd, sta) > 0) + ieee802_11_update_beacons(hapd->iface); + } + + /* Maintain state machine reference on all link STAs, this is needed + * during group rekey handling. + */ + wpa_auth_sta_deinit(sta->wpa_sm); + sta->wpa_sm = origin_sta->wpa_sm; + + /* + * Do not initialize the EAPOL state machine. + * TODO: Maybe it is needed? + */ + sta->eapol_sm = NULL; + + wpa_printf(MSG_DEBUG, "MLD: link=%u, association OK (aid=%u)", + hapd->mld_link_id, sta->aid); + + sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC_REQ_OK; + + /* TODO: What other processing is required? */ + + if (!offload && add_associated_sta(hapd, sta, reassoc)) + status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; +out: + wpabuf_free(mlbuf); + link->status = status; + + if (!offload) + ieee80211_ml_build_assoc_resp(hapd, link); + + wpa_printf(MSG_DEBUG, "MLD: link: status=%u", status); + if (status != WLAN_STATUS_SUCCESS) { + if (sta) + ap_free_sta(hapd, sta); + return -1; + } + + return 0; +} + + +bool hostapd_is_mld_ap(struct hostapd_data *hapd) +{ + if (!hapd->conf->mld_ap) + return false; + + if (!hapd->iface || !hapd->iface->interfaces || + hapd->iface->interfaces->count <= 1) + return false; + + return true; +} + +#endif /* CONFIG_IEEE80211BE */ + + +int hostapd_process_assoc_ml_info(struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *ies, size_t ies_len, + bool reassoc, int tx_link_status, + bool offload) +{ +#ifdef CONFIG_IEEE80211BE + unsigned int i; + + if (!hostapd_is_mld_ap(hapd)) + return 0; + + for (i = 0; i < MAX_NUM_MLD_LINKS; i++) { + struct hostapd_data *bss = NULL; + struct mld_link_info *link = &sta->mld_info.links[i]; + bool link_bss_found = false; + + if (!link->valid || i == sta->mld_assoc_link_id) + continue; + + for_each_mld_link(bss, hapd) { + if (bss == hapd) + continue; + + if (bss->mld_link_id != i) + continue; + + link_bss_found = true; + break; + } + + if (!link_bss_found || TEST_FAIL()) { + wpa_printf(MSG_DEBUG, + "MLD: No link match for link_id=%u", i); + + link->status = WLAN_STATUS_UNSPECIFIED_FAILURE; + if (!offload) + ieee80211_ml_build_assoc_resp(hapd, link); + } else if (tx_link_status != WLAN_STATUS_SUCCESS) { + /* TX link rejected the connection */ + link->status = WLAN_STATUS_DENIED_TX_LINK_NOT_ACCEPTED; + if (!offload) + ieee80211_ml_build_assoc_resp(hapd, link); + } else { + if (ieee80211_ml_process_link(bss, sta, link, + ies, ies_len, reassoc, + offload)) + return -1; + } + } +#endif /* CONFIG_IEEE80211BE */ + + return 0; +} + + +static void send_deauth(struct hostapd_data *hapd, const u8 *addr, + u16 reason_code) +{ + int send_len; + struct ieee80211_mgmt reply; + + os_memset(&reply, 0, sizeof(reply)); + reply.frame_control = + IEEE80211_FC(WLAN_FC_TYPE_MGMT, WLAN_FC_STYPE_DEAUTH); + os_memcpy(reply.da, addr, ETH_ALEN); + os_memcpy(reply.sa, hapd->own_addr, ETH_ALEN); + os_memcpy(reply.bssid, hapd->own_addr, ETH_ALEN); + + send_len = IEEE80211_HDRLEN + sizeof(reply.u.deauth); + reply.u.deauth.reason_code = host_to_le16(reason_code); + + if (hostapd_drv_send_mlme(hapd, &reply, send_len, 0, NULL, 0, 0) < 0) + wpa_printf(MSG_INFO, "Failed to send deauth: %s", + strerror(errno)); +} + + +static int add_associated_sta(struct hostapd_data *hapd, + struct sta_info *sta, int reassoc) +{ + struct ieee80211_ht_capabilities ht_cap; + struct ieee80211_vht_capabilities vht_cap; + struct ieee80211_he_capabilities he_cap; + struct ieee80211_eht_capabilities eht_cap; + int set = 1; + const u8 *mld_link_addr = NULL; + bool mld_link_sta = false; + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) { + u8 mld_link_id = hapd->mld_link_id; + + mld_link_sta = sta->mld_assoc_link_id != mld_link_id; + mld_link_addr = sta->mld_info.links[mld_link_id].peer_addr; + + if (hapd->mld_link_id != sta->mld_assoc_link_id) + set = 0; + } +#endif /* CONFIG_IEEE80211BE */ + + /* + * Remove the STA entry to ensure the STA PS state gets cleared and + * configuration gets updated. This is relevant for cases, such as + * FT-over-the-DS, where a station re-associates back to the same AP but + * skips the authentication flow, or if working with a driver that + * does not support full AP client state. + * + * Skip this if the STA has already completed FT reassociation and the + * TK has been configured since the TX/RX PN must not be reset to 0 for + * the same key. + * + * FT-over-the-DS has a special case where the STA entry (and as such, + * the TK) has not yet been configured to the driver depending on which + * driver interface is used. For that case, allow add-STA operation to + * be used (instead of set-STA). This is needed to allow mac80211-based + * drivers to accept the STA parameter configuration. Since this is + * after a new FT-over-DS exchange, a new TK has been derived, so key + * reinstallation is not a concern for this case. + */ + wpa_printf(MSG_DEBUG, "Add associated STA " MACSTR + " (added_unassoc=%d auth_alg=%u ft_over_ds=%u reassoc=%d authorized=%d ft_tk=%d fils_tk=%d)", + MAC2STR(sta->addr), sta->added_unassoc, sta->auth_alg, + sta->ft_over_ds, reassoc, + !!(sta->flags & WLAN_STA_AUTHORIZED), + wpa_auth_sta_ft_tk_already_set(sta->wpa_sm), + wpa_auth_sta_fils_tk_already_set(sta->wpa_sm)); + + if (!mld_link_sta && !sta->added_unassoc && + (!(sta->flags & WLAN_STA_AUTHORIZED) || + (reassoc && sta->ft_over_ds && sta->auth_alg == WLAN_AUTH_FT) || + (!wpa_auth_sta_ft_tk_already_set(sta->wpa_sm) && + !wpa_auth_sta_fils_tk_already_set(sta->wpa_sm)))) { + hostapd_drv_sta_remove(hapd, sta->addr); + wpa_auth_sm_event(sta->wpa_sm, WPA_DRV_STA_REMOVED); + set = 0; + + /* Do not allow the FT-over-DS exception to be used more than + * once per authentication exchange to guarantee a new TK is + * used here */ + sta->ft_over_ds = 0; + } + + if (sta->flags & WLAN_STA_HT) + hostapd_get_ht_capab(hapd, sta->ht_capabilities, &ht_cap); +#ifdef CONFIG_IEEE80211AC + if (sta->flags & WLAN_STA_VHT) + hostapd_get_vht_capab(hapd, sta->vht_capabilities, &vht_cap); +#endif /* CONFIG_IEEE80211AC */ +#ifdef CONFIG_IEEE80211AX + if (sta->flags & WLAN_STA_HE) { + hostapd_get_he_capab(hapd, sta->he_capab, &he_cap, + sta->he_capab_len); + } +#endif /* CONFIG_IEEE80211AX */ +#ifdef CONFIG_IEEE80211BE + if (sta->flags & WLAN_STA_EHT) + hostapd_get_eht_capab(hapd, sta->eht_capab, &eht_cap, + sta->eht_capab_len); +#endif /* CONFIG_IEEE80211BE */ + + /* + * Add the station with forced WLAN_STA_ASSOC flag. The sta->flags + * will be set when the ACK frame for the (Re)Association Response frame + * is processed (TX status driver event). + */ + if (hostapd_sta_add(hapd, sta->addr, sta->aid, sta->capability, + sta->supported_rates, sta->supported_rates_len, + sta->listen_interval, + sta->flags & WLAN_STA_HT ? &ht_cap : NULL, + sta->flags & WLAN_STA_VHT ? &vht_cap : NULL, + sta->flags & WLAN_STA_HE ? &he_cap : NULL, + sta->flags & WLAN_STA_HE ? sta->he_capab_len : 0, + sta->flags & WLAN_STA_EHT ? &eht_cap : NULL, + sta->flags & WLAN_STA_EHT ? sta->eht_capab_len : 0, + sta->he_6ghz_capab, + sta->flags | WLAN_STA_ASSOC, sta->qosinfo, + sta->vht_opmode, sta->p2p_ie ? 1 : 0, + set, mld_link_addr, mld_link_sta)) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_NOTICE, + "Could not %s STA to kernel driver", + set ? "set" : "add"); + + if (sta->added_unassoc) { + hostapd_drv_sta_remove(hapd, sta->addr); + sta->added_unassoc = 0; + } + + return -1; + } + + sta->added_unassoc = 0; + + return 0; +} + + +static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *addr, u16 status_code, int reassoc, + const u8 *ies, size_t ies_len, int rssi, + int omit_rsnxe, bool allow_mld_addr_trans) +{ + int send_len; + u8 *buf; + size_t buflen; + struct ieee80211_mgmt *reply; + u8 *p; + u16 res = WLAN_STATUS_SUCCESS; + + buflen = sizeof(struct ieee80211_mgmt) + 1024; +#ifdef CONFIG_FILS + if (sta && sta->fils_hlp_resp) + buflen += wpabuf_len(sta->fils_hlp_resp); + if (sta) + buflen += 150; +#endif /* CONFIG_FILS */ +#ifdef CONFIG_OWE + if (sta && (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE)) + buflen += 150; +#endif /* CONFIG_OWE */ +#ifdef CONFIG_DPP2 + if (sta && sta->dpp_pfs) + buflen += 5 + sta->dpp_pfs->curve->prime_len; +#endif /* CONFIG_DPP2 */ +#ifdef CONFIG_IEEE80211BE + if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { + buflen += hostapd_eid_eht_capab_len(hapd, IEEE80211_MODE_AP); + buflen += 3 + sizeof(struct ieee80211_eht_operation); + if (hapd->iconf->punct_bitmap) + buflen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE; + } +#endif /* CONFIG_IEEE80211BE */ + + buf = os_zalloc(buflen); + if (!buf) { + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto done; + } + reply = (struct ieee80211_mgmt *) buf; + reply->frame_control = + IEEE80211_FC(WLAN_FC_TYPE_MGMT, + (reassoc ? WLAN_FC_STYPE_REASSOC_RESP : + WLAN_FC_STYPE_ASSOC_RESP)); + + os_memcpy(reply->da, addr, ETH_ALEN); + os_memcpy(reply->sa, hapd->own_addr, ETH_ALEN); + os_memcpy(reply->bssid, hapd->own_addr, ETH_ALEN); + + send_len = IEEE80211_HDRLEN; + send_len += sizeof(reply->u.assoc_resp); + reply->u.assoc_resp.capab_info = + host_to_le16(hostapd_own_capab_info(hapd)); + reply->u.assoc_resp.status_code = host_to_le16(status_code); + + reply->u.assoc_resp.aid = host_to_le16((sta ? sta->aid : 0) | + BIT(14) | BIT(15)); + /* Supported rates */ + p = hostapd_eid_supp_rates(hapd, reply->u.assoc_resp.variable); + /* Extended supported rates */ + p = hostapd_eid_ext_supp_rates(hapd, p); + + /* Radio measurement capabilities */ + p = hostapd_eid_rm_enabled_capab(hapd, p, buf + buflen - p); + +#ifdef CONFIG_MBO + if (status_code == WLAN_STATUS_DENIED_POOR_CHANNEL_CONDITIONS && + rssi != 0) { + int delta = hapd->iconf->rssi_reject_assoc_rssi - rssi; + + p = hostapd_eid_mbo_rssi_assoc_rej(hapd, p, buf + buflen - p, + delta); + } +#endif /* CONFIG_MBO */ + +#ifdef CONFIG_IEEE80211R_AP + if (sta && status_code == WLAN_STATUS_SUCCESS) { + /* IEEE 802.11r: Mobility Domain Information, Fast BSS + * Transition Information, RSN, [RIC Response] */ + p = wpa_sm_write_assoc_resp_ies(sta->wpa_sm, p, + buf + buflen - p, + sta->auth_alg, ies, ies_len, + omit_rsnxe); + if (!p) { + wpa_printf(MSG_DEBUG, + "FT: Failed to write AssocResp IEs"); + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto done; + } + } +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_FILS + if (sta && status_code == WLAN_STATUS_SUCCESS && + (sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK)) + p = wpa_auth_write_assoc_resp_fils(sta->wpa_sm, p, + buf + buflen - p, + ies, ies_len); +#endif /* CONFIG_FILS */ + +#ifdef CONFIG_OWE + if (sta && status_code == WLAN_STATUS_SUCCESS && + (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE)) + p = wpa_auth_write_assoc_resp_owe(sta->wpa_sm, p, + buf + buflen - p, + ies, ies_len); +#endif /* CONFIG_OWE */ + + if (sta && status_code == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) + p = hostapd_eid_assoc_comeback_time(hapd, sta, p); + + p = hostapd_eid_ht_capabilities(hapd, p); + p = hostapd_eid_ht_operation(hapd, p); + +#ifdef CONFIG_IEEE80211AC + if (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac && + !is_6ghz_op_class(hapd->iconf->op_class)) { + u32 nsts = 0, sta_nsts; + + if (sta && hapd->conf->use_sta_nsts && sta->vht_capabilities) { + struct ieee80211_vht_capabilities *capa; + + nsts = (hapd->iface->conf->vht_capab >> + VHT_CAP_BEAMFORMEE_STS_OFFSET) & 7; + capa = sta->vht_capabilities; + sta_nsts = (le_to_host32(capa->vht_capabilities_info) >> + VHT_CAP_BEAMFORMEE_STS_OFFSET) & 7; + + if (nsts < sta_nsts) + nsts = 0; + else + nsts = sta_nsts; + } + p = hostapd_eid_vht_capabilities(hapd, p, nsts); + p = hostapd_eid_vht_operation(hapd, p); + } +#endif /* CONFIG_IEEE80211AC */ + +#ifdef CONFIG_IEEE80211AX + if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) { + p = hostapd_eid_he_capab(hapd, p, IEEE80211_MODE_AP); + p = hostapd_eid_he_operation(hapd, p); + p = hostapd_eid_cca(hapd, p); + p = hostapd_eid_spatial_reuse(hapd, p); + p = hostapd_eid_he_mu_edca_parameter_set(hapd, p); + p = hostapd_eid_he_6ghz_band_cap(hapd, p); + } +#endif /* CONFIG_IEEE80211AX */ + + p = hostapd_eid_ext_capab(hapd, p, false); + p = hostapd_eid_bss_max_idle_period(hapd, p, sta->max_idle_period); + if (sta && sta->qos_map_enabled) + p = hostapd_eid_qos_map_set(hapd, p); + +#ifdef CONFIG_FST + if (hapd->iface->fst_ies) { + os_memcpy(p, wpabuf_head(hapd->iface->fst_ies), + wpabuf_len(hapd->iface->fst_ies)); + p += wpabuf_len(hapd->iface->fst_ies); + } +#endif /* CONFIG_FST */ + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->rsnxe_override_ft && + buf + buflen - p >= + (long int) wpabuf_len(hapd->conf->rsnxe_override_ft) && + sta && sta->auth_alg == WLAN_AUTH_FT) { + wpa_printf(MSG_DEBUG, "TESTING: RSNXE FT override"); + os_memcpy(p, wpabuf_head(hapd->conf->rsnxe_override_ft), + wpabuf_len(hapd->conf->rsnxe_override_ft)); + p += wpabuf_len(hapd->conf->rsnxe_override_ft); + goto rsnxe_done; + } +#endif /* CONFIG_TESTING_OPTIONS */ + if (!omit_rsnxe) + p = hostapd_eid_rsnxe(hapd, p, buf + buflen - p); +#ifdef CONFIG_TESTING_OPTIONS +rsnxe_done: +#endif /* CONFIG_TESTING_OPTIONS */ + +#ifdef CONFIG_IEEE80211BE + if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) { + if (hapd->conf->mld_ap) + p = hostapd_eid_eht_ml_assoc(hapd, sta, p); + p = hostapd_eid_eht_capab(hapd, p, IEEE80211_MODE_AP); + p = hostapd_eid_eht_operation(hapd, p); + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_OWE + if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) && + sta && sta->owe_ecdh && status_code == WLAN_STATUS_SUCCESS && + wpa_auth_sta_key_mgmt(sta->wpa_sm) == WPA_KEY_MGMT_OWE && + !wpa_auth_sta_get_pmksa(sta->wpa_sm)) { + struct wpabuf *pub; + + pub = crypto_ecdh_get_pubkey(sta->owe_ecdh, 0); + if (!pub) { + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto done; + } + /* OWE Diffie-Hellman Parameter element */ + *p++ = WLAN_EID_EXTENSION; /* Element ID */ + *p++ = 1 + 2 + wpabuf_len(pub); /* Length */ + *p++ = WLAN_EID_EXT_OWE_DH_PARAM; /* Element ID Extension */ + WPA_PUT_LE16(p, sta->owe_group); + p += 2; + os_memcpy(p, wpabuf_head(pub), wpabuf_len(pub)); + p += wpabuf_len(pub); + wpabuf_free(pub); + } +#endif /* CONFIG_OWE */ + +#ifdef CONFIG_DPP2 + if (DPP_VERSION > 1 && (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_DPP) && + sta && sta->dpp_pfs && status_code == WLAN_STATUS_SUCCESS && + wpa_auth_sta_key_mgmt(sta->wpa_sm) == WPA_KEY_MGMT_DPP) { + os_memcpy(p, wpabuf_head(sta->dpp_pfs->ie), + wpabuf_len(sta->dpp_pfs->ie)); + p += wpabuf_len(sta->dpp_pfs->ie); + } +#endif /* CONFIG_DPP2 */ + +#ifdef CONFIG_IEEE80211AC + if (sta && hapd->conf->vendor_vht && (sta->flags & WLAN_STA_VENDOR_VHT)) + p = hostapd_eid_vendor_vht(hapd, p); +#endif /* CONFIG_IEEE80211AC */ + + if (sta && (sta->flags & WLAN_STA_WMM)) + p = hostapd_eid_wmm(hapd, p); + +#ifdef CONFIG_WPS + if (sta && + ((sta->flags & WLAN_STA_WPS) || + ((sta->flags & WLAN_STA_MAYBE_WPS) && hapd->conf->wpa))) { + struct wpabuf *wps = wps_build_assoc_resp_ie(); + if (wps) { + os_memcpy(p, wpabuf_head(wps), wpabuf_len(wps)); + p += wpabuf_len(wps); + wpabuf_free(wps); + } + } +#endif /* CONFIG_WPS */ + + if (sta && (sta->flags & WLAN_STA_MULTI_AP)) + p = hostapd_eid_multi_ap(hapd, p, buf + buflen - p); + +#ifdef CONFIG_P2P + if (sta && sta->p2p_ie && hapd->p2p_group) { + struct wpabuf *p2p_resp_ie; + enum p2p_status_code status; + switch (status_code) { + case WLAN_STATUS_SUCCESS: + status = P2P_SC_SUCCESS; + break; + case WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA: + status = P2P_SC_FAIL_LIMIT_REACHED; + break; + default: + status = P2P_SC_FAIL_INVALID_PARAMS; + break; + } + p2p_resp_ie = p2p_group_assoc_resp_ie(hapd->p2p_group, status); + if (p2p_resp_ie) { + os_memcpy(p, wpabuf_head(p2p_resp_ie), + wpabuf_len(p2p_resp_ie)); + p += wpabuf_len(p2p_resp_ie); + wpabuf_free(p2p_resp_ie); + } + } +#endif /* CONFIG_P2P */ + +#ifdef CONFIG_P2P_MANAGER + if (hapd->conf->p2p & P2P_MANAGE) + p = hostapd_eid_p2p_manage(hapd, p); +#endif /* CONFIG_P2P_MANAGER */ + + p = hostapd_eid_mbo(hapd, p, buf + buflen - p); + + if (hapd->conf->assocresp_elements && + (size_t) (buf + buflen - p) >= + wpabuf_len(hapd->conf->assocresp_elements)) { + os_memcpy(p, wpabuf_head(hapd->conf->assocresp_elements), + wpabuf_len(hapd->conf->assocresp_elements)); + p += wpabuf_len(hapd->conf->assocresp_elements); + } + + send_len += p - reply->u.assoc_resp.variable; + +#ifdef CONFIG_FILS + if (sta && + (sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK) && + status_code == WLAN_STATUS_SUCCESS) { + struct ieee802_11_elems elems; + + if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) == + ParseFailed || !elems.fils_session) { + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto done; + } + + /* FILS Session */ + *p++ = WLAN_EID_EXTENSION; /* Element ID */ + *p++ = 1 + FILS_SESSION_LEN; /* Length */ + *p++ = WLAN_EID_EXT_FILS_SESSION; /* Element ID Extension */ + os_memcpy(p, elems.fils_session, FILS_SESSION_LEN); + send_len += 2 + 1 + FILS_SESSION_LEN; + + send_len = fils_encrypt_assoc(sta->wpa_sm, buf, send_len, + buflen, sta->fils_hlp_resp); + if (send_len < 0) { + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto done; + } + } +#endif /* CONFIG_FILS */ + + if (hostapd_drv_send_mlme(hapd, reply, send_len, 0, NULL, 0, 0) < 0) { + wpa_printf(MSG_INFO, "Failed to send assoc resp: %s", + strerror(errno)); + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + } + +done: + os_free(buf); + return res; +} + + +#ifdef CONFIG_OWE +u8 * owe_assoc_req_process(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *owe_dh, u8 owe_dh_len, + u8 *owe_buf, size_t owe_buf_len, u16 *status) +{ +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->own_ie_override) { + wpa_printf(MSG_DEBUG, "OWE: Using IE override"); + *status = WLAN_STATUS_SUCCESS; + return wpa_auth_write_assoc_resp_owe(sta->wpa_sm, owe_buf, + owe_buf_len, NULL, 0); + } +#endif /* CONFIG_TESTING_OPTIONS */ + + if (wpa_auth_sta_get_pmksa(sta->wpa_sm)) { + wpa_printf(MSG_DEBUG, "OWE: Using PMKSA caching"); + owe_buf = wpa_auth_write_assoc_resp_owe(sta->wpa_sm, owe_buf, + owe_buf_len, NULL, 0); + *status = WLAN_STATUS_SUCCESS; + return owe_buf; + } + + if (sta->owe_pmk && sta->external_dh_updated) { + wpa_printf(MSG_DEBUG, "OWE: Using previously derived PMK"); + *status = WLAN_STATUS_SUCCESS; + return owe_buf; + } + + *status = owe_process_assoc_req(hapd, sta, owe_dh, owe_dh_len); + if (*status != WLAN_STATUS_SUCCESS) + return NULL; + + owe_buf = wpa_auth_write_assoc_resp_owe(sta->wpa_sm, owe_buf, + owe_buf_len, NULL, 0); + + if (sta->owe_ecdh && owe_buf) { + struct wpabuf *pub; + + pub = crypto_ecdh_get_pubkey(sta->owe_ecdh, 0); + if (!pub) { + *status = WLAN_STATUS_UNSPECIFIED_FAILURE; + return owe_buf; + } + + /* OWE Diffie-Hellman Parameter element */ + *owe_buf++ = WLAN_EID_EXTENSION; /* Element ID */ + *owe_buf++ = 1 + 2 + wpabuf_len(pub); /* Length */ + *owe_buf++ = WLAN_EID_EXT_OWE_DH_PARAM; /* Element ID Extension + */ + WPA_PUT_LE16(owe_buf, sta->owe_group); + owe_buf += 2; + os_memcpy(owe_buf, wpabuf_head(pub), wpabuf_len(pub)); + owe_buf += wpabuf_len(pub); + wpabuf_free(pub); + } + + return owe_buf; +} +#endif /* CONFIG_OWE */ + + +#ifdef CONFIG_FILS + +void fils_hlp_finish_assoc(struct hostapd_data *hapd, struct sta_info *sta) +{ + u16 reply_res; + + wpa_printf(MSG_DEBUG, "FILS: Finish association with " MACSTR, + MAC2STR(sta->addr)); + eloop_cancel_timeout(fils_hlp_timeout, hapd, sta); + if (!sta->fils_pending_assoc_req) + return; + reply_res = send_assoc_resp(hapd, sta, sta->addr, WLAN_STATUS_SUCCESS, + sta->fils_pending_assoc_is_reassoc, + sta->fils_pending_assoc_req, + sta->fils_pending_assoc_req_len, 0, 0, + true); + os_free(sta->fils_pending_assoc_req); + sta->fils_pending_assoc_req = NULL; + sta->fils_pending_assoc_req_len = 0; + wpabuf_free(sta->fils_hlp_resp); + sta->fils_hlp_resp = NULL; + wpabuf_free(sta->hlp_dhcp_discover); + sta->hlp_dhcp_discover = NULL; + + /* + * Remove the station in case transmission of a success response fails. + * At this point the station was already added associated to the driver. + */ + if (reply_res != WLAN_STATUS_SUCCESS) + hostapd_drv_sta_remove(hapd, sta->addr); +} + + +void fils_hlp_timeout(void *eloop_ctx, void *eloop_data) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = eloop_data; + + wpa_printf(MSG_DEBUG, + "FILS: HLP response timeout - continue with association response for " + MACSTR, MAC2STR(sta->addr)); + if (sta->fils_drv_assoc_finish) + hostapd_notify_assoc_fils_finish(hapd, sta); + else + fils_hlp_finish_assoc(hapd, sta); +} + +#endif /* CONFIG_FILS */ + + +#ifdef CONFIG_IEEE80211BE +static struct sta_info * handle_mlo_translate(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len, bool reassoc, + struct hostapd_data **assoc_hapd) +{ + struct sta_info *sta; + struct ieee802_11_elems elems; + u8 mld_addr[ETH_ALEN]; + const u8 *pos; + + if (!hapd->iconf->ieee80211be || hapd->conf->disable_11be) + return NULL; + + if (reassoc) { + len -= IEEE80211_HDRLEN + sizeof(mgmt->u.reassoc_req); + pos = mgmt->u.reassoc_req.variable; + } else { + len -= IEEE80211_HDRLEN + sizeof(mgmt->u.assoc_req); + pos = mgmt->u.assoc_req.variable; + } + + if (ieee802_11_parse_elems(pos, len, &elems, 1) == ParseFailed) + return NULL; + + if (hostapd_process_ml_assoc_req_addr(hapd, elems.basic_mle, + elems.basic_mle_len, + mld_addr)) + return NULL; + + sta = ap_get_sta(hapd, mld_addr); + if (!sta) + return NULL; + + wpa_printf(MSG_DEBUG, "MLD: assoc: mld=" MACSTR ", link=" MACSTR, + MAC2STR(mld_addr), MAC2STR(mgmt->sa)); + + return hostapd_ml_get_assoc_sta(hapd, sta, assoc_hapd); +} +#endif /* CONFIG_IEEE80211BE */ + + +static void handle_assoc(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len, + int reassoc, int rssi) +{ + u16 capab_info, listen_interval, seq_ctrl, fc; + int resp = WLAN_STATUS_SUCCESS; + u16 reply_res = WLAN_STATUS_UNSPECIFIED_FAILURE; + const u8 *pos; + int left, i; + struct sta_info *sta; + u8 *tmp = NULL; +#ifdef CONFIG_FILS + int delay_assoc = 0; +#endif /* CONFIG_FILS */ + int omit_rsnxe = 0; + bool set_beacon = false; + bool mld_addrs_not_translated = false; + + if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_req) : + sizeof(mgmt->u.assoc_req))) { + wpa_printf(MSG_INFO, "handle_assoc(reassoc=%d) - too short payload (len=%lu)", + reassoc, (unsigned long) len); + return; + } + +#ifdef CONFIG_TESTING_OPTIONS + if (reassoc) { + if (hapd->iconf->ignore_reassoc_probability > 0.0 && + drand48() < hapd->iconf->ignore_reassoc_probability) { + wpa_printf(MSG_INFO, + "TESTING: ignoring reassoc request from " + MACSTR, MAC2STR(mgmt->sa)); + return; + } + } else { + if (hapd->iconf->ignore_assoc_probability > 0.0 && + drand48() < hapd->iconf->ignore_assoc_probability) { + wpa_printf(MSG_INFO, + "TESTING: ignoring assoc request from " + MACSTR, MAC2STR(mgmt->sa)); + return; + } + } +#endif /* CONFIG_TESTING_OPTIONS */ + + fc = le_to_host16(mgmt->frame_control); + seq_ctrl = le_to_host16(mgmt->seq_ctrl); + + if (reassoc) { + capab_info = le_to_host16(mgmt->u.reassoc_req.capab_info); + listen_interval = le_to_host16( + mgmt->u.reassoc_req.listen_interval); + wpa_printf(MSG_DEBUG, "reassociation request: STA=" MACSTR + " capab_info=0x%02x listen_interval=%d current_ap=" + MACSTR " seq_ctrl=0x%x%s", + MAC2STR(mgmt->sa), capab_info, listen_interval, + MAC2STR(mgmt->u.reassoc_req.current_ap), + seq_ctrl, (fc & WLAN_FC_RETRY) ? " retry" : ""); + left = len - (IEEE80211_HDRLEN + sizeof(mgmt->u.reassoc_req)); + pos = mgmt->u.reassoc_req.variable; + } else { + capab_info = le_to_host16(mgmt->u.assoc_req.capab_info); + listen_interval = le_to_host16( + mgmt->u.assoc_req.listen_interval); + wpa_printf(MSG_DEBUG, "association request: STA=" MACSTR + " capab_info=0x%02x listen_interval=%d " + "seq_ctrl=0x%x%s", + MAC2STR(mgmt->sa), capab_info, listen_interval, + seq_ctrl, (fc & WLAN_FC_RETRY) ? " retry" : ""); + left = len - (IEEE80211_HDRLEN + sizeof(mgmt->u.assoc_req)); + pos = mgmt->u.assoc_req.variable; + } + + sta = ap_get_sta(hapd, mgmt->sa); + +#ifdef CONFIG_IEEE80211BE + /* + * It is possible that the association frame is from an associated + * non-AP MLD station, that tries to re-associate using different link + * addresses. In such a case, try to find the station based on the AP + * MLD MAC address. + */ + if (!sta) { + struct hostapd_data *assoc_hapd; + + sta = handle_mlo_translate(hapd, mgmt, len, reassoc, + &assoc_hapd); + if (sta) { + wpa_printf(MSG_DEBUG, + "MLD: Switching to assoc hapd/station"); + hapd = assoc_hapd; + mld_addrs_not_translated = true; + } + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_IEEE80211R_AP + if (sta && sta->auth_alg == WLAN_AUTH_FT && + (sta->flags & WLAN_STA_AUTH) == 0) { + wpa_printf(MSG_DEBUG, "FT: Allow STA " MACSTR " to associate " + "prior to authentication since it is using " + "over-the-DS FT", MAC2STR(mgmt->sa)); + + /* + * Mark station as authenticated, to avoid adding station + * entry in the driver as associated and not authenticated + */ + sta->flags |= WLAN_STA_AUTH; + } else +#endif /* CONFIG_IEEE80211R_AP */ + if (sta == NULL || (sta->flags & WLAN_STA_AUTH) == 0) { + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == + HOSTAPD_MODE_IEEE80211AD) { + int acl_res; + struct radius_sta info; + + acl_res = ieee802_11_allowed_address(hapd, mgmt->sa, + (const u8 *) mgmt, + len, &info); + if (acl_res == HOSTAPD_ACL_REJECT) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "Ignore Association Request frame from " + MACSTR " due to ACL reject", + MAC2STR(mgmt->sa)); + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + if (acl_res == HOSTAPD_ACL_PENDING) + return; + + /* DMG/IEEE 802.11ad does not use authentication. + * Allocate sta entry upon association. */ + sta = ap_sta_add(hapd, mgmt->sa); + if (!sta) { + hostapd_logger(hapd, mgmt->sa, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Failed to add STA"); + resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; + goto fail; + } + + acl_res = ieee802_11_set_radius_info( + hapd, sta, acl_res, &info); + if (acl_res) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "Skip authentication for DMG/IEEE 802.11ad"); + sta->flags |= WLAN_STA_AUTH; + wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH); + sta->auth_alg = WLAN_AUTH_OPEN; + } else { + hostapd_logger(hapd, mgmt->sa, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Station tried to associate before authentication (aid=%d flags=0x%x)", + sta ? sta->aid : -1, + sta ? sta->flags : 0); + send_deauth(hapd, mgmt->sa, + WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA); + return; + } + } + + if ((fc & WLAN_FC_RETRY) && + sta->last_seq_ctrl != WLAN_INVALID_MGMT_SEQ && + sta->last_seq_ctrl == seq_ctrl && + sta->last_subtype == (reassoc ? WLAN_FC_STYPE_REASSOC_REQ : + WLAN_FC_STYPE_ASSOC_REQ)) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "Drop repeated association frame seq_ctrl=0x%x", + seq_ctrl); + return; + } + sta->last_seq_ctrl = seq_ctrl; + sta->last_subtype = reassoc ? WLAN_FC_STYPE_REASSOC_REQ : + WLAN_FC_STYPE_ASSOC_REQ; + + if (hapd->tkip_countermeasures) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + if (listen_interval > hapd->conf->max_listen_interval) { + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "Too large Listen Interval (%d)", + listen_interval); + resp = WLAN_STATUS_ASSOC_DENIED_LISTEN_INT_TOO_LARGE; + goto fail; + } + +#ifdef CONFIG_MBO + if (hapd->conf->mbo_enabled && hapd->mbo_assoc_disallow) { + resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; + goto fail; + } + + if (hapd->iconf->rssi_reject_assoc_rssi && rssi && + rssi < hapd->iconf->rssi_reject_assoc_rssi && + (sta->auth_rssi == 0 || + sta->auth_rssi < hapd->iconf->rssi_reject_assoc_rssi)) { + resp = WLAN_STATUS_DENIED_POOR_CHANNEL_CONDITIONS; + goto fail; + } +#endif /* CONFIG_MBO */ + + if (hapd->conf->wpa && check_sa_query(hapd, sta, reassoc)) { + resp = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY; + goto fail; + } + + /* + * sta->capability is used in check_assoc_ies() for RRM enabled + * capability element. + */ + sta->capability = capab_info; + +#ifdef CONFIG_FILS + if (sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK) { + int res; + + /* The end of the payload is encrypted. Need to decrypt it + * before parsing. */ + + tmp = os_memdup(pos, left); + if (!tmp) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + res = fils_decrypt_assoc(sta->wpa_sm, sta->fils_session, mgmt, + len, tmp, left); + if (res < 0) { + resp = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + pos = tmp; + left = res; + } +#endif /* CONFIG_FILS */ + + /* followed by SSID and Supported rates; and HT capabilities if 802.11n + * is used */ + resp = check_assoc_ies(hapd, sta, pos, left, reassoc); + if (resp != WLAN_STATUS_SUCCESS) + goto fail; + omit_rsnxe = !get_ie(pos, left, WLAN_EID_RSNX); + + if (hostapd_get_aid(hapd, sta) < 0) { + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "No room for more AIDs"); + resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; + goto fail; + } + + sta->listen_interval = listen_interval; + + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G) + sta->flags |= WLAN_STA_NONERP; + for (i = 0; i < sta->supported_rates_len; i++) { + if ((sta->supported_rates[i] & 0x7f) > 22) { + sta->flags &= ~WLAN_STA_NONERP; + break; + } + } + if (sta->flags & WLAN_STA_NONERP && !sta->nonerp_set) { + sta->nonerp_set = 1; + hapd->iface->num_sta_non_erp++; + if (hapd->iface->num_sta_non_erp == 1) + set_beacon = true; + } + + if (!(sta->capability & WLAN_CAPABILITY_SHORT_SLOT_TIME) && + !sta->no_short_slot_time_set) { + sta->no_short_slot_time_set = 1; + hapd->iface->num_sta_no_short_slot_time++; + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == + HOSTAPD_MODE_IEEE80211G && + hapd->iface->num_sta_no_short_slot_time == 1) + set_beacon = true; + } + + if (sta->capability & WLAN_CAPABILITY_SHORT_PREAMBLE) + sta->flags |= WLAN_STA_SHORT_PREAMBLE; + else + sta->flags &= ~WLAN_STA_SHORT_PREAMBLE; + + if (!(sta->capability & WLAN_CAPABILITY_SHORT_PREAMBLE) && + !sta->no_short_preamble_set) { + sta->no_short_preamble_set = 1; + hapd->iface->num_sta_no_short_preamble++; + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G + && hapd->iface->num_sta_no_short_preamble == 1) + set_beacon = true; + } + + if (update_ht_state(hapd, sta) > 0) + set_beacon = true; + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "association OK (aid %d)", sta->aid); + /* Station will be marked associated, after it acknowledges AssocResp + */ + sta->flags |= WLAN_STA_ASSOC_REQ_OK; + + if ((sta->flags & WLAN_STA_MFP) && sta->sa_query_timed_out) { + wpa_printf(MSG_DEBUG, "Allowing %sassociation after timed out " + "SA Query procedure", reassoc ? "re" : ""); + /* TODO: Send a protected Disassociate frame to the STA using + * the old key and Reason Code "Previous Authentication no + * longer valid". Make sure this is only sent protected since + * unprotected frame would be received by the STA that is now + * trying to associate. + */ + } + + /* Make sure that the previously registered inactivity timer will not + * remove the STA immediately. */ + sta->timeout_next = STA_NULLFUNC; + +#ifdef CONFIG_TAXONOMY + taxonomy_sta_info_assoc_req(hapd, sta, pos, left); +#endif /* CONFIG_TAXONOMY */ + + sta->pending_wds_enable = 0; + +#ifdef CONFIG_FILS + if (sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK) { + if (fils_process_hlp(hapd, sta, pos, left) > 0) + delay_assoc = 1; + } +#endif /* CONFIG_FILS */ + + if (set_beacon) + ieee802_11_update_beacons(hapd->iface); + + fail: + + /* + * In case of a successful response, add the station to the driver. + * Otherwise, the kernel may ignore Data frames before we process the + * ACK frame (TX status). In case of a failure, this station will be + * removed. + * + * Note that this is not compliant with the IEEE 802.11 standard that + * states that a non-AP station should transition into the + * authenticated/associated state only after the station acknowledges + * the (Re)Association Response frame. However, still do this as: + * + * 1. In case the station does not acknowledge the (Re)Association + * Response frame, it will be removed. + * 2. Data frames will be dropped in the kernel until the station is + * set into authorized state, and there are no significant known + * issues with processing other non-Data Class 3 frames during this + * window. + */ + if (sta) + hostapd_process_assoc_ml_info(hapd, sta, pos, left, reassoc, + resp, false); + + if (resp == WLAN_STATUS_SUCCESS && sta && + add_associated_sta(hapd, sta, reassoc)) + resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA; + +#ifdef CONFIG_FILS + if (sta && delay_assoc && resp == WLAN_STATUS_SUCCESS && + eloop_is_timeout_registered(fils_hlp_timeout, hapd, sta) && + sta->fils_pending_assoc_req) { + /* Do not reschedule fils_hlp_timeout in case the station + * retransmits (Re)Association Request frame while waiting for + * the previously started FILS HLP wait, so that the timeout can + * be determined from the first pending attempt. */ + wpa_printf(MSG_DEBUG, + "FILS: Continue waiting for HLP processing before sending (Re)Association Response frame to " + MACSTR, MAC2STR(sta->addr)); + os_free(tmp); + return; + } + if (sta) { + eloop_cancel_timeout(fils_hlp_timeout, hapd, sta); + os_free(sta->fils_pending_assoc_req); + sta->fils_pending_assoc_req = NULL; + sta->fils_pending_assoc_req_len = 0; + wpabuf_free(sta->fils_hlp_resp); + sta->fils_hlp_resp = NULL; + } + if (sta && delay_assoc && resp == WLAN_STATUS_SUCCESS) { + sta->fils_pending_assoc_req = tmp; + sta->fils_pending_assoc_req_len = left; + sta->fils_pending_assoc_is_reassoc = reassoc; + sta->fils_drv_assoc_finish = 0; + wpa_printf(MSG_DEBUG, + "FILS: Waiting for HLP processing before sending (Re)Association Response frame to " + MACSTR, MAC2STR(sta->addr)); + eloop_cancel_timeout(fils_hlp_timeout, hapd, sta); + eloop_register_timeout(0, hapd->conf->fils_hlp_wait_time * 1024, + fils_hlp_timeout, hapd, sta); + return; + } +#endif /* CONFIG_FILS */ + + if (resp >= 0) + reply_res = send_assoc_resp(hapd, + mld_addrs_not_translated ? + NULL : sta, + mgmt->sa, resp, reassoc, + pos, left, rssi, omit_rsnxe, + !mld_addrs_not_translated); + os_free(tmp); + + /* + * Remove the station in case transmission of a success response fails + * (the STA was added associated to the driver) or if the station was + * previously added unassociated. + */ + if (sta && ((reply_res != WLAN_STATUS_SUCCESS && + resp == WLAN_STATUS_SUCCESS) || sta->added_unassoc)) { + hostapd_drv_sta_remove(hapd, sta->addr); + sta->added_unassoc = 0; + } +} + + +static void hostapd_deauth_sta(struct hostapd_data *hapd, + struct sta_info *sta, + const struct ieee80211_mgmt *mgmt) +{ + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "deauthentication: STA=" MACSTR " reason_code=%d", + MAC2STR(mgmt->sa), le_to_host16(mgmt->u.deauth.reason_code)); + + ap_sta_set_authorized(hapd, sta, 0); + sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ; + sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC | + WLAN_STA_ASSOC_REQ_OK); + hostapd_set_sta_flags(hapd, sta); + wpa_auth_sm_event(sta->wpa_sm, WPA_DEAUTH); + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "deauthenticated"); + mlme_deauthenticate_indication( + hapd, sta, le_to_host16(mgmt->u.deauth.reason_code)); + sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST; + ieee802_1x_notify_port_enabled(sta->eapol_sm, 0); + ap_free_sta(hapd, sta); +} + + +static void hostapd_disassoc_sta(struct hostapd_data *hapd, + struct sta_info *sta, + const struct ieee80211_mgmt *mgmt) +{ + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "disassocation: STA=" MACSTR " reason_code=%d", + MAC2STR(mgmt->sa), le_to_host16(mgmt->u.disassoc.reason_code)); + + ap_sta_set_authorized(hapd, sta, 0); + sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ; + sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK); + hostapd_set_sta_flags(hapd, sta); + wpa_auth_sm_event(sta->wpa_sm, WPA_DISASSOC); + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "disassociated"); + sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST; + ieee802_1x_notify_port_enabled(sta->eapol_sm, 0); + /* Stop Accounting and IEEE 802.1X sessions, but leave the STA + * authenticated. */ + accounting_sta_stop(hapd, sta); + ieee802_1x_free_station(hapd, sta); + if (sta->ipaddr) + hostapd_drv_br_delete_ip_neigh(hapd, 4, (u8 *) &sta->ipaddr); + ap_sta_ip6addr_del(hapd, sta); + hostapd_drv_sta_remove(hapd, sta->addr); + sta->added_unassoc = 0; + + if (sta->timeout_next == STA_NULLFUNC || + sta->timeout_next == STA_DISASSOC) { + sta->timeout_next = STA_DEAUTH; + eloop_cancel_timeout(ap_handle_timer, hapd, sta); + eloop_register_timeout(AP_DEAUTH_DELAY, 0, ap_handle_timer, + hapd, sta); + } + + mlme_disassociate_indication( + hapd, sta, le_to_host16(mgmt->u.disassoc.reason_code)); + + /* DMG/IEEE 802.11ad does not use deauthication. Deallocate sta upon + * disassociation. */ + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) { + sta->flags &= ~WLAN_STA_AUTH; + wpa_auth_sm_event(sta->wpa_sm, WPA_DEAUTH); + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "deauthenticated"); + ap_free_sta(hapd, sta); + } +} + + +static bool hostapd_ml_handle_disconnect(struct hostapd_data *hapd, + struct sta_info *sta, + const struct ieee80211_mgmt *mgmt, + bool disassoc) +{ +#ifdef CONFIG_IEEE80211BE + struct hostapd_data *assoc_hapd, *tmp_hapd; + struct sta_info *assoc_sta; + struct sta_info *tmp_sta; + + if (!hostapd_is_mld_ap(hapd)) + return false; + + /* + * Get the station on which the association was performed, as it holds + * the information about all the other links. + */ + assoc_sta = hostapd_ml_get_assoc_sta(hapd, sta, &assoc_hapd); + if (!assoc_sta) + return false; + + for_each_mld_link(tmp_hapd, assoc_hapd) { + if (tmp_hapd == assoc_hapd) + continue; + + if (!assoc_sta->mld_info.links[tmp_hapd->mld_link_id].valid) + continue; + + for (tmp_sta = tmp_hapd->sta_list; tmp_sta; + tmp_sta = tmp_sta->next) { + if (tmp_sta->mld_assoc_link_id != + assoc_sta->mld_assoc_link_id || + tmp_sta->aid != assoc_sta->aid) + continue; + + if (!disassoc) + hostapd_deauth_sta(tmp_hapd, tmp_sta, mgmt); + else + hostapd_disassoc_sta(tmp_hapd, tmp_sta, mgmt); + break; + } + } + + /* Remove the station on which the association was performed. */ + if (!disassoc) + hostapd_deauth_sta(assoc_hapd, assoc_sta, mgmt); + else + hostapd_disassoc_sta(assoc_hapd, assoc_sta, mgmt); + + return true; +#else /* CONFIG_IEEE80211BE */ + return false; +#endif /* CONFIG_IEEE80211BE */ +} + + +static void handle_disassoc(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len) +{ + struct sta_info *sta; + + if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.disassoc)) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "handle_disassoc - too short payload (len=%lu)", + (unsigned long) len); + return; + } + + sta = ap_get_sta(hapd, mgmt->sa); + if (!sta) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Station " MACSTR + " trying to disassociate, but it is not associated", + MAC2STR(mgmt->sa)); + return; + } + + if (hostapd_ml_handle_disconnect(hapd, sta, mgmt, true)) + return; + + hostapd_disassoc_sta(hapd, sta, mgmt); +} + + +static void handle_deauth(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len) +{ + struct sta_info *sta; + + if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.deauth)) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "handle_deauth - too short payload (len=%lu)", + (unsigned long) len); + return; + } + + /* Clear the PTKSA cache entries for PASN */ + ptksa_cache_flush(hapd->ptksa, mgmt->sa, WPA_CIPHER_NONE); + + sta = ap_get_sta(hapd, mgmt->sa); + if (!sta) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Station " MACSTR + " trying to deauthenticate, but it is not authenticated", + MAC2STR(mgmt->sa)); + return; + } + + if (hostapd_ml_handle_disconnect(hapd, sta, mgmt, false)) + return; + + hostapd_deauth_sta(hapd, sta, mgmt); +} + + +static void handle_beacon(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len, + struct hostapd_frame_info *fi) +{ + struct ieee802_11_elems elems; + + if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.beacon)) { + wpa_printf(MSG_INFO, "handle_beacon - too short payload (len=%lu)", + (unsigned long) len); + return; + } + + (void) ieee802_11_parse_elems(mgmt->u.beacon.variable, + len - (IEEE80211_HDRLEN + + sizeof(mgmt->u.beacon)), &elems, + 0); + + ap_list_process_beacon(hapd->iface, mgmt, &elems, fi); +} + + +static int robust_action_frame(u8 category) +{ + return category != WLAN_ACTION_PUBLIC && + category != WLAN_ACTION_HT; +} + + +static int handle_action(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len, + unsigned int freq) +{ + struct sta_info *sta; + u8 *action __maybe_unused; + + if (len < IEEE80211_HDRLEN + 2 + 1) { + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "handle_action - too short payload (len=%lu)", + (unsigned long) len); + return 0; + } + + action = (u8 *) &mgmt->u.action.u; + wpa_printf(MSG_DEBUG, "RX_ACTION category %u action %u sa " MACSTR + " da " MACSTR " len %d freq %u", + mgmt->u.action.category, *action, + MAC2STR(mgmt->sa), MAC2STR(mgmt->da), (int) len, freq); + + sta = ap_get_sta(hapd, mgmt->sa); + + if (mgmt->u.action.category != WLAN_ACTION_PUBLIC && + (sta == NULL || !(sta->flags & WLAN_STA_ASSOC))) { + wpa_printf(MSG_DEBUG, "IEEE 802.11: Ignored Action " + "frame (category=%u) from unassociated STA " MACSTR, + mgmt->u.action.category, MAC2STR(mgmt->sa)); + return 0; + } + + if (sta && (sta->flags & WLAN_STA_MFP) && + !(mgmt->frame_control & host_to_le16(WLAN_FC_ISWEP)) && + robust_action_frame(mgmt->u.action.category)) { + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "Dropped unprotected Robust Action frame from " + "an MFP STA"); + return 0; + } + + if (sta) { + u16 fc = le_to_host16(mgmt->frame_control); + u16 seq_ctrl = le_to_host16(mgmt->seq_ctrl); + + if ((fc & WLAN_FC_RETRY) && + sta->last_seq_ctrl != WLAN_INVALID_MGMT_SEQ && + sta->last_seq_ctrl == seq_ctrl && + sta->last_subtype == WLAN_FC_STYPE_ACTION) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "Drop repeated action frame seq_ctrl=0x%x", + seq_ctrl); + return 1; + } + + sta->last_seq_ctrl = seq_ctrl; + sta->last_subtype = WLAN_FC_STYPE_ACTION; + } + + switch (mgmt->u.action.category) { +#ifdef CONFIG_IEEE80211R_AP + case WLAN_ACTION_FT: + if (!sta || + wpa_ft_action_rx(sta->wpa_sm, (u8 *) &mgmt->u.action, + len - IEEE80211_HDRLEN)) + break; + return 1; +#endif /* CONFIG_IEEE80211R_AP */ + case WLAN_ACTION_WMM: + hostapd_wmm_action(hapd, mgmt, len); + return 1; + case WLAN_ACTION_SA_QUERY: + ieee802_11_sa_query_action(hapd, mgmt, len); + return 1; +#ifdef CONFIG_WNM_AP + case WLAN_ACTION_WNM: + ieee802_11_rx_wnm_action_ap(hapd, mgmt, len); + return 1; +#endif /* CONFIG_WNM_AP */ +#ifdef CONFIG_FST + case WLAN_ACTION_FST: + if (hapd->iface->fst) + fst_rx_action(hapd->iface->fst, mgmt, len); + else + wpa_printf(MSG_DEBUG, + "FST: Ignore FST Action frame - no FST attached"); + return 1; +#endif /* CONFIG_FST */ + case WLAN_ACTION_PUBLIC: + case WLAN_ACTION_PROTECTED_DUAL: + if (len >= IEEE80211_HDRLEN + 2 && + mgmt->u.action.u.public_action.action == + WLAN_PA_20_40_BSS_COEX) { + hostapd_2040_coex_action(hapd, mgmt, len); + return 1; + } +#ifdef CONFIG_DPP + if (len >= IEEE80211_HDRLEN + 6 && + mgmt->u.action.u.vs_public_action.action == + WLAN_PA_VENDOR_SPECIFIC && + WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) == + OUI_WFA && + mgmt->u.action.u.vs_public_action.variable[0] == + DPP_OUI_TYPE) { + const u8 *pos, *end; + + pos = mgmt->u.action.u.vs_public_action.oui; + end = ((const u8 *) mgmt) + len; + hostapd_dpp_rx_action(hapd, mgmt->sa, pos, end - pos, + freq); + return 1; + } + if (len >= IEEE80211_HDRLEN + 2 && + (mgmt->u.action.u.public_action.action == + WLAN_PA_GAS_INITIAL_RESP || + mgmt->u.action.u.public_action.action == + WLAN_PA_GAS_COMEBACK_RESP)) { + const u8 *pos, *end; + + pos = &mgmt->u.action.u.public_action.action; + end = ((const u8 *) mgmt) + len; + if (gas_query_ap_rx(hapd->gas, mgmt->sa, + mgmt->u.action.category, + pos, end - pos, freq) == 0) + return 1; + } +#endif /* CONFIG_DPP */ +#ifdef CONFIG_NAN_USD + if (mgmt->u.action.category == WLAN_ACTION_PUBLIC && + len >= IEEE80211_HDRLEN + 5 && + mgmt->u.action.u.vs_public_action.action == + WLAN_PA_VENDOR_SPECIFIC && + WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) == + OUI_WFA && + mgmt->u.action.u.vs_public_action.variable[0] == + NAN_OUI_TYPE) { + const u8 *pos, *end; + + pos = mgmt->u.action.u.vs_public_action.variable; + end = ((const u8 *) mgmt) + len; + pos++; + hostapd_nan_usd_rx_sdf(hapd, mgmt->sa, freq, + pos, end - pos); + return 1; + } +#endif /* CONFIG_NAN_USD */ + if (hapd->public_action_cb) { + hapd->public_action_cb(hapd->public_action_cb_ctx, + (u8 *) mgmt, len, freq); + } + if (hapd->public_action_cb2) { + hapd->public_action_cb2(hapd->public_action_cb2_ctx, + (u8 *) mgmt, len, freq); + } + if (hapd->public_action_cb || hapd->public_action_cb2) + return 1; + break; + case WLAN_ACTION_VENDOR_SPECIFIC: + if (hapd->vendor_action_cb) { + if (hapd->vendor_action_cb(hapd->vendor_action_cb_ctx, + (u8 *) mgmt, len, freq) == 0) + return 1; + } + break; +#ifndef CONFIG_NO_RRM + case WLAN_ACTION_RADIO_MEASUREMENT: + hostapd_handle_radio_measurement(hapd, (const u8 *) mgmt, len); + return 1; +#endif /* CONFIG_NO_RRM */ + } + + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "handle_action - unknown action category %d or invalid " + "frame", + mgmt->u.action.category); + if (!is_multicast_ether_addr(mgmt->da) && + !(mgmt->u.action.category & 0x80) && + !is_multicast_ether_addr(mgmt->sa)) { + struct ieee80211_mgmt *resp; + + /* + * IEEE 802.11-REVma/D9.0 - 7.3.1.11 + * Return the Action frame to the source without change + * except that MSB of the Category set to 1. + */ + wpa_printf(MSG_DEBUG, "IEEE 802.11: Return unknown Action " + "frame back to sender"); + resp = os_memdup(mgmt, len); + if (resp == NULL) + return 0; + os_memcpy(resp->da, resp->sa, ETH_ALEN); + os_memcpy(resp->sa, hapd->own_addr, ETH_ALEN); + os_memcpy(resp->bssid, hapd->own_addr, ETH_ALEN); + resp->u.action.category |= 0x80; + + if (hostapd_drv_send_mlme(hapd, resp, len, 0, NULL, 0, 0) < 0) { + wpa_printf(MSG_ERROR, "IEEE 802.11: Failed to send " + "Action frame"); + } + os_free(resp); + } + + return 1; +} + + +/** + * notify_mgmt_frame - Notify of Management frames on the control interface + * @hapd: hostapd BSS data structure (the BSS to which the Management frame was + * sent to) + * @buf: Management frame data (starting from the IEEE 802.11 header) + * @len: Length of frame data in octets + * + * Notify the control interface of any received Management frame. + */ +static void notify_mgmt_frame(struct hostapd_data *hapd, const u8 *buf, + size_t len) +{ + + int hex_len = len * 2 + 1; + char *hex = os_malloc(hex_len); + + if (hex) { + wpa_snprintf_hex(hex, hex_len, buf, len); + wpa_msg_ctrl(hapd->msg_ctx, MSG_INFO, + AP_MGMT_FRAME_RECEIVED "buf=%s", hex); + os_free(hex); + } +} + + +/** + * ieee802_11_mgmt - process incoming IEEE 802.11 management frames + * @hapd: hostapd BSS data structure (the BSS to which the management frame was + * sent to) + * @buf: management frame data (starting from IEEE 802.11 header) + * @len: length of frame data in octets + * @fi: meta data about received frame (signal level, etc.) + * + * Process all incoming IEEE 802.11 management frames. This will be called for + * each frame received from the kernel driver through wlan#ap interface. In + * addition, it can be called to re-inserted pending frames (e.g., when using + * external RADIUS server as an MAC ACL). + */ +int ieee802_11_mgmt(struct hostapd_data *hapd, const u8 *buf, size_t len, + struct hostapd_frame_info *fi) +{ + struct ieee80211_mgmt *mgmt; + u16 fc, stype; + int ret = 0; + unsigned int freq; + int ssi_signal = fi ? fi->ssi_signal : 0; +#ifdef CONFIG_NAN_USD + static const u8 nan_network_id[ETH_ALEN] = + { 0x51, 0x6f, 0x9a, 0x01, 0x00, 0x00 }; +#endif /* CONFIG_NAN_USD */ + + if (len < 24) + return 0; + + if (fi && fi->freq) + freq = fi->freq; + else + freq = hapd->iface->freq; + + mgmt = (struct ieee80211_mgmt *) buf; + fc = le_to_host16(mgmt->frame_control); + stype = WLAN_FC_GET_STYPE(fc); + + if (is_multicast_ether_addr(mgmt->sa) || + is_zero_ether_addr(mgmt->sa) || + ether_addr_equal(mgmt->sa, hapd->own_addr)) { + /* Do not process any frames with unexpected/invalid SA so that + * we do not add any state for unexpected STA addresses or end + * up sending out frames to unexpected destination. */ + wpa_printf(MSG_DEBUG, "MGMT: Invalid SA=" MACSTR + " in received frame - ignore this frame silently", + MAC2STR(mgmt->sa)); + return 0; + } + + if (stype == WLAN_FC_STYPE_BEACON) { + handle_beacon(hapd, mgmt, len, fi); + return 1; + } + + if (!is_broadcast_ether_addr(mgmt->bssid) && +#ifdef CONFIG_P2P + /* Invitation responses can be sent with the peer MAC as BSSID */ + !((hapd->conf->p2p & P2P_GROUP_OWNER) && + stype == WLAN_FC_STYPE_ACTION) && +#endif /* CONFIG_P2P */ +#ifdef CONFIG_MESH + !(hapd->conf->mesh & MESH_ENABLED) && +#endif /* CONFIG_MESH */ +#ifdef CONFIG_IEEE80211BE + !(hapd->conf->mld_ap && + ether_addr_equal(hapd->mld->mld_addr, mgmt->bssid)) && +#endif /* CONFIG_IEEE80211BE */ + !ether_addr_equal(mgmt->bssid, hapd->own_addr)) { + wpa_printf(MSG_INFO, "MGMT: BSSID=" MACSTR " not our address", + MAC2STR(mgmt->bssid)); + return 0; + } + + if (hapd->iface->state != HAPD_IFACE_ENABLED) { + wpa_printf(MSG_DEBUG, "MGMT: Ignore management frame while interface is not enabled (SA=" MACSTR " DA=" MACSTR " subtype=%u)", + MAC2STR(mgmt->sa), MAC2STR(mgmt->da), stype); + return 1; + } + + if (stype == WLAN_FC_STYPE_PROBE_REQ) { + handle_probe_req(hapd, mgmt, len, ssi_signal); + return 1; + } + + if ((!is_broadcast_ether_addr(mgmt->da) || + stype != WLAN_FC_STYPE_ACTION) && +#ifdef CONFIG_IEEE80211BE + !(hapd->conf->mld_ap && + ether_addr_equal(hapd->mld->mld_addr, mgmt->bssid)) && +#endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_NAN_USD + !ether_addr_equal(mgmt->da, nan_network_id) && +#endif /* CONFIG_NAN_USD */ + !ether_addr_equal(mgmt->da, hapd->own_addr)) { + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "MGMT: DA=" MACSTR " not our address", + MAC2STR(mgmt->da)); + return 0; + } + + if (hapd->iconf->track_sta_max_num) + sta_track_add(hapd->iface, mgmt->sa, ssi_signal); + + if (hapd->conf->notify_mgmt_frames) + notify_mgmt_frame(hapd, buf, len); + + switch (stype) { + case WLAN_FC_STYPE_AUTH: + wpa_printf(MSG_DEBUG, "mgmt::auth"); + handle_auth(hapd, mgmt, len, ssi_signal, 0); + ret = 1; + break; + case WLAN_FC_STYPE_ASSOC_REQ: + wpa_printf(MSG_DEBUG, "mgmt::assoc_req"); + handle_assoc(hapd, mgmt, len, 0, ssi_signal); + ret = 1; + break; + case WLAN_FC_STYPE_REASSOC_REQ: + wpa_printf(MSG_DEBUG, "mgmt::reassoc_req"); + handle_assoc(hapd, mgmt, len, 1, ssi_signal); + ret = 1; + break; + case WLAN_FC_STYPE_DISASSOC: + wpa_printf(MSG_DEBUG, "mgmt::disassoc"); + handle_disassoc(hapd, mgmt, len); + ret = 1; + break; + case WLAN_FC_STYPE_DEAUTH: + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "mgmt::deauth"); + handle_deauth(hapd, mgmt, len); + ret = 1; + break; + case WLAN_FC_STYPE_ACTION: + wpa_printf(MSG_DEBUG, "mgmt::action"); + ret = handle_action(hapd, mgmt, len, freq); + break; + default: + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "unknown mgmt frame subtype %d", stype); + break; + } + + return ret; +} + + +static void handle_auth_cb(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len, int ok) +{ + u16 auth_alg, auth_transaction, status_code; + struct sta_info *sta; + bool success_status; + + sta = ap_get_sta(hapd, mgmt->da); + if (!sta) { + wpa_printf(MSG_DEBUG, "handle_auth_cb: STA " MACSTR + " not found", + MAC2STR(mgmt->da)); + return; + } + + if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) { + wpa_printf(MSG_INFO, "handle_auth_cb - too short payload (len=%lu)", + (unsigned long) len); + auth_alg = 0; + auth_transaction = 0; + status_code = WLAN_STATUS_UNSPECIFIED_FAILURE; + goto fail; + } + + auth_alg = le_to_host16(mgmt->u.auth.auth_alg); + auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction); + status_code = le_to_host16(mgmt->u.auth.status_code); + + if (!ok) { + hostapd_logger(hapd, mgmt->da, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_NOTICE, + "did not acknowledge authentication response"); + goto fail; + } + + if (status_code == WLAN_STATUS_SUCCESS && + ((auth_alg == WLAN_AUTH_OPEN && auth_transaction == 2) || + (auth_alg == WLAN_AUTH_SHARED_KEY && auth_transaction == 4))) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "authenticated"); + sta->flags |= WLAN_STA_AUTH; + if (sta->added_unassoc) + hostapd_set_sta_flags(hapd, sta); + return; + } + +fail: + success_status = status_code == WLAN_STATUS_SUCCESS; +#ifdef CONFIG_SAE + if (auth_alg == WLAN_AUTH_SAE && auth_transaction == 1) + success_status = sae_status_success(hapd, status_code); +#endif /* CONFIG_SAE */ + if (!success_status && sta->added_unassoc) { + hostapd_drv_sta_remove(hapd, sta->addr); + sta->added_unassoc = 0; + } +} + + +static void hostapd_set_wds_encryption(struct hostapd_data *hapd, + struct sta_info *sta, + char *ifname_wds) +{ +#ifdef CONFIG_WEP + int i; + struct hostapd_ssid *ssid = &hapd->conf->ssid; + + if (hapd->conf->ieee802_1x || hapd->conf->wpa) + return; + + for (i = 0; i < 4; i++) { + if (ssid->wep.key[i] && + hostapd_drv_set_key(ifname_wds, hapd, WPA_ALG_WEP, NULL, i, + 0, i == ssid->wep.idx, NULL, 0, + ssid->wep.key[i], ssid->wep.len[i], + i == ssid->wep.idx ? + KEY_FLAG_GROUP_RX_TX_DEFAULT : + KEY_FLAG_GROUP_RX_TX)) { + wpa_printf(MSG_WARNING, + "Could not set WEP keys for WDS interface; %s", + ifname_wds); + break; + } + } +#endif /* CONFIG_WEP */ +} + + +#ifdef CONFIG_IEEE80211BE +static void ieee80211_ml_link_sta_assoc_cb(struct hostapd_data *hapd, + struct sta_info *sta, + struct mld_link_info *link, + bool ok) +{ + bool updated = false; + + if (!ok) { + hostapd_logger(hapd, link->peer_addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "did not acknowledge association response"); + sta->flags &= ~WLAN_STA_ASSOC_REQ_OK; + + /* The STA is added only in case of SUCCESS */ + if (link->status == WLAN_STATUS_SUCCESS) + hostapd_drv_sta_remove(hapd, sta->addr); + + return; + } + + if (link->status != WLAN_STATUS_SUCCESS) + return; + + sta->flags |= WLAN_STA_ASSOC; + sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE; + + if (!hapd->conf->ieee802_1x && !hapd->conf->wpa) + updated = ap_sta_set_authorized_flag(hapd, sta, 1); + + hostapd_set_sta_flags(hapd, sta); + if (updated) + ap_sta_set_authorized_event(hapd, sta, 1); + + /* + * TODOs: + * - IEEE 802.1X port enablement is not needed as done on the station + * doing the connection. + * - Not handling accounting + * - Need to handle VLAN configuration + */ +} +#endif /* CONFIG_IEEE80211BE */ + + +static void hostapd_ml_handle_assoc_cb(struct hostapd_data *hapd, + struct sta_info *sta, bool ok) +{ +#ifdef CONFIG_IEEE80211BE + struct hostapd_data *tmp_hapd; + + if (!hostapd_is_mld_ap(hapd)) + return; + + for_each_mld_link(tmp_hapd, hapd) { + struct mld_link_info *link; + struct sta_info *tmp_sta; + + if (tmp_hapd == hapd) + continue; + + link = &sta->mld_info.links[tmp_hapd->mld_link_id]; + if (!link->valid) + continue; + + for (tmp_sta = tmp_hapd->sta_list; tmp_sta; + tmp_sta = tmp_sta->next) { + if (tmp_sta == sta || + tmp_sta->mld_assoc_link_id != + sta->mld_assoc_link_id || + tmp_sta->aid != sta->aid) + continue; + + ieee80211_ml_link_sta_assoc_cb(tmp_hapd, tmp_sta, link, + ok); + break; + } + } +#endif /* CONFIG_IEEE80211BE */ +} + + +static void handle_assoc_cb(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len, int reassoc, int ok) +{ + u16 status; + struct sta_info *sta; + int new_assoc = 1; + + sta = ap_get_sta(hapd, mgmt->da); + if (!sta) { + wpa_printf(MSG_INFO, "handle_assoc_cb: STA " MACSTR " not found", + MAC2STR(mgmt->da)); + return; + } + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta) && + hapd->mld_link_id != sta->mld_assoc_link_id) { + /* See ieee80211_ml_link_sta_assoc_cb() for the MLD case */ + wpa_printf(MSG_DEBUG, + "%s: MLD: ignore on link station (%d != %d)", + __func__, hapd->mld_link_id, sta->mld_assoc_link_id); + return; + } +#endif /* CONFIG_IEEE80211BE */ + + if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_resp) : + sizeof(mgmt->u.assoc_resp))) { + wpa_printf(MSG_INFO, + "handle_assoc_cb(reassoc=%d) - too short payload (len=%lu)", + reassoc, (unsigned long) len); + hostapd_drv_sta_remove(hapd, sta->addr); + return; + } + + if (reassoc) + status = le_to_host16(mgmt->u.reassoc_resp.status_code); + else + status = le_to_host16(mgmt->u.assoc_resp.status_code); + + if (!ok) { + hostapd_logger(hapd, mgmt->da, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "did not acknowledge association response"); + sta->flags &= ~WLAN_STA_ASSOC_REQ_OK; + /* The STA is added only in case of SUCCESS */ + if (status == WLAN_STATUS_SUCCESS) + hostapd_drv_sta_remove(hapd, sta->addr); + + goto handle_ml; + } + + if (status != WLAN_STATUS_SUCCESS) + goto handle_ml; + + /* Stop previous accounting session, if one is started, and allocate + * new session id for the new session. */ + accounting_sta_stop(hapd, sta); + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "associated (aid %d)", + sta->aid); + + if (sta->flags & WLAN_STA_ASSOC) + new_assoc = 0; + sta->flags |= WLAN_STA_ASSOC; + sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE; + if ((!hapd->conf->ieee802_1x && !hapd->conf->wpa && + !hapd->conf->osen) || + sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK || + sta->auth_alg == WLAN_AUTH_FT) { + /* + * Open, static WEP, FT protocol, or FILS; no separate + * authorization step. + */ + ap_sta_set_authorized(hapd, sta, 1); + } + + if (reassoc) + mlme_reassociate_indication(hapd, sta); + else + mlme_associate_indication(hapd, sta); + + sta->sa_query_timed_out = 0; + + if (sta->eapol_sm == NULL) { + /* + * This STA does not use RADIUS server for EAP authentication, + * so bind it to the selected VLAN interface now, since the + * interface selection is not going to change anymore. + */ + if (ap_sta_bind_vlan(hapd, sta) < 0) + goto handle_ml; + } else if (sta->vlan_id) { + /* VLAN ID already set (e.g., by PMKSA caching), so bind STA */ + if (ap_sta_bind_vlan(hapd, sta) < 0) + goto handle_ml; + } + + hostapd_set_sta_flags(hapd, sta); + + if (!(sta->flags & WLAN_STA_WDS) && sta->pending_wds_enable) { + wpa_printf(MSG_DEBUG, "Enable 4-address WDS mode for STA " + MACSTR " based on pending request", + MAC2STR(sta->addr)); + sta->pending_wds_enable = 0; + sta->flags |= WLAN_STA_WDS; + } + + /* WPS not supported on backhaul BSS. Disable 4addr mode on fronthaul */ + if ((sta->flags & WLAN_STA_WDS) || + (sta->flags & WLAN_STA_MULTI_AP && + (hapd->conf->multi_ap & BACKHAUL_BSS) && + hapd->conf->wds_sta && + !(sta->flags & WLAN_STA_WPS))) { + int ret; + char ifname_wds[IFNAMSIZ + 1]; + + wpa_printf(MSG_DEBUG, "Reenable 4-address WDS mode for STA " + MACSTR " (aid %u)", + MAC2STR(sta->addr), sta->aid); + ret = hostapd_set_wds_sta(hapd, ifname_wds, sta->addr, + sta->aid, 1); + if (!ret) + hostapd_set_wds_encryption(hapd, sta, ifname_wds); + } + + if (sta->auth_alg == WLAN_AUTH_FT) + wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FT); + else + wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC); + hapd->new_assoc_sta_cb(hapd, sta, !new_assoc); + ieee802_1x_notify_port_enabled(sta->eapol_sm, 1); + +#ifdef CONFIG_FILS + if ((sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK) && + fils_set_tk(sta->wpa_sm) < 0) { + wpa_printf(MSG_DEBUG, "FILS: TK configuration failed"); + ap_sta_disconnect(hapd, sta, sta->addr, + WLAN_REASON_UNSPECIFIED); + return; + } +#endif /* CONFIG_FILS */ + + if (sta->pending_eapol_rx) { + struct os_reltime now, age; + + os_get_reltime(&now); + os_reltime_sub(&now, &sta->pending_eapol_rx->rx_time, &age); + if (age.sec == 0 && age.usec < 200000) { + wpa_printf(MSG_DEBUG, + "Process pending EAPOL frame that was received from " MACSTR " just before association notification", + MAC2STR(sta->addr)); + ieee802_1x_receive( + hapd, mgmt->da, + wpabuf_head(sta->pending_eapol_rx->buf), + wpabuf_len(sta->pending_eapol_rx->buf), + sta->pending_eapol_rx->encrypted); + } + wpabuf_free(sta->pending_eapol_rx->buf); + os_free(sta->pending_eapol_rx); + sta->pending_eapol_rx = NULL; + } + +handle_ml: + hostapd_ml_handle_assoc_cb(hapd, sta, ok); +} + + +static void handle_deauth_cb(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len, int ok) +{ + struct sta_info *sta; + if (is_multicast_ether_addr(mgmt->da)) + return; + sta = ap_get_sta(hapd, mgmt->da); + if (!sta) { + wpa_printf(MSG_DEBUG, "handle_deauth_cb: STA " MACSTR + " not found", MAC2STR(mgmt->da)); + return; + } + if (ok) + wpa_printf(MSG_DEBUG, "STA " MACSTR " acknowledged deauth", + MAC2STR(sta->addr)); + else + wpa_printf(MSG_DEBUG, "STA " MACSTR " did not acknowledge " + "deauth", MAC2STR(sta->addr)); + + ap_sta_deauth_cb(hapd, sta); +} + + +static void handle_disassoc_cb(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len, int ok) +{ + struct sta_info *sta; + if (is_multicast_ether_addr(mgmt->da)) + return; + sta = ap_get_sta(hapd, mgmt->da); + if (!sta) { + wpa_printf(MSG_DEBUG, "handle_disassoc_cb: STA " MACSTR + " not found", MAC2STR(mgmt->da)); + return; + } + if (ok) + wpa_printf(MSG_DEBUG, "STA " MACSTR " acknowledged disassoc", + MAC2STR(sta->addr)); + else + wpa_printf(MSG_DEBUG, "STA " MACSTR " did not acknowledge " + "disassoc", MAC2STR(sta->addr)); + + ap_sta_disassoc_cb(hapd, sta); +} + + +static void handle_action_cb(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len, int ok) +{ + struct sta_info *sta; +#ifndef CONFIG_NO_RRM + const struct rrm_measurement_report_element *report; +#endif /* CONFIG_NO_RRM */ + +#ifdef CONFIG_DPP + if (len >= IEEE80211_HDRLEN + 6 && + mgmt->u.action.category == WLAN_ACTION_PUBLIC && + mgmt->u.action.u.vs_public_action.action == + WLAN_PA_VENDOR_SPECIFIC && + WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) == + OUI_WFA && + mgmt->u.action.u.vs_public_action.variable[0] == + DPP_OUI_TYPE) { + const u8 *pos, *end; + + pos = &mgmt->u.action.u.vs_public_action.variable[1]; + end = ((const u8 *) mgmt) + len; + hostapd_dpp_tx_status(hapd, mgmt->da, pos, end - pos, ok); + return; + } + if (len >= IEEE80211_HDRLEN + 2 && + mgmt->u.action.category == WLAN_ACTION_PUBLIC && + (mgmt->u.action.u.public_action.action == + WLAN_PA_GAS_INITIAL_REQ || + mgmt->u.action.u.public_action.action == + WLAN_PA_GAS_COMEBACK_REQ)) { + const u8 *pos, *end; + + pos = mgmt->u.action.u.public_action.variable; + end = ((const u8 *) mgmt) + len; + gas_query_ap_tx_status(hapd->gas, mgmt->da, pos, end - pos, ok); + return; + } +#endif /* CONFIG_DPP */ + if (is_multicast_ether_addr(mgmt->da)) + return; + sta = ap_get_sta(hapd, mgmt->da); + if (!sta) { + wpa_printf(MSG_DEBUG, "handle_action_cb: STA " MACSTR + " not found", MAC2STR(mgmt->da)); + return; + } + +#ifdef CONFIG_HS20 + if (ok && len >= IEEE80211_HDRLEN + 2 && + mgmt->u.action.category == WLAN_ACTION_WNM && + mgmt->u.action.u.vs_public_action.action == WNM_NOTIFICATION_REQ && + sta->hs20_deauth_on_ack) { + wpa_printf(MSG_DEBUG, "HS 2.0: Deauthenticate STA " MACSTR + " on acknowledging the WNM-Notification", + MAC2STR(sta->addr)); + ap_sta_session_timeout(hapd, sta, 0); + return; + } +#endif /* CONFIG_HS20 */ + +#ifndef CONFIG_NO_RRM + if (len < 24 + 5 + sizeof(*report)) + return; + report = (const struct rrm_measurement_report_element *) + &mgmt->u.action.u.rrm.variable[2]; + if (mgmt->u.action.category == WLAN_ACTION_RADIO_MEASUREMENT && + mgmt->u.action.u.rrm.action == WLAN_RRM_RADIO_MEASUREMENT_REQUEST && + report->eid == WLAN_EID_MEASURE_REQUEST && + report->len >= 3 && + report->type == MEASURE_TYPE_BEACON) + hostapd_rrm_beacon_req_tx_status(hapd, mgmt, len, ok); +#endif /* CONFIG_NO_RRM */ +} + + +/** + * ieee802_11_mgmt_cb - Process management frame TX status callback + * @hapd: hostapd BSS data structure (the BSS from which the management frame + * was sent from) + * @buf: management frame data (starting from IEEE 802.11 header) + * @len: length of frame data in octets + * @stype: management frame subtype from frame control field + * @ok: Whether the frame was ACK'ed + */ +void ieee802_11_mgmt_cb(struct hostapd_data *hapd, const u8 *buf, size_t len, + u16 stype, int ok) +{ + const struct ieee80211_mgmt *mgmt; + mgmt = (const struct ieee80211_mgmt *) buf; + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->ext_mgmt_frame_handling) { + size_t hex_len = 2 * len + 1; + char *hex = os_malloc(hex_len); + + if (hex) { + wpa_snprintf_hex(hex, hex_len, buf, len); + wpa_msg(hapd->msg_ctx, MSG_INFO, + "MGMT-TX-STATUS stype=%u ok=%d buf=%s", + stype, ok, hex); + os_free(hex); + } + return; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + switch (stype) { + case WLAN_FC_STYPE_AUTH: + wpa_printf(MSG_DEBUG, "mgmt::auth cb"); + handle_auth_cb(hapd, mgmt, len, ok); + break; + case WLAN_FC_STYPE_ASSOC_RESP: + wpa_printf(MSG_DEBUG, "mgmt::assoc_resp cb"); + handle_assoc_cb(hapd, mgmt, len, 0, ok); + break; + case WLAN_FC_STYPE_REASSOC_RESP: + wpa_printf(MSG_DEBUG, "mgmt::reassoc_resp cb"); + handle_assoc_cb(hapd, mgmt, len, 1, ok); + break; + case WLAN_FC_STYPE_PROBE_RESP: + wpa_printf(MSG_EXCESSIVE, "mgmt::proberesp cb ok=%d", ok); + break; + case WLAN_FC_STYPE_DEAUTH: + wpa_printf(MSG_DEBUG, "mgmt::deauth cb"); + handle_deauth_cb(hapd, mgmt, len, ok); + break; + case WLAN_FC_STYPE_DISASSOC: + wpa_printf(MSG_DEBUG, "mgmt::disassoc cb"); + handle_disassoc_cb(hapd, mgmt, len, ok); + break; + case WLAN_FC_STYPE_ACTION: + wpa_printf(MSG_DEBUG, "mgmt::action cb ok=%d", ok); + handle_action_cb(hapd, mgmt, len, ok); + break; + default: + wpa_printf(MSG_INFO, "unknown mgmt cb frame subtype %d", stype); + break; + } +} + + +int ieee802_11_get_mib(struct hostapd_data *hapd, char *buf, size_t buflen) +{ + /* TODO */ + return 0; +} + + +int ieee802_11_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, + char *buf, size_t buflen) +{ + /* TODO */ + return 0; +} + + +void hostapd_tx_status(struct hostapd_data *hapd, const u8 *addr, + const u8 *buf, size_t len, int ack) +{ + struct sta_info *sta; + struct hostapd_iface *iface = hapd->iface; + + sta = ap_get_sta(hapd, addr); + if (sta == NULL && iface->num_bss > 1) { + size_t j; + for (j = 0; j < iface->num_bss; j++) { + hapd = iface->bss[j]; + sta = ap_get_sta(hapd, addr); + if (sta) + break; + } + } + if (sta == NULL || !(sta->flags & WLAN_STA_ASSOC)) + return; + if (sta->flags & WLAN_STA_PENDING_POLL) { + wpa_printf(MSG_DEBUG, "STA " MACSTR " %s pending " + "activity poll", MAC2STR(sta->addr), + ack ? "ACKed" : "did not ACK"); + if (ack) + sta->flags &= ~WLAN_STA_PENDING_POLL; + } + + ieee802_1x_tx_status(hapd, sta, buf, len, ack); +} + + +void hostapd_client_poll_ok(struct hostapd_data *hapd, const u8 *addr) +{ + struct sta_info *sta; + struct hostapd_iface *iface = hapd->iface; + + sta = ap_get_sta(hapd, addr); + if (sta == NULL && iface->num_bss > 1) { + size_t j; + for (j = 0; j < iface->num_bss; j++) { + hapd = iface->bss[j]; + sta = ap_get_sta(hapd, addr); + if (sta) + break; + } + } + if (sta == NULL) + return; + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_POLL_OK MACSTR, + MAC2STR(sta->addr)); + if (!(sta->flags & WLAN_STA_PENDING_POLL)) + return; + + wpa_printf(MSG_DEBUG, "STA " MACSTR " ACKed pending " + "activity poll", MAC2STR(sta->addr)); + sta->flags &= ~WLAN_STA_PENDING_POLL; +} + + +void ieee802_11_rx_from_unknown(struct hostapd_data *hapd, const u8 *src, + int wds) +{ + struct sta_info *sta; + + sta = ap_get_sta(hapd, src); + if (sta && + ((sta->flags & WLAN_STA_ASSOC) || + ((sta->flags & WLAN_STA_ASSOC_REQ_OK) && wds))) { + if (!hapd->conf->wds_sta) + return; + + if ((sta->flags & (WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK)) == + WLAN_STA_ASSOC_REQ_OK) { + wpa_printf(MSG_DEBUG, + "Postpone 4-address WDS mode enabling for STA " + MACSTR " since TX status for AssocResp is not yet known", + MAC2STR(sta->addr)); + sta->pending_wds_enable = 1; + return; + } + + if (wds && !(sta->flags & WLAN_STA_WDS)) { + int ret; + char ifname_wds[IFNAMSIZ + 1]; + + wpa_printf(MSG_DEBUG, "Enable 4-address WDS mode for " + "STA " MACSTR " (aid %u)", + MAC2STR(sta->addr), sta->aid); + sta->flags |= WLAN_STA_WDS; + ret = hostapd_set_wds_sta(hapd, ifname_wds, + sta->addr, sta->aid, 1); + if (!ret) + hostapd_set_wds_encryption(hapd, sta, + ifname_wds); + } + return; + } + + wpa_printf(MSG_DEBUG, "Data/PS-poll frame from not associated STA " + MACSTR, MAC2STR(src)); + if (is_multicast_ether_addr(src) || is_zero_ether_addr(src) || + ether_addr_equal(src, hapd->own_addr)) { + /* Broadcast bit set in SA or unexpected SA?! Ignore the frame + * silently. */ + return; + } + + if (sta && (sta->flags & WLAN_STA_ASSOC_REQ_OK)) { + wpa_printf(MSG_DEBUG, "Association Response to the STA has " + "already been sent, but no TX status yet known - " + "ignore Class 3 frame issue with " MACSTR, + MAC2STR(src)); + return; + } + + if (sta && (sta->flags & WLAN_STA_AUTH)) + hostapd_drv_sta_disassoc( + hapd, src, + WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA); + else + hostapd_drv_sta_deauth( + hapd, src, + WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA); +} + + +static u8 * hostapd_add_tpe_info(u8 *eid, u8 tx_pwr_count, + enum max_tx_pwr_interpretation tx_pwr_intrpn, + u8 tx_pwr_cat, u8 tx_pwr) +{ + int i; + + *eid++ = WLAN_EID_TRANSMIT_POWER_ENVELOPE; /* Element ID */ + *eid++ = 2 + tx_pwr_count; /* Length */ + + /* + * Transmit Power Information field + * bits 0-2 : Maximum Transmit Power Count + * bits 3-5 : Maximum Transmit Power Interpretation + * bits 6-7 : Maximum Transmit Power Category + */ + *eid++ = tx_pwr_count | (tx_pwr_intrpn << 3) | (tx_pwr_cat << 6); + + /* Maximum Transmit Power field */ + for (i = 0; i <= tx_pwr_count; i++) + *eid++ = tx_pwr; + + return eid; +} + + +/* + * TODO: Extract power limits from channel data after 6G regulatory + * support. + */ +#define REG_PSD_MAX_TXPOWER_FOR_DEFAULT_CLIENT (-1) /* dBm/MHz */ +#define REG_PSD_MAX_TXPOWER_FOR_SUBORDINATE_CLIENT 5 /* dBm/MHz */ + +u8 * hostapd_eid_txpower_envelope(struct hostapd_data *hapd, u8 *eid) +{ + struct hostapd_iface *iface = hapd->iface; + struct hostapd_config *iconf = iface->conf; + struct hostapd_hw_modes *mode = iface->current_mode; + struct hostapd_channel_data *chan; + int dfs, i; + u8 channel, tx_pwr_count, local_pwr_constraint; + int max_tx_power; + u8 tx_pwr; + + if (!mode) + return eid; + + if (ieee80211_freq_to_chan(iface->freq, &channel) == NUM_HOSTAPD_MODES) + return eid; + + for (i = 0; i < mode->num_channels; i++) { + if (mode->channels[i].freq == iface->freq) + break; + } + if (i == mode->num_channels) + return eid; + +#ifdef CONFIG_IEEE80211AX + /* IEEE Std 802.11ax-2021, Annex E.2.7 (6 GHz band in the United + * States): An AP that is an Indoor Access Point per regulatory rules + * shall send at least two Transmit Power Envelope elements in Beacon + * and Probe Response frames as follows: + * - Maximum Transmit Power Category subfield = Default; + * Unit interpretation = Regulatory client EIRP PSD + * - Maximum Transmit Power Category subfield = Subordinate Device; + * Unit interpretation = Regulatory client EIRP PSD + */ + if (is_6ghz_op_class(iconf->op_class)) { + enum max_tx_pwr_interpretation tx_pwr_intrpn; + + /* Same Maximum Transmit Power for all 20 MHz bands */ + tx_pwr_count = 0; + tx_pwr_intrpn = REGULATORY_CLIENT_EIRP_PSD; + + /* Default Transmit Power Envelope for Global Operating Class */ + if (hapd->iconf->reg_def_cli_eirp_psd != -1) + tx_pwr = hapd->iconf->reg_def_cli_eirp_psd; + else + tx_pwr = REG_PSD_MAX_TXPOWER_FOR_DEFAULT_CLIENT * 2; + + eid = hostapd_add_tpe_info(eid, tx_pwr_count, tx_pwr_intrpn, + REG_DEFAULT_CLIENT, tx_pwr); + + /* Indoor Access Point must include an additional TPE for + * subordinate devices */ + if (he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type)) { + /* TODO: Extract PSD limits from channel data */ + if (hapd->iconf->reg_sub_cli_eirp_psd != -1) + tx_pwr = hapd->iconf->reg_sub_cli_eirp_psd; + else + tx_pwr = REG_PSD_MAX_TXPOWER_FOR_SUBORDINATE_CLIENT * 2; + eid = hostapd_add_tpe_info(eid, tx_pwr_count, + tx_pwr_intrpn, + REG_SUBORDINATE_CLIENT, + tx_pwr); + } + + if (iconf->reg_def_cli_eirp != -1 && + he_reg_is_sp(iconf->he_6ghz_reg_pwr_type)) + eid = hostapd_add_tpe_info( + eid, tx_pwr_count, REGULATORY_CLIENT_EIRP, + REG_DEFAULT_CLIENT, + hapd->iconf->reg_def_cli_eirp); + + return eid; + } +#endif /* CONFIG_IEEE80211AX */ + + switch (hostapd_get_oper_chwidth(iconf)) { + case CONF_OPER_CHWIDTH_USE_HT: + if (iconf->secondary_channel == 0) { + /* Max Transmit Power count = 0 (20 MHz) */ + tx_pwr_count = 0; + } else { + /* Max Transmit Power count = 1 (20, 40 MHz) */ + tx_pwr_count = 1; + } + break; + case CONF_OPER_CHWIDTH_80MHZ: + /* Max Transmit Power count = 2 (20, 40, and 80 MHz) */ + tx_pwr_count = 2; + break; + case CONF_OPER_CHWIDTH_80P80MHZ: + case CONF_OPER_CHWIDTH_160MHZ: + /* Max Transmit Power count = 3 (20, 40, 80, 160/80+80 MHz) */ + tx_pwr_count = 3; + break; + default: + return eid; + } + + /* + * Below local_pwr_constraint logic is referred from + * hostapd_eid_pwr_constraint. + * + * Check if DFS is required by regulatory. + */ + dfs = hostapd_is_dfs_required(hapd->iface); + if (dfs < 0) + dfs = 0; + + /* + * In order to meet regulations when TPC is not implemented using + * a transmit power that is below the legal maximum (including any + * mitigation factor) should help. In this case, indicate 3 dB below + * maximum allowed transmit power. + */ + if (hapd->iconf->local_pwr_constraint == -1) + local_pwr_constraint = (dfs == 0) ? 0 : 3; + else + local_pwr_constraint = hapd->iconf->local_pwr_constraint; + + /* + * A STA that is not an AP shall use a transmit power less than or + * equal to the local maximum transmit power level for the channel. + * The local maximum transmit power can be calculated from the formula: + * local max TX pwr = max TX pwr - local pwr constraint + * Where max TX pwr is maximum transmit power level specified for + * channel in Country element and local pwr constraint is specified + * for channel in this Power Constraint element. + */ + chan = &mode->channels[i]; + max_tx_power = chan->max_tx_power - local_pwr_constraint; + + /* + * Local Maximum Transmit power is encoded as two's complement + * with a 0.5 dB step. + */ + max_tx_power *= 2; /* in 0.5 dB steps */ + if (max_tx_power > 127) { + /* 63.5 has special meaning of 63.5 dBm or higher */ + max_tx_power = 127; + } + if (max_tx_power < -128) + max_tx_power = -128; + if (max_tx_power < 0) + tx_pwr = 0x80 + max_tx_power + 128; + else + tx_pwr = max_tx_power; + + return hostapd_add_tpe_info(eid, tx_pwr_count, LOCAL_EIRP, + 0 /* Reserved for bands other than 6 GHz */, + tx_pwr); +} + + +u8 * hostapd_eid_wb_chsw_wrapper(struct hostapd_data *hapd, u8 *eid) +{ + u8 bw, chan1 = 0, chan2 = 0; + int freq1; + + if (!hapd->cs_freq_params.channel || + (!hapd->cs_freq_params.vht_enabled && + !hapd->cs_freq_params.he_enabled && + !hapd->cs_freq_params.eht_enabled)) + return eid; + + /* bandwidth: 0: 40, 1: 80, 160, 80+80, 4: 320 as per + * IEEE P802.11-REVme/D4.0, 9.4.2.159 and Table 9-314. */ + switch (hapd->cs_freq_params.bandwidth) { + case 40: + bw = 0; + break; + case 80: + bw = 1; + break; + case 160: + bw = 1; + break; + case 320: + bw = 4; + break; + default: + /* not valid VHT bandwidth or not in CSA */ + return eid; + } + + freq1 = hapd->cs_freq_params.center_freq1 ? + hapd->cs_freq_params.center_freq1 : + hapd->cs_freq_params.freq; + if (ieee80211_freq_to_chan(freq1, &chan1) != + HOSTAPD_MODE_IEEE80211A) + return eid; + + if (hapd->cs_freq_params.center_freq2 && + ieee80211_freq_to_chan(hapd->cs_freq_params.center_freq2, + &chan2) != HOSTAPD_MODE_IEEE80211A) + return eid; + + *eid++ = WLAN_EID_CHANNEL_SWITCH_WRAPPER; + *eid++ = 5; /* Length of Channel Switch Wrapper */ + *eid++ = WLAN_EID_WIDE_BW_CHSWITCH; + *eid++ = 3; /* Length of Wide Bandwidth Channel Switch element */ + *eid++ = bw; /* New Channel Width */ + if (hapd->cs_freq_params.bandwidth == 160) { + /* Update the CCFS0 and CCFS1 values in the element based on + * IEEE P802.11-REVme/D4.0, Table 9-314 */ + + /* CCFS1 - The channel center frequency index of the 160 MHz + * channel. */ + chan2 = chan1; + + /* CCFS0 - The channel center frequency index of the 80 MHz + * channel segment that contains the primary channel. */ + if (hapd->cs_freq_params.channel < chan1) + chan1 -= 8; + else + chan1 += 8; + } + *eid++ = chan1; /* New Channel Center Frequency Segment 0 */ + *eid++ = chan2; /* New Channel Center Frequency Segment 1 */ + + return eid; +} + + +static size_t hostapd_eid_nr_db_len(struct hostapd_data *hapd, + size_t *current_len) +{ + struct hostapd_neighbor_entry *nr; + size_t total_len = 0, len = *current_len; + + dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry, + list) { + if (!nr->nr || wpabuf_len(nr->nr) < 12) + continue; + + if (nr->short_ssid == hapd->conf->ssid.short_ssid) + continue; + + /* Start a new element */ + if (!len || + len + RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN > 255) { + len = RNR_HEADER_LEN; + total_len += RNR_HEADER_LEN; + } + + len += RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN; + total_len += RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN; + } + + *current_len = len; + return total_len; +} + + +struct mbssid_ie_profiles { + u8 start; + u8 end; +}; + +static bool hostapd_skip_rnr(size_t i, struct mbssid_ie_profiles *skip_profiles, + bool ap_mld, u8 tbtt_info_len, bool mld_update, + struct hostapd_data *reporting_hapd, + struct hostapd_data *bss) +{ + if (skip_profiles && + i >= skip_profiles->start && i < skip_profiles->end) + return true; + + /* No need to report if length is for normal TBTT and the BSS is + * affiliated with an AP MLD. MLD TBTT will include this. */ + if (tbtt_info_len == RNR_TBTT_INFO_LEN && ap_mld) + return true; + + /* No need to report if length is for MLD TBTT and the BSS is not + * affiliated with an aP MLD. Normal TBTT will include this. */ + if (tbtt_info_len == RNR_TBTT_INFO_MLD_LEN && !ap_mld) + return true; + +#ifdef CONFIG_IEEE80211BE + /* If building for co-location and they are ML partners, no need to + * include since the ML RNR will carry this. */ + if (!mld_update && hostapd_is_ml_partner(reporting_hapd, bss)) + return true; + + /* If building for ML RNR and they are not ML partners, don't include. + */ + if (mld_update && !hostapd_is_ml_partner(reporting_hapd, bss)) + return true; +#endif /* CONFIG_IEEE80211BE */ + + return false; +} + + +static size_t +hostapd_eid_rnr_iface_len(struct hostapd_data *hapd, + struct hostapd_data *reporting_hapd, + size_t *current_len, + struct mbssid_ie_profiles *skip_profiles, + bool mld_update) +{ + size_t total_len = 0, len = *current_len; + int tbtt_count, total_tbtt_count = 0; + size_t i, start; + u8 tbtt_info_len = mld_update ? RNR_TBTT_INFO_MLD_LEN : + RNR_TBTT_INFO_LEN; + +repeat_rnr_len: + start = 0; + tbtt_count = 0; + + while (start < hapd->iface->num_bss) { + if (!len || + len + RNR_TBTT_HEADER_LEN + tbtt_info_len > 255 || + tbtt_count >= RNR_TBTT_INFO_COUNT_MAX) { + len = RNR_HEADER_LEN; + total_len += RNR_HEADER_LEN; + tbtt_count = 0; + } + + len += RNR_TBTT_HEADER_LEN; + total_len += RNR_TBTT_HEADER_LEN; + + for (i = start; i < hapd->iface->num_bss; i++) { + struct hostapd_data *bss = hapd->iface->bss[i]; + bool ap_mld = false; + + if (!bss || !bss->conf || !bss->started) + continue; + +#ifdef CONFIG_IEEE80211BE + ap_mld = bss->conf->mld_ap; +#endif /* CONFIG_IEEE80211BE */ + + if (bss == reporting_hapd || + bss->conf->ignore_broadcast_ssid) + continue; + + if (hostapd_skip_rnr(i, skip_profiles, ap_mld, + tbtt_info_len, mld_update, + reporting_hapd, bss)) + continue; + + if (len + tbtt_info_len > 255 || + tbtt_count >= RNR_TBTT_INFO_COUNT_MAX) + break; + + len += tbtt_info_len; + total_len += tbtt_info_len; + tbtt_count++; + } + start = i; + } + + total_tbtt_count += tbtt_count; + + /* If building for co-location, re-build again but this time include + * ML TBTTs. + */ + if (!mld_update && tbtt_info_len == RNR_TBTT_INFO_LEN) { + tbtt_info_len = RNR_TBTT_INFO_MLD_LEN; + + /* If no TBTT was found, adjust the len and total_len since it + * would have incremented before we checked all BSSs. */ + if (!tbtt_count) { + len -= RNR_TBTT_HEADER_LEN; + total_len -= RNR_TBTT_HEADER_LEN; + } + + goto repeat_rnr_len; + } + + /* This is possible when in the re-built case and no suitable TBTT was + * found. Adjust the length accordingly. */ + if (!tbtt_count && total_tbtt_count) { + len -= RNR_TBTT_HEADER_LEN; + total_len -= RNR_TBTT_HEADER_LEN; + } + + if (!total_tbtt_count) + total_len = 0; + else + *current_len = len; + + return total_len; +} + + +enum colocation_mode { + NO_COLOCATED_6GHZ, + STANDALONE_6GHZ, + COLOCATED_6GHZ, + COLOCATED_LOWER_BAND, +}; + +static enum colocation_mode get_colocation_mode(struct hostapd_data *hapd) +{ + u8 i; + bool is_6ghz = is_6ghz_op_class(hapd->iconf->op_class); + + if (!hapd->iface || !hapd->iface->interfaces) + return NO_COLOCATED_6GHZ; + + if (is_6ghz && hapd->iface->interfaces->count == 1) + return STANDALONE_6GHZ; + + for (i = 0; i < hapd->iface->interfaces->count; i++) { + struct hostapd_iface *iface; + bool is_colocated_6ghz; + + iface = hapd->iface->interfaces->iface[i]; + if (iface == hapd->iface || !iface || !iface->conf) + continue; + + is_colocated_6ghz = is_6ghz_op_class(iface->conf->op_class); + if (!is_6ghz && is_colocated_6ghz) + return COLOCATED_LOWER_BAND; + if (is_6ghz && !is_colocated_6ghz) + return COLOCATED_6GHZ; + } + + if (is_6ghz) + return STANDALONE_6GHZ; + + return NO_COLOCATED_6GHZ; +} + + +static size_t hostapd_eid_rnr_colocation_len(struct hostapd_data *hapd, + size_t *current_len) +{ + struct hostapd_iface *iface; + size_t len = 0; + size_t i; + + if (!hapd->iface || !hapd->iface->interfaces) + return 0; + + for (i = 0; i < hapd->iface->interfaces->count; i++) { + iface = hapd->iface->interfaces->iface[i]; + + if (!iface || iface == hapd->iface || + iface->state != HAPD_IFACE_ENABLED || + !is_6ghz_op_class(iface->conf->op_class)) + continue; + + len += hostapd_eid_rnr_iface_len(iface->bss[0], hapd, + current_len, NULL, false); + } + + return len; +} + + +static size_t hostapd_eid_rnr_mlo_len(struct hostapd_data *hapd, u32 type, + size_t *current_len) +{ + size_t len = 0; +#ifdef CONFIG_IEEE80211BE + struct hostapd_iface *iface; + size_t i; + + if (!hapd->iface || !hapd->iface->interfaces || !hapd->conf->mld_ap) + return 0; + + /* TODO: Allow for FILS/Action as well */ + if (type != WLAN_FC_STYPE_BEACON && type != WLAN_FC_STYPE_PROBE_RESP) + return 0; + + for (i = 0; i < hapd->iface->interfaces->count; i++) { + iface = hapd->iface->interfaces->iface[i]; + + if (!iface || iface == hapd->iface || + hapd->iface->freq == iface->freq) + continue; + + len += hostapd_eid_rnr_iface_len(iface->bss[0], hapd, + current_len, NULL, true); + } +#endif /* CONFIG_IEEE80211BE */ + + return len; +} + + +size_t hostapd_eid_rnr_len(struct hostapd_data *hapd, u32 type, + bool include_mld_params) +{ + size_t total_len = 0, current_len = 0; + enum colocation_mode mode = get_colocation_mode(hapd); + + switch (type) { + case WLAN_FC_STYPE_BEACON: + if (hapd->conf->rnr) + total_len += hostapd_eid_nr_db_len(hapd, ¤t_len); + /* fallthrough */ + case WLAN_FC_STYPE_PROBE_RESP: + if (mode == COLOCATED_LOWER_BAND) + total_len += + hostapd_eid_rnr_colocation_len(hapd, + ¤t_len); + + if (hapd->conf->rnr && hapd->iface->num_bss > 1 && + !hapd->iconf->mbssid) + total_len += hostapd_eid_rnr_iface_len(hapd, hapd, + ¤t_len, + NULL, false); + break; + case WLAN_FC_STYPE_ACTION: + if (hapd->iface->num_bss > 1 && mode == STANDALONE_6GHZ) + total_len += hostapd_eid_rnr_iface_len(hapd, hapd, + ¤t_len, + NULL, false); + break; + } + + /* For EMA Beacons, MLD neighbor repoting is added as part of + * MBSSID RNR. */ + if (include_mld_params && + (type != WLAN_FC_STYPE_BEACON || + hapd->iconf->mbssid != ENHANCED_MBSSID_ENABLED)) + total_len += hostapd_eid_rnr_mlo_len(hapd, type, ¤t_len); + + return total_len; +} + + +static u8 * hostapd_eid_nr_db(struct hostapd_data *hapd, u8 *eid, + size_t *current_len) +{ + struct hostapd_neighbor_entry *nr; + size_t len = *current_len; + u8 *size_offset = (eid - len) + 1; + + dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry, + list) { + if (!nr->nr || wpabuf_len(nr->nr) < 12) + continue; + + if (nr->short_ssid == hapd->conf->ssid.short_ssid) + continue; + + /* Start a new element */ + if (!len || + len + RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN > 255) { + *eid++ = WLAN_EID_REDUCED_NEIGHBOR_REPORT; + size_offset = eid++; + len = RNR_HEADER_LEN; + } + + /* TBTT Information Header subfield (2 octets) */ + *eid++ = 0; + /* TBTT Information Length */ + *eid++ = RNR_TBTT_INFO_LEN; + /* Operating Class */ + *eid++ = wpabuf_head_u8(nr->nr)[10]; + /* Channel Number */ + *eid++ = wpabuf_head_u8(nr->nr)[11]; + len += RNR_TBTT_HEADER_LEN; + /* TBTT Information Set */ + /* TBTT Information field */ + /* Neighbor AP TBTT Offset */ + *eid++ = RNR_NEIGHBOR_AP_OFFSET_UNKNOWN; + /* BSSID */ + os_memcpy(eid, nr->bssid, ETH_ALEN); + eid += ETH_ALEN; + /* Short SSID */ + os_memcpy(eid, &nr->short_ssid, 4); + eid += 4; + /* BSS parameters */ + *eid++ = nr->bss_parameters; + /* 20 MHz PSD */ + *eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER; + len += RNR_TBTT_INFO_LEN; + *size_offset = (eid - size_offset) - 1; + } + + *current_len = len; + return eid; +} + + +static bool hostapd_eid_rnr_bss(struct hostapd_data *hapd, + struct hostapd_data *reporting_hapd, + struct mbssid_ie_profiles *skip_profiles, + size_t i, u8 *tbtt_count, size_t *len, + u8 **pos, u8 **tbtt_count_pos, u8 tbtt_info_len, + u8 op_class, bool mld_update) +{ + struct hostapd_iface *iface = hapd->iface; + struct hostapd_data *bss = iface->bss[i]; + u8 bss_param = 0; + bool ap_mld = false; + u8 *eid = *pos; + +#ifdef CONFIG_IEEE80211BE + ap_mld = !!hapd->conf->mld_ap; +#endif /* CONFIG_IEEE80211BE */ + + if (!bss || !bss->conf || !bss->started || + bss == reporting_hapd || bss->conf->ignore_broadcast_ssid) + return false; + + if (hostapd_skip_rnr(i, skip_profiles, ap_mld, tbtt_info_len, + mld_update, reporting_hapd, bss)) + return false; + + if (*len + RNR_TBTT_INFO_LEN > 255 || + *tbtt_count >= RNR_TBTT_INFO_COUNT_MAX) + return true; + + if (!(*tbtt_count)) { + /* Add neighbor report header info only if there is at least + * one TBTT info available. */ + *tbtt_count_pos = eid++; + *eid++ = tbtt_info_len; + *eid++ = op_class; + *eid++ = bss->iconf->channel; + *len += RNR_TBTT_HEADER_LEN; + } + + *eid++ = RNR_NEIGHBOR_AP_OFFSET_UNKNOWN; + os_memcpy(eid, bss->own_addr, ETH_ALEN); + eid += ETH_ALEN; + os_memcpy(eid, &bss->conf->ssid.short_ssid, 4); + eid += 4; + if (bss->conf->ssid.short_ssid == reporting_hapd->conf->ssid.short_ssid) + bss_param |= RNR_BSS_PARAM_SAME_SSID; + + if (iface->conf->mbssid != MBSSID_DISABLED && iface->num_bss > 1) { + bss_param |= RNR_BSS_PARAM_MULTIPLE_BSSID; + if (bss == hostapd_mbssid_get_tx_bss(hapd)) + bss_param |= RNR_BSS_PARAM_TRANSMITTED_BSSID; + } + + if (is_6ghz_op_class(hapd->iconf->op_class) && + bss->conf->unsol_bcast_probe_resp_interval) + bss_param |= RNR_BSS_PARAM_UNSOLIC_PROBE_RESP_ACTIVE; + + bss_param |= RNR_BSS_PARAM_CO_LOCATED; + + *eid++ = bss_param; + *eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER; + +#ifdef CONFIG_IEEE80211BE + if (ap_mld) { + u8 param_ch = bss->eht_mld_bss_param_change; + bool is_partner; + + /* If BSS is not a partner of the reporting_hapd + * a) MLD ID advertised shall be 255. + * b) Link ID advertised shall be 15. + * c) BPCC advertised shall be 255 */ + is_partner = hostapd_is_ml_partner(bss, reporting_hapd); + /* MLD ID */ + *eid++ = is_partner ? hostapd_get_mld_id(bss) : 0xFF; + /* Link ID (Bit 3 to Bit 0) + * BPCC (Bit 4 to Bit 7) */ + *eid++ = is_partner ? + bss->mld_link_id | ((param_ch & 0xF) << 4) : + (MAX_NUM_MLD_LINKS | 0xF0); + /* BPCC (Bit 3 to Bit 0) */ + *eid = is_partner ? ((param_ch & 0xF0) >> 4) : 0x0F; +#ifdef CONFIG_TESTING_OPTIONS + if (bss->conf->mld_indicate_disabled) + *eid |= RNR_TBTT_INFO_MLD_PARAM2_LINK_DISABLED; +#endif /* CONFIG_TESTING_OPTIONS */ + eid++; + } +#endif /* CONFIG_IEEE80211BE */ + + *len += tbtt_info_len; + (*tbtt_count)++; + *pos = eid; + + return false; +} + + +static u8 * hostapd_eid_rnr_iface(struct hostapd_data *hapd, + struct hostapd_data *reporting_hapd, + u8 *eid, size_t *current_len, + struct mbssid_ie_profiles *skip_profiles, + bool mld_update) +{ + struct hostapd_iface *iface = hapd->iface; + size_t i, start; + size_t len = *current_len; + u8 *eid_start = eid, *size_offset = (eid - len) + 1; + u8 *tbtt_count_pos = size_offset + 1; + u8 tbtt_count, total_tbtt_count = 0, op_class, channel; + u8 tbtt_info_len = mld_update ? RNR_TBTT_INFO_MLD_LEN : + RNR_TBTT_INFO_LEN; + + if (!(iface->drv_flags & WPA_DRIVER_FLAGS_AP_CSA) || !iface->freq) + return eid; + + if (ieee80211_freq_to_channel_ext(iface->freq, + hapd->iconf->secondary_channel, + hostapd_get_oper_chwidth(hapd->iconf), + &op_class, &channel) == + NUM_HOSTAPD_MODES) + return eid; + +repeat_rnr: + start = 0; + tbtt_count = 0; + while (start < iface->num_bss) { + if (!len || + len + RNR_TBTT_HEADER_LEN + tbtt_info_len > 255 || + tbtt_count >= RNR_TBTT_INFO_COUNT_MAX) { + eid_start = eid; + *eid++ = WLAN_EID_REDUCED_NEIGHBOR_REPORT; + size_offset = eid++; + len = RNR_HEADER_LEN; + tbtt_count = 0; + } + + for (i = start; i < iface->num_bss; i++) { + if (hostapd_eid_rnr_bss(hapd, reporting_hapd, + skip_profiles, i, + &tbtt_count, &len, &eid, + &tbtt_count_pos, tbtt_info_len, + op_class, mld_update)) + break; + } + + start = i; + + if (tbtt_count) { + *tbtt_count_pos = RNR_TBTT_INFO_COUNT(tbtt_count - 1); + *size_offset = (eid - size_offset) - 1; + } + } + + total_tbtt_count += tbtt_count; + + /* If building for co-location, re-build again but this time include + * ML TBTTs. + */ + if (!mld_update && tbtt_info_len == RNR_TBTT_INFO_LEN) { + tbtt_info_len = RNR_TBTT_INFO_MLD_LEN; + goto repeat_rnr; + } + + if (!total_tbtt_count) + return eid_start; + + *current_len = len; + return eid; +} + + +u8 * hostapd_eid_rnr_colocation(struct hostapd_data *hapd, u8 *eid, + size_t *current_len) +{ + struct hostapd_iface *iface; + size_t i; + + if (!hapd->iface || !hapd->iface->interfaces) + return eid; + + for (i = 0; i < hapd->iface->interfaces->count; i++) { + iface = hapd->iface->interfaces->iface[i]; + + if (!iface || iface == hapd->iface || + iface->state != HAPD_IFACE_ENABLED || + !is_6ghz_op_class(iface->conf->op_class)) + continue; + + eid = hostapd_eid_rnr_iface(iface->bss[0], hapd, eid, + current_len, NULL, false); + } + + return eid; +} + + +u8 * hostapd_eid_rnr_mlo(struct hostapd_data *hapd, u32 type, + u8 *eid, size_t *current_len) +{ +#ifdef CONFIG_IEEE80211BE + struct hostapd_iface *iface; + size_t i; + + if (!hapd->iface || !hapd->iface->interfaces || !hapd->conf->mld_ap) + return eid; + + /* TODO: Allow for FILS/Action as well */ + if (type != WLAN_FC_STYPE_BEACON && type != WLAN_FC_STYPE_PROBE_RESP) + return eid; + + for (i = 0; i < hapd->iface->interfaces->count; i++) { + iface = hapd->iface->interfaces->iface[i]; + + if (!iface || iface == hapd->iface || + hapd->iface->freq == iface->freq) + continue; + + eid = hostapd_eid_rnr_iface(iface->bss[0], hapd, eid, + current_len, NULL, true); + } +#endif /* CONFIG_IEEE80211BE */ + + return eid; +} + + +u8 * hostapd_eid_rnr(struct hostapd_data *hapd, u8 *eid, u32 type, + bool include_mld_params) +{ + u8 *eid_start = eid; + size_t current_len = 0; + enum colocation_mode mode = get_colocation_mode(hapd); + + switch (type) { + case WLAN_FC_STYPE_BEACON: + if (hapd->conf->rnr) + eid = hostapd_eid_nr_db(hapd, eid, ¤t_len); + /* fallthrough */ + case WLAN_FC_STYPE_PROBE_RESP: + if (mode == COLOCATED_LOWER_BAND) + eid = hostapd_eid_rnr_colocation(hapd, eid, + ¤t_len); + + if (hapd->conf->rnr && hapd->iface->num_bss > 1 && + !hapd->iconf->mbssid) + eid = hostapd_eid_rnr_iface(hapd, hapd, eid, + ¤t_len, NULL, false); + break; + case WLAN_FC_STYPE_ACTION: + if (hapd->iface->num_bss > 1 && mode == STANDALONE_6GHZ) + eid = hostapd_eid_rnr_iface(hapd, hapd, eid, + ¤t_len, NULL, false); + break; + default: + return eid_start; + } + + /* For EMA Beacons, MLD neighbor repoting is added as part of + * MBSSID RNR. */ + if (include_mld_params && + (type != WLAN_FC_STYPE_BEACON || + hapd->iconf->mbssid != ENHANCED_MBSSID_ENABLED)) + eid = hostapd_eid_rnr_mlo(hapd, type, eid, ¤t_len); + + if (eid == eid_start + 2) + return eid_start; + + return eid; +} + + +static bool mbssid_known_bss(unsigned int i, const u8 *known_bss, + size_t known_bss_len) +{ + if (!known_bss || known_bss_len <= i / 8) + return false; + known_bss = &known_bss[i / 8]; + return *known_bss & (u8) (BIT(i % 8)); +} + + +static size_t hostapd_mbssid_ext_capa(struct hostapd_data *bss, + struct hostapd_data *tx_bss, u8 *buf) +{ + u8 ext_capa_tx[20], *ext_capa_tx_end, ext_capa[20], *ext_capa_end; + size_t ext_capa_len, ext_capa_tx_len; + + ext_capa_tx_end = hostapd_eid_ext_capab(tx_bss, ext_capa_tx, + true); + ext_capa_tx_len = ext_capa_tx_end - ext_capa_tx; + ext_capa_end = hostapd_eid_ext_capab(bss, ext_capa, true); + ext_capa_len = ext_capa_end - ext_capa; + if (ext_capa_tx_len != ext_capa_len || + os_memcmp(ext_capa_tx, ext_capa, ext_capa_len) != 0) { + os_memcpy(buf, ext_capa, ext_capa_len); + return ext_capa_len; + } + + return 0; +} + + +static size_t hostapd_eid_mbssid_elem_len(struct hostapd_data *hapd, + u32 frame_type, size_t *bss_index, + const u8 *known_bss, + size_t known_bss_len) +{ + struct hostapd_data *tx_bss = hostapd_mbssid_get_tx_bss(hapd); + size_t len, i; + u8 ext_capa[20]; + + /* Element ID: 1 octet + * Length: 1 octet + * MaxBSSID Indicator: 1 octet + * Optional Subelements: vatiable + * + * Total fixed length: 3 octets + * + * 1 octet in len for the MaxBSSID Indicator field. + */ + len = 1; + + for (i = *bss_index; i < hapd->iface->num_bss; i++) { + struct hostapd_data *bss = hapd->iface->bss[i]; + const u8 *auth, *rsn = NULL, *rsnx = NULL; + size_t nontx_profile_len, auth_len; + u8 ie_count = 0; + + if (!bss || !bss->conf || !bss->started || + mbssid_known_bss(i, known_bss, known_bss_len)) + continue; + + /* + * Sublement ID: 1 octet + * Length: 1 octet + * Nontransmitted capabilities: 4 octets + * SSID element: 2 + variable + * Multiple BSSID Index Element: 3 octets (+2 octets in beacons) + * Fixed length = 1 + 1 + 4 + 2 + 3 = 11 + */ + nontx_profile_len = 11 + bss->conf->ssid.ssid_len; + + if (frame_type == WLAN_FC_STYPE_BEACON) + nontx_profile_len += 2; + + auth = wpa_auth_get_wpa_ie(bss->wpa_auth, &auth_len); + if (auth) { + rsn = get_ie(auth, auth_len, WLAN_EID_RSN); + if (rsn) + nontx_profile_len += 2 + rsn[1]; + + rsnx = get_ie(auth, auth_len, WLAN_EID_RSNX); + if (rsnx) + nontx_profile_len += 2 + rsnx[1]; + } + + nontx_profile_len += hostapd_mbssid_ext_capa(bss, tx_bss, + ext_capa); + + if (!rsn && hostapd_wpa_ie(tx_bss, WLAN_EID_RSN)) + ie_count++; + if (!rsnx && hostapd_wpa_ie(tx_bss, WLAN_EID_RSNX)) + ie_count++; + if (bss->conf->xrates_supported) + nontx_profile_len += 8; + else if (hapd->conf->xrates_supported) + ie_count++; + if (ie_count) + nontx_profile_len += 4 + ie_count; + + if (len + nontx_profile_len > 255) + break; + + len += nontx_profile_len; + } + + *bss_index = i; + + /* Add 2 octets to get the full size of the element */ + return len + 2; +} + + +size_t hostapd_eid_mbssid_len(struct hostapd_data *hapd, u32 frame_type, + u8 *elem_count, const u8 *known_bss, + size_t known_bss_len, size_t *rnr_len) +{ + size_t len = 0, bss_index = 1; + bool ap_mld = false; + +#ifdef CONFIG_IEEE80211BE + ap_mld = hapd->conf->mld_ap; +#endif /* CONFIG_IEEE80211BE */ + + if (!hapd->iconf->mbssid || hapd->iface->num_bss <= 1 || + (frame_type != WLAN_FC_STYPE_BEACON && + frame_type != WLAN_FC_STYPE_PROBE_RESP)) + return 0; + + if (frame_type == WLAN_FC_STYPE_BEACON) { + if (!elem_count) { + wpa_printf(MSG_INFO, + "MBSSID: Insufficient data for Beacon frames"); + return 0; + } + *elem_count = 0; + } + + while (bss_index < hapd->iface->num_bss) { + size_t rnr_count = bss_index; + + len += hostapd_eid_mbssid_elem_len(hapd, frame_type, + &bss_index, known_bss, + known_bss_len); + + if (frame_type == WLAN_FC_STYPE_BEACON) + *elem_count += 1; + if (hapd->iconf->mbssid == ENHANCED_MBSSID_ENABLED && rnr_len) { + size_t rnr_cur_len = 0; + struct mbssid_ie_profiles skip_profiles = { + rnr_count, bss_index + }; + + *rnr_len += hostapd_eid_rnr_iface_len( + hapd, hostapd_mbssid_get_tx_bss(hapd), + &rnr_cur_len, &skip_profiles, ap_mld); + } + } + + if (hapd->iconf->mbssid == ENHANCED_MBSSID_ENABLED && rnr_len) + *rnr_len += hostapd_eid_rnr_len(hapd, frame_type, false); + + return len; +} + + +static u8 * hostapd_eid_mbssid_elem(struct hostapd_data *hapd, u8 *eid, u8 *end, + u32 frame_type, u8 max_bssid_indicator, + size_t *bss_index, u8 elem_count, + const u8 *known_bss, size_t known_bss_len) +{ + struct hostapd_data *tx_bss = hostapd_mbssid_get_tx_bss(hapd); + size_t i; + u8 *eid_len_offset, *max_bssid_indicator_offset; + + *eid++ = WLAN_EID_MULTIPLE_BSSID; + eid_len_offset = eid++; + max_bssid_indicator_offset = eid++; + + for (i = *bss_index; i < hapd->iface->num_bss; i++) { + struct hostapd_data *bss = hapd->iface->bss[i]; + struct hostapd_bss_config *conf; + u8 *eid_len_pos, *nontx_bss_start = eid; + const u8 *auth, *rsn = NULL, *rsnx = NULL; + u8 ie_count = 0, non_inherit_ie[3]; + size_t auth_len = 0; + u16 capab_info; + + if (!bss || !bss->conf || !bss->started || + mbssid_known_bss(i, known_bss, known_bss_len)) + continue; + conf = bss->conf; + + *eid++ = WLAN_MBSSID_SUBELEMENT_NONTRANSMITTED_BSSID_PROFILE; + eid_len_pos = eid++; + + capab_info = hostapd_own_capab_info(bss); + *eid++ = WLAN_EID_NONTRANSMITTED_BSSID_CAPA; + *eid++ = sizeof(capab_info); + WPA_PUT_LE16(eid, capab_info); + eid += sizeof(capab_info); + + *eid++ = WLAN_EID_SSID; + *eid++ = conf->ssid.ssid_len; + os_memcpy(eid, conf->ssid.ssid, conf->ssid.ssid_len); + eid += conf->ssid.ssid_len; + + *eid++ = WLAN_EID_MULTIPLE_BSSID_INDEX; + if (frame_type == WLAN_FC_STYPE_BEACON) { + *eid++ = 3; + *eid++ = i; /* BSSID Index */ + if (hapd->iconf->mbssid == ENHANCED_MBSSID_ENABLED && + (conf->dtim_period % elem_count)) + conf->dtim_period = elem_count; + *eid++ = conf->dtim_period; + /* The driver is expected to update the DTIM Count + * field for each BSS that corresponds to a + * nontransmitted BSSID. The value is initialized to + * 0 here so that the DTIM count would be somewhat + * functional even if the driver were not to update + * this. */ + *eid++ = 0; /* DTIM Count */ + } else { + /* Probe Request frame does not include DTIM Period and + * DTIM Count fields. */ + *eid++ = 1; + *eid++ = i; /* BSSID Index */ + } + + auth = wpa_auth_get_wpa_ie(bss->wpa_auth, &auth_len); + if (auth) { + rsn = get_ie(auth, auth_len, WLAN_EID_RSN); + if (rsn) { + os_memcpy(eid, rsn, 2 + rsn[1]); + eid += 2 + rsn[1]; + } + + rsnx = get_ie(auth, auth_len, WLAN_EID_RSNX); + if (rsnx) { + os_memcpy(eid, rsnx, 2 + rsnx[1]); + eid += 2 + rsnx[1]; + } + } + + eid += hostapd_mbssid_ext_capa(bss, tx_bss, eid); + + /* List of Element ID values in increasing order */ + if (!rsn && hostapd_wpa_ie(tx_bss, WLAN_EID_RSN)) + non_inherit_ie[ie_count++] = WLAN_EID_RSN; + if (hapd->conf->xrates_supported && + !bss->conf->xrates_supported) + non_inherit_ie[ie_count++] = WLAN_EID_EXT_SUPP_RATES; + if (!rsnx && hostapd_wpa_ie(tx_bss, WLAN_EID_RSNX)) + non_inherit_ie[ie_count++] = WLAN_EID_RSNX; + if (ie_count) { + *eid++ = WLAN_EID_EXTENSION; + *eid++ = 2 + ie_count + 1; + *eid++ = WLAN_EID_EXT_NON_INHERITANCE; + *eid++ = ie_count; + os_memcpy(eid, non_inherit_ie, ie_count); + eid += ie_count; + *eid++ = 0; /* No Element ID Extension List */ + } + + *eid_len_pos = (eid - eid_len_pos) - 1; + + if (((eid - eid_len_offset) - 1) > 255) { + eid = nontx_bss_start; + break; + } + } + + *bss_index = i; + *max_bssid_indicator_offset = max_bssid_indicator; + if (*max_bssid_indicator_offset < 1) + *max_bssid_indicator_offset = 1; + *eid_len_offset = (eid - eid_len_offset) - 1; + return eid; +} + + +u8 * hostapd_eid_mbssid(struct hostapd_data *hapd, u8 *eid, u8 *end, + unsigned int frame_stype, u8 elem_count, + u8 **elem_offset, + const u8 *known_bss, size_t known_bss_len, u8 *rnr_eid, + u8 *rnr_count, u8 **rnr_offset, size_t rnr_len) +{ + size_t bss_index = 1, cur_len = 0; + u8 elem_index = 0, *rnr_start_eid = rnr_eid; + bool add_rnr, ap_mld = false; + +#ifdef CONFIG_IEEE80211BE + ap_mld = hapd->conf->mld_ap; +#endif /* CONFIG_IEEE80211BE */ + + if (!hapd->iconf->mbssid || hapd->iface->num_bss <= 1 || + (frame_stype != WLAN_FC_STYPE_BEACON && + frame_stype != WLAN_FC_STYPE_PROBE_RESP)) + return eid; + + if (frame_stype == WLAN_FC_STYPE_BEACON && !elem_offset) { + wpa_printf(MSG_INFO, + "MBSSID: Insufficient data for Beacon frames"); + return eid; + } + + add_rnr = hapd->iconf->mbssid == ENHANCED_MBSSID_ENABLED && + frame_stype == WLAN_FC_STYPE_BEACON && + rnr_eid && rnr_count && rnr_offset && rnr_len; + + while (bss_index < hapd->iface->num_bss) { + unsigned int rnr_start_count = bss_index; + + if (frame_stype == WLAN_FC_STYPE_BEACON) { + if (elem_index == elem_count) { + wpa_printf(MSG_WARNING, + "MBSSID: Larger number of elements than there is room in the provided array"); + break; + } + + elem_offset[elem_index] = eid; + elem_index = elem_index + 1; + } + eid = hostapd_eid_mbssid_elem(hapd, eid, end, frame_stype, + hostapd_max_bssid_indicator(hapd), + &bss_index, elem_count, + known_bss, known_bss_len); + + if (add_rnr) { + struct mbssid_ie_profiles skip_profiles = { + rnr_start_count, bss_index + }; + + rnr_offset[*rnr_count] = rnr_eid; + *rnr_count = *rnr_count + 1; + cur_len = 0; + rnr_eid = hostapd_eid_rnr_iface( + hapd, hostapd_mbssid_get_tx_bss(hapd), + rnr_eid, &cur_len, &skip_profiles, ap_mld); + } + } + + if (add_rnr && (size_t) (rnr_eid - rnr_start_eid) < rnr_len) { + rnr_offset[*rnr_count] = rnr_eid; + *rnr_count = *rnr_count + 1; + cur_len = 0; + + if (hapd->conf->rnr) + rnr_eid = hostapd_eid_nr_db(hapd, rnr_eid, &cur_len); + if (get_colocation_mode(hapd) == COLOCATED_LOWER_BAND) + rnr_eid = hostapd_eid_rnr_colocation(hapd, rnr_eid, + &cur_len); + } + + return eid; +} + +#endif /* CONFIG_NATIVE_WINDOWS */ diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h new file mode 100644 index 0000000..dd4995f --- /dev/null +++ b/src/ap/ieee802_11.h @@ -0,0 +1,267 @@ +/* + * hostapd / IEEE 802.11 Management + * Copyright (c) 2002-2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef IEEE802_11_H +#define IEEE802_11_H + +struct hostapd_iface; +struct hostapd_data; +struct sta_info; +struct hostapd_frame_info; +struct ieee80211_ht_capabilities; +struct ieee80211_vht_capabilities; +struct ieee80211_mgmt; +struct radius_sta; +enum ieee80211_op_mode; +enum oper_chan_width; +struct ieee802_11_elems; +struct sae_pk; +struct sae_pt; +struct sae_password_entry; +struct mld_info; + +int ieee802_11_mgmt(struct hostapd_data *hapd, const u8 *buf, size_t len, + struct hostapd_frame_info *fi); +void ieee802_11_mgmt_cb(struct hostapd_data *hapd, const u8 *buf, size_t len, + u16 stype, int ok); +void hostapd_2040_coex_action(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len); +#ifdef NEED_AP_MLME +int ieee802_11_get_mib(struct hostapd_data *hapd, char *buf, size_t buflen); +int ieee802_11_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, + char *buf, size_t buflen); +#else /* NEED_AP_MLME */ +static inline int ieee802_11_get_mib(struct hostapd_data *hapd, char *buf, + size_t buflen) +{ + return 0; +} + +static inline int ieee802_11_get_mib_sta(struct hostapd_data *hapd, + struct sta_info *sta, + char *buf, size_t buflen) +{ + return 0; +} +#endif /* NEED_AP_MLME */ +u16 hostapd_own_capab_info(struct hostapd_data *hapd); +void ap_ht2040_timeout(void *eloop_data, void *user_data); +u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid, + bool mbssid_complete); +u8 * hostapd_eid_qos_map_set(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_ext_supp_rates(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_rm_enabled_capab(struct hostapd_data *hapd, u8 *eid, + size_t len); +u8 * hostapd_eid_ht_capabilities(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_ht_operation(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_vht_capabilities(struct hostapd_data *hapd, u8 *eid, u32 nsts); +u8 * hostapd_eid_vht_operation(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_vendor_vht(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_wb_chsw_wrapper(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_txpower_envelope(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_he_capab(struct hostapd_data *hapd, u8 *eid, + enum ieee80211_op_mode opmode); +u8 * hostapd_eid_he_operation(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_he_mu_edca_parameter_set(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_spatial_reuse(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_he_6ghz_band_cap(struct hostapd_data *hapd, u8 *eid); + +int hostapd_ht_operation_update(struct hostapd_iface *iface); +void ieee802_11_send_sa_query_req(struct hostapd_data *hapd, + const u8 *addr, const u8 *trans_id); +void hostapd_get_ht_capab(struct hostapd_data *hapd, + struct ieee80211_ht_capabilities *ht_cap, + struct ieee80211_ht_capabilities *neg_ht_cap); +void hostapd_get_vht_capab(struct hostapd_data *hapd, + struct ieee80211_vht_capabilities *vht_cap, + struct ieee80211_vht_capabilities *neg_vht_cap); +void hostapd_get_he_capab(struct hostapd_data *hapd, + const struct ieee80211_he_capabilities *he_cap, + struct ieee80211_he_capabilities *neg_he_cap, + size_t he_capab_len); +void hostapd_get_eht_capab(struct hostapd_data *hapd, + const struct ieee80211_eht_capabilities *src, + struct ieee80211_eht_capabilities *dest, + size_t len); +u8 * hostapd_eid_eht_ml_beacon(struct hostapd_data *hapd, + struct mld_info *mld_info, + u8 *eid, bool include_mld_id); +u8 * hostapd_eid_eht_ml_assoc(struct hostapd_data *hapd, struct sta_info *info, + u8 *eid); +size_t hostapd_eid_eht_ml_beacon_len(struct hostapd_data *hapd, + struct mld_info *info, + bool include_mld_id); +struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd); +const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len); +u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd, + struct ieee802_11_elems *elems, + struct sta_info *sta); +int hostapd_process_ml_assoc_req_addr(struct hostapd_data *hapd, + const u8 *basic_mle, size_t basic_mle_len, + u8 *mld_addr); +int hostapd_get_aid(struct hostapd_data *hapd, struct sta_info *sta); +u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *ht_capab); +u16 copy_sta_vendor_vht(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *ie, size_t len); + +int update_ht_state(struct hostapd_data *hapd, struct sta_info *sta); +void ht40_intolerant_add(struct hostapd_iface *iface, struct sta_info *sta); +void ht40_intolerant_remove(struct hostapd_iface *iface, struct sta_info *sta); +u16 copy_sta_vht_capab(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *vht_capab); +u16 copy_sta_vht_oper(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *vht_oper); +u16 set_sta_vht_opmode(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *vht_opmode); +u16 copy_sta_he_capab(struct hostapd_data *hapd, struct sta_info *sta, + enum ieee80211_op_mode opmode, const u8 *he_capab, + size_t he_capab_len); +u16 copy_sta_he_6ghz_capab(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *he_6ghz_capab); +int hostapd_get_he_twt_responder(struct hostapd_data *hapd, + enum ieee80211_op_mode mode); +bool hostapd_get_ht_vht_twt_responder(struct hostapd_data *hapd); +u8 * hostapd_eid_cca(struct hostapd_data *hapd, u8 *eid); +void hostapd_tx_status(struct hostapd_data *hapd, const u8 *addr, + const u8 *buf, size_t len, int ack); +void ieee802_11_rx_from_unknown(struct hostapd_data *hapd, const u8 *src, + int wds); +u8 * hostapd_eid_assoc_comeback_time(struct hostapd_data *hapd, + struct sta_info *sta, u8 *eid); +void ieee802_11_sa_query_action(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len); +u8 * hostapd_eid_interworking(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_adv_proto(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_roaming_consortium(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_time_adv(struct hostapd_data *hapd, u8 *eid); +u8 * hostapd_eid_time_zone(struct hostapd_data *hapd, u8 *eid); +int hostapd_update_time_adv(struct hostapd_data *hapd); +void hostapd_client_poll_ok(struct hostapd_data *hapd, const u8 *addr); +u8 * hostapd_eid_bss_max_idle_period(struct hostapd_data *hapd, u8 *eid, + u16 value); + +int auth_sae_init_committed(struct hostapd_data *hapd, struct sta_info *sta); +#ifdef CONFIG_SAE +void sae_clear_retransmit_timer(struct hostapd_data *hapd, + struct sta_info *sta); +void sae_accept_sta(struct hostapd_data *hapd, struct sta_info *sta); +#else /* CONFIG_SAE */ +static inline void sae_clear_retransmit_timer(struct hostapd_data *hapd, + struct sta_info *sta) +{ +} +#endif /* CONFIG_SAE */ + +#ifdef CONFIG_MBO + +u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, size_t len); + +u8 hostapd_mbo_ie_len(struct hostapd_data *hapd); + +u8 * hostapd_eid_mbo_rssi_assoc_rej(struct hostapd_data *hapd, u8 *eid, + size_t len, int delta); + +#else /* CONFIG_MBO */ + +static inline u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, + size_t len) +{ + return eid; +} + +static inline u8 hostapd_mbo_ie_len(struct hostapd_data *hapd) +{ + return 0; +} + +#endif /* CONFIG_MBO */ + +void ap_copy_sta_supp_op_classes(struct sta_info *sta, + const u8 *supp_op_classes, + size_t supp_op_classes_len); + +u8 * hostapd_eid_fils_indic(struct hostapd_data *hapd, u8 *eid, int hessid); +void ieee802_11_finish_fils_auth(struct hostapd_data *hapd, + struct sta_info *sta, int success, + struct wpabuf *erp_resp, + const u8 *msk, size_t msk_len); +u8 * owe_assoc_req_process(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *owe_dh, u8 owe_dh_len, + u8 *owe_buf, size_t owe_buf_len, u16 *status); +u16 owe_process_rsn_ie(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *rsn_ie, size_t rsn_ie_len, + const u8 *owe_dh, size_t owe_dh_len, + const u8 *link_addr); +u16 owe_validate_request(struct hostapd_data *hapd, const u8 *peer, + const u8 *rsn_ie, size_t rsn_ie_len, + const u8 *owe_dh, size_t owe_dh_len); +void fils_hlp_timeout(void *eloop_ctx, void *eloop_data); +void fils_hlp_finish_assoc(struct hostapd_data *hapd, struct sta_info *sta); +void handle_auth_fils(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *pos, size_t len, u16 auth_alg, + u16 auth_transaction, u16 status_code, + void (*cb)(struct hostapd_data *hapd, + struct sta_info *sta, + u16 resp, struct wpabuf *data, int pub)); + +size_t hostapd_eid_owe_trans_len(struct hostapd_data *hapd); +u8 * hostapd_eid_owe_trans(struct hostapd_data *hapd, u8 *eid, size_t len); + +size_t hostapd_eid_dpp_cc_len(struct hostapd_data *hapd); +u8 * hostapd_eid_dpp_cc(struct hostapd_data *hapd, u8 *eid, size_t len); + +int get_tx_parameters(struct sta_info *sta, int ap_max_chanwidth, + int ap_seg1_idx, int *bandwidth, int *seg1_idx); + +void auth_sae_process_commit(void *eloop_ctx, void *user_ctx); +u8 * hostapd_eid_rsnxe(struct hostapd_data *hapd, u8 *eid, size_t len); +u16 check_ext_capab(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *ext_capab_ie, size_t ext_capab_ie_len); +size_t hostapd_eid_rnr_len(struct hostapd_data *hapd, u32 type, + bool include_mld_params); +u8 * hostapd_eid_rnr(struct hostapd_data *hapd, u8 *eid, u32 type, + bool include_mld_params); +int ieee802_11_set_radius_info(struct hostapd_data *hapd, struct sta_info *sta, + int res, struct radius_sta *info); +size_t hostapd_eid_eht_capab_len(struct hostapd_data *hapd, + enum ieee80211_op_mode opmode); +u8 * hostapd_eid_eht_capab(struct hostapd_data *hapd, u8 *eid, + enum ieee80211_op_mode opmode); +u8 * hostapd_eid_eht_operation(struct hostapd_data *hapd, u8 *eid); +u16 copy_sta_eht_capab(struct hostapd_data *hapd, struct sta_info *sta, + enum ieee80211_op_mode opmode, + const u8 *he_capab, size_t he_capab_len, + const u8 *eht_capab, size_t eht_capab_len); +size_t hostapd_eid_mbssid_len(struct hostapd_data *hapd, u32 frame_type, + u8 *elem_count, const u8 *known_bss, + size_t known_bss_len, size_t *rnr_len); +u8 * hostapd_eid_mbssid(struct hostapd_data *hapd, u8 *eid, u8 *end, + unsigned int frame_stype, u8 elem_count, + u8 **elem_offset, + const u8 *known_bss, size_t known_bss_len, u8 *rnr_eid, + u8 *rnr_count, u8 **rnr_offset, size_t rnr_len); +bool hostapd_is_mld_ap(struct hostapd_data *hapd); +const char * sae_get_password(struct hostapd_data *hapd, + struct sta_info *sta, const char *rx_id, + struct sae_password_entry **pw_entry, + struct sae_pt **s_pt, const struct sae_pk **s_pk); +struct sta_info * hostapd_ml_get_assoc_sta(struct hostapd_data *hapd, + struct sta_info *sta, + struct hostapd_data **assoc_hapd); +int hostapd_process_assoc_ml_info(struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *ies, size_t ies_len, + bool reassoc, int tx_link_status, + bool offload); + +#endif /* IEEE802_11_H */ diff --git a/src/ap/ieee802_11_auth.c b/src/ap/ieee802_11_auth.c new file mode 100644 index 0000000..913a995 --- /dev/null +++ b/src/ap/ieee802_11_auth.c @@ -0,0 +1,751 @@ +/* + * hostapd / IEEE 802.11 authentication (ACL) + * Copyright (c) 2003-2022, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * Access control list for IEEE 802.11 authentication can uses statically + * configured ACL from configuration files or an external RADIUS server. + * Results from external RADIUS queries are cached to allow faster + * authentication frame processing. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "radius/radius.h" +#include "radius/radius_client.h" +#include "hostapd.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "sta_info.h" +#include "wpa_auth.h" +#include "ieee802_11.h" +#include "ieee802_1x.h" +#include "ieee802_11_auth.h" + +#define RADIUS_ACL_TIMEOUT 30 + + +struct hostapd_cached_radius_acl { + struct os_reltime timestamp; + macaddr addr; + int accepted; /* HOSTAPD_ACL_* */ + struct hostapd_cached_radius_acl *next; + struct radius_sta info; +}; + + +struct hostapd_acl_query_data { + struct os_reltime timestamp; + u8 radius_id; + macaddr addr; + u8 *auth_msg; /* IEEE 802.11 authentication frame from station */ + size_t auth_msg_len; + struct hostapd_acl_query_data *next; + bool radius_psk; + int akm; + u8 *anonce; + u8 *eapol; + size_t eapol_len; +}; + + +#ifndef CONFIG_NO_RADIUS +static void hostapd_acl_cache_free_entry(struct hostapd_cached_radius_acl *e) +{ + os_free(e->info.identity); + os_free(e->info.radius_cui); + hostapd_free_psk_list(e->info.psk); + os_free(e); +} + + +static void hostapd_acl_cache_free(struct hostapd_cached_radius_acl *acl_cache) +{ + struct hostapd_cached_radius_acl *prev; + + while (acl_cache) { + prev = acl_cache; + acl_cache = acl_cache->next; + hostapd_acl_cache_free_entry(prev); + } +} + + +static int hostapd_acl_cache_get(struct hostapd_data *hapd, const u8 *addr, + struct radius_sta *out) +{ + struct hostapd_cached_radius_acl *entry; + struct os_reltime now; + + os_get_reltime(&now); + + for (entry = hapd->acl_cache; entry; entry = entry->next) { + if (!ether_addr_equal(entry->addr, addr)) + continue; + + if (os_reltime_expired(&now, &entry->timestamp, + RADIUS_ACL_TIMEOUT)) + return -1; /* entry has expired */ + *out = entry->info; + + return entry->accepted; + } + + return -1; +} +#endif /* CONFIG_NO_RADIUS */ + + +static void hostapd_acl_query_free(struct hostapd_acl_query_data *query) +{ + if (!query) + return; + os_free(query->auth_msg); + os_free(query->anonce); + os_free(query->eapol); + os_free(query); +} + + +#ifndef CONFIG_NO_RADIUS +static int hostapd_radius_acl_query(struct hostapd_data *hapd, const u8 *addr, + struct hostapd_acl_query_data *query) +{ + struct radius_msg *msg; + char buf[128]; + + query->radius_id = radius_client_get_id(hapd->radius); + msg = radius_msg_new(RADIUS_CODE_ACCESS_REQUEST, query->radius_id); + if (!msg) + return -1; + + if (radius_msg_make_authenticator(msg) < 0) { + wpa_printf(MSG_INFO, "Could not make Request Authenticator"); + goto fail; + } + + if (!radius_msg_add_msg_auth(msg)) + goto fail; + + os_snprintf(buf, sizeof(buf), RADIUS_ADDR_FORMAT, MAC2STR(addr)); + if (!radius_msg_add_attr(msg, RADIUS_ATTR_USER_NAME, (u8 *) buf, + os_strlen(buf))) { + wpa_printf(MSG_DEBUG, "Could not add User-Name"); + goto fail; + } + + if (!radius_msg_add_attr_user_password( + msg, (u8 *) buf, os_strlen(buf), + hapd->conf->radius->auth_server->shared_secret, + hapd->conf->radius->auth_server->shared_secret_len)) { + wpa_printf(MSG_DEBUG, "Could not add User-Password"); + goto fail; + } + + if (add_common_radius_attr(hapd, hapd->conf->radius_auth_req_attr, + NULL, msg) < 0) + goto fail; + + os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT, + MAC2STR(addr)); + if (!radius_msg_add_attr(msg, RADIUS_ATTR_CALLING_STATION_ID, + (u8 *) buf, os_strlen(buf))) { + wpa_printf(MSG_DEBUG, "Could not add Calling-Station-Id"); + goto fail; + } + + os_snprintf(buf, sizeof(buf), "CONNECT 11Mbps 802.11b"); + if (!radius_msg_add_attr(msg, RADIUS_ATTR_CONNECT_INFO, + (u8 *) buf, os_strlen(buf))) { + wpa_printf(MSG_DEBUG, "Could not add Connect-Info"); + goto fail; + } + + if (query->akm && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_WLAN_AKM_SUITE, + wpa_akm_to_suite(query->akm))) { + wpa_printf(MSG_DEBUG, "Could not add WLAN-AKM-Suite"); + goto fail; + } + + if (query->anonce && + !radius_msg_add_ext_vs(msg, RADIUS_ATTR_EXT_VENDOR_SPECIFIC_5, + RADIUS_VENDOR_ID_FREERADIUS, + RADIUS_VENDOR_ATTR_FREERADIUS_802_1X_ANONCE, + query->anonce, WPA_NONCE_LEN)) { + wpa_printf(MSG_DEBUG, "Could not add FreeRADIUS-802.1X-Anonce"); + goto fail; + } + + if (query->eapol && + !radius_msg_add_ext_vs(msg, RADIUS_ATTR_EXT_VENDOR_SPECIFIC_5, + RADIUS_VENDOR_ID_FREERADIUS, + RADIUS_VENDOR_ATTR_FREERADIUS_802_1X_EAPOL_KEY_MSG, + query->eapol, query->eapol_len)) { + wpa_printf(MSG_DEBUG, "Could not add FreeRADIUS-802.1X-EAPoL-Key-Msg"); + goto fail; + } + + if (radius_client_send(hapd->radius, msg, RADIUS_AUTH, addr) < 0) + goto fail; + return 0; + + fail: + radius_msg_free(msg); + return -1; +} +#endif /* CONFIG_NO_RADIUS */ + + +/** + * hostapd_check_acl - Check a specified STA against accept/deny ACLs + * @hapd: hostapd BSS data + * @addr: MAC address of the STA + * @vlan_id: Buffer for returning VLAN ID + * Returns: HOSTAPD_ACL_ACCEPT, HOSTAPD_ACL_REJECT, or HOSTAPD_ACL_PENDING + */ +int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr, + struct vlan_description *vlan_id) +{ + if (hostapd_maclist_found(hapd->conf->accept_mac, + hapd->conf->num_accept_mac, addr, vlan_id)) + return HOSTAPD_ACL_ACCEPT; + + if (hostapd_maclist_found(hapd->conf->deny_mac, + hapd->conf->num_deny_mac, addr, vlan_id)) + return HOSTAPD_ACL_REJECT; + + if (hapd->conf->macaddr_acl == ACCEPT_UNLESS_DENIED) + return HOSTAPD_ACL_ACCEPT; + if (hapd->conf->macaddr_acl == DENY_UNLESS_ACCEPTED) + return HOSTAPD_ACL_REJECT; + + return HOSTAPD_ACL_PENDING; +} + + +/** + * hostapd_allowed_address - Check whether a specified STA can be authenticated + * @hapd: hostapd BSS data + * @addr: MAC address of the STA + * @msg: Authentication message + * @len: Length of msg in octets + * @out.session_timeout: Buffer for returning session timeout (from RADIUS) + * @out.acct_interim_interval: Buffer for returning account interval (from + * RADIUS) + * @out.vlan_id: Buffer for returning VLAN ID + * @out.psk: Linked list buffer for returning WPA PSK + * @out.identity: Buffer for returning identity (from RADIUS) + * @out.radius_cui: Buffer for returning CUI (from RADIUS) + * @is_probe_req: Whether this query for a Probe Request frame + * Returns: HOSTAPD_ACL_ACCEPT, HOSTAPD_ACL_REJECT, or HOSTAPD_ACL_PENDING + * + * The caller is responsible for properly cloning the returned out->identity and + * out->radius_cui and out->psk values. + */ +int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr, + const u8 *msg, size_t len, struct radius_sta *out, + int is_probe_req) +{ + int res; + + os_memset(out, 0, sizeof(*out)); + + res = hostapd_check_acl(hapd, addr, &out->vlan_id); + if (res != HOSTAPD_ACL_PENDING) + return res; + + if (hapd->conf->macaddr_acl == USE_EXTERNAL_RADIUS_AUTH) { +#ifdef CONFIG_NO_RADIUS + return HOSTAPD_ACL_REJECT; +#else /* CONFIG_NO_RADIUS */ + struct hostapd_acl_query_data *query; + + if (is_probe_req) { + /* Skip RADIUS queries for Probe Request frames to avoid + * excessive load on the authentication server. */ + return HOSTAPD_ACL_ACCEPT; + }; + + if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED) + os_memset(&out->vlan_id, 0, sizeof(out->vlan_id)); + + /* Check whether ACL cache has an entry for this station */ + res = hostapd_acl_cache_get(hapd, addr, out); + if (res == HOSTAPD_ACL_ACCEPT || + res == HOSTAPD_ACL_ACCEPT_TIMEOUT) + return res; + if (res == HOSTAPD_ACL_REJECT) + return HOSTAPD_ACL_REJECT; + + query = hapd->acl_queries; + while (query) { + if (ether_addr_equal(query->addr, addr)) { + /* pending query in RADIUS retransmit queue; + * do not generate a new one */ + return HOSTAPD_ACL_PENDING; + } + query = query->next; + } + + if (!hapd->conf->radius->auth_server) + return HOSTAPD_ACL_REJECT; + + /* No entry in the cache - query external RADIUS server */ + query = os_zalloc(sizeof(*query)); + if (!query) { + wpa_printf(MSG_ERROR, "malloc for query data failed"); + return HOSTAPD_ACL_REJECT; + } + os_get_reltime(&query->timestamp); + os_memcpy(query->addr, addr, ETH_ALEN); + if (hostapd_radius_acl_query(hapd, addr, query)) { + wpa_printf(MSG_DEBUG, + "Failed to send Access-Request for ACL query."); + hostapd_acl_query_free(query); + return HOSTAPD_ACL_REJECT; + } + + query->auth_msg = os_memdup(msg, len); + if (!query->auth_msg) { + wpa_printf(MSG_ERROR, + "Failed to allocate memory for auth frame."); + hostapd_acl_query_free(query); + return HOSTAPD_ACL_REJECT; + } + query->auth_msg_len = len; + query->next = hapd->acl_queries; + hapd->acl_queries = query; + + /* Queued data will be processed in hostapd_acl_recv_radius() + * when RADIUS server replies to the sent Access-Request. */ + return HOSTAPD_ACL_PENDING; +#endif /* CONFIG_NO_RADIUS */ + } + + return HOSTAPD_ACL_REJECT; +} + + +#ifndef CONFIG_NO_RADIUS +static void hostapd_acl_expire_cache(struct hostapd_data *hapd, + struct os_reltime *now) +{ + struct hostapd_cached_radius_acl *prev, *entry, *tmp; + + prev = NULL; + entry = hapd->acl_cache; + + while (entry) { + if (os_reltime_expired(now, &entry->timestamp, + RADIUS_ACL_TIMEOUT)) { + wpa_printf(MSG_DEBUG, "Cached ACL entry for " MACSTR + " has expired.", MAC2STR(entry->addr)); + if (prev) + prev->next = entry->next; + else + hapd->acl_cache = entry->next; + hostapd_drv_set_radius_acl_expire(hapd, entry->addr); + tmp = entry; + entry = entry->next; + hostapd_acl_cache_free_entry(tmp); + continue; + } + + prev = entry; + entry = entry->next; + } +} + + +static void hostapd_acl_expire_queries(struct hostapd_data *hapd, + struct os_reltime *now) +{ + struct hostapd_acl_query_data *prev, *entry, *tmp; + + prev = NULL; + entry = hapd->acl_queries; + + while (entry) { + if (os_reltime_expired(now, &entry->timestamp, + RADIUS_ACL_TIMEOUT)) { + wpa_printf(MSG_DEBUG, "ACL query for " MACSTR + " has expired.", MAC2STR(entry->addr)); + if (prev) + prev->next = entry->next; + else + hapd->acl_queries = entry->next; + + tmp = entry; + entry = entry->next; + hostapd_acl_query_free(tmp); + continue; + } + + prev = entry; + entry = entry->next; + } +} + + +/** + * hostapd_acl_expire - ACL cache expiration callback + * @hapd: struct hostapd_data * + */ +void hostapd_acl_expire(struct hostapd_data *hapd) +{ + struct os_reltime now; + + os_get_reltime(&now); + hostapd_acl_expire_cache(hapd, &now); + hostapd_acl_expire_queries(hapd, &now); +} + + +static void decode_tunnel_passwords(struct hostapd_data *hapd, + const u8 *shared_secret, + size_t shared_secret_len, + struct radius_msg *msg, + struct radius_msg *req, + struct hostapd_cached_radius_acl *cache) +{ + int passphraselen; + char *passphrase; + size_t i; + struct hostapd_sta_wpa_psk_short *psk; + + /* + * Decode all tunnel passwords as PSK and save them into a linked list. + */ + for (i = 0; ; i++) { + passphrase = radius_msg_get_tunnel_password( + msg, &passphraselen, shared_secret, shared_secret_len, + req, i); + /* + * Passphrase is NULL iff there is no i-th Tunnel-Password + * attribute in msg. + */ + if (!passphrase) + break; + + /* + * Passphase should be 8..63 chars (to be hashed with SSID) + * or 64 chars hex string (no separate hashing with SSID). + */ + + if (passphraselen < MIN_PASSPHRASE_LEN || + passphraselen > MAX_PASSPHRASE_LEN + 1) + goto free_pass; + + /* + * passphrase does not contain the NULL termination. + * Add it here as pbkdf2_sha1() requires it. + */ + psk = os_zalloc(sizeof(struct hostapd_sta_wpa_psk_short)); + if (psk) { + if ((passphraselen == MAX_PASSPHRASE_LEN + 1) && + (hexstr2bin(passphrase, psk->psk, PMK_LEN) < 0)) { + hostapd_logger(hapd, cache->addr, + HOSTAPD_MODULE_RADIUS, + HOSTAPD_LEVEL_WARNING, + "invalid hex string (%d chars) in Tunnel-Password", + passphraselen); + goto skip; + } else if (passphraselen <= MAX_PASSPHRASE_LEN) { + os_memcpy(psk->passphrase, passphrase, + passphraselen); + psk->is_passphrase = 1; + } + psk->next = cache->info.psk; + cache->info.psk = psk; + psk = NULL; + } +skip: + os_free(psk); +free_pass: + os_free(passphrase); + } +} + + +/** + * hostapd_acl_recv_radius - Process incoming RADIUS Authentication messages + * @msg: RADIUS response message + * @req: RADIUS request message + * @shared_secret: RADIUS shared secret + * @shared_secret_len: Length of shared_secret in octets + * @data: Context data (struct hostapd_data *) + * Returns: RADIUS_RX_PROCESSED if RADIUS message was a reply to ACL query (and + * was processed here) or RADIUS_RX_UNKNOWN if not. + */ +static RadiusRxResult +hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req, + const u8 *shared_secret, size_t shared_secret_len, + void *data) +{ + struct hostapd_data *hapd = data; + struct hostapd_acl_query_data *query, *prev; + struct hostapd_cached_radius_acl *cache; + struct radius_sta *info; + struct radius_hdr *hdr = radius_msg_get_hdr(msg); + + query = hapd->acl_queries; + prev = NULL; + while (query) { + if (query->radius_id == hdr->identifier) + break; + prev = query; + query = query->next; + } + if (!query) + return RADIUS_RX_UNKNOWN; + + wpa_printf(MSG_DEBUG, + "Found matching Access-Request for RADIUS message (id=%d)", + query->radius_id); + + if (radius_msg_verify( + msg, shared_secret, shared_secret_len, req, + hapd->conf->radius_require_message_authenticator)) { + wpa_printf(MSG_INFO, + "Incoming RADIUS packet did not have correct authenticator - dropped"); + return RADIUS_RX_INVALID_AUTHENTICATOR; + } + + if (hdr->code != RADIUS_CODE_ACCESS_ACCEPT && + hdr->code != RADIUS_CODE_ACCESS_REJECT) { + wpa_printf(MSG_DEBUG, + "Unknown RADIUS message code %d to ACL query", + hdr->code); + return RADIUS_RX_UNKNOWN; + } + + /* Insert Accept/Reject info into ACL cache */ + cache = os_zalloc(sizeof(*cache)); + if (!cache) { + wpa_printf(MSG_DEBUG, "Failed to add ACL cache entry"); + goto done; + } + os_get_reltime(&cache->timestamp); + os_memcpy(cache->addr, query->addr, sizeof(cache->addr)); + info = &cache->info; + if (hdr->code == RADIUS_CODE_ACCESS_ACCEPT) { + u8 *buf; + size_t len; + + if (radius_msg_get_attr_int32(msg, RADIUS_ATTR_SESSION_TIMEOUT, + &info->session_timeout) == 0) + cache->accepted = HOSTAPD_ACL_ACCEPT_TIMEOUT; + else + cache->accepted = HOSTAPD_ACL_ACCEPT; + + if (radius_msg_get_attr_int32( + msg, RADIUS_ATTR_ACCT_INTERIM_INTERVAL, + &info->acct_interim_interval) == 0 && + info->acct_interim_interval < 60) { + wpa_printf(MSG_DEBUG, + "Ignored too small Acct-Interim-Interval %d for STA " + MACSTR, + info->acct_interim_interval, + MAC2STR(query->addr)); + info->acct_interim_interval = 0; + } + + if (hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED) + info->vlan_id.notempty = !!radius_msg_get_vlanid( + msg, &info->vlan_id.untagged, + MAX_NUM_TAGGED_VLAN, info->vlan_id.tagged); + + decode_tunnel_passwords(hapd, shared_secret, shared_secret_len, + msg, req, cache); + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME, + &buf, &len, NULL) == 0) { + info->identity = os_zalloc(len + 1); + if (info->identity) + os_memcpy(info->identity, buf, len); + } + if (radius_msg_get_attr_ptr( + msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, + &buf, &len, NULL) == 0) { + info->radius_cui = os_zalloc(len + 1); + if (info->radius_cui) + os_memcpy(info->radius_cui, buf, len); + } + + if (hapd->conf->wpa_psk_radius == PSK_RADIUS_REQUIRED && + !info->psk) + cache->accepted = HOSTAPD_ACL_REJECT; + + if (info->vlan_id.notempty && + !hostapd_vlan_valid(hapd->conf->vlan, &info->vlan_id)) { + hostapd_logger(hapd, query->addr, + HOSTAPD_MODULE_RADIUS, + HOSTAPD_LEVEL_INFO, + "Invalid VLAN %d%s received from RADIUS server", + info->vlan_id.untagged, + info->vlan_id.tagged[0] ? "+" : ""); + os_memset(&info->vlan_id, 0, sizeof(info->vlan_id)); + } + if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_REQUIRED && + !info->vlan_id.notempty) + cache->accepted = HOSTAPD_ACL_REJECT; + } else + cache->accepted = HOSTAPD_ACL_REJECT; + cache->next = hapd->acl_cache; + hapd->acl_cache = cache; + + if (query->radius_psk) { + struct sta_info *sta; + bool success = cache->accepted == HOSTAPD_ACL_ACCEPT || + cache->accepted == HOSTAPD_ACL_ACCEPT_TIMEOUT; + + sta = ap_get_sta(hapd, query->addr); + if (!sta || !sta->wpa_sm) { + wpa_printf(MSG_DEBUG, + "No STA/SM entry found for the RADIUS PSK response"); + goto done; + } +#ifdef NEED_AP_MLME + if (success && + (ieee802_11_set_radius_info(hapd, sta, cache->accepted, + info) < 0 || + ap_sta_bind_vlan(hapd, sta) < 0)) + success = false; +#endif /* NEED_AP_MLME */ + wpa_auth_sta_radius_psk_resp(sta->wpa_sm, success); + } else { +#ifdef CONFIG_DRIVER_RADIUS_ACL + hostapd_drv_set_radius_acl_auth(hapd, query->addr, + cache->accepted, + info->session_timeout); +#else /* CONFIG_DRIVER_RADIUS_ACL */ +#ifdef NEED_AP_MLME + /* Re-send original authentication frame for 802.11 processing + */ + wpa_printf(MSG_DEBUG, + "Re-sending authentication frame after successful RADIUS ACL query"); + ieee802_11_mgmt(hapd, query->auth_msg, query->auth_msg_len, + NULL); +#endif /* NEED_AP_MLME */ +#endif /* CONFIG_DRIVER_RADIUS_ACL */ + } + + done: + if (!prev) + hapd->acl_queries = query->next; + else + prev->next = query->next; + + hostapd_acl_query_free(query); + + return RADIUS_RX_PROCESSED; +} +#endif /* CONFIG_NO_RADIUS */ + + +/** + * hostapd_acl_init: Initialize IEEE 802.11 ACL + * @hapd: hostapd BSS data + * Returns: 0 on success, -1 on failure + */ +int hostapd_acl_init(struct hostapd_data *hapd) +{ +#ifndef CONFIG_NO_RADIUS + if (radius_client_register(hapd->radius, RADIUS_AUTH, + hostapd_acl_recv_radius, hapd)) + return -1; +#endif /* CONFIG_NO_RADIUS */ + + return 0; +} + + +/** + * hostapd_acl_deinit - Deinitialize IEEE 802.11 ACL + * @hapd: hostapd BSS data + */ +void hostapd_acl_deinit(struct hostapd_data *hapd) +{ + struct hostapd_acl_query_data *query, *prev; + +#ifndef CONFIG_NO_RADIUS + hostapd_acl_cache_free(hapd->acl_cache); + hapd->acl_cache = NULL; +#endif /* CONFIG_NO_RADIUS */ + + query = hapd->acl_queries; + hapd->acl_queries = NULL; + while (query) { + prev = query; + query = query->next; + hostapd_acl_query_free(prev); + } +} + + +void hostapd_copy_psk_list(struct hostapd_sta_wpa_psk_short **psk, + struct hostapd_sta_wpa_psk_short *src) +{ + if (!psk) + return; + + if (src) + src->ref++; + + *psk = src; +} + + +void hostapd_free_psk_list(struct hostapd_sta_wpa_psk_short *psk) +{ + if (psk && psk->ref) { + /* This will be freed when the last reference is dropped. */ + psk->ref--; + return; + } + + while (psk) { + struct hostapd_sta_wpa_psk_short *prev = psk; + psk = psk->next; + bin_clear_free(prev, sizeof(*prev)); + } +} + + +#ifndef CONFIG_NO_RADIUS +void hostapd_acl_req_radius_psk(struct hostapd_data *hapd, const u8 *addr, + int key_mgmt, const u8 *anonce, + const u8 *eapol, size_t eapol_len) +{ + struct hostapd_acl_query_data *query; + + query = os_zalloc(sizeof(*query)); + if (!query) + return; + + query->radius_psk = true; + query->akm = key_mgmt; + os_get_reltime(&query->timestamp); + os_memcpy(query->addr, addr, ETH_ALEN); + if (anonce) + query->anonce = os_memdup(anonce, WPA_NONCE_LEN); + if (eapol) { + query->eapol = os_memdup(eapol, eapol_len); + query->eapol_len = eapol_len; + } + if (hostapd_radius_acl_query(hapd, addr, query)) { + wpa_printf(MSG_DEBUG, + "Failed to send Access-Request for RADIUS PSK/ACL query"); + hostapd_acl_query_free(query); + return; + } + + query->next = hapd->acl_queries; + hapd->acl_queries = query; +} +#endif /* CONFIG_NO_RADIUS */ diff --git a/src/ap/ieee802_11_auth.h b/src/ap/ieee802_11_auth.h new file mode 100644 index 0000000..22ae1a9 --- /dev/null +++ b/src/ap/ieee802_11_auth.h @@ -0,0 +1,43 @@ +/* + * hostapd / IEEE 802.11 authentication (ACL) + * Copyright (c) 2003-2022, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef IEEE802_11_AUTH_H +#define IEEE802_11_AUTH_H + +enum { + HOSTAPD_ACL_REJECT = 0, + HOSTAPD_ACL_ACCEPT = 1, + HOSTAPD_ACL_PENDING = 2, + HOSTAPD_ACL_ACCEPT_TIMEOUT = 3 +}; + +struct radius_sta { + u32 session_timeout; + u32 acct_interim_interval; + struct vlan_description vlan_id; + struct hostapd_sta_wpa_psk_short *psk; + char *identity; + char *radius_cui; +}; + +int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr, + struct vlan_description *vlan_id); +int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr, + const u8 *msg, size_t len, struct radius_sta *out, + int is_probe_req); +int hostapd_acl_init(struct hostapd_data *hapd); +void hostapd_acl_deinit(struct hostapd_data *hapd); +void hostapd_free_psk_list(struct hostapd_sta_wpa_psk_short *psk); +void hostapd_acl_expire(struct hostapd_data *hapd); +void hostapd_copy_psk_list(struct hostapd_sta_wpa_psk_short **psk, + struct hostapd_sta_wpa_psk_short *src); +void hostapd_acl_req_radius_psk(struct hostapd_data *hapd, const u8 *addr, + int key_mgmt, const u8 *anonce, + const u8 *eapol, size_t eapol_len); + +#endif /* IEEE802_11_AUTH_H */ diff --git a/src/ap/ieee802_11_eht.c b/src/ap/ieee802_11_eht.c new file mode 100644 index 0000000..b935ee8 --- /dev/null +++ b/src/ap/ieee802_11_eht.c @@ -0,0 +1,1405 @@ +/* + * hostapd / IEEE 802.11be EHT + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include "utils/common.h" +#include "crypto/crypto.h" +#include "crypto/dh_groups.h" +#include "hostapd.h" +#include "sta_info.h" +#include "ieee802_11.h" + + +static u16 ieee80211_eht_ppet_size(u16 ppe_thres_hdr, const u8 *phy_cap_info) +{ + u8 ru; + u16 sz = 0; + + if ((phy_cap_info[EHT_PHYCAP_PPE_THRESHOLD_PRESENT_IDX] & + EHT_PHYCAP_PPE_THRESHOLD_PRESENT) == 0) + return 0; + + ru = (ppe_thres_hdr & + EHT_PPE_THRES_RU_INDEX_MASK) >> EHT_PPE_THRES_RU_INDEX_SHIFT; + while (ru) { + if (ru & 0x1) + sz++; + ru >>= 1; + } + + sz = sz * (1 + ((ppe_thres_hdr & EHT_PPE_THRES_NSS_MASK) >> + EHT_PPE_THRES_NSS_SHIFT)); + sz = (sz * 6) + 9; + if (sz % 8) + sz += 8; + sz /= 8; + + return sz; +} + + +static u8 ieee80211_eht_mcs_set_size(enum hostapd_hw_mode mode, u8 opclass, + int he_oper_chwidth, const u8 *he_phy_cap, + const u8 *eht_phy_cap) +{ + u8 sz = EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; + bool band24, band5, band6; + u8 he_phy_cap_chwidth = ~HE_PHYCAP_CHANNEL_WIDTH_MASK; + u8 cap_chwidth; + + switch (he_oper_chwidth) { + case CONF_OPER_CHWIDTH_80P80MHZ: + he_phy_cap_chwidth |= + HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G; + /* fall through */ + case CONF_OPER_CHWIDTH_160MHZ: + he_phy_cap_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G; + /* fall through */ + case CONF_OPER_CHWIDTH_80MHZ: + case CONF_OPER_CHWIDTH_USE_HT: + he_phy_cap_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G | + HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G; + break; + } + + cap_chwidth = he_phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX]; + if (he_oper_chwidth != -1) + he_phy_cap_chwidth &= cap_chwidth; + else + he_phy_cap_chwidth = cap_chwidth; + + band24 = mode == HOSTAPD_MODE_IEEE80211B || + mode == HOSTAPD_MODE_IEEE80211G || + mode == NUM_HOSTAPD_MODES; + band5 = mode == HOSTAPD_MODE_IEEE80211A || + mode == NUM_HOSTAPD_MODES; + band6 = is_6ghz_op_class(opclass); + + if (band24 && + (he_phy_cap_chwidth & HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G) == 0) + return EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY; + + if (band5 && + (he_phy_cap_chwidth & + (HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G | + HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G | + HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G)) == 0) + return EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY; + + if (band5 && + (he_phy_cap_chwidth & + (HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G | + HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G))) + sz += EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; + + if (band6 && + (eht_phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] & + EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK)) + sz += EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS; + + return sz; +} + + +size_t hostapd_eid_eht_capab_len(struct hostapd_data *hapd, + enum ieee80211_op_mode opmode) +{ + struct hostapd_hw_modes *mode; + struct eht_capabilities *eht_cap; + size_t len = 3 + 2 + EHT_PHY_CAPAB_LEN; + + mode = hapd->iface->current_mode; + if (!mode) + return 0; + + eht_cap = &mode->eht_capab[opmode]; + if (!eht_cap->eht_supported) + return 0; + + len += ieee80211_eht_mcs_set_size(mode->mode, hapd->iconf->op_class, + hapd->iconf->he_oper_chwidth, + mode->he_capab[opmode].phy_cap, + eht_cap->phy_cap); + len += ieee80211_eht_ppet_size(WPA_GET_LE16(&eht_cap->ppet[0]), + eht_cap->phy_cap); + + return len; +} + + +u8 * hostapd_eid_eht_capab(struct hostapd_data *hapd, u8 *eid, + enum ieee80211_op_mode opmode) +{ + struct hostapd_hw_modes *mode; + struct eht_capabilities *eht_cap; + struct ieee80211_eht_capabilities *cap; + size_t mcs_nss_len, ppe_thresh_len; + u8 *pos = eid, *length_pos; + + mode = hapd->iface->current_mode; + if (!mode) + return eid; + + eht_cap = &mode->eht_capab[opmode]; + if (!eht_cap->eht_supported) + return eid; + + *pos++ = WLAN_EID_EXTENSION; + length_pos = pos++; + *pos++ = WLAN_EID_EXT_EHT_CAPABILITIES; + + cap = (struct ieee80211_eht_capabilities *) pos; + os_memset(cap, 0, sizeof(*cap)); + cap->mac_cap = host_to_le16(eht_cap->mac_cap); + os_memcpy(cap->phy_cap, eht_cap->phy_cap, EHT_PHY_CAPAB_LEN); + + if (!is_6ghz_op_class(hapd->iconf->op_class)) + cap->phy_cap[EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_IDX] &= + ~EHT_PHYCAP_320MHZ_IN_6GHZ_SUPPORT_MASK; + if (!hapd->iface->conf->eht_phy_capab.su_beamformer) + cap->phy_cap[EHT_PHYCAP_SU_BEAMFORMER_IDX] &= + ~EHT_PHYCAP_SU_BEAMFORMER; + + if (!hapd->iface->conf->eht_phy_capab.su_beamformee) + cap->phy_cap[EHT_PHYCAP_SU_BEAMFORMEE_IDX] &= + ~EHT_PHYCAP_SU_BEAMFORMEE; + + if (!hapd->iface->conf->eht_phy_capab.mu_beamformer) + cap->phy_cap[EHT_PHYCAP_MU_BEAMFORMER_IDX] &= + ~EHT_PHYCAP_MU_BEAMFORMER_MASK; + + pos = cap->optional; + + mcs_nss_len = ieee80211_eht_mcs_set_size(mode->mode, + hapd->iconf->op_class, + hapd->iconf->he_oper_chwidth, + mode->he_capab[opmode].phy_cap, + eht_cap->phy_cap); + if (mcs_nss_len) { + os_memcpy(pos, eht_cap->mcs, mcs_nss_len); + pos += mcs_nss_len; + } + + ppe_thresh_len = ieee80211_eht_ppet_size( + WPA_GET_LE16(&eht_cap->ppet[0]), + eht_cap->phy_cap); + if (ppe_thresh_len) { + os_memcpy(pos, eht_cap->ppet, ppe_thresh_len); + pos += ppe_thresh_len; + } + + *length_pos = pos - (eid + 2); + return pos; +} + + +u8 * hostapd_eid_eht_operation(struct hostapd_data *hapd, u8 *eid) +{ + struct hostapd_config *conf = hapd->iconf; + struct ieee80211_eht_operation *oper; + u8 *pos = eid, seg0 = 0, seg1 = 0; + enum oper_chan_width chwidth; + size_t elen = 1 + 4; + bool eht_oper_info_present; + u16 punct_bitmap = hostapd_get_punct_bitmap(hapd); + + if (!hapd->iface->current_mode) + return eid; + + if (is_6ghz_op_class(conf->op_class)) + chwidth = op_class_to_ch_width(conf->op_class); + else + chwidth = conf->eht_oper_chwidth; + + eht_oper_info_present = chwidth == CONF_OPER_CHWIDTH_320MHZ || + punct_bitmap; + + if (eht_oper_info_present) + elen += 3; + + if (punct_bitmap) + elen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE; + + *pos++ = WLAN_EID_EXTENSION; + *pos++ = 1 + elen; + *pos++ = WLAN_EID_EXT_EHT_OPERATION; + + oper = (struct ieee80211_eht_operation *) pos; + oper->oper_params = 0; + + if (hapd->iconf->eht_default_pe_duration) + oper->oper_params |= EHT_OPER_DEFAULT_PE_DURATION; + + /* TODO: Fill in appropriate EHT-MCS max Nss information */ + oper->basic_eht_mcs_nss_set[0] = 0x11; + oper->basic_eht_mcs_nss_set[1] = 0x00; + oper->basic_eht_mcs_nss_set[2] = 0x00; + oper->basic_eht_mcs_nss_set[3] = 0x00; + + if (!eht_oper_info_present) + return pos + elen; + + oper->oper_params |= EHT_OPER_INFO_PRESENT; + seg0 = hostapd_get_oper_centr_freq_seg0_idx(conf); + + switch (chwidth) { + case CONF_OPER_CHWIDTH_320MHZ: + oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_320MHZ; + seg1 = seg0; + if (hapd->iconf->channel < seg0) + seg0 -= 16; + else + seg0 += 16; + break; + case CONF_OPER_CHWIDTH_160MHZ: + oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_160MHZ; + seg1 = seg0; + if (hapd->iconf->channel < seg0) + seg0 -= 8; + else + seg0 += 8; + break; + case CONF_OPER_CHWIDTH_80MHZ: + oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_80MHZ; + break; + case CONF_OPER_CHWIDTH_USE_HT: + if (seg0) + oper->oper_info.control |= EHT_OPER_CHANNEL_WIDTH_40MHZ; + break; + default: + return eid; + } + + oper->oper_info.ccfs0 = seg0 ? seg0 : hapd->iconf->channel; + oper->oper_info.ccfs1 = seg1; + + if (punct_bitmap) { + oper->oper_params |= EHT_OPER_DISABLED_SUBCHAN_BITMAP_PRESENT; + oper->oper_info.disabled_chan_bitmap = + host_to_le16(punct_bitmap); + } + + return pos + elen; +} + + +static bool check_valid_eht_mcs_nss(struct hostapd_data *hapd, const u8 *ap_mcs, + const u8 *sta_mcs, u8 mcs_count, u8 map_len) +{ + unsigned int i, j; + + for (i = 0; i < mcs_count; i++) { + ap_mcs += i * 3; + sta_mcs += i * 3; + + for (j = 0; j < map_len; j++) { + if (((ap_mcs[j] >> 4) & 0xFF) == 0) + continue; + + if ((sta_mcs[j] & 0xFF) == 0) + continue; + + return true; + } + } + + wpa_printf(MSG_DEBUG, + "No matching EHT MCS found between AP TX and STA RX"); + return false; +} + + +static bool check_valid_eht_mcs(struct hostapd_data *hapd, + const u8 *sta_eht_capab, + enum ieee80211_op_mode opmode) +{ + struct hostapd_hw_modes *mode; + const struct ieee80211_eht_capabilities *capab; + const u8 *ap_mcs, *sta_mcs; + u8 mcs_count = 1; + + mode = hapd->iface->current_mode; + if (!mode) + return true; + + ap_mcs = mode->eht_capab[opmode].mcs; + capab = (const struct ieee80211_eht_capabilities *) sta_eht_capab; + sta_mcs = capab->optional; + + if (ieee80211_eht_mcs_set_size(mode->mode, hapd->iconf->op_class, + hapd->iconf->he_oper_chwidth, + mode->he_capab[opmode].phy_cap, + mode->eht_capab[opmode].phy_cap) == + EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY) + return check_valid_eht_mcs_nss( + hapd, ap_mcs, sta_mcs, 1, + EHT_PHYCAP_MCS_NSS_LEN_20MHZ_ONLY); + + switch (hapd->iface->conf->eht_oper_chwidth) { + case CONF_OPER_CHWIDTH_320MHZ: + mcs_count++; + /* fall through */ + case CONF_OPER_CHWIDTH_80P80MHZ: + case CONF_OPER_CHWIDTH_160MHZ: + mcs_count++; + break; + default: + break; + } + + return check_valid_eht_mcs_nss(hapd, ap_mcs, sta_mcs, mcs_count, + EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS); +} + + +static bool ieee80211_invalid_eht_cap_size(enum hostapd_hw_mode mode, + u8 opclass, const u8 *he_cap, + const u8 *eht_cap, size_t len) +{ + const struct ieee80211_he_capabilities *he_capab; + struct ieee80211_eht_capabilities *cap; + const u8 *he_phy_cap; + size_t cap_len; + u16 ppe_thres_hdr; + + he_capab = (const struct ieee80211_he_capabilities *) he_cap; + he_phy_cap = he_capab->he_phy_capab_info; + cap = (struct ieee80211_eht_capabilities *) eht_cap; + cap_len = sizeof(*cap) - sizeof(cap->optional); + if (len < cap_len) + return true; + + cap_len += ieee80211_eht_mcs_set_size(mode, opclass, -1, he_phy_cap, + cap->phy_cap); + if (len < cap_len) + return true; + + ppe_thres_hdr = len > cap_len + 1 ? + WPA_GET_LE16(&eht_cap[cap_len]) : 0x01ff; + cap_len += ieee80211_eht_ppet_size(ppe_thres_hdr, cap->phy_cap); + + return len < cap_len; +} + + +u16 copy_sta_eht_capab(struct hostapd_data *hapd, struct sta_info *sta, + enum ieee80211_op_mode opmode, + const u8 *he_capab, size_t he_capab_len, + const u8 *eht_capab, size_t eht_capab_len) +{ + struct hostapd_hw_modes *c_mode = hapd->iface->current_mode; + enum hostapd_hw_mode mode = c_mode ? c_mode->mode : NUM_HOSTAPD_MODES; + + if (!hapd->iconf->ieee80211be || hapd->conf->disable_11be || + !he_capab || he_capab_len < IEEE80211_HE_CAPAB_MIN_LEN || + !eht_capab || + ieee80211_invalid_eht_cap_size(mode, hapd->iconf->op_class, + he_capab, eht_capab, + eht_capab_len) || + !check_valid_eht_mcs(hapd, eht_capab, opmode)) { + sta->flags &= ~WLAN_STA_EHT; + os_free(sta->eht_capab); + sta->eht_capab = NULL; + return WLAN_STATUS_SUCCESS; + } + + os_free(sta->eht_capab); + sta->eht_capab = os_memdup(eht_capab, eht_capab_len); + if (!sta->eht_capab) { + sta->eht_capab_len = 0; + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + sta->flags |= WLAN_STA_EHT; + sta->eht_capab_len = eht_capab_len; + + return WLAN_STATUS_SUCCESS; +} + + +void hostapd_get_eht_capab(struct hostapd_data *hapd, + const struct ieee80211_eht_capabilities *src, + struct ieee80211_eht_capabilities *dest, + size_t len) +{ + if (!src || !dest) + return; + + if (len > sizeof(*dest)) + len = sizeof(*dest); + /* TODO: mask out unsupported features */ + + os_memset(dest, 0, sizeof(*dest)); + os_memcpy(dest, src, len); +} + + +static u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd, + u8 *eid, struct mld_info *mld_info, + bool include_mld_id) +{ + struct wpabuf *buf; + u16 control; + u8 *pos = eid; + const u8 *ptr; + size_t len, slice_len; + u8 link_id; + u8 common_info_len; + u16 mld_cap; + u8 max_simul_links, active_links; + + /* + * As the Multi-Link element can exceed the size of 255 bytes need to + * first build it and then handle fragmentation. + */ + buf = wpabuf_alloc(1024); + if (!buf) + return pos; + + /* Multi-Link Control field */ + control = MULTI_LINK_CONTROL_TYPE_BASIC | + BASIC_MULTI_LINK_CTRL_PRES_LINK_ID | + BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT | + BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA | + BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA; + + /* + * Set the basic Multi-Link common information. Hard code the common + * info length to 13 based on the length of the present fields: + * Length (1) + MLD address (6) + Link ID (1) + + * BSS Parameters Change Count (1) + EML Capabilities (2) + + * MLD Capabilities and Operations (2) + */ +#define EHT_ML_COMMON_INFO_LEN 13 + common_info_len = EHT_ML_COMMON_INFO_LEN; + + if (include_mld_id) { + /* AP MLD ID */ + control |= BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID; + common_info_len++; + } + + wpabuf_put_le16(buf, control); + + wpabuf_put_u8(buf, common_info_len); + + /* Own MLD MAC Address */ + wpabuf_put_data(buf, hapd->mld->mld_addr, ETH_ALEN); + + /* Own Link ID */ + wpabuf_put_u8(buf, hapd->mld_link_id); + + /* Currently hard code the BSS Parameters Change Count to 0x1 */ + wpabuf_put_u8(buf, 0x1); + + wpa_printf(MSG_DEBUG, "MLD: EML Capabilities=0x%x", + hapd->iface->mld_eml_capa); + wpabuf_put_le16(buf, hapd->iface->mld_eml_capa); + + mld_cap = hapd->iface->mld_mld_capa; + max_simul_links = mld_cap & EHT_ML_MLD_CAPA_MAX_NUM_SIM_LINKS_MASK; + active_links = hapd->mld->num_links - 1; + + if (active_links > max_simul_links) { + wpa_printf(MSG_ERROR, + "MLD: Error in max simultaneous links, advertised: 0x%x current: 0x%x", + max_simul_links, active_links); + active_links = max_simul_links; + } + + mld_cap &= ~EHT_ML_MLD_CAPA_MAX_NUM_SIM_LINKS_MASK; + mld_cap |= active_links & EHT_ML_MLD_CAPA_MAX_NUM_SIM_LINKS_MASK; + + /* TODO: Advertise T2LM based on driver support as well */ + mld_cap &= ~EHT_ML_MLD_CAPA_TID_TO_LINK_MAP_NEG_SUPP_MSK; + + wpa_printf(MSG_DEBUG, "MLD: MLD Capabilities and Operations=0x%x", + mld_cap); + wpabuf_put_le16(buf, mld_cap); + + if (include_mld_id) { + wpa_printf(MSG_DEBUG, "MLD: AP MLD ID=0x%x", + hostapd_get_mld_id(hapd)); + wpabuf_put_u8(buf, hostapd_get_mld_id(hapd)); + } + + if (!mld_info) + goto out; + + /* Add link info for the other links */ + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + struct mld_link_info *link = &mld_info->links[link_id]; + struct hostapd_data *link_bss; + + /* + * control (2) + station info length (1) + MAC address (6) + + * beacon interval (2) + TSF offset (8) + DTIM info (2) + BSS + * parameters change counter (1) + station profile length. + */ +#define EHT_ML_STA_INFO_LEN 22 + size_t total_len = EHT_ML_STA_INFO_LEN + + link->resp_sta_profile_len; + + /* Skip the local one */ + if (link_id == hapd->mld_link_id || !link->valid) + continue; + + link_bss = hostapd_mld_get_link_bss(hapd, link_id); + if (!link_bss) { + wpa_printf(MSG_ERROR, + "MLD: Couldn't find link BSS - skip it"); + continue; + } + + /* Per-STA Profile subelement */ + wpabuf_put_u8(buf, EHT_ML_SUB_ELEM_PER_STA_PROFILE); + + if (total_len <= 255) + wpabuf_put_u8(buf, total_len); + else + wpabuf_put_u8(buf, 255); + + /* STA Control */ + control = (link_id & 0xf) | + EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK | + EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK | + EHT_PER_STA_CTRL_TSF_OFFSET_PRESENT_MSK | + EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK | + EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK | + EHT_PER_STA_CTRL_BSS_PARAM_CNT_PRESENT_MSK; + wpabuf_put_le16(buf, control); + + /* STA Info */ + + /* STA Info Length */ + wpabuf_put_u8(buf, EHT_ML_STA_INFO_LEN - 2); + wpabuf_put_data(buf, link->local_addr, ETH_ALEN); + wpabuf_put_le16(buf, link_bss->iconf->beacon_int); + + /* TSF Offset */ + /* + * TODO: Currently setting TSF offset to zero. However, this + * information needs to come from the driver. + */ + wpabuf_put_le64(buf, 0); + + /* DTIM Info */ + wpabuf_put_u8(buf, 0); /* DTIM Count */ + wpabuf_put_u8(buf, link_bss->conf->dtim_period); + + /* BSS Parameters Change Count */ + wpabuf_put_u8(buf, hapd->eht_mld_bss_param_change); + + if (!link->resp_sta_profile) + continue; + + /* Fragment the sub element if needed */ + if (total_len <= 255) { + wpabuf_put_data(buf, link->resp_sta_profile, + link->resp_sta_profile_len); + } else { + ptr = link->resp_sta_profile; + len = link->resp_sta_profile_len; + + slice_len = 255 - EHT_ML_STA_INFO_LEN; + + wpabuf_put_data(buf, ptr, slice_len); + len -= slice_len; + ptr += slice_len; + + while (len) { + if (len <= 255) + slice_len = len; + else + slice_len = 255; + + wpabuf_put_u8(buf, EHT_ML_SUB_ELEM_FRAGMENT); + wpabuf_put_u8(buf, slice_len); + wpabuf_put_data(buf, ptr, slice_len); + + len -= slice_len; + ptr += slice_len; + } + } + } + +out: + /* Fragment the Multi-Link element, if needed */ + len = wpabuf_len(buf); + ptr = wpabuf_head(buf); + + if (len <= 254) + slice_len = len; + else + slice_len = 254; + + *pos++ = WLAN_EID_EXTENSION; + *pos++ = slice_len + 1; + *pos++ = WLAN_EID_EXT_MULTI_LINK; + os_memcpy(pos, ptr, slice_len); + + ptr += slice_len; + pos += slice_len; + len -= slice_len; + + while (len) { + if (len <= 255) + slice_len = len; + else + slice_len = 255; + + *pos++ = WLAN_EID_FRAGMENT; + *pos++ = slice_len; + os_memcpy(pos, ptr, slice_len); + + ptr += slice_len; + pos += slice_len; + len -= slice_len; + } + + wpabuf_free(buf); + return pos; +} + + +static u8 * hostapd_eid_eht_reconf_ml(struct hostapd_data *hapd, u8 *eid) +{ +#ifdef CONFIG_TESTING_OPTIONS + struct hostapd_data *other_hapd; + u16 control; + u8 *pos = eid; + unsigned int i; + + wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML"); + + /* First check if the element needs to be added */ + for (i = 0; i < hapd->iface->interfaces->count; i++) { + other_hapd = hapd->iface->interfaces->iface[i]->bss[0]; + + wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML: %u", + other_hapd->eht_mld_link_removal_count); + + if (other_hapd->eht_mld_link_removal_count) + break; + } + + /* No link is going to be removed */ + if (i == hapd->iface->interfaces->count) + return eid; + + wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML: Adding element"); + + /* The length will be set at the end */ + *pos++ = WLAN_EID_EXTENSION; + *pos++ = 0; + *pos++ = WLAN_EID_EXT_MULTI_LINK; + + /* Set the Multi-Link Control field */ + control = MULTI_LINK_CONTROL_TYPE_RECONF; + WPA_PUT_LE16(pos, control); + pos += 2; + + /* Common Info doesn't include any information */ + *pos++ = 1; + + /* Add the per station profiles */ + for (i = 0; i < hapd->iface->interfaces->count; i++) { + other_hapd = hapd->iface->interfaces->iface[i]->bss[0]; + if (!other_hapd->eht_mld_link_removal_count) + continue; + + /* Subelement ID is 0 */ + *pos++ = 0; + *pos++ = 5; + + control = other_hapd->mld_link_id | + EHT_PER_STA_RECONF_CTRL_AP_REMOVAL_TIMER; + + WPA_PUT_LE16(pos, control); + pos += 2; + + /* STA profile length */ + *pos++ = 3; + + WPA_PUT_LE16(pos, other_hapd->eht_mld_link_removal_count); + pos += 2; + } + + eid[1] = pos - eid - 2; + + wpa_hexdump(MSG_DEBUG, "MLD: Reconfiguration ML", eid, eid[1] + 2); + return pos; +#else /* CONFIG_TESTING_OPTIONS */ + return eid; +#endif /* CONFIG_TESTING_OPTIONS */ +} + + +static size_t hostapd_eid_eht_ml_len(struct mld_info *info, + bool include_mld_id) +{ + size_t len = 0; + size_t eht_ml_len = 2 + EHT_ML_COMMON_INFO_LEN; + u8 link_id; + + if (include_mld_id) + eht_ml_len++; + + for (link_id = 0; info && link_id < ARRAY_SIZE(info->links); + link_id++) { + struct mld_link_info *link; + size_t sta_len = EHT_ML_STA_INFO_LEN; + + link = &info->links[link_id]; + if (!link->valid) + continue; + + sta_len += link->resp_sta_profile_len; + + /* Element data and (fragmentation) headers */ + eht_ml_len += sta_len; + eht_ml_len += 2 + sta_len / 255 * 2; + } + + /* Element data */ + len += eht_ml_len; + + /* First header (254 bytes of data) */ + len += 3; + + /* Fragmentation headers; +1 for shorter first chunk */ + len += (eht_ml_len + 1) / 255 * 2; + + return len; +} +#undef EHT_ML_COMMON_INFO_LEN +#undef EHT_ML_STA_INFO_LEN + + +u8 * hostapd_eid_eht_ml_beacon(struct hostapd_data *hapd, + struct mld_info *info, + u8 *eid, bool include_mld_id) +{ + eid = hostapd_eid_eht_basic_ml_common(hapd, eid, info, include_mld_id); + return hostapd_eid_eht_reconf_ml(hapd, eid); +} + + + +u8 * hostapd_eid_eht_ml_assoc(struct hostapd_data *hapd, struct sta_info *info, + u8 *eid) +{ + if (!ap_sta_is_mld(hapd, info)) + return eid; + + eid = hostapd_eid_eht_basic_ml_common(hapd, eid, &info->mld_info, + false); + ap_sta_free_sta_profile(&info->mld_info); + return hostapd_eid_eht_reconf_ml(hapd, eid); +} + + +size_t hostapd_eid_eht_ml_beacon_len(struct hostapd_data *hapd, + struct mld_info *info, + bool include_mld_id) +{ + return hostapd_eid_eht_ml_len(info, include_mld_id); +} + + +struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd) +{ + struct wpabuf *buf = wpabuf_alloc(12); + + if (!buf) + return NULL; + + wpabuf_put_u8(buf, WLAN_EID_EXTENSION); + wpabuf_put_u8(buf, 10); + wpabuf_put_u8(buf, WLAN_EID_EXT_MULTI_LINK); + wpabuf_put_le16(buf, MULTI_LINK_CONTROL_TYPE_BASIC); + wpabuf_put_u8(buf, ETH_ALEN + 1); + wpabuf_put_data(buf, hapd->mld->mld_addr, ETH_ALEN); + + return buf; +} + + +#ifdef CONFIG_SAE + +static const u8 * +sae_commit_skip_fixed_fields(const struct ieee80211_mgmt *mgmt, size_t len, + const u8 *pos, u16 status_code) +{ + u16 group; + size_t prime_len; + struct crypto_ec *ec; + + if (status_code != WLAN_STATUS_SAE_HASH_TO_ELEMENT) + return pos; + + /* SAE H2E commit message (group, scalar, FFE) */ + if (len < 2) { + wpa_printf(MSG_DEBUG, + "EHT: SAE Group is not present"); + return NULL; + } + + group = WPA_GET_LE16(pos); + pos += 2; + + /* TODO: How to parse when the group is unknown? */ + ec = crypto_ec_init(group); + if (!ec) { + const struct dh_group *dh = dh_groups_get(group); + + if (!dh) { + wpa_printf(MSG_DEBUG, "EHT: Unknown SAE group %u", + group); + return NULL; + } + + prime_len = dh->prime_len; + } else { + prime_len = crypto_ec_prime_len(ec); + } + + wpa_printf(MSG_DEBUG, "EHT: SAE scalar length is %zu", prime_len); + + /* scalar */ + pos += prime_len; + + if (ec) { + pos += prime_len * 2; + crypto_ec_deinit(ec); + } else { + pos += prime_len; + } + + if (pos - mgmt->u.auth.variable > (int) len) { + wpa_printf(MSG_DEBUG, + "EHT: Too short SAE commit Authentication frame"); + return NULL; + } + + wpa_hexdump(MSG_DEBUG, "EHT: SAE: Authentication frame elements", + pos, (int) len - (pos - mgmt->u.auth.variable)); + + return pos; +} + + +static const u8 * +sae_confirm_skip_fixed_fields(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len, + const u8 *pos, u16 status_code) +{ + struct sta_info *sta; + + if (status_code == WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION) + return pos; + + /* send confirm integer */ + pos += 2; + + /* + * At this stage we should already have an MLD station and actually SA + * will be replaced with the MLD MAC address by the driver. + */ + sta = ap_get_sta(hapd, mgmt->sa); + if (!sta) { + wpa_printf(MSG_DEBUG, "SAE: No MLD STA for SAE confirm"); + return NULL; + } + + if (!sta->sae || sta->sae->state < SAE_COMMITTED || !sta->sae->tmp) { + if (sta->sae) + wpa_printf(MSG_DEBUG, "SAE: Invalid state=%u", + sta->sae->state); + else + wpa_printf(MSG_DEBUG, "SAE: No SAE context"); + return NULL; + } + + wpa_printf(MSG_DEBUG, "SAE: confirm: kck_len=%zu", + sta->sae->tmp->kck_len); + + pos += sta->sae->tmp->kck_len; + + if (pos - mgmt->u.auth.variable > (int) len) { + wpa_printf(MSG_DEBUG, + "EHT: Too short SAE confirm Authentication frame"); + return NULL; + } + + return pos; +} + +#endif /* CONFIG_SAE */ + + +static const u8 * auth_skip_fixed_fields(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len) +{ + u16 auth_alg = le_to_host16(mgmt->u.auth.auth_alg); +#ifdef CONFIG_SAE + u16 auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction); + u16 status_code = le_to_host16(mgmt->u.auth.status_code); +#endif /* CONFIG_SAE */ + const u8 *pos = mgmt->u.auth.variable; + + /* Skip fixed fields as based on IEE P802.11-REVme/D3.0, Table 9-69 + * (Presence of fields and elements in Authentications frames) */ + switch (auth_alg) { + case WLAN_AUTH_OPEN: + return pos; +#ifdef CONFIG_SAE + case WLAN_AUTH_SAE: + if (auth_transaction == 1) { + if (status_code == WLAN_STATUS_SUCCESS) { + wpa_printf(MSG_DEBUG, + "EHT: SAE H2E is mandatory for MLD"); + goto out; + } + + return sae_commit_skip_fixed_fields(mgmt, len, pos, + status_code); + } else if (auth_transaction == 2) { + return sae_confirm_skip_fixed_fields(hapd, mgmt, len, + pos, status_code); + } + + return pos; +#endif /* CONFIG_SAE */ + /* TODO: Support additional algorithms that can be used for MLO */ + case WLAN_AUTH_FT: + case WLAN_AUTH_FILS_SK: + case WLAN_AUTH_FILS_SK_PFS: + case WLAN_AUTH_FILS_PK: + case WLAN_AUTH_PASN: + default: + break; + } + +#ifdef CONFIG_SAE +out: +#endif /* CONFIG_SAE */ + wpa_printf(MSG_DEBUG, + "TODO: Authentication algorithm %u not supported with MLD", + auth_alg); + return NULL; +} + + +const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len) +{ + struct ieee802_11_elems elems; + const u8 *pos; + + if (!hapd->conf->mld_ap) + return NULL; + + len -= offsetof(struct ieee80211_mgmt, u.auth.variable); + + pos = auth_skip_fixed_fields(hapd, mgmt, len); + if (!pos) + return NULL; + + if (ieee802_11_parse_elems(pos, + (int)len - (pos - mgmt->u.auth.variable), + &elems, 0) == ParseFailed) { + wpa_printf(MSG_DEBUG, + "MLD: Failed parsing Authentication frame"); + } + + if (!elems.basic_mle || !elems.basic_mle_len) + return NULL; + + return get_basic_mle_mld_addr(elems.basic_mle, elems.basic_mle_len); +} + + +static int hostapd_mld_validate_assoc_info(struct hostapd_data *hapd, + struct sta_info *sta) +{ + u8 link_id; + struct mld_info *info = &sta->mld_info; + + if (!ap_sta_is_mld(hapd, sta)) { + wpa_printf(MSG_DEBUG, "MLD: Not a non-AP MLD"); + return 0; + } + + /* + * Iterate over the links negotiated in the (Re)Association Request + * frame and validate that they are indeed valid links in the local AP + * MLD. + * + * While at it, also update the local address for the links in the + * mld_info, so it could be easily available for later flows, e.g., for + * the RSN Authenticator, etc. + */ + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + struct hostapd_data *other_hapd; + + if (!info->links[link_id].valid || link_id == hapd->mld_link_id) + continue; + + other_hapd = hostapd_mld_get_link_bss(hapd, link_id); + if (!other_hapd) { + wpa_printf(MSG_DEBUG, "MLD: Invalid link ID=%u", + link_id); + return -1; + } + + os_memcpy(info->links[link_id].local_addr, other_hapd->own_addr, + ETH_ALEN); + } + + return 0; +} + + +int hostapd_process_ml_assoc_req_addr(struct hostapd_data *hapd, + const u8 *basic_mle, size_t basic_mle_len, + u8 *mld_addr) +{ + struct wpabuf *mlbuf = ieee802_11_defrag(basic_mle, basic_mle_len, + true); + struct ieee80211_eht_ml *ml; + struct eht_ml_basic_common_info *common_info; + size_t ml_len, common_info_len; + int ret = -1; + u16 ml_control; + + if (!mlbuf) + return WLAN_STATUS_SUCCESS; + + ml = (struct ieee80211_eht_ml *) wpabuf_head(mlbuf); + ml_len = wpabuf_len(mlbuf); + + if (ml_len < sizeof(*ml)) + goto out; + + ml_control = le_to_host16(ml->ml_control); + if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) != + MULTI_LINK_CONTROL_TYPE_BASIC) { + wpa_printf(MSG_DEBUG, "MLD: Invalid ML type=%u", + ml_control & MULTI_LINK_CONTROL_TYPE_MASK); + goto out; + } + + /* Common Info Length and MLD MAC Address must always be present */ + common_info_len = 1 + ETH_ALEN; + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_LINK_ID) { + wpa_printf(MSG_DEBUG, "MLD: Link ID Info not expected"); + goto out; + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT) { + wpa_printf(MSG_DEBUG, + "MLD: BSS Parameters Change Count not expected"); + goto out; + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO) { + wpa_printf(MSG_DEBUG, + "MLD: Medium Synchronization Delay Information not expected"); + goto out; + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) + common_info_len += 2; + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA) + common_info_len += 2; + + if (sizeof(*ml) + common_info_len > ml_len) { + wpa_printf(MSG_DEBUG, "MLD: Not enough bytes for common info"); + goto out; + } + + common_info = (struct eht_ml_basic_common_info *) ml->variable; + + /* Common information length includes the length octet */ + if (common_info->len != common_info_len) { + wpa_printf(MSG_DEBUG, + "MLD: Invalid common info len=%u", common_info->len); + goto out; + } + + /* Get the MLD MAC Address */ + os_memcpy(mld_addr, common_info->mld_addr, ETH_ALEN); + ret = 0; + +out: + wpabuf_free(mlbuf); + return ret; +} + + +u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd, + struct ieee802_11_elems *elems, + struct sta_info *sta) +{ + struct wpabuf *mlbuf; + const struct ieee80211_eht_ml *ml; + const struct eht_ml_basic_common_info *common_info; + size_t ml_len, common_info_len; + struct mld_link_info *link_info; + struct mld_info *info = &sta->mld_info; + const u8 *pos; + int ret = -1; + u16 ml_control; + + mlbuf = ieee802_11_defrag(elems->basic_mle, elems->basic_mle_len, true); + if (!mlbuf) + return WLAN_STATUS_SUCCESS; + + ml = wpabuf_head(mlbuf); + ml_len = wpabuf_len(mlbuf); + + ml_control = le_to_host16(ml->ml_control); + if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) != + MULTI_LINK_CONTROL_TYPE_BASIC) { + wpa_printf(MSG_DEBUG, "MLD: Invalid ML type=%u", + ml_control & MULTI_LINK_CONTROL_TYPE_MASK); + goto out; + } + + /* Common Info length and MLD MAC address must always be present */ + common_info_len = 1 + ETH_ALEN; + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_LINK_ID) { + wpa_printf(MSG_DEBUG, "MLD: Link ID info not expected"); + goto out; + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT) { + wpa_printf(MSG_DEBUG, "MLD: BSS params change not expected"); + goto out; + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO) { + wpa_printf(MSG_DEBUG, "MLD: Sync delay not expected"); + goto out; + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) { + common_info_len += 2; + } else { + wpa_printf(MSG_DEBUG, "MLD: EML capabilities not present"); + } + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA) { + common_info_len += 2; + + } else { + wpa_printf(MSG_DEBUG, "MLD: MLD capabilities not present"); + goto out; + } + + wpa_printf(MSG_DEBUG, "MLD: expected_common_info_len=%lu", + common_info_len); + + if (sizeof(*ml) + common_info_len > ml_len) { + wpa_printf(MSG_DEBUG, "MLD: Not enough bytes for common info"); + goto out; + } + + common_info = (const struct eht_ml_basic_common_info *) ml->variable; + + /* Common information length includes the length octet */ + if (common_info->len != common_info_len) { + wpa_printf(MSG_DEBUG, + "MLD: Invalid common info len=%u (expected %zu)", + common_info->len, common_info_len); + goto out; + } + + pos = common_info->variable; + + if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) { + info->common_info.eml_capa = WPA_GET_LE16(pos); + pos += 2; + } else { + info->common_info.eml_capa = 0; + } + + info->common_info.mld_capa = WPA_GET_LE16(pos); + pos += 2; + + wpa_printf(MSG_DEBUG, "MLD: addr=" MACSTR ", eml=0x%x, mld=0x%x", + MAC2STR(info->common_info.mld_addr), + info->common_info.eml_capa, info->common_info.mld_capa); + + /* Check the MLD MAC Address */ + if (!ether_addr_equal(info->common_info.mld_addr, + common_info->mld_addr)) { + wpa_printf(MSG_DEBUG, + "MLD: MLD address mismatch between authentication (" + MACSTR ") and association (" MACSTR ")", + MAC2STR(info->common_info.mld_addr), + MAC2STR(common_info->mld_addr)); + goto out; + } + + info->links[hapd->mld_link_id].valid = 1; + + /* Parse the link info field */ + ml_len -= sizeof(*ml) + common_info_len; + + while (ml_len > 2) { + size_t sub_elem_len = *(pos + 1); + size_t sta_info_len; + u16 control; + + wpa_printf(MSG_DEBUG, "MLD: sub element len=%zu", + sub_elem_len); + + if (2 + sub_elem_len > ml_len) { + wpa_printf(MSG_DEBUG, + "MLD: Invalid link info len: %zu %zu", + 2 + sub_elem_len, ml_len); + goto out; + } + + if (*pos == MULTI_LINK_SUB_ELEM_ID_VENDOR) { + wpa_printf(MSG_DEBUG, + "MLD: Skip vendor specific subelement"); + + pos += 2 + sub_elem_len; + ml_len -= 2 + sub_elem_len; + continue; + } + + if (*pos != MULTI_LINK_SUB_ELEM_ID_PER_STA_PROFILE) { + wpa_printf(MSG_DEBUG, + "MLD: Skip unknown Multi-Link element subelement ID=%u", + *pos); + pos += 2 + sub_elem_len; + ml_len -= 2 + sub_elem_len; + continue; + } + + /* Skip the subelement ID and the length */ + pos += 2; + ml_len -= 2; + + /* Get the station control field */ + if (sub_elem_len < 2) { + wpa_printf(MSG_DEBUG, + "MLD: Too short Per-STA Profile subelement"); + goto out; + } + control = WPA_GET_LE16(pos); + link_info = &info->links[control & + EHT_PER_STA_CTRL_LINK_ID_MSK]; + pos += 2; + ml_len -= 2; + sub_elem_len -= 2; + + if (!(control & EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK)) { + wpa_printf(MSG_DEBUG, + "MLD: Per-STA complete profile expected"); + goto out; + } + + if (!(control & EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK)) { + wpa_printf(MSG_DEBUG, + "MLD: Per-STA MAC address not present"); + goto out; + } + + if ((control & (EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK | + EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK))) { + wpa_printf(MSG_DEBUG, + "MLD: Beacon/DTIM interval not expected"); + goto out; + } + + /* The length octet and the MAC address must be present */ + sta_info_len = 1 + ETH_ALEN; + + if (control & EHT_PER_STA_CTRL_NSTR_LINK_PAIR_PRESENT_MSK) { + if (control & EHT_PER_STA_CTRL_NSTR_BM_SIZE_MSK) + link_info->nstr_bitmap_len = 2; + else + link_info->nstr_bitmap_len = 1; + } + + sta_info_len += link_info->nstr_bitmap_len; + + if (sta_info_len > ml_len || sta_info_len != *pos || + sta_info_len > sub_elem_len) { + wpa_printf(MSG_DEBUG, "MLD: Invalid STA Info length"); + goto out; + } + + /* skip the length */ + pos++; + ml_len--; + + /* get the link address */ + os_memcpy(link_info->peer_addr, pos, ETH_ALEN); + wpa_printf(MSG_DEBUG, + "MLD: assoc: link id=%u, addr=" MACSTR, + control & EHT_PER_STA_CTRL_LINK_ID_MSK, + MAC2STR(link_info->peer_addr)); + + pos += ETH_ALEN; + ml_len -= ETH_ALEN; + + /* Get the NSTR bitmap */ + if (link_info->nstr_bitmap_len) { + os_memcpy(link_info->nstr_bitmap, pos, + link_info->nstr_bitmap_len); + pos += link_info->nstr_bitmap_len; + ml_len -= link_info->nstr_bitmap_len; + } + + sub_elem_len -= sta_info_len; + + wpa_printf(MSG_DEBUG, "MLD: STA Profile len=%zu", sub_elem_len); + if (sub_elem_len > ml_len) + goto out; + + if (sub_elem_len > 2) + link_info->capability = WPA_GET_LE16(pos); + + pos += sub_elem_len; + ml_len -= sub_elem_len; + + wpa_printf(MSG_DEBUG, "MLD: link ctrl=0x%x, " MACSTR + ", nstr bitmap len=%u", + control, MAC2STR(link_info->peer_addr), + link_info->nstr_bitmap_len); + + link_info->valid = true; + } + + if (ml_len) { + wpa_printf(MSG_DEBUG, "MLD: %zu bytes left after parsing. fail", + ml_len); + goto out; + } + + ret = hostapd_mld_validate_assoc_info(hapd, sta); +out: + wpabuf_free(mlbuf); + if (ret) { + os_memset(info, 0, sizeof(*info)); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + return WLAN_STATUS_SUCCESS; +} diff --git a/src/ap/ieee802_11_he.c b/src/ap/ieee802_11_he.c new file mode 100644 index 0000000..a2deda6 --- /dev/null +++ b/src/ap/ieee802_11_he.c @@ -0,0 +1,571 @@ +/* + * hostapd / IEEE 802.11ax HE + * Copyright (c) 2016-2017, Qualcomm Atheros, Inc. + * Copyright (c) 2019 John Crispin + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "common/hw_features_common.h" +#include "hostapd.h" +#include "ap_config.h" +#include "beacon.h" +#include "sta_info.h" +#include "ieee802_11.h" +#include "dfs.h" + +static u8 ieee80211_he_ppet_size(u8 ppe_thres_hdr, const u8 *phy_cap_info) +{ + u8 sz = 0, ru; + + if ((phy_cap_info[HE_PHYCAP_PPE_THRESHOLD_PRESENT_IDX] & + HE_PHYCAP_PPE_THRESHOLD_PRESENT) == 0) + return 0; + + ru = (ppe_thres_hdr >> HE_PPE_THRES_RU_INDEX_BITMASK_SHIFT) & + HE_PPE_THRES_RU_INDEX_BITMASK_MASK; + /* Count the number of 1 bits in RU Index Bitmask */ + while (ru) { + if (ru & 0x1) + sz++; + ru >>= 1; + } + + /* fixed header of 3 (NSTS) + 4 (RU Index Bitmask) = 7 bits */ + /* 6 * (NSTS + 1) bits for bit 1 in RU Index Bitmask */ + sz *= 1 + (ppe_thres_hdr & HE_PPE_THRES_NSS_MASK); + sz = (sz * 6) + 7; + /* PPE Pad to count the number of needed full octets */ + sz = (sz + 7) / 8; + + return sz; +} + + +static u8 ieee80211_he_mcs_set_size(const u8 *phy_cap_info) +{ + u8 sz = 4; + + if (phy_cap_info[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX] & + HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G) + sz += 4; + if (phy_cap_info[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX] & + HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G) + sz += 4; + + return sz; +} + + +static int ieee80211_invalid_he_cap_size(const u8 *buf, size_t len) +{ + struct ieee80211_he_capabilities *cap; + size_t cap_len; + u8 ppe_thres_hdr; + + cap = (struct ieee80211_he_capabilities *) buf; + cap_len = sizeof(*cap) - sizeof(cap->optional); + if (len < cap_len) + return 1; + + cap_len += ieee80211_he_mcs_set_size(cap->he_phy_capab_info); + if (len < cap_len) + return 1; + + ppe_thres_hdr = len > cap_len ? buf[cap_len] : 0xff; + cap_len += ieee80211_he_ppet_size(ppe_thres_hdr, + cap->he_phy_capab_info); + + return len < cap_len; +} + + +u8 * hostapd_eid_he_capab(struct hostapd_data *hapd, u8 *eid, + enum ieee80211_op_mode opmode) +{ + struct ieee80211_he_capabilities *cap; + struct hostapd_hw_modes *mode = hapd->iface->current_mode; + u8 he_oper_chwidth = ~HE_PHYCAP_CHANNEL_WIDTH_MASK; + u8 *pos = eid; + u8 ie_size = 0, mcs_nss_size = 4, ppet_size = 0; + + if (!mode) + return eid; + + ie_size = sizeof(*cap) - sizeof(cap->optional); + ppet_size = ieee80211_he_ppet_size(mode->he_capab[opmode].ppet[0], + mode->he_capab[opmode].phy_cap); + + switch (hapd->iface->conf->he_oper_chwidth) { + case CONF_OPER_CHWIDTH_80P80MHZ: + he_oper_chwidth |= + HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G; + mcs_nss_size += 4; + /* fall through */ + case CONF_OPER_CHWIDTH_160MHZ: + he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G; + mcs_nss_size += 4; + /* fall through */ + case CONF_OPER_CHWIDTH_80MHZ: + case CONF_OPER_CHWIDTH_USE_HT: + he_oper_chwidth |= HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G | + HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G; + break; + default: + break; + } + + ie_size += mcs_nss_size + ppet_size; + + *pos++ = WLAN_EID_EXTENSION; + *pos++ = 1 + ie_size; + *pos++ = WLAN_EID_EXT_HE_CAPABILITIES; + + cap = (struct ieee80211_he_capabilities *) pos; + os_memset(cap, 0, sizeof(*cap)); + + os_memcpy(cap->he_mac_capab_info, mode->he_capab[opmode].mac_cap, + HE_MAX_MAC_CAPAB_SIZE); + os_memcpy(cap->he_phy_capab_info, mode->he_capab[opmode].phy_cap, + HE_MAX_PHY_CAPAB_SIZE); + os_memcpy(cap->optional, mode->he_capab[opmode].mcs, mcs_nss_size); + if (ppet_size) + os_memcpy(&cap->optional[mcs_nss_size], + mode->he_capab[opmode].ppet, ppet_size); + + if (hapd->iface->conf->he_phy_capab.he_su_beamformer) + cap->he_phy_capab_info[HE_PHYCAP_SU_BEAMFORMER_CAPAB_IDX] |= + HE_PHYCAP_SU_BEAMFORMER_CAPAB; + else + cap->he_phy_capab_info[HE_PHYCAP_SU_BEAMFORMER_CAPAB_IDX] &= + ~HE_PHYCAP_SU_BEAMFORMER_CAPAB; + + if (hapd->iface->conf->he_phy_capab.he_su_beamformee) + cap->he_phy_capab_info[HE_PHYCAP_SU_BEAMFORMEE_CAPAB_IDX] |= + HE_PHYCAP_SU_BEAMFORMEE_CAPAB; + else + cap->he_phy_capab_info[HE_PHYCAP_SU_BEAMFORMEE_CAPAB_IDX] &= + ~HE_PHYCAP_SU_BEAMFORMEE_CAPAB; + + if (hapd->iface->conf->he_phy_capab.he_mu_beamformer) + cap->he_phy_capab_info[HE_PHYCAP_MU_BEAMFORMER_CAPAB_IDX] |= + HE_PHYCAP_MU_BEAMFORMER_CAPAB; + else + cap->he_phy_capab_info[HE_PHYCAP_MU_BEAMFORMER_CAPAB_IDX] &= + ~HE_PHYCAP_MU_BEAMFORMER_CAPAB; + + cap->he_phy_capab_info[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX] &= + he_oper_chwidth; + + pos += ie_size; + + return pos; +} + + +u8 * hostapd_eid_he_operation(struct hostapd_data *hapd, u8 *eid) +{ + struct ieee80211_he_operation *oper; + u8 *pos = eid; + int oper_size = 6; + u32 params = 0; + + if (!hapd->iface->current_mode) + return eid; + + if (is_6ghz_op_class(hapd->iconf->op_class)) + oper_size += 5; + + *pos++ = WLAN_EID_EXTENSION; + *pos++ = 1 + oper_size; + *pos++ = WLAN_EID_EXT_HE_OPERATION; + + oper = (struct ieee80211_he_operation *) pos; + os_memset(oper, 0, sizeof(*oper)); + + if (hapd->iface->conf->he_op.he_default_pe_duration) + params |= (hapd->iface->conf->he_op.he_default_pe_duration << + HE_OPERATION_DFLT_PE_DURATION_OFFSET); + + if (hapd->iface->conf->he_op.he_twt_required) + params |= HE_OPERATION_TWT_REQUIRED; + + if (hapd->iface->conf->he_op.he_rts_threshold) + params |= (hapd->iface->conf->he_op.he_rts_threshold << + HE_OPERATION_RTS_THRESHOLD_OFFSET); + + if (hapd->iface->conf->he_op.he_er_su_disable) + params |= HE_OPERATION_ER_SU_DISABLE; + + if (hapd->iface->conf->he_op.he_bss_color_disabled || + hapd->cca_in_progress) + params |= HE_OPERATION_BSS_COLOR_DISABLED; + if (hapd->iface->conf->he_op.he_bss_color_partial) + params |= HE_OPERATION_BSS_COLOR_PARTIAL; + params |= hapd->iface->conf->he_op.he_bss_color << + HE_OPERATION_BSS_COLOR_OFFSET; + + /* HE minimum required basic MCS and NSS for STAs */ + oper->he_mcs_nss_set = + host_to_le16(hapd->iface->conf->he_op.he_basic_mcs_nss_set); + + /* TODO: conditional MaxBSSID Indicator subfield */ + + pos += 6; /* skip the fixed part */ + + if (is_6ghz_op_class(hapd->iconf->op_class)) { + enum oper_chan_width oper_chwidth = + hostapd_get_oper_chwidth(hapd->iconf); + u8 seg0 = hapd->iconf->he_oper_centr_freq_seg0_idx; + u8 seg1 = hostapd_get_oper_centr_freq_seg1_idx(hapd->iconf); + u8 control; +#ifdef CONFIG_IEEE80211BE + u16 punct_bitmap = hostapd_get_punct_bitmap(hapd); + + if (punct_bitmap) { + punct_update_legacy_bw(punct_bitmap, + hapd->iconf->channel, + &oper_chwidth, &seg0, &seg1); + } +#endif /* CONFIG_IEEE80211BE */ + + if (!seg0) + seg0 = hapd->iconf->channel; + + params |= HE_OPERATION_6GHZ_OPER_INFO; + + /* 6 GHz Operation Information field + * IEEE Std 802.11ax-2021, 9.4.2.249 HE Operation element, + * Figure 9-788k + */ + *pos++ = hapd->iconf->channel; /* Primary Channel */ + + /* Control: + * bits 0-1: Channel Width + * bit 2: Duplicate Beacon + * bits 3-5: Regulatory Info + */ + /* Channel Width */ + if (seg1) + control = 3; + else + control = center_idx_to_bw_6ghz(seg0); + + control |= hapd->iconf->he_6ghz_reg_pwr_type << + HE_6GHZ_OPER_INFO_CTRL_REG_INFO_SHIFT; + + *pos++ = control; + + /* Channel Center Freq Seg0/Seg1 */ + if (oper_chwidth == CONF_OPER_CHWIDTH_160MHZ || + oper_chwidth == CONF_OPER_CHWIDTH_320MHZ) { + /* + * Seg 0 indicates the channel center frequency index of + * the 160 MHz channel. + */ + seg1 = seg0; + if (hapd->iconf->channel < seg0) + seg0 -= 8; + else + seg0 += 8; + } + + *pos++ = seg0; + *pos++ = seg1; + /* Minimum Rate */ + *pos++ = 6; /* TODO: what should be set here? */ + } + + oper->he_oper_params = host_to_le32(params); + + return pos; +} + + +u8 * hostapd_eid_he_mu_edca_parameter_set(struct hostapd_data *hapd, u8 *eid) +{ + struct ieee80211_he_mu_edca_parameter_set *edca; + u8 *pos; + size_t i; + + pos = (u8 *) &hapd->iface->conf->he_mu_edca; + for (i = 0; i < sizeof(*edca); i++) { + if (pos[i]) + break; + } + if (i == sizeof(*edca)) + return eid; /* no MU EDCA Parameters configured */ + + pos = eid; + *pos++ = WLAN_EID_EXTENSION; + *pos++ = 1 + sizeof(*edca); + *pos++ = WLAN_EID_EXT_HE_MU_EDCA_PARAMS; + + edca = (struct ieee80211_he_mu_edca_parameter_set *) pos; + os_memcpy(edca, &hapd->iface->conf->he_mu_edca, sizeof(*edca)); + + wpa_hexdump(MSG_DEBUG, "HE: MU EDCA Parameter Set element", + pos, sizeof(*edca)); + + pos += sizeof(*edca); + + return pos; +} + + +u8 * hostapd_eid_spatial_reuse(struct hostapd_data *hapd, u8 *eid) +{ + struct ieee80211_spatial_reuse *spr; + u8 *pos = eid, *spr_param; + u8 sz = 1; + + if (!hapd->iface->conf->spr.sr_control) + return eid; + + if (hapd->iface->conf->spr.sr_control & + SPATIAL_REUSE_NON_SRG_OFFSET_PRESENT) + sz++; + + if (hapd->iface->conf->spr.sr_control & + SPATIAL_REUSE_SRG_INFORMATION_PRESENT) + sz += 18; + + *pos++ = WLAN_EID_EXTENSION; + *pos++ = 1 + sz; + *pos++ = WLAN_EID_EXT_SPATIAL_REUSE; + + spr = (struct ieee80211_spatial_reuse *) pos; + os_memset(spr, 0, sizeof(*spr)); + + spr->sr_ctrl = hapd->iface->conf->spr.sr_control; + pos++; + spr_param = spr->params; + if (spr->sr_ctrl & SPATIAL_REUSE_NON_SRG_OFFSET_PRESENT) { + *spr_param++ = + hapd->iface->conf->spr.non_srg_obss_pd_max_offset; + pos++; + } + if (spr->sr_ctrl & SPATIAL_REUSE_SRG_INFORMATION_PRESENT) { + *spr_param++ = hapd->iface->conf->spr.srg_obss_pd_min_offset; + *spr_param++ = hapd->iface->conf->spr.srg_obss_pd_max_offset; + os_memcpy(spr_param, + hapd->iface->conf->spr.srg_bss_color_bitmap, 8); + spr_param += 8; + os_memcpy(spr_param, + hapd->iface->conf->spr.srg_partial_bssid_bitmap, 8); + pos += 18; + } + + return pos; +} + + +u8 * hostapd_eid_he_6ghz_band_cap(struct hostapd_data *hapd, u8 *eid) +{ + struct hostapd_config *conf = hapd->iface->conf; + struct hostapd_hw_modes *mode = hapd->iface->current_mode; + struct he_capabilities *he_cap; + struct ieee80211_he_6ghz_band_cap *cap; + u16 capab; + u8 *pos; + + if (!mode || !is_6ghz_op_class(hapd->iconf->op_class) || + !is_6ghz_freq(hapd->iface->freq)) + return eid; + + he_cap = &mode->he_capab[IEEE80211_MODE_AP]; + capab = he_cap->he_6ghz_capa & HE_6GHZ_BAND_CAP_MIN_MPDU_START; + capab |= (conf->he_6ghz_max_ampdu_len_exp << + HE_6GHZ_BAND_CAP_MAX_AMPDU_LEN_EXP_SHIFT) & + HE_6GHZ_BAND_CAP_MAX_AMPDU_LEN_EXP_MASK; + capab |= (conf->he_6ghz_max_mpdu << + HE_6GHZ_BAND_CAP_MAX_MPDU_LEN_SHIFT) & + HE_6GHZ_BAND_CAP_MAX_MPDU_LEN_MASK; + capab |= HE_6GHZ_BAND_CAP_SMPS_DISABLED; + if (conf->he_6ghz_rx_ant_pat) + capab |= HE_6GHZ_BAND_CAP_RX_ANTPAT_CONS; + if (conf->he_6ghz_tx_ant_pat) + capab |= HE_6GHZ_BAND_CAP_TX_ANTPAT_CONS; + + pos = eid; + *pos++ = WLAN_EID_EXTENSION; + *pos++ = 1 + sizeof(*cap); + *pos++ = WLAN_EID_EXT_HE_6GHZ_BAND_CAP; + + cap = (struct ieee80211_he_6ghz_band_cap *) pos; + cap->capab = host_to_le16(capab); + pos += sizeof(*cap); + + return pos; +} + + +void hostapd_get_he_capab(struct hostapd_data *hapd, + const struct ieee80211_he_capabilities *he_cap, + struct ieee80211_he_capabilities *neg_he_cap, + size_t he_capab_len) +{ + if (!he_cap) + return; + + if (he_capab_len > sizeof(*neg_he_cap)) + he_capab_len = sizeof(*neg_he_cap); + /* TODO: mask out unsupported features */ + + os_memcpy(neg_he_cap, he_cap, he_capab_len); +} + + +static int check_valid_he_mcs(struct hostapd_data *hapd, const u8 *sta_he_capab, + enum ieee80211_op_mode opmode) +{ + u16 sta_rx_mcs_set, ap_tx_mcs_set; + u8 mcs_count = 0; + const u16 *ap_mcs_set, *sta_mcs_set; + int i; + + if (!hapd->iface->current_mode) + return 1; + ap_mcs_set = (u16 *) hapd->iface->current_mode->he_capab[opmode].mcs; + sta_mcs_set = (u16 *) ((const struct ieee80211_he_capabilities *) + sta_he_capab)->optional; + + /* + * Disable HE capabilities for STAs for which there is not even a single + * allowed MCS in any supported number of streams, i.e., STA is + * advertising 3 (not supported) as HE MCS rates for all supported + * band/stream cases. + */ + switch (hapd->iface->conf->he_oper_chwidth) { + case CONF_OPER_CHWIDTH_80P80MHZ: + mcs_count = 3; + break; + case CONF_OPER_CHWIDTH_160MHZ: + mcs_count = 2; + break; + default: + mcs_count = 1; + break; + } + + for (i = 0; i < mcs_count; i++) { + int j; + + /* AP Tx MCS map vs. STA Rx MCS map */ + sta_rx_mcs_set = WPA_GET_LE16((const u8 *) &sta_mcs_set[i * 2]); + ap_tx_mcs_set = WPA_GET_LE16((const u8 *) + &ap_mcs_set[(i * 2) + 1]); + + for (j = 0; j < HE_NSS_MAX_STREAMS; j++) { + if (((ap_tx_mcs_set >> (j * 2)) & 0x3) == 3) + continue; + + if (((sta_rx_mcs_set >> (j * 2)) & 0x3) == 3) + continue; + + return 1; + } + } + + wpa_printf(MSG_DEBUG, + "No matching HE MCS found between AP TX and STA RX"); + + return 0; +} + + +u16 copy_sta_he_capab(struct hostapd_data *hapd, struct sta_info *sta, + enum ieee80211_op_mode opmode, const u8 *he_capab, + size_t he_capab_len) +{ + if (!he_capab || !(sta->flags & WLAN_STA_WMM) || + !hapd->iconf->ieee80211ax || hapd->conf->disable_11ax || + !check_valid_he_mcs(hapd, he_capab, opmode) || + ieee80211_invalid_he_cap_size(he_capab, he_capab_len) || + he_capab_len > sizeof(struct ieee80211_he_capabilities)) { + sta->flags &= ~WLAN_STA_HE; + os_free(sta->he_capab); + sta->he_capab = NULL; + return WLAN_STATUS_SUCCESS; + } + + if (!sta->he_capab) { + sta->he_capab = + os_zalloc(sizeof(struct ieee80211_he_capabilities)); + if (!sta->he_capab) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + sta->flags |= WLAN_STA_HE; + os_memset(sta->he_capab, 0, sizeof(struct ieee80211_he_capabilities)); + os_memcpy(sta->he_capab, he_capab, he_capab_len); + sta->he_capab_len = he_capab_len; + + return WLAN_STATUS_SUCCESS; +} + + +u16 copy_sta_he_6ghz_capab(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *he_6ghz_capab) +{ + if (!he_6ghz_capab || !hapd->iconf->ieee80211ax || + hapd->conf->disable_11ax || + !is_6ghz_op_class(hapd->iconf->op_class)) { + sta->flags &= ~WLAN_STA_6GHZ; + os_free(sta->he_6ghz_capab); + sta->he_6ghz_capab = NULL; + return WLAN_STATUS_SUCCESS; + } + + if (!sta->he_6ghz_capab) { + sta->he_6ghz_capab = + os_zalloc(sizeof(struct ieee80211_he_6ghz_band_cap)); + if (!sta->he_6ghz_capab) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + sta->flags |= WLAN_STA_6GHZ; + os_memcpy(sta->he_6ghz_capab, he_6ghz_capab, + sizeof(struct ieee80211_he_6ghz_band_cap)); + + return WLAN_STATUS_SUCCESS; +} + + +int hostapd_get_he_twt_responder(struct hostapd_data *hapd, + enum ieee80211_op_mode mode) +{ + u8 *mac_cap; + + if (!hapd->iface->current_mode || + !hapd->iface->current_mode->he_capab[mode].he_supported || + !hapd->iconf->ieee80211ax || hapd->conf->disable_11ax) + return 0; + + mac_cap = hapd->iface->current_mode->he_capab[mode].mac_cap; + + return !!(mac_cap[HE_MAC_CAPAB_0] & HE_MACCAP_TWT_RESPONDER) && + hapd->iface->conf->he_op.he_twt_responder; +} + + +u8 * hostapd_eid_cca(struct hostapd_data *hapd, u8 *eid) +{ + if (!hapd->cca_in_progress) + return eid; + + /* BSS Color Change Announcement element */ + *eid++ = WLAN_EID_EXTENSION; + *eid++ = 3; + *eid++ = WLAN_EID_EXT_COLOR_CHANGE_ANNOUNCEMENT; + *eid++ = hapd->cca_count; /* Color Switch Countdown */ + *eid++ = hapd->cca_color; /* New BSS Color Information */ + + return eid; +} diff --git a/src/ap/ieee802_11_ht.c b/src/ap/ieee802_11_ht.c new file mode 100644 index 0000000..f90f125 --- /dev/null +++ b/src/ap/ieee802_11_ht.c @@ -0,0 +1,534 @@ +/* + * hostapd / IEEE 802.11n HT + * Copyright (c) 2002-2009, Jouni Malinen + * Copyright (c) 2007-2008, Intel Corporation + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/ieee802_11_defs.h" +#include "hostapd.h" +#include "ap_config.h" +#include "sta_info.h" +#include "beacon.h" +#include "ieee802_11.h" +#include "hw_features.h" +#include "ap_drv_ops.h" + + +u8 * hostapd_eid_ht_capabilities(struct hostapd_data *hapd, u8 *eid) +{ + struct ieee80211_ht_capabilities *cap; + u8 *pos = eid; + + if (!hapd->iconf->ieee80211n || !hapd->iface->current_mode || + hapd->conf->disable_11n || is_6ghz_op_class(hapd->iconf->op_class)) + return eid; + + *pos++ = WLAN_EID_HT_CAP; + *pos++ = sizeof(*cap); + + cap = (struct ieee80211_ht_capabilities *) pos; + os_memset(cap, 0, sizeof(*cap)); + cap->ht_capabilities_info = host_to_le16(hapd->iconf->ht_capab); + cap->a_mpdu_params = hapd->iface->current_mode->a_mpdu_params; + os_memcpy(cap->supported_mcs_set, hapd->iface->current_mode->mcs_set, + 16); + + /* TODO: ht_extended_capabilities (now fully disabled) */ + /* TODO: tx_bf_capability_info (now fully disabled) */ + /* TODO: asel_capabilities (now fully disabled) */ + + pos += sizeof(*cap); + + if (hapd->iconf->obss_interval) { + struct ieee80211_obss_scan_parameters *scan_params; + + *pos++ = WLAN_EID_OVERLAPPING_BSS_SCAN_PARAMS; + *pos++ = sizeof(*scan_params); + + scan_params = (struct ieee80211_obss_scan_parameters *) pos; + os_memset(scan_params, 0, sizeof(*scan_params)); + scan_params->width_trigger_scan_interval = + host_to_le16(hapd->iconf->obss_interval); + + /* Fill in default values for remaining parameters + * (IEEE Std 802.11-2012, 8.4.2.61 and MIB defval) */ + scan_params->scan_passive_dwell = + host_to_le16(20); + scan_params->scan_active_dwell = + host_to_le16(10); + scan_params->scan_passive_total_per_channel = + host_to_le16(200); + scan_params->scan_active_total_per_channel = + host_to_le16(20); + scan_params->channel_transition_delay_factor = + host_to_le16(5); + scan_params->scan_activity_threshold = + host_to_le16(25); + + pos += sizeof(*scan_params); + } + + return pos; +} + + +u8 * hostapd_eid_ht_operation(struct hostapd_data *hapd, u8 *eid) +{ + struct ieee80211_ht_operation *oper; + u8 *pos = eid; + + if (!hapd->iconf->ieee80211n || hapd->conf->disable_11n || + is_6ghz_op_class(hapd->iconf->op_class)) + return eid; + + *pos++ = WLAN_EID_HT_OPERATION; + *pos++ = sizeof(*oper); + + oper = (struct ieee80211_ht_operation *) pos; + os_memset(oper, 0, sizeof(*oper)); + + oper->primary_chan = hapd->iconf->channel; + oper->operation_mode = host_to_le16(hapd->iface->ht_op_mode); + if (hapd->iconf->secondary_channel == 1) + oper->ht_param |= HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE | + HT_INFO_HT_PARAM_STA_CHNL_WIDTH; + if (hapd->iconf->secondary_channel == -1) + oper->ht_param |= HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW | + HT_INFO_HT_PARAM_STA_CHNL_WIDTH; + + pos += sizeof(*oper); + + return pos; +} + + +/* +op_mode +Set to 0 (HT pure) under the following conditions + - all STAs in the BSS are 20/40 MHz HT in 20/40 MHz BSS or + - all STAs in the BSS are 20 MHz HT in 20 MHz BSS +Set to 1 (HT non-member protection) if there may be non-HT STAs + in both the primary and the secondary channel +Set to 2 if only HT STAs are associated in BSS, + however and at least one 20 MHz HT STA is associated +Set to 3 (HT mixed mode) when one or more non-HT STAs are associated +*/ +int hostapd_ht_operation_update(struct hostapd_iface *iface) +{ + u16 cur_op_mode, new_op_mode; + int op_mode_changes = 0; + + if (!iface->conf->ieee80211n || iface->conf->ht_op_mode_fixed) + return 0; + + wpa_printf(MSG_DEBUG, "%s current operation mode=0x%X", + __func__, iface->ht_op_mode); + + if (!(iface->ht_op_mode & HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT) + && iface->num_sta_ht_no_gf) { + iface->ht_op_mode |= HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT; + op_mode_changes++; + } else if ((iface->ht_op_mode & + HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT) && + iface->num_sta_ht_no_gf == 0) { + iface->ht_op_mode &= ~HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT; + op_mode_changes++; + } + + if (!(iface->ht_op_mode & HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT) && + (iface->num_sta_no_ht || iface->olbc_ht)) { + iface->ht_op_mode |= HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT; + op_mode_changes++; + } else if ((iface->ht_op_mode & + HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT) && + (iface->num_sta_no_ht == 0 && !iface->olbc_ht)) { + iface->ht_op_mode &= ~HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT; + op_mode_changes++; + } + + if (iface->num_sta_no_ht) + new_op_mode = HT_PROT_NON_HT_MIXED; + else if (iface->conf->secondary_channel && iface->num_sta_ht_20mhz) + new_op_mode = HT_PROT_20MHZ_PROTECTION; + else if (iface->olbc_ht) + new_op_mode = HT_PROT_NONMEMBER_PROTECTION; + else + new_op_mode = HT_PROT_NO_PROTECTION; + + cur_op_mode = iface->ht_op_mode & HT_OPER_OP_MODE_HT_PROT_MASK; + if (cur_op_mode != new_op_mode) { + iface->ht_op_mode &= ~HT_OPER_OP_MODE_HT_PROT_MASK; + iface->ht_op_mode |= new_op_mode; + op_mode_changes++; + } + + wpa_printf(MSG_DEBUG, "%s new operation mode=0x%X changes=%d", + __func__, iface->ht_op_mode, op_mode_changes); + + return op_mode_changes; +} + + +static int is_40_allowed(struct hostapd_iface *iface, int channel) +{ + int pri_freq, sec_freq; + int affected_start, affected_end; + int pri = 2407 + 5 * channel; + + if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G) + return 1; + + pri_freq = hostapd_hw_get_freq(iface->bss[0], iface->conf->channel); + + if (iface->conf->secondary_channel > 0) + sec_freq = pri_freq + 20; + else + sec_freq = pri_freq - 20; + + affected_start = (pri_freq + sec_freq) / 2 - 25; + affected_end = (pri_freq + sec_freq) / 2 + 25; + if ((pri < affected_start || pri > affected_end)) + return 1; /* not within affected channel range */ + + wpa_printf(MSG_ERROR, "40 MHz affected channel range: [%d,%d] MHz", + affected_start, affected_end); + wpa_printf(MSG_ERROR, "Neighboring BSS: freq=%d", pri); + return 0; +} + + +void hostapd_2040_coex_action(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len) +{ + struct hostapd_iface *iface = hapd->iface; + struct ieee80211_2040_bss_coex_ie *bc_ie; + struct ieee80211_2040_intol_chan_report *ic_report; + int is_ht40_allowed = 1; + int i; + const u8 *start = (const u8 *) mgmt; + const u8 *data = start + IEEE80211_HDRLEN + 2; + struct sta_info *sta; + + wpa_printf(MSG_DEBUG, + "HT: Received 20/40 BSS Coexistence Management frame from " + MACSTR, MAC2STR(mgmt->sa)); + + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "hostapd_public_action - action=%d", + mgmt->u.action.u.public_action.action); + + if (!(iface->conf->ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET)) { + wpa_printf(MSG_DEBUG, + "Ignore 20/40 BSS Coexistence Management frame since 40 MHz capability is not enabled"); + return; + } + + if (len < IEEE80211_HDRLEN + 2 + sizeof(*bc_ie)) { + wpa_printf(MSG_DEBUG, + "Ignore too short 20/40 BSS Coexistence Management frame"); + return; + } + + /* 20/40 BSS Coexistence element */ + bc_ie = (struct ieee80211_2040_bss_coex_ie *) data; + if (bc_ie->element_id != WLAN_EID_20_40_BSS_COEXISTENCE || + bc_ie->length < 1) { + wpa_printf(MSG_DEBUG, "Unexpected IE (%u,%u) in coex report", + bc_ie->element_id, bc_ie->length); + return; + } + if (len < IEEE80211_HDRLEN + 2 + 2 + bc_ie->length) { + wpa_printf(MSG_DEBUG, + "Truncated 20/40 BSS Coexistence element"); + return; + } + data += 2 + bc_ie->length; + + wpa_printf(MSG_DEBUG, + "20/40 BSS Coexistence Information field: 0x%x (%s%s%s%s%s%s)", + bc_ie->coex_param, + (bc_ie->coex_param & BIT(0)) ? "[InfoReq]" : "", + (bc_ie->coex_param & BIT(1)) ? "[40MHzIntolerant]" : "", + (bc_ie->coex_param & BIT(2)) ? "[20MHzBSSWidthReq]" : "", + (bc_ie->coex_param & BIT(3)) ? "[OBSSScanExemptionReq]" : "", + (bc_ie->coex_param & BIT(4)) ? + "[OBSSScanExemptionGrant]" : "", + (bc_ie->coex_param & (BIT(5) | BIT(6) | BIT(7))) ? + "[Reserved]" : ""); + + if (bc_ie->coex_param & WLAN_20_40_BSS_COEX_20MHZ_WIDTH_REQ) { + /* Intra-BSS communication prohibiting 20/40 MHz BSS operation + */ + sta = ap_get_sta(hapd, mgmt->sa); + if (!sta || !(sta->flags & WLAN_STA_ASSOC)) { + wpa_printf(MSG_DEBUG, + "Ignore intra-BSS 20/40 BSS Coexistence Management frame from not-associated STA"); + return; + } + + hostapd_logger(hapd, mgmt->sa, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "20 MHz BSS width request bit is set in BSS coexistence information field"); + is_ht40_allowed = 0; + } + + if (bc_ie->coex_param & WLAN_20_40_BSS_COEX_40MHZ_INTOL) { + /* Inter-BSS communication prohibiting 20/40 MHz BSS operation + */ + hostapd_logger(hapd, mgmt->sa, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "40 MHz intolerant bit is set in BSS coexistence information field"); + is_ht40_allowed = 0; + } + + /* 20/40 BSS Intolerant Channel Report element (zero or more times) */ + while (start + len - data >= 3 && + data[0] == WLAN_EID_20_40_BSS_INTOLERANT && data[1] >= 1) { + u8 ielen = data[1]; + + if (ielen > start + len - data - 2) { + wpa_printf(MSG_DEBUG, + "Truncated 20/40 BSS Intolerant Channel Report element"); + return; + } + ic_report = (struct ieee80211_2040_intol_chan_report *) data; + wpa_printf(MSG_DEBUG, + "20/40 BSS Intolerant Channel Report: Operating Class %u", + ic_report->op_class); + + /* Go through the channel report to find any BSS there in the + * affected channel range */ + for (i = 0; i < ielen - 1; i++) { + u8 chan = ic_report->variable[i]; + + if (chan == iface->conf->channel) + continue; /* matching own primary channel */ + if (is_40_allowed(iface, chan)) + continue; /* not within affected channels */ + hostapd_logger(hapd, mgmt->sa, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "20_40_INTOLERANT channel %d reported", + chan); + is_ht40_allowed = 0; + } + + data += 2 + ielen; + } + wpa_printf(MSG_DEBUG, "is_ht40_allowed=%d num_sta_ht40_intolerant=%d", + is_ht40_allowed, iface->num_sta_ht40_intolerant); + + if (!is_ht40_allowed && + (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX)) { + if (iface->conf->secondary_channel) { + hostapd_logger(hapd, mgmt->sa, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Switching to 20 MHz operation"); + iface->conf->secondary_channel = 0; + ieee802_11_set_beacons(iface); + } + if (!iface->num_sta_ht40_intolerant && + iface->conf->obss_interval) { + unsigned int delay_time; + delay_time = OVERLAPPING_BSS_TRANS_DELAY_FACTOR * + iface->conf->obss_interval; + eloop_cancel_timeout(ap_ht2040_timeout, hapd->iface, + NULL); + eloop_register_timeout(delay_time, 0, ap_ht2040_timeout, + hapd->iface, NULL); + wpa_printf(MSG_DEBUG, + "Reschedule HT 20/40 timeout to occur in %u seconds", + delay_time); + } + } +} + + +u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *ht_capab) +{ + /* + * Disable HT caps for STAs associated to no-HT BSSes, or for stations + * that did not specify a valid WMM IE in the (Re)Association Request + * frame. + */ + if (!ht_capab || !(sta->flags & WLAN_STA_WMM) || + !hapd->iconf->ieee80211n || hapd->conf->disable_11n) { + sta->flags &= ~WLAN_STA_HT; + os_free(sta->ht_capabilities); + sta->ht_capabilities = NULL; + return WLAN_STATUS_SUCCESS; + } + + if (sta->ht_capabilities == NULL) { + sta->ht_capabilities = + os_zalloc(sizeof(struct ieee80211_ht_capabilities)); + if (sta->ht_capabilities == NULL) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + sta->flags |= WLAN_STA_HT; + os_memcpy(sta->ht_capabilities, ht_capab, + sizeof(struct ieee80211_ht_capabilities)); + + return WLAN_STATUS_SUCCESS; +} + + +void ht40_intolerant_add(struct hostapd_iface *iface, struct sta_info *sta) +{ + if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G) + return; + + wpa_printf(MSG_INFO, "HT: Forty MHz Intolerant is set by STA " MACSTR + " in Association Request", MAC2STR(sta->addr)); + + if (sta->ht40_intolerant_set) + return; + + sta->ht40_intolerant_set = 1; + iface->num_sta_ht40_intolerant++; + eloop_cancel_timeout(ap_ht2040_timeout, iface, NULL); + + if (iface->conf->secondary_channel && + (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX)) { + iface->conf->secondary_channel = 0; + ieee802_11_set_beacons(iface); + } +} + + +void ht40_intolerant_remove(struct hostapd_iface *iface, struct sta_info *sta) +{ + if (!sta->ht40_intolerant_set) + return; + + sta->ht40_intolerant_set = 0; + iface->num_sta_ht40_intolerant--; + + if (iface->num_sta_ht40_intolerant == 0 && + (iface->conf->ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET) && + (iface->drv_flags & WPA_DRIVER_FLAGS_HT_2040_COEX)) { + unsigned int delay_time = OVERLAPPING_BSS_TRANS_DELAY_FACTOR * + iface->conf->obss_interval; + wpa_printf(MSG_DEBUG, + "HT: Start 20->40 MHz transition timer (%d seconds)", + delay_time); + eloop_cancel_timeout(ap_ht2040_timeout, iface, NULL); + eloop_register_timeout(delay_time, 0, ap_ht2040_timeout, + iface, NULL); + } +} + + +static void update_sta_ht(struct hostapd_data *hapd, struct sta_info *sta) +{ + u16 ht_capab; + + ht_capab = le_to_host16(sta->ht_capabilities->ht_capabilities_info); + wpa_printf(MSG_DEBUG, "HT: STA " MACSTR " HT Capabilities Info: " + "0x%04x", MAC2STR(sta->addr), ht_capab); + if ((ht_capab & HT_CAP_INFO_GREEN_FIELD) == 0) { + if (!sta->no_ht_gf_set) { + sta->no_ht_gf_set = 1; + hapd->iface->num_sta_ht_no_gf++; + } + wpa_printf(MSG_DEBUG, "%s STA " MACSTR " - no greenfield, num " + "of non-gf stations %d", + __func__, MAC2STR(sta->addr), + hapd->iface->num_sta_ht_no_gf); + } + if ((ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET) == 0) { + if (!sta->ht_20mhz_set) { + sta->ht_20mhz_set = 1; + hapd->iface->num_sta_ht_20mhz++; + } + wpa_printf(MSG_DEBUG, "%s STA " MACSTR " - 20 MHz HT, num of " + "20MHz HT STAs %d", + __func__, MAC2STR(sta->addr), + hapd->iface->num_sta_ht_20mhz); + } + + if (ht_capab & HT_CAP_INFO_40MHZ_INTOLERANT) + ht40_intolerant_add(hapd->iface, sta); +} + + +static void update_sta_no_ht(struct hostapd_data *hapd, struct sta_info *sta) +{ + if (!sta->no_ht_set) { + sta->no_ht_set = 1; + hapd->iface->num_sta_no_ht++; + } + if (hapd->iconf->ieee80211n) { + wpa_printf(MSG_DEBUG, "%s STA " MACSTR " - no HT, num of " + "non-HT stations %d", + __func__, MAC2STR(sta->addr), + hapd->iface->num_sta_no_ht); + } +} + + +int update_ht_state(struct hostapd_data *hapd, struct sta_info *sta) +{ + if ((sta->flags & WLAN_STA_HT) && sta->ht_capabilities) + update_sta_ht(hapd, sta); + else + update_sta_no_ht(hapd, sta); + + return hostapd_ht_operation_update(hapd->iface); +} + + +void hostapd_get_ht_capab(struct hostapd_data *hapd, + struct ieee80211_ht_capabilities *ht_cap, + struct ieee80211_ht_capabilities *neg_ht_cap) +{ + u16 cap; + + if (ht_cap == NULL) + return; + os_memcpy(neg_ht_cap, ht_cap, sizeof(*neg_ht_cap)); + cap = le_to_host16(neg_ht_cap->ht_capabilities_info); + + /* + * Mask out HT features we don't support, but don't overwrite + * non-symmetric features like STBC and SMPS. Just because + * we're not in dynamic SMPS mode the STA might still be. + */ + cap &= (hapd->iconf->ht_capab | HT_CAP_INFO_RX_STBC_MASK | + HT_CAP_INFO_TX_STBC | HT_CAP_INFO_SMPS_MASK); + + /* + * STBC needs to be handled specially + * if we don't support RX STBC, mask out TX STBC in the STA's HT caps + * if we don't support TX STBC, mask out RX STBC in the STA's HT caps + */ + if (!(hapd->iconf->ht_capab & HT_CAP_INFO_RX_STBC_MASK)) + cap &= ~HT_CAP_INFO_TX_STBC; + if (!(hapd->iconf->ht_capab & HT_CAP_INFO_TX_STBC)) + cap &= ~HT_CAP_INFO_RX_STBC_MASK; + + neg_ht_cap->ht_capabilities_info = host_to_le16(cap); +} + + +void ap_ht2040_timeout(void *eloop_data, void *user_data) +{ + struct hostapd_iface *iface = eloop_data; + + wpa_printf(MSG_INFO, "Switching to 40 MHz operation"); + + iface->conf->secondary_channel = iface->secondary_ch; + ieee802_11_set_beacons(iface); +} diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c new file mode 100644 index 0000000..3dd3a6a --- /dev/null +++ b/src/ap/ieee802_11_shared.c @@ -0,0 +1,1228 @@ +/* + * hostapd / IEEE 802.11 Management + * Copyright (c) 2002-2024, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "common/ocv.h" +#include "common/wpa_ctrl.h" +#include "hostapd.h" +#include "sta_info.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "wpa_auth.h" +#include "dpp_hostapd.h" +#include "ieee802_11.h" + + +static u8 * hostapd_eid_timeout_interval(u8 *pos, u8 type, u32 value) +{ + *pos++ = WLAN_EID_TIMEOUT_INTERVAL; + *pos++ = 5; + *pos++ = type; + WPA_PUT_LE32(pos, value); + pos += 4; + + return pos; +} + + +u8 * hostapd_eid_assoc_comeback_time(struct hostapd_data *hapd, + struct sta_info *sta, u8 *eid) +{ + u32 timeout, tu; + struct os_reltime now, passed; + u8 type = WLAN_TIMEOUT_ASSOC_COMEBACK; + + os_get_reltime(&now); + os_reltime_sub(&now, &sta->sa_query_start, &passed); + tu = (passed.sec * 1000000 + passed.usec) / 1024; + if (hapd->conf->assoc_sa_query_max_timeout > tu) + timeout = hapd->conf->assoc_sa_query_max_timeout - tu; + else + timeout = 0; + if (timeout < hapd->conf->assoc_sa_query_max_timeout) + timeout++; /* add some extra time for local timers */ + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->test_assoc_comeback_type != -1) + type = hapd->conf->test_assoc_comeback_type; +#endif /* CONFIG_TESTING_OPTIONS */ + return hostapd_eid_timeout_interval(eid, type, timeout); +} + + +/* MLME-SAQuery.request */ +void ieee802_11_send_sa_query_req(struct hostapd_data *hapd, + const u8 *addr, const u8 *trans_id) +{ +#if defined(CONFIG_OCV) || defined(CONFIG_IEEE80211BE) + struct sta_info *sta = ap_get_sta(hapd, addr); +#endif /* CONFIG_OCV || CONFIG_IEEE80211BE */ + struct ieee80211_mgmt *mgmt; + u8 *oci_ie = NULL; + u8 oci_ie_len = 0; + u8 *end; + const u8 *own_addr = hapd->own_addr; + + wpa_printf(MSG_DEBUG, "IEEE 802.11: Sending SA Query Request to " + MACSTR, MAC2STR(addr)); + wpa_hexdump(MSG_DEBUG, "IEEE 802.11: SA Query Transaction ID", + trans_id, WLAN_SA_QUERY_TR_ID_LEN); + +#ifdef CONFIG_OCV + if (sta && wpa_auth_uses_ocv(sta->wpa_sm)) { + struct wpa_channel_info ci; + + if (hostapd_drv_channel_info(hapd, &ci) != 0) { + wpa_printf(MSG_WARNING, + "Failed to get channel info for OCI element in SA Query Request"); + return; + } +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->oci_freq_override_saquery_req) { + wpa_printf(MSG_INFO, + "TEST: Override OCI frequency %d -> %u MHz", + ci.frequency, + hapd->conf->oci_freq_override_saquery_req); + ci.frequency = + hapd->conf->oci_freq_override_saquery_req; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + oci_ie_len = OCV_OCI_EXTENDED_LEN; + oci_ie = os_zalloc(oci_ie_len); + if (!oci_ie) { + wpa_printf(MSG_WARNING, + "Failed to allocate buffer for OCI element in SA Query Request"); + return; + } + + if (ocv_insert_extended_oci(&ci, oci_ie) < 0) { + os_free(oci_ie); + return; + } + } +#endif /* CONFIG_OCV */ + + mgmt = os_zalloc(sizeof(*mgmt) + oci_ie_len); + if (!mgmt) { + wpa_printf(MSG_DEBUG, + "Failed to allocate buffer for SA Query Response frame"); + os_free(oci_ie); + return; + } + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) + own_addr = hapd->mld->mld_addr; +#endif /* CONFIG_IEEE80211BE */ + + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memcpy(mgmt->da, addr, ETH_ALEN); + os_memcpy(mgmt->sa, own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, own_addr, ETH_ALEN); + mgmt->u.action.category = WLAN_ACTION_SA_QUERY; + mgmt->u.action.u.sa_query_req.action = WLAN_SA_QUERY_REQUEST; + os_memcpy(mgmt->u.action.u.sa_query_req.trans_id, trans_id, + WLAN_SA_QUERY_TR_ID_LEN); + end = mgmt->u.action.u.sa_query_req.variable; +#ifdef CONFIG_OCV + if (oci_ie_len > 0) { + os_memcpy(end, oci_ie, oci_ie_len); + end += oci_ie_len; + } +#endif /* CONFIG_OCV */ + if (hostapd_drv_send_mlme(hapd, mgmt, end - (u8 *) mgmt, 0, NULL, 0, 0) + < 0) + wpa_printf(MSG_INFO, "ieee802_11_send_sa_query_req: send failed"); + + os_free(mgmt); + os_free(oci_ie); +} + + +static void ieee802_11_send_sa_query_resp(struct hostapd_data *hapd, + const u8 *sa, const u8 *trans_id) +{ + struct sta_info *sta; + struct ieee80211_mgmt *resp; + u8 *oci_ie = NULL; + u8 oci_ie_len = 0; + u8 *end; + const u8 *own_addr = hapd->own_addr; + + wpa_printf(MSG_DEBUG, "IEEE 802.11: Received SA Query Request from " + MACSTR, MAC2STR(sa)); + wpa_hexdump(MSG_DEBUG, "IEEE 802.11: SA Query Transaction ID", + trans_id, WLAN_SA_QUERY_TR_ID_LEN); + + sta = ap_get_sta(hapd, sa); + if (sta == NULL || !(sta->flags & WLAN_STA_ASSOC)) { + wpa_printf(MSG_DEBUG, "IEEE 802.11: Ignore SA Query Request " + "from unassociated STA " MACSTR, MAC2STR(sa)); + return; + } + +#ifdef CONFIG_OCV + if (wpa_auth_uses_ocv(sta->wpa_sm)) { + struct wpa_channel_info ci; + + if (hostapd_drv_channel_info(hapd, &ci) != 0) { + wpa_printf(MSG_WARNING, + "Failed to get channel info for OCI element in SA Query Response"); + return; + } +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->oci_freq_override_saquery_resp) { + wpa_printf(MSG_INFO, + "TEST: Override OCI frequency %d -> %u MHz", + ci.frequency, + hapd->conf->oci_freq_override_saquery_resp); + ci.frequency = + hapd->conf->oci_freq_override_saquery_resp; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + oci_ie_len = OCV_OCI_EXTENDED_LEN; + oci_ie = os_zalloc(oci_ie_len); + if (!oci_ie) { + wpa_printf(MSG_WARNING, + "Failed to allocate buffer for for OCI element in SA Query Response"); + return; + } + + if (ocv_insert_extended_oci(&ci, oci_ie) < 0) { + os_free(oci_ie); + return; + } + } +#endif /* CONFIG_OCV */ + + resp = os_zalloc(sizeof(*resp) + oci_ie_len); + if (!resp) { + wpa_printf(MSG_DEBUG, + "Failed to allocate buffer for SA Query Response frame"); + os_free(oci_ie); + return; + } + + wpa_printf(MSG_DEBUG, "IEEE 802.11: Sending SA Query Response to " + MACSTR, MAC2STR(sa)); + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) + own_addr = hapd->mld->mld_addr; +#endif /* CONFIG_IEEE80211BE */ + + resp->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memcpy(resp->da, sa, ETH_ALEN); + os_memcpy(resp->sa, own_addr, ETH_ALEN); + os_memcpy(resp->bssid, own_addr, ETH_ALEN); + resp->u.action.category = WLAN_ACTION_SA_QUERY; + resp->u.action.u.sa_query_req.action = WLAN_SA_QUERY_RESPONSE; + os_memcpy(resp->u.action.u.sa_query_req.trans_id, trans_id, + WLAN_SA_QUERY_TR_ID_LEN); + end = resp->u.action.u.sa_query_req.variable; +#ifdef CONFIG_OCV + if (oci_ie_len > 0) { + os_memcpy(end, oci_ie, oci_ie_len); + end += oci_ie_len; + } +#endif /* CONFIG_OCV */ + if (hostapd_drv_send_mlme(hapd, resp, end - (u8 *) resp, 0, NULL, 0, 0) + < 0) + wpa_printf(MSG_INFO, "ieee80211_mgmt_sa_query_request: send failed"); + + os_free(resp); + os_free(oci_ie); +} + + +void ieee802_11_sa_query_action(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len) +{ + struct sta_info *sta; + int i; + const u8 *sa = mgmt->sa; + const u8 action_type = mgmt->u.action.u.sa_query_resp.action; + const u8 *trans_id = mgmt->u.action.u.sa_query_resp.trans_id; + + if (((const u8 *) mgmt) + len < + mgmt->u.action.u.sa_query_resp.variable) { + wpa_printf(MSG_DEBUG, + "IEEE 802.11: Too short SA Query Action frame (len=%lu)", + (unsigned long) len); + return; + } + if (is_multicast_ether_addr(mgmt->da)) { + wpa_printf(MSG_DEBUG, + "IEEE 802.11: Ignore group-addressed SA Query frame (A1=" MACSTR " A2=" MACSTR ")", + MAC2STR(mgmt->da), MAC2STR(mgmt->sa)); + return; + } + + sta = ap_get_sta(hapd, sa); + +#ifdef CONFIG_OCV + if (sta && wpa_auth_uses_ocv(sta->wpa_sm)) { + struct ieee802_11_elems elems; + struct wpa_channel_info ci; + int tx_chanwidth; + int tx_seg1_idx; + size_t ies_len; + const u8 *ies; + + ies = mgmt->u.action.u.sa_query_resp.variable; + ies_len = len - (ies - (u8 *) mgmt); + if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == + ParseFailed) { + wpa_printf(MSG_DEBUG, + "SA Query: Failed to parse elements"); + return; + } + + if (hostapd_drv_channel_info(hapd, &ci) != 0) { + wpa_printf(MSG_WARNING, + "Failed to get channel info to validate received OCI in SA Query Action frame"); + return; + } + + if (get_sta_tx_parameters(sta->wpa_sm, + channel_width_to_int(ci.chanwidth), + ci.seg1_idx, &tx_chanwidth, + &tx_seg1_idx) < 0) + return; + + if (ocv_verify_tx_params(elems.oci, elems.oci_len, &ci, + tx_chanwidth, tx_seg1_idx) != + OCI_SUCCESS) { + wpa_msg(hapd->msg_ctx, MSG_INFO, OCV_FAILURE "addr=" + MACSTR " frame=saquery%s error=%s", + MAC2STR(sa), + action_type == WLAN_SA_QUERY_REQUEST ? + "req" : "resp", ocv_errorstr); + return; + } + } +#endif /* CONFIG_OCV */ + + if (action_type == WLAN_SA_QUERY_REQUEST) { + if (sta) + sta->post_csa_sa_query = 0; + ieee802_11_send_sa_query_resp(hapd, sa, trans_id); + return; + } + + if (action_type != WLAN_SA_QUERY_RESPONSE) { + wpa_printf(MSG_DEBUG, "IEEE 802.11: Unexpected SA Query " + "Action %d", action_type); + return; + } + + wpa_printf(MSG_DEBUG, "IEEE 802.11: Received SA Query Response from " + MACSTR, MAC2STR(sa)); + wpa_hexdump(MSG_DEBUG, "IEEE 802.11: SA Query Transaction ID", + trans_id, WLAN_SA_QUERY_TR_ID_LEN); + + /* MLME-SAQuery.confirm */ + + if (sta == NULL || sta->sa_query_trans_id == NULL) { + wpa_printf(MSG_DEBUG, "IEEE 802.11: No matching STA with " + "pending SA Query request found"); + return; + } + + for (i = 0; i < sta->sa_query_count; i++) { + if (os_memcmp(sta->sa_query_trans_id + + i * WLAN_SA_QUERY_TR_ID_LEN, + trans_id, WLAN_SA_QUERY_TR_ID_LEN) == 0) + break; + } + + if (i >= sta->sa_query_count) { + wpa_printf(MSG_DEBUG, "IEEE 802.11: No matching SA Query " + "transaction identifier found"); + return; + } + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "Reply to pending SA Query received"); + ap_sta_stop_sa_query(hapd, sta); +} + + +static void hostapd_ext_capab_byte(struct hostapd_data *hapd, u8 *pos, int idx, + bool mbssid_complete) +{ + *pos = 0x00; + + switch (idx) { + case 0: /* Bits 0-7 */ + if (hapd->iconf->obss_interval) + *pos |= 0x01; /* Bit 0 - Coexistence management */ + if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_CSA) + *pos |= 0x04; /* Bit 2 - Extended Channel Switching */ + break; + case 1: /* Bits 8-15 */ + if (hapd->conf->proxy_arp) + *pos |= 0x10; /* Bit 12 - Proxy ARP */ + if (hapd->conf->coloc_intf_reporting) { + /* Bit 13 - Collocated Interference Reporting */ + *pos |= 0x20; + } + break; + case 2: /* Bits 16-23 */ + if (hapd->conf->wnm_sleep_mode) + *pos |= 0x02; /* Bit 17 - WNM-Sleep Mode */ + if (hapd->conf->bss_transition) + *pos |= 0x08; /* Bit 19 - BSS Transition */ + if (hapd->iconf->mbssid) + *pos |= 0x40; /* Bit 22 - Multiple BSSID */ + break; + case 3: /* Bits 24-31 */ +#ifdef CONFIG_WNM_AP + *pos |= 0x02; /* Bit 25 - SSID List */ +#endif /* CONFIG_WNM_AP */ + if (hapd->conf->time_advertisement == 2) + *pos |= 0x08; /* Bit 27 - UTC TSF Offset */ + if (hapd->conf->interworking) + *pos |= 0x80; /* Bit 31 - Interworking */ + break; + case 4: /* Bits 32-39 */ + if (hapd->conf->qos_map_set_len) + *pos |= 0x01; /* Bit 32 - QoS Map */ + if (hapd->conf->tdls & TDLS_PROHIBIT) + *pos |= 0x40; /* Bit 38 - TDLS Prohibited */ + if (hapd->conf->tdls & TDLS_PROHIBIT_CHAN_SWITCH) { + /* Bit 39 - TDLS Channel Switching Prohibited */ + *pos |= 0x80; + } + break; + case 5: /* Bits 40-47 */ +#ifdef CONFIG_HS20 + if (hapd->conf->hs20) + *pos |= 0x40; /* Bit 46 - WNM-Notification */ +#endif /* CONFIG_HS20 */ +#ifdef CONFIG_MBO + if (hapd->conf->mbo_enabled) + *pos |= 0x40; /* Bit 46 - WNM-Notification */ +#endif /* CONFIG_MBO */ + break; + case 6: /* Bits 48-55 */ + if (hapd->conf->ssid.utf8_ssid) + *pos |= 0x01; /* Bit 48 - UTF-8 SSID */ + break; + case 7: /* Bits 56-63 */ + break; + case 8: /* Bits 64-71 */ + if (hapd->conf->ftm_responder) + *pos |= 0x40; /* Bit 70 - FTM responder */ + if (hapd->conf->ftm_initiator) + *pos |= 0x80; /* Bit 71 - FTM initiator */ + break; + case 9: /* Bits 72-79 */ +#ifdef CONFIG_FILS + if ((hapd->conf->wpa & WPA_PROTO_RSN) && + wpa_key_mgmt_fils(hapd->conf->wpa_key_mgmt)) + *pos |= 0x01; +#endif /* CONFIG_FILS */ +#ifdef CONFIG_IEEE80211AX + if (hostapd_get_he_twt_responder(hapd, IEEE80211_MODE_AP)) + *pos |= 0x40; /* Bit 78 - TWT responder */ +#endif /* CONFIG_IEEE80211AX */ + if (hostapd_get_ht_vht_twt_responder(hapd)) + *pos |= 0x40; /* Bit 78 - TWT responder */ + break; + case 10: /* Bits 80-87 */ +#ifdef CONFIG_SAE + if (hapd->conf->wpa && + wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt)) { + int in_use = hostapd_sae_pw_id_in_use(hapd->conf); + + if (in_use) + *pos |= 0x02; /* Bit 81 - SAE Password + * Identifiers In Use */ + if (in_use == 2) + *pos |= 0x04; /* Bit 82 - SAE Password + * Identifiers Used Exclusively */ + } +#endif /* CONFIG_SAE */ + if (hapd->conf->beacon_prot && + (hapd->iface->drv_flags & + WPA_DRIVER_FLAGS_BEACON_PROTECTION)) + *pos |= 0x10; /* Bit 84 - Beacon Protection Enabled */ + if (hapd->iconf->mbssid == ENHANCED_MBSSID_ENABLED) + *pos |= 0x08; /* Bit 83 - Enhanced multiple BSSID */ + if (mbssid_complete) + *pos |= 0x01; /* Bit 80 - Complete List of NonTxBSSID + * Profiles */ + break; + case 11: /* Bits 88-95 */ +#ifdef CONFIG_SAE_PK + if (hapd->conf->wpa && + wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) && + hostapd_sae_pk_exclusively(hapd->conf)) + *pos |= 0x01; /* Bit 88 - SAE PK Exclusively */ +#endif /* CONFIG_SAE_PK */ + break; + } +} + + +u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid, + bool mbssid_complete) +{ + u8 *pos = eid; + u8 len = EXT_CAPA_MAX_LEN, i; + + if (len < hapd->iface->extended_capa_len) + len = hapd->iface->extended_capa_len; + + *pos++ = WLAN_EID_EXT_CAPAB; + *pos++ = len; + for (i = 0; i < len; i++, pos++) { + hostapd_ext_capab_byte(hapd, pos, i, mbssid_complete); + + if (i < hapd->iface->extended_capa_len) { + *pos &= ~hapd->iface->extended_capa_mask[i]; + *pos |= hapd->iface->extended_capa[i]; + } + + if (i < EXT_CAPA_MAX_LEN) { + *pos &= ~hapd->conf->ext_capa_mask[i]; + *pos |= hapd->conf->ext_capa[i]; + } + + /* Clear bits 83 and 22 if EMA and MBSSID are not enabled + * otherwise association fails with some clients */ + if (i == 10 && hapd->iconf->mbssid < ENHANCED_MBSSID_ENABLED) + *pos &= ~0x08; + if (i == 2 && !hapd->iconf->mbssid) + *pos &= ~0x40; + } + + while (len > 0 && eid[1 + len] == 0) { + len--; + eid[1] = len; + } + if (len == 0) + return eid; + + return eid + 2 + len; +} + + +u8 * hostapd_eid_qos_map_set(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; + u8 len = hapd->conf->qos_map_set_len; + + if (!len) + return eid; + + *pos++ = WLAN_EID_QOS_MAP_SET; + *pos++ = len; + os_memcpy(pos, hapd->conf->qos_map_set, len); + pos += len; + + return pos; +} + + +u8 * hostapd_eid_interworking(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; +#ifdef CONFIG_INTERWORKING + u8 *len; + + if (!hapd->conf->interworking) + return eid; + + *pos++ = WLAN_EID_INTERWORKING; + len = pos++; + + *pos = hapd->conf->access_network_type; + if (hapd->conf->internet) + *pos |= INTERWORKING_ANO_INTERNET; + if (hapd->conf->asra) + *pos |= INTERWORKING_ANO_ASRA; + if (hapd->conf->esr) + *pos |= INTERWORKING_ANO_ESR; + if (hapd->conf->uesa) + *pos |= INTERWORKING_ANO_UESA; + pos++; + + if (hapd->conf->venue_info_set) { + *pos++ = hapd->conf->venue_group; + *pos++ = hapd->conf->venue_type; + } + + if (!is_zero_ether_addr(hapd->conf->hessid)) { + os_memcpy(pos, hapd->conf->hessid, ETH_ALEN); + pos += ETH_ALEN; + } + + *len = pos - len - 1; +#endif /* CONFIG_INTERWORKING */ + + return pos; +} + + +u8 * hostapd_eid_adv_proto(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; +#ifdef CONFIG_INTERWORKING + + /* TODO: Separate configuration for ANQP? */ + if (!hapd->conf->interworking) + return eid; + + *pos++ = WLAN_EID_ADV_PROTO; + *pos++ = 2; + *pos++ = 0x7F; /* Query Response Length Limit | PAME-BI */ + *pos++ = ACCESS_NETWORK_QUERY_PROTOCOL; +#endif /* CONFIG_INTERWORKING */ + + return pos; +} + + +u8 * hostapd_eid_roaming_consortium(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; +#ifdef CONFIG_INTERWORKING + u8 *len; + unsigned int i, count; + + if (!hapd->conf->interworking || + hapd->conf->roaming_consortium == NULL || + hapd->conf->roaming_consortium_count == 0) + return eid; + + *pos++ = WLAN_EID_ROAMING_CONSORTIUM; + len = pos++; + + /* Number of ANQP OIs (in addition to the max 3 listed here) */ + if (hapd->conf->roaming_consortium_count > 3 + 255) + *pos++ = 255; + else if (hapd->conf->roaming_consortium_count > 3) + *pos++ = hapd->conf->roaming_consortium_count - 3; + else + *pos++ = 0; + + /* OU #1 and #2 Lengths */ + *pos = hapd->conf->roaming_consortium[0].len; + if (hapd->conf->roaming_consortium_count > 1) + *pos |= hapd->conf->roaming_consortium[1].len << 4; + pos++; + + if (hapd->conf->roaming_consortium_count > 3) + count = 3; + else + count = hapd->conf->roaming_consortium_count; + + for (i = 0; i < count; i++) { + os_memcpy(pos, hapd->conf->roaming_consortium[i].oi, + hapd->conf->roaming_consortium[i].len); + pos += hapd->conf->roaming_consortium[i].len; + } + + *len = pos - len - 1; +#endif /* CONFIG_INTERWORKING */ + + return pos; +} + + +u8 * hostapd_eid_time_adv(struct hostapd_data *hapd, u8 *eid) +{ + if (hapd->conf->time_advertisement != 2) + return eid; + + if (hapd->time_adv == NULL && + hostapd_update_time_adv(hapd) < 0) + return eid; + + if (hapd->time_adv == NULL) + return eid; + + os_memcpy(eid, wpabuf_head(hapd->time_adv), + wpabuf_len(hapd->time_adv)); + eid += wpabuf_len(hapd->time_adv); + + return eid; +} + + +u8 * hostapd_eid_time_zone(struct hostapd_data *hapd, u8 *eid) +{ + size_t len; + + if (hapd->conf->time_advertisement != 2 || !hapd->conf->time_zone) + return eid; + + len = os_strlen(hapd->conf->time_zone); + + *eid++ = WLAN_EID_TIME_ZONE; + *eid++ = len; + os_memcpy(eid, hapd->conf->time_zone, len); + eid += len; + + return eid; +} + + +int hostapd_update_time_adv(struct hostapd_data *hapd) +{ + const int elen = 2 + 1 + 10 + 5 + 1; + struct os_time t; + struct os_tm tm; + u8 *pos; + + if (hapd->conf->time_advertisement != 2) + return 0; + + if (os_get_time(&t) < 0 || os_gmtime(t.sec, &tm) < 0) + return -1; + + if (!hapd->time_adv) { + hapd->time_adv = wpabuf_alloc(elen); + if (hapd->time_adv == NULL) + return -1; + pos = wpabuf_put(hapd->time_adv, elen); + } else + pos = wpabuf_mhead_u8(hapd->time_adv); + + *pos++ = WLAN_EID_TIME_ADVERTISEMENT; + *pos++ = 1 + 10 + 5 + 1; + + *pos++ = 2; /* UTC time at which the TSF timer is 0 */ + + /* Time Value at TSF 0 */ + /* FIX: need to calculate this based on the current TSF value */ + WPA_PUT_LE16(pos, tm.year); /* Year */ + pos += 2; + *pos++ = tm.month; /* Month */ + *pos++ = tm.day; /* Day of month */ + *pos++ = tm.hour; /* Hours */ + *pos++ = tm.min; /* Minutes */ + *pos++ = tm.sec; /* Seconds */ + WPA_PUT_LE16(pos, 0); /* Milliseconds (not used) */ + pos += 2; + *pos++ = 0; /* Reserved */ + + /* Time Error */ + /* TODO: fill in an estimate on the error */ + *pos++ = 0; + *pos++ = 0; + *pos++ = 0; + *pos++ = 0; + *pos++ = 0; + + *pos++ = hapd->time_update_counter++; + + return 0; +} + + +u8 * hostapd_eid_bss_max_idle_period(struct hostapd_data *hapd, u8 *eid, + u16 value) +{ + u8 *pos = eid; + +#ifdef CONFIG_WNM_AP + if (hapd->conf->ap_max_inactivity > 0 && + hapd->conf->bss_max_idle) { + unsigned int val; + *pos++ = WLAN_EID_BSS_MAX_IDLE_PERIOD; + *pos++ = 3; + val = hapd->conf->ap_max_inactivity; + if (val > 68000) + val = 68000; + val *= 1000; + val /= 1024; + if (val == 0) + val = 1; + if (val > 65535) + val = 65535; + if (value) + val = value; + WPA_PUT_LE16(pos, val); + pos += 2; + /* Set the Protected Keep-Alive Required bit based on + * configuration */ + *pos++ = hapd->conf->bss_max_idle == 2 ? BIT(0) : 0x00; + } +#endif /* CONFIG_WNM_AP */ + + return pos; +} + + +#ifdef CONFIG_MBO + +u8 * hostapd_eid_mbo_rssi_assoc_rej(struct hostapd_data *hapd, u8 *eid, + size_t len, int delta) +{ + u8 mbo[4]; + + mbo[0] = OCE_ATTR_ID_RSSI_BASED_ASSOC_REJECT; + mbo[1] = 2; + /* Delta RSSI */ + mbo[2] = delta; + /* Retry delay */ + mbo[3] = hapd->iconf->rssi_reject_assoc_timeout; + + return eid + mbo_add_ie(eid, len, mbo, 4); +} + + +u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, size_t len) +{ + u8 mbo[9], *mbo_pos = mbo; + u8 *pos = eid; + + if (!hapd->conf->mbo_enabled && + !OCE_STA_CFON_ENABLED(hapd) && !OCE_AP_ENABLED(hapd)) + return eid; + + if (hapd->conf->mbo_enabled) { + *mbo_pos++ = MBO_ATTR_ID_AP_CAPA_IND; + *mbo_pos++ = 1; + /* Not Cellular aware */ + *mbo_pos++ = 0; + } + + if (hapd->conf->mbo_enabled && hapd->mbo_assoc_disallow) { + *mbo_pos++ = MBO_ATTR_ID_ASSOC_DISALLOW; + *mbo_pos++ = 1; + *mbo_pos++ = hapd->mbo_assoc_disallow; + } + + if (OCE_STA_CFON_ENABLED(hapd) || OCE_AP_ENABLED(hapd)) { + u8 ctrl; + + ctrl = OCE_RELEASE; + if (OCE_STA_CFON_ENABLED(hapd) && !OCE_AP_ENABLED(hapd)) + ctrl |= OCE_IS_STA_CFON; + + *mbo_pos++ = OCE_ATTR_ID_CAPA_IND; + *mbo_pos++ = 1; + *mbo_pos++ = ctrl; + } + + pos += mbo_add_ie(pos, len, mbo, mbo_pos - mbo); + + return pos; +} + + +u8 hostapd_mbo_ie_len(struct hostapd_data *hapd) +{ + u8 len; + + if (!hapd->conf->mbo_enabled && + !OCE_STA_CFON_ENABLED(hapd) && !OCE_AP_ENABLED(hapd)) + return 0; + + /* + * MBO IE header (6) + Capability Indication attribute (3) + + * Association Disallowed attribute (3) = 12 + */ + len = 6; + if (hapd->conf->mbo_enabled) + len += 3 + (hapd->mbo_assoc_disallow ? 3 : 0); + + /* OCE capability indication attribute (3) */ + if (OCE_STA_CFON_ENABLED(hapd) || OCE_AP_ENABLED(hapd)) + len += 3; + + return len; +} + +#endif /* CONFIG_MBO */ + + +#ifdef CONFIG_OWE +static int hostapd_eid_owe_trans_enabled(struct hostapd_data *hapd) +{ + return hapd->conf->owe_transition_ssid_len > 0 && + !is_zero_ether_addr(hapd->conf->owe_transition_bssid); +} +#endif /* CONFIG_OWE */ + + +size_t hostapd_eid_owe_trans_len(struct hostapd_data *hapd) +{ +#ifdef CONFIG_OWE + if (!hostapd_eid_owe_trans_enabled(hapd)) + return 0; + return 6 + ETH_ALEN + 1 + hapd->conf->owe_transition_ssid_len; +#else /* CONFIG_OWE */ + return 0; +#endif /* CONFIG_OWE */ +} + + +u8 * hostapd_eid_owe_trans(struct hostapd_data *hapd, u8 *eid, + size_t len) +{ +#ifdef CONFIG_OWE + u8 *pos = eid; + size_t elen; + + if (hapd->conf->owe_transition_ifname[0] && + !hostapd_eid_owe_trans_enabled(hapd)) + hostapd_owe_trans_get_info(hapd); + + if (!hostapd_eid_owe_trans_enabled(hapd)) + return pos; + + elen = hostapd_eid_owe_trans_len(hapd); + if (len < elen) { + wpa_printf(MSG_DEBUG, + "OWE: Not enough room in the buffer for OWE IE"); + return pos; + } + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = elen - 2; + WPA_PUT_BE24(pos, OUI_WFA); + pos += 3; + *pos++ = OWE_OUI_TYPE; + os_memcpy(pos, hapd->conf->owe_transition_bssid, ETH_ALEN); + pos += ETH_ALEN; + *pos++ = hapd->conf->owe_transition_ssid_len; + os_memcpy(pos, hapd->conf->owe_transition_ssid, + hapd->conf->owe_transition_ssid_len); + pos += hapd->conf->owe_transition_ssid_len; + + return pos; +#else /* CONFIG_OWE */ + return eid; +#endif /* CONFIG_OWE */ +} + + +size_t hostapd_eid_dpp_cc_len(struct hostapd_data *hapd) +{ +#ifdef CONFIG_DPP2 + if (hostapd_dpp_configurator_connectivity(hapd)) + return 6; +#endif /* CONFIG_DPP2 */ + return 0; +} + + +u8 * hostapd_eid_dpp_cc(struct hostapd_data *hapd, u8 *eid, size_t len) +{ + u8 *pos = eid; + +#ifdef CONFIG_DPP2 + if (!hostapd_dpp_configurator_connectivity(hapd) || len < 6) + return pos; + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = 4; + WPA_PUT_BE24(pos, OUI_WFA); + pos += 3; + *pos++ = DPP_CC_OUI_TYPE; +#endif /* CONFIG_DPP2 */ + + return pos; +} + + +void ap_copy_sta_supp_op_classes(struct sta_info *sta, + const u8 *supp_op_classes, + size_t supp_op_classes_len) +{ + if (!supp_op_classes) + return; + os_free(sta->supp_op_classes); + sta->supp_op_classes = os_malloc(1 + supp_op_classes_len); + if (!sta->supp_op_classes) + return; + + sta->supp_op_classes[0] = supp_op_classes_len; + os_memcpy(sta->supp_op_classes + 1, supp_op_classes, + supp_op_classes_len); +} + + +u8 * hostapd_eid_fils_indic(struct hostapd_data *hapd, u8 *eid, int hessid) +{ + u8 *pos = eid; +#ifdef CONFIG_FILS + u8 *len; + u16 fils_info = 0; + size_t realms; + struct fils_realm *realm; + + if (!(hapd->conf->wpa & WPA_PROTO_RSN) || + !wpa_key_mgmt_fils(hapd->conf->wpa_key_mgmt)) + return pos; + + realms = dl_list_len(&hapd->conf->fils_realms); + if (realms > 7) + realms = 7; /* 3 bit count field limits this to max 7 */ + + *pos++ = WLAN_EID_FILS_INDICATION; + len = pos++; + /* TODO: B0..B2: Number of Public Key Identifiers */ + if (hapd->conf->erp_domain) { + /* B3..B5: Number of Realm Identifiers */ + fils_info |= realms << 3; + } + /* TODO: B6: FILS IP Address Configuration */ + if (hapd->conf->fils_cache_id_set) + fils_info |= BIT(7); + if (hessid && !is_zero_ether_addr(hapd->conf->hessid)) + fils_info |= BIT(8); /* HESSID Included */ + /* FILS Shared Key Authentication without PFS Supported */ + fils_info |= BIT(9); + if (hapd->conf->fils_dh_group) { + /* FILS Shared Key Authentication with PFS Supported */ + fils_info |= BIT(10); + } + /* TODO: B11: FILS Public Key Authentication Supported */ + /* B12..B15: Reserved */ + WPA_PUT_LE16(pos, fils_info); + pos += 2; + if (hapd->conf->fils_cache_id_set) { + os_memcpy(pos, hapd->conf->fils_cache_id, FILS_CACHE_ID_LEN); + pos += FILS_CACHE_ID_LEN; + } + if (hessid && !is_zero_ether_addr(hapd->conf->hessid)) { + os_memcpy(pos, hapd->conf->hessid, ETH_ALEN); + pos += ETH_ALEN; + } + + dl_list_for_each(realm, &hapd->conf->fils_realms, struct fils_realm, + list) { + if (realms == 0) + break; + realms--; + os_memcpy(pos, realm->hash, 2); + pos += 2; + } + *len = pos - len - 1; +#endif /* CONFIG_FILS */ + + return pos; +} + + +#ifdef CONFIG_OCV +int get_tx_parameters(struct sta_info *sta, int ap_max_chanwidth, + int ap_seg1_idx, int *bandwidth, int *seg1_idx) +{ + int ht_40mhz = 0; + int vht_80p80 = 0; + int requested_bw; + + if (sta->ht_capabilities) + ht_40mhz = !!(sta->ht_capabilities->ht_capabilities_info & + HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET); + + if (sta->vht_operation) { + struct ieee80211_vht_operation *oper = sta->vht_operation; + + /* + * If a VHT Operation element was present, use it to determine + * the supported channel bandwidth. + */ + if (oper->vht_op_info_chwidth == CHANWIDTH_USE_HT) { + requested_bw = ht_40mhz ? 40 : 20; + } else if (oper->vht_op_info_chan_center_freq_seg1_idx == 0) { + requested_bw = 80; + } else { + int diff; + + requested_bw = 160; + diff = abs((int) + oper->vht_op_info_chan_center_freq_seg0_idx - + (int) + oper->vht_op_info_chan_center_freq_seg1_idx); + vht_80p80 = oper->vht_op_info_chan_center_freq_seg1_idx + != 0 && diff > 16; + } + } else if (sta->vht_capabilities) { + struct ieee80211_vht_capabilities *capab; + int vht_chanwidth; + + capab = sta->vht_capabilities; + + /* + * If only the VHT Capabilities element is present (e.g., for + * normal clients), use it to determine the supported channel + * bandwidth. + */ + vht_chanwidth = capab->vht_capabilities_info & + VHT_CAP_SUPP_CHAN_WIDTH_MASK; + vht_80p80 = capab->vht_capabilities_info & + VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ; + + /* TODO: Also take into account Extended NSS BW Support field */ + requested_bw = vht_chanwidth ? 160 : 80; + } else { + requested_bw = ht_40mhz ? 40 : 20; + } + + *bandwidth = requested_bw < ap_max_chanwidth ? + requested_bw : ap_max_chanwidth; + + *seg1_idx = 0; + if (ap_seg1_idx && vht_80p80) + *seg1_idx = ap_seg1_idx; + + return 0; +} +#endif /* CONFIG_OCV */ + + +u8 * hostapd_eid_rsnxe(struct hostapd_data *hapd, u8 *eid, size_t len) +{ + u8 *pos = eid; + bool sae_pk = false; + u32 capab = 0, tmp; + size_t flen; + + if (!(hapd->conf->wpa & WPA_PROTO_RSN)) + return eid; + +#ifdef CONFIG_SAE_PK + sae_pk = hostapd_sae_pk_in_use(hapd->conf); +#endif /* CONFIG_SAE_PK */ + + if (wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) && + (hapd->conf->sae_pwe == SAE_PWE_HASH_TO_ELEMENT || + hapd->conf->sae_pwe == SAE_PWE_BOTH || + hostapd_sae_pw_id_in_use(hapd->conf) || sae_pk || + wpa_key_mgmt_sae_ext_key(hapd->conf->wpa_key_mgmt)) && + hapd->conf->sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK) { + capab |= BIT(WLAN_RSNX_CAPAB_SAE_H2E); +#ifdef CONFIG_SAE_PK + if (sae_pk) + capab |= BIT(WLAN_RSNX_CAPAB_SAE_PK); +#endif /* CONFIG_SAE_PK */ + } + + if (hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_LTF_AP) + capab |= BIT(WLAN_RSNX_CAPAB_SECURE_LTF); + if (hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_RTT_AP) + capab |= BIT(WLAN_RSNX_CAPAB_SECURE_RTT); + if (hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_PROT_RANGE_NEG_AP) + capab |= BIT(WLAN_RSNX_CAPAB_URNM_MFPR); + if (hapd->conf->ssid_protection) + capab |= BIT(WLAN_RSNX_CAPAB_SSID_PROTECTION); + + if (!capab) + return eid; /* no supported extended RSN capabilities */ + tmp = capab; + flen = 0; + while (tmp) { + flen++; + tmp >>= 8; + } + + if (len < 2 + flen) + return eid; /* no supported extended RSN capabilities */ + capab |= flen - 1; /* bit 0-3 = Field length (n - 1) */ + + *pos++ = WLAN_EID_RSNX; + *pos++ = flen; + while (capab) { + *pos++ = capab & 0xff; + capab >>= 8; + } + + return pos; +} + + +u16 check_ext_capab(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *ext_capab_ie, size_t ext_capab_ie_len) +{ + /* check for QoS Map support */ + if (ext_capab_ie_len >= 5) { + if (ext_capab_ie[4] & 0x01) + sta->qos_map_enabled = 1; + } + + if (ext_capab_ie_len > 0) { + sta->ecsa_supported = !!(ext_capab_ie[0] & BIT(2)); + os_free(sta->ext_capability); + sta->ext_capability = os_malloc(1 + ext_capab_ie_len); + if (sta->ext_capability) { + sta->ext_capability[0] = ext_capab_ie_len; + os_memcpy(sta->ext_capability + 1, ext_capab_ie, + ext_capab_ie_len); + } + } + + return WLAN_STATUS_SUCCESS; +} + + +struct sta_info * hostapd_ml_get_assoc_sta(struct hostapd_data *hapd, + struct sta_info *sta, + struct hostapd_data **assoc_hapd) +{ +#ifdef CONFIG_IEEE80211BE + struct hostapd_data *other_hapd = NULL; + struct sta_info *tmp_sta; + + if (!ap_sta_is_mld(hapd, sta)) + return NULL; + + *assoc_hapd = hapd; + + /* The station is the one on which the association was performed */ + if (sta->mld_assoc_link_id == hapd->mld_link_id) + return sta; + + other_hapd = hostapd_mld_get_link_bss(hapd, sta->mld_assoc_link_id); + if (!other_hapd) { + wpa_printf(MSG_DEBUG, "MLD: No link match for link_id=%u", + sta->mld_assoc_link_id); + return sta; + } + + /* + * Iterate over the stations and find the one with the matching link ID + * and association ID. + */ + for (tmp_sta = other_hapd->sta_list; tmp_sta; tmp_sta = tmp_sta->next) { + if (tmp_sta->mld_assoc_link_id == sta->mld_assoc_link_id && + tmp_sta->aid == sta->aid) { + *assoc_hapd = other_hapd; + return tmp_sta; + } + } +#endif /* CONFIG_IEEE80211BE */ + + return sta; +} + + +bool hostapd_get_ht_vht_twt_responder(struct hostapd_data *hapd) +{ + return hapd->iconf->ht_vht_twt_responder && + ((hapd->iconf->ieee80211n && !hapd->conf->disable_11n) || + (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac)) && + (hapd->iface->drv_flags2 & + WPA_DRIVER_FLAGS2_HT_VHT_TWT_RESPONDER); +} diff --git a/src/ap/ieee802_11_vht.c b/src/ap/ieee802_11_vht.c new file mode 100644 index 0000000..4dc325c --- /dev/null +++ b/src/ap/ieee802_11_vht.c @@ -0,0 +1,371 @@ +/* + * hostapd / IEEE 802.11ac VHT + * Copyright (c) 2002-2009, Jouni Malinen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of BSD license + * + * See README and COPYING for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "common/hw_features_common.h" +#include "hostapd.h" +#include "ap_config.h" +#include "sta_info.h" +#include "beacon.h" +#include "ieee802_11.h" +#include "dfs.h" + + +u8 * hostapd_eid_vht_capabilities(struct hostapd_data *hapd, u8 *eid, u32 nsts) +{ + struct ieee80211_vht_capabilities *cap; + struct hostapd_hw_modes *mode = hapd->iface->current_mode; + u8 *pos = eid; + + if (!mode || is_6ghz_op_class(hapd->iconf->op_class)) + return eid; + + if (mode->mode == HOSTAPD_MODE_IEEE80211G && hapd->conf->vendor_vht && + mode->vht_capab == 0 && hapd->iface->hw_features) { + int i; + + for (i = 0; i < hapd->iface->num_hw_features; i++) { + if (hapd->iface->hw_features[i].mode == + HOSTAPD_MODE_IEEE80211A) { + mode = &hapd->iface->hw_features[i]; + break; + } + } + } + + *pos++ = WLAN_EID_VHT_CAP; + *pos++ = sizeof(*cap); + + cap = (struct ieee80211_vht_capabilities *) pos; + os_memset(cap, 0, sizeof(*cap)); + cap->vht_capabilities_info = host_to_le32( + hapd->iface->conf->vht_capab); + + if (nsts != 0) { + u32 hapd_nsts; + + hapd_nsts = le_to_host32(cap->vht_capabilities_info); + hapd_nsts = (hapd_nsts >> VHT_CAP_BEAMFORMEE_STS_OFFSET) & 7; + cap->vht_capabilities_info &= + ~(host_to_le32(hapd_nsts << + VHT_CAP_BEAMFORMEE_STS_OFFSET)); + cap->vht_capabilities_info |= + host_to_le32(nsts << VHT_CAP_BEAMFORMEE_STS_OFFSET); + } + + /* Supported MCS set comes from hw */ + os_memcpy(&cap->vht_supported_mcs_set, mode->vht_mcs_set, 8); + + pos += sizeof(*cap); + + return pos; +} + + +u8 * hostapd_eid_vht_operation(struct hostapd_data *hapd, u8 *eid) +{ + struct ieee80211_vht_operation *oper; + u8 *pos = eid; + enum oper_chan_width oper_chwidth = + hostapd_get_oper_chwidth(hapd->iconf); + u8 seg0 = hapd->iconf->vht_oper_centr_freq_seg0_idx; + u8 seg1 = hapd->iconf->vht_oper_centr_freq_seg1_idx; +#ifdef CONFIG_IEEE80211BE + u16 punct_bitmap = hostapd_get_punct_bitmap(hapd); +#endif /* CONFIG_IEEE80211BE */ + + if (is_6ghz_op_class(hapd->iconf->op_class)) + return eid; + + *pos++ = WLAN_EID_VHT_OPERATION; + *pos++ = sizeof(*oper); + + oper = (struct ieee80211_vht_operation *) pos; + os_memset(oper, 0, sizeof(*oper)); + +#ifdef CONFIG_IEEE80211BE + if (punct_bitmap) { + punct_update_legacy_bw(punct_bitmap, + hapd->iconf->channel, + &oper_chwidth, &seg0, &seg1); + } +#endif /* CONFIG_IEEE80211BE */ + + /* + * center freq = 5 GHz + (5 * index) + * So index 42 gives center freq 5.210 GHz + * which is channel 42 in 5G band + */ + oper->vht_op_info_chan_center_freq_seg0_idx = seg0; + oper->vht_op_info_chan_center_freq_seg1_idx = seg1; + + oper->vht_op_info_chwidth = oper_chwidth; + if (oper_chwidth == CONF_OPER_CHWIDTH_160MHZ) { + /* + * Convert 160 MHz channel width to new style as interop + * workaround. + */ + oper->vht_op_info_chwidth = CHANWIDTH_80MHZ; + oper->vht_op_info_chan_center_freq_seg1_idx = + oper->vht_op_info_chan_center_freq_seg0_idx; + if (hapd->iconf->channel < + hapd->iconf->vht_oper_centr_freq_seg0_idx) + oper->vht_op_info_chan_center_freq_seg0_idx -= 8; + else + oper->vht_op_info_chan_center_freq_seg0_idx += 8; + } else if (oper_chwidth == CONF_OPER_CHWIDTH_80P80MHZ) { + /* + * Convert 80+80 MHz channel width to new style as interop + * workaround. + */ + oper->vht_op_info_chwidth = CHANWIDTH_80MHZ; + } + + /* VHT Basic MCS set comes from hw */ + /* Hard code 1 stream, MCS0-7 is a min Basic VHT MCS rates */ + oper->vht_basic_mcs_set = host_to_le16(0xfffc); + pos += sizeof(*oper); + + return pos; +} + + +static int check_valid_vht_mcs(struct hostapd_hw_modes *mode, + const u8 *sta_vht_capab) +{ + const struct ieee80211_vht_capabilities *vht_cap; + struct ieee80211_vht_capabilities ap_vht_cap; + u16 sta_rx_mcs_set, ap_tx_mcs_set; + int i; + + if (!mode) + return 1; + + /* + * Disable VHT caps for STAs for which there is not even a single + * allowed MCS in any supported number of streams, i.e., STA is + * advertising 3 (not supported) as VHT MCS rates for all supported + * stream cases. + */ + os_memcpy(&ap_vht_cap.vht_supported_mcs_set, mode->vht_mcs_set, + sizeof(ap_vht_cap.vht_supported_mcs_set)); + vht_cap = (const struct ieee80211_vht_capabilities *) sta_vht_capab; + + /* AP Tx MCS map vs. STA Rx MCS map */ + sta_rx_mcs_set = le_to_host16(vht_cap->vht_supported_mcs_set.rx_map); + ap_tx_mcs_set = le_to_host16(ap_vht_cap.vht_supported_mcs_set.tx_map); + + for (i = 0; i < VHT_RX_NSS_MAX_STREAMS; i++) { + if ((ap_tx_mcs_set & (0x3 << (i * 2))) == 3) + continue; + + if ((sta_rx_mcs_set & (0x3 << (i * 2))) == 3) + continue; + + return 1; + } + + wpa_printf(MSG_DEBUG, + "No matching VHT MCS found between AP TX and STA RX"); + return 0; +} + + +u16 copy_sta_vht_capab(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *vht_capab) +{ + /* Disable VHT caps for STAs associated to no-VHT BSSes. */ + if (!vht_capab || !(sta->flags & WLAN_STA_WMM) || + !hapd->iconf->ieee80211ac || hapd->conf->disable_11ac || + !check_valid_vht_mcs(hapd->iface->current_mode, vht_capab)) { + sta->flags &= ~WLAN_STA_VHT; + os_free(sta->vht_capabilities); + sta->vht_capabilities = NULL; + return WLAN_STATUS_SUCCESS; + } + + if (sta->vht_capabilities == NULL) { + sta->vht_capabilities = + os_zalloc(sizeof(struct ieee80211_vht_capabilities)); + if (sta->vht_capabilities == NULL) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + sta->flags |= WLAN_STA_VHT; + os_memcpy(sta->vht_capabilities, vht_capab, + sizeof(struct ieee80211_vht_capabilities)); + + return WLAN_STATUS_SUCCESS; +} + + +u16 copy_sta_vht_oper(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *vht_oper) +{ + if (!vht_oper) { + os_free(sta->vht_operation); + sta->vht_operation = NULL; + return WLAN_STATUS_SUCCESS; + } + + if (!sta->vht_operation) { + sta->vht_operation = + os_zalloc(sizeof(struct ieee80211_vht_operation)); + if (!sta->vht_operation) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + os_memcpy(sta->vht_operation, vht_oper, + sizeof(struct ieee80211_vht_operation)); + + return WLAN_STATUS_SUCCESS; +} + + +u16 copy_sta_vendor_vht(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *ie, size_t len) +{ + const u8 *vht_capab; + unsigned int vht_capab_len; + + if (!ie || len < 5 + 2 + sizeof(struct ieee80211_vht_capabilities) || + hapd->conf->disable_11ac) + goto no_capab; + + /* The VHT Capabilities element embedded in vendor VHT */ + vht_capab = ie + 5; + if (vht_capab[0] != WLAN_EID_VHT_CAP) + goto no_capab; + vht_capab_len = vht_capab[1]; + if (vht_capab_len < sizeof(struct ieee80211_vht_capabilities) || + (int) vht_capab_len > ie + len - vht_capab - 2) + goto no_capab; + vht_capab += 2; + + if (sta->vht_capabilities == NULL) { + sta->vht_capabilities = + os_zalloc(sizeof(struct ieee80211_vht_capabilities)); + if (sta->vht_capabilities == NULL) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + sta->flags |= WLAN_STA_VHT | WLAN_STA_VENDOR_VHT; + os_memcpy(sta->vht_capabilities, vht_capab, + sizeof(struct ieee80211_vht_capabilities)); + return WLAN_STATUS_SUCCESS; + +no_capab: + sta->flags &= ~WLAN_STA_VENDOR_VHT; + return WLAN_STATUS_SUCCESS; +} + + +u8 * hostapd_eid_vendor_vht(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; + + /* Vendor VHT is applicable only to 2.4 GHz */ + if (!hapd->iface->current_mode || + hapd->iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G) + return eid; + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = (5 + /* The Vendor OUI, type and subtype */ + 2 + sizeof(struct ieee80211_vht_capabilities) + + 2 + sizeof(struct ieee80211_vht_operation)); + + WPA_PUT_BE32(pos, (OUI_BROADCOM << 8) | VENDOR_VHT_TYPE); + pos += 4; + *pos++ = VENDOR_VHT_SUBTYPE; + pos = hostapd_eid_vht_capabilities(hapd, pos, 0); + pos = hostapd_eid_vht_operation(hapd, pos); + + return pos; +} + + +u16 set_sta_vht_opmode(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *vht_oper_notif) +{ + if (!vht_oper_notif) { + sta->flags &= ~WLAN_STA_VHT_OPMODE_ENABLED; + return WLAN_STATUS_SUCCESS; + } + + sta->flags |= WLAN_STA_VHT_OPMODE_ENABLED; + sta->vht_opmode = *vht_oper_notif; + return WLAN_STATUS_SUCCESS; +} + + +void hostapd_get_vht_capab(struct hostapd_data *hapd, + struct ieee80211_vht_capabilities *vht_cap, + struct ieee80211_vht_capabilities *neg_vht_cap) +{ + u32 cap, own_cap, sym_caps; + + if (vht_cap == NULL) + return; + os_memcpy(neg_vht_cap, vht_cap, sizeof(*neg_vht_cap)); + + cap = le_to_host32(neg_vht_cap->vht_capabilities_info); + own_cap = hapd->iconf->vht_capab; + + /* mask out symmetric VHT capabilities we don't support */ + sym_caps = VHT_CAP_SHORT_GI_80 | VHT_CAP_SHORT_GI_160; + cap &= ~sym_caps | (own_cap & sym_caps); + + /* mask out beamformer/beamformee caps if not supported */ + if (!(own_cap & VHT_CAP_SU_BEAMFORMER_CAPABLE)) + cap &= ~(VHT_CAP_SU_BEAMFORMEE_CAPABLE | + VHT_CAP_BEAMFORMEE_STS_MAX); + + if (!(own_cap & VHT_CAP_SU_BEAMFORMEE_CAPABLE)) + cap &= ~(VHT_CAP_SU_BEAMFORMER_CAPABLE | + VHT_CAP_SOUNDING_DIMENSION_MAX); + + if (!(own_cap & VHT_CAP_MU_BEAMFORMER_CAPABLE)) + cap &= ~VHT_CAP_MU_BEAMFORMEE_CAPABLE; + + if (!(own_cap & VHT_CAP_MU_BEAMFORMEE_CAPABLE)) + cap &= ~VHT_CAP_MU_BEAMFORMER_CAPABLE; + + /* mask channel widths we don't support */ + switch (own_cap & VHT_CAP_SUPP_CHAN_WIDTH_MASK) { + case VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ: + break; + case VHT_CAP_SUPP_CHAN_WIDTH_160MHZ: + if (cap & VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ) { + cap &= ~VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ; + cap |= VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; + } + break; + default: + cap &= ~VHT_CAP_SUPP_CHAN_WIDTH_MASK; + break; + } + + if (!(cap & VHT_CAP_SUPP_CHAN_WIDTH_MASK)) + cap &= ~VHT_CAP_SHORT_GI_160; + + /* + * if we don't support RX STBC, mask out TX STBC in the STA's HT caps + * if we don't support TX STBC, mask out RX STBC in the STA's HT caps + */ + if (!(own_cap & VHT_CAP_RXSTBC_MASK)) + cap &= ~VHT_CAP_TXSTBC; + if (!(own_cap & VHT_CAP_TXSTBC)) + cap &= ~VHT_CAP_RXSTBC_MASK; + + neg_vht_cap->vht_capabilities_info = host_to_le32(cap); +} diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c new file mode 100644 index 0000000..f4103ac --- /dev/null +++ b/src/ap/ieee802_1x.c @@ -0,0 +1,3144 @@ +/* + * hostapd / IEEE 802.1X-2004 Authenticator + * Copyright (c) 2002-2019, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#ifdef CONFIG_SQLITE +#include +#endif /* CONFIG_SQLITE */ + +#include "utils/common.h" +#include "utils/eloop.h" +#include "crypto/md5.h" +#include "crypto/crypto.h" +#include "crypto/random.h" +#include "common/ieee802_11_defs.h" +#include "radius/radius.h" +#include "radius/radius_client.h" +#include "eap_server/eap.h" +#include "eap_common/eap_wsc_common.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "eapol_auth/eapol_auth_sm_i.h" +#include "p2p/p2p.h" +#include "hostapd.h" +#include "accounting.h" +#include "sta_info.h" +#include "wpa_auth.h" +#include "preauth_auth.h" +#include "pmksa_cache_auth.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "wps_hostapd.h" +#include "hs20.h" +/* FIX: Not really a good thing to require ieee802_11.h here.. (FILS) */ +#include "ieee802_11.h" +#include "ieee802_1x.h" +#include "wpa_auth_kay.h" + + +#ifdef CONFIG_HS20 +static void ieee802_1x_wnm_notif_send(void *eloop_ctx, void *timeout_ctx); +#endif /* CONFIG_HS20 */ +static bool ieee802_1x_finished(struct hostapd_data *hapd, + struct sta_info *sta, int success, + int remediation, bool logoff); + + +static void ieee802_1x_send(struct hostapd_data *hapd, struct sta_info *sta, + u8 type, const u8 *data, size_t datalen) +{ + u8 *buf; + struct ieee802_1x_hdr *xhdr; + size_t len; + int encrypt = 0; + + len = sizeof(*xhdr) + datalen; + buf = os_zalloc(len); + if (!buf) { + wpa_printf(MSG_ERROR, "malloc() failed for %s(len=%lu)", + __func__, (unsigned long) len); + return; + } + + xhdr = (struct ieee802_1x_hdr *) buf; + xhdr->version = hapd->conf->eapol_version; +#ifdef CONFIG_MACSEC + if (xhdr->version > 2 && hapd->conf->macsec_policy == 0) + xhdr->version = 2; +#endif /* CONFIG_MACSEC */ + xhdr->type = type; + xhdr->length = host_to_be16(datalen); + + if (datalen > 0 && data != NULL) + os_memcpy(xhdr + 1, data, datalen); + + if (wpa_auth_pairwise_set(sta->wpa_sm)) + encrypt = 1; +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->ext_eapol_frame_io) { + size_t hex_len = 2 * len + 1; + char *hex = os_malloc(hex_len); + + if (hex) { + wpa_snprintf_hex(hex, hex_len, buf, len); + wpa_msg(hapd->msg_ctx, MSG_INFO, + "EAPOL-TX " MACSTR " %s", + MAC2STR(sta->addr), hex); + os_free(hex); + } + } else +#endif /* CONFIG_TESTING_OPTIONS */ + if (sta->flags & WLAN_STA_PREAUTH) { + rsn_preauth_send(hapd, sta, buf, len); + } else { + int link_id = -1; + +#ifdef CONFIG_IEEE80211BE + link_id = hapd->conf->mld_ap ? hapd->mld_link_id : -1; +#endif /* CONFIG_IEEE80211BE */ + hostapd_drv_hapd_send_eapol( + hapd, sta->addr, buf, len, + encrypt, hostapd_sta_flags_to_drv(sta->flags), link_id); + } + + os_free(buf); +} + + +static void ieee802_1x_set_authorized(struct hostapd_data *hapd, + struct sta_info *sta, + bool authorized, bool mld) +{ + int res; + bool update; + + if (sta->flags & WLAN_STA_PREAUTH) + return; + + update = ap_sta_set_authorized_flag(hapd, sta, authorized); + res = hostapd_set_authorized(hapd, sta, authorized); + if (update) + ap_sta_set_authorized_event(hapd, sta, authorized); + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, "%sauthorizing port", + authorized ? "" : "un"); + + if (!mld && res && errno != ENOENT) { + wpa_printf(MSG_DEBUG, "Could not set station " MACSTR + " flags for kernel driver (errno=%d).", + MAC2STR(sta->addr), errno); + } else if (mld && res) { + wpa_printf(MSG_DEBUG, + "MLD: Could not set station " MACSTR " flags", + MAC2STR(sta->addr)); + } + + if (authorized) { + os_get_reltime(&sta->connected_time); + accounting_sta_start(hapd, sta); + } +} + + +static void ieee802_1x_ml_set_sta_authorized(struct hostapd_data *hapd, + struct sta_info *sta, + bool authorized) +{ +#ifdef CONFIG_IEEE80211BE + unsigned int i, link_id; + + if (!hostapd_is_mld_ap(hapd)) + return; + + /* + * Authorizing the station should be done only in the station + * performing the association + */ + if (authorized && hapd->mld_link_id != sta->mld_assoc_link_id) + return; + + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + struct mld_link_info *link = &sta->mld_info.links[link_id]; + + if (!link->valid) + continue; + + for (i = 0; i < hapd->iface->interfaces->count; i++) { + struct sta_info *tmp_sta; + struct hostapd_data *tmp_hapd = + hapd->iface->interfaces->iface[i]->bss[0]; + + if (!hostapd_is_ml_partner(hapd, tmp_hapd)) + continue; + + for (tmp_sta = tmp_hapd->sta_list; tmp_sta; + tmp_sta = tmp_sta->next) { + if (tmp_sta == sta || + tmp_sta->mld_assoc_link_id != + sta->mld_assoc_link_id || + tmp_sta->aid != sta->aid) + continue; + + ieee802_1x_set_authorized(tmp_hapd, tmp_sta, + authorized, true); + break; + } + } + } +#endif /* CONFIG_IEEE80211BE */ +} + + + +void ieee802_1x_set_sta_authorized(struct hostapd_data *hapd, + struct sta_info *sta, int authorized) +{ + ieee802_1x_set_authorized(hapd, sta, authorized, false); + ieee802_1x_ml_set_sta_authorized(hapd, sta, !!authorized); +} + + +#ifdef CONFIG_WEP +#ifndef CONFIG_FIPS +#ifndef CONFIG_NO_RC4 + +static void ieee802_1x_tx_key_one(struct hostapd_data *hapd, + struct sta_info *sta, + int idx, int broadcast, + u8 *key_data, size_t key_len) +{ + u8 *buf, *ekey; + struct ieee802_1x_hdr *hdr; + struct ieee802_1x_eapol_key *key; + size_t len, ekey_len; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (!sm) + return; + + len = sizeof(*key) + key_len; + buf = os_zalloc(sizeof(*hdr) + len); + if (!buf) + return; + + hdr = (struct ieee802_1x_hdr *) buf; + key = (struct ieee802_1x_eapol_key *) (hdr + 1); + key->type = EAPOL_KEY_TYPE_RC4; + WPA_PUT_BE16(key->key_length, key_len); + wpa_get_ntp_timestamp(key->replay_counter); + if (os_memcmp(key->replay_counter, + hapd->last_1x_eapol_key_replay_counter, + IEEE8021X_REPLAY_COUNTER_LEN) <= 0) { + /* NTP timestamp did not increment from last EAPOL-Key frame; + * use previously used value + 1 instead. */ + inc_byte_array(hapd->last_1x_eapol_key_replay_counter, + IEEE8021X_REPLAY_COUNTER_LEN); + os_memcpy(key->replay_counter, + hapd->last_1x_eapol_key_replay_counter, + IEEE8021X_REPLAY_COUNTER_LEN); + } else { + os_memcpy(hapd->last_1x_eapol_key_replay_counter, + key->replay_counter, + IEEE8021X_REPLAY_COUNTER_LEN); + } + + if (random_get_bytes(key->key_iv, sizeof(key->key_iv))) { + wpa_printf(MSG_ERROR, "Could not get random numbers"); + os_free(buf); + return; + } + + key->key_index = idx | (broadcast ? 0 : BIT(7)); + if (hapd->conf->eapol_key_index_workaround) { + /* According to some information, WinXP Supplicant seems to + * interpret bit7 as an indication whether the key is to be + * activated, so make it possible to enable workaround that + * sets this bit for all keys. */ + key->key_index |= BIT(7); + } + + /* Key is encrypted using "Key-IV + MSK[0..31]" as the RC4-key and + * MSK[32..63] is used to sign the message. */ + if (!sm->eap_if->eapKeyData || sm->eap_if->eapKeyDataLen < 64) { + wpa_printf(MSG_ERROR, + "No eapKeyData available for encrypting and signing EAPOL-Key"); + os_free(buf); + return; + } + os_memcpy((u8 *) (key + 1), key_data, key_len); + ekey_len = sizeof(key->key_iv) + 32; + ekey = os_malloc(ekey_len); + if (!ekey) { + wpa_printf(MSG_ERROR, "Could not encrypt key"); + os_free(buf); + return; + } + os_memcpy(ekey, key->key_iv, sizeof(key->key_iv)); + os_memcpy(ekey + sizeof(key->key_iv), sm->eap_if->eapKeyData, 32); + rc4_skip(ekey, ekey_len, 0, (u8 *) (key + 1), key_len); + os_free(ekey); + + /* This header is needed here for HMAC-MD5, but it will be regenerated + * in ieee802_1x_send() */ + hdr->version = hapd->conf->eapol_version; +#ifdef CONFIG_MACSEC + if (hdr->version > 2) + hdr->version = 2; +#endif /* CONFIG_MACSEC */ + hdr->type = IEEE802_1X_TYPE_EAPOL_KEY; + hdr->length = host_to_be16(len); + hmac_md5(sm->eap_if->eapKeyData + 32, 32, buf, sizeof(*hdr) + len, + key->key_signature); + + wpa_printf(MSG_DEBUG, "IEEE 802.1X: Sending EAPOL-Key to " MACSTR + " (%s index=%d)", MAC2STR(sm->addr), + broadcast ? "broadcast" : "unicast", idx); + ieee802_1x_send(hapd, sta, IEEE802_1X_TYPE_EAPOL_KEY, (u8 *) key, len); + if (sta->eapol_sm) + sta->eapol_sm->dot1xAuthEapolFramesTx++; + os_free(buf); +} + + +static void ieee802_1x_tx_key(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct eapol_authenticator *eapol = hapd->eapol_auth; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (!sm || !sm->eap_if->eapKeyData) + return; + + wpa_printf(MSG_DEBUG, "IEEE 802.1X: Sending EAPOL-Key(s) to " MACSTR, + MAC2STR(sta->addr)); + +#ifndef CONFIG_NO_VLAN + if (sta->vlan_id > 0) { + wpa_printf(MSG_ERROR, "Using WEP with vlans is not supported."); + return; + } +#endif /* CONFIG_NO_VLAN */ + + if (eapol->default_wep_key) { + ieee802_1x_tx_key_one(hapd, sta, eapol->default_wep_key_idx, 1, + eapol->default_wep_key, + hapd->conf->default_wep_key_len); + } + + if (hapd->conf->individual_wep_key_len > 0) { + u8 *ikey; + + ikey = os_malloc(hapd->conf->individual_wep_key_len); + if (!ikey || + random_get_bytes(ikey, hapd->conf->individual_wep_key_len)) + { + wpa_printf(MSG_ERROR, + "Could not generate random individual WEP key"); + os_free(ikey); + return; + } + + wpa_hexdump_key(MSG_DEBUG, "Individual WEP key", + ikey, hapd->conf->individual_wep_key_len); + + ieee802_1x_tx_key_one(hapd, sta, 0, 0, ikey, + hapd->conf->individual_wep_key_len); + + /* TODO: set encryption in TX callback, i.e., only after STA + * has ACKed EAPOL-Key frame */ + if (hostapd_drv_set_key(hapd->conf->iface, hapd, WPA_ALG_WEP, + sta->addr, 0, 0, 1, NULL, 0, ikey, + hapd->conf->individual_wep_key_len, + KEY_FLAG_PAIRWISE_RX_TX)) { + wpa_printf(MSG_ERROR, + "Could not set individual WEP encryption"); + } + + os_free(ikey); + } +} + +#endif /* CONFIG_NO_RC4 */ +#endif /* CONFIG_FIPS */ +#endif /* CONFIG_WEP */ + + +const char *radius_mode_txt(struct hostapd_data *hapd) +{ + switch (hapd->iface->conf->hw_mode) { + case HOSTAPD_MODE_IEEE80211AD: + return "802.11ad"; + case HOSTAPD_MODE_IEEE80211A: + return "802.11a"; + case HOSTAPD_MODE_IEEE80211G: + return "802.11g"; + case HOSTAPD_MODE_IEEE80211B: + default: + return "802.11b"; + } +} + + +int radius_sta_rate(struct hostapd_data *hapd, struct sta_info *sta) +{ + int i; + u8 rate = 0; + + for (i = 0; i < sta->supported_rates_len; i++) + if ((sta->supported_rates[i] & 0x7f) > rate) + rate = sta->supported_rates[i] & 0x7f; + + return rate; +} + + +#ifndef CONFIG_NO_RADIUS +static void ieee802_1x_learn_identity(struct hostapd_data *hapd, + struct eapol_state_machine *sm, + const u8 *eap, size_t len) +{ + const u8 *identity; + size_t identity_len; + const struct eap_hdr *hdr = (const struct eap_hdr *) eap; + + if (len <= sizeof(struct eap_hdr) || + (hdr->code == EAP_CODE_RESPONSE && + eap[sizeof(struct eap_hdr)] != EAP_TYPE_IDENTITY) || + (hdr->code == EAP_CODE_INITIATE && + eap[sizeof(struct eap_hdr)] != EAP_ERP_TYPE_REAUTH) || + (hdr->code != EAP_CODE_RESPONSE && + hdr->code != EAP_CODE_INITIATE)) + return; + + eap_erp_update_identity(sm->eap, eap, len); + identity = eap_get_identity(sm->eap, &identity_len); + if (!identity) + return; + + /* Save station identity for future RADIUS packets */ + os_free(sm->identity); + sm->identity = (u8 *) dup_binstr(identity, identity_len); + if (!sm->identity) { + sm->identity_len = 0; + return; + } + + sm->identity_len = identity_len; + hostapd_logger(hapd, sm->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, "STA identity '%s'", sm->identity); + sm->dot1xAuthEapolRespIdFramesRx++; +} + + +static int add_common_radius_sta_attr_rsn(struct hostapd_data *hapd, + struct hostapd_radius_attr *req_attr, + struct sta_info *sta, + struct radius_msg *msg) +{ + u32 suite; + int ver, val; + + ver = wpa_auth_sta_wpa_version(sta->wpa_sm); + val = wpa_auth_get_pairwise(sta->wpa_sm); + suite = wpa_cipher_to_suite(ver, val); + if (val != -1 && + !hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_WLAN_PAIRWISE_CIPHER) && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_WLAN_PAIRWISE_CIPHER, + suite)) { + wpa_printf(MSG_ERROR, "Could not add WLAN-Pairwise-Cipher"); + return -1; + } + + suite = wpa_cipher_to_suite(((hapd->conf->wpa & 0x2) || + hapd->conf->osen) ? + WPA_PROTO_RSN : WPA_PROTO_WPA, + hapd->conf->wpa_group); + if (!hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_WLAN_GROUP_CIPHER) && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_WLAN_GROUP_CIPHER, + suite)) { + wpa_printf(MSG_ERROR, "Could not add WLAN-Group-Cipher"); + return -1; + } + + val = wpa_auth_sta_key_mgmt(sta->wpa_sm); + suite = wpa_akm_to_suite(val); + if (val != -1 && + !hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_WLAN_AKM_SUITE) && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_WLAN_AKM_SUITE, + suite)) { + wpa_printf(MSG_ERROR, "Could not add WLAN-AKM-Suite"); + return -1; + } + + if (hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + suite = wpa_cipher_to_suite(WPA_PROTO_RSN, + hapd->conf->group_mgmt_cipher); + if (!hostapd_config_get_radius_attr( + req_attr, RADIUS_ATTR_WLAN_GROUP_MGMT_CIPHER) && + !radius_msg_add_attr_int32( + msg, RADIUS_ATTR_WLAN_GROUP_MGMT_CIPHER, suite)) { + wpa_printf(MSG_ERROR, + "Could not add WLAN-Group-Mgmt-Cipher"); + return -1; + } + } + + return 0; +} + + +static int add_common_radius_sta_attr(struct hostapd_data *hapd, + struct hostapd_radius_attr *req_attr, + struct sta_info *sta, + struct radius_msg *msg) +{ + char buf[128]; + + if (!hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_SERVICE_TYPE) && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_SERVICE_TYPE, + RADIUS_SERVICE_TYPE_FRAMED)) { + wpa_printf(MSG_ERROR, "Could not add Service-Type"); + return -1; + } + + if (!hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_NAS_PORT) && + sta->aid > 0 && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT, sta->aid)) { + wpa_printf(MSG_ERROR, "Could not add NAS-Port"); + return -1; + } + + os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT, + MAC2STR(sta->addr)); + buf[sizeof(buf) - 1] = '\0'; + if (!radius_msg_add_attr(msg, RADIUS_ATTR_CALLING_STATION_ID, + (u8 *) buf, os_strlen(buf))) { + wpa_printf(MSG_ERROR, "Could not add Calling-Station-Id"); + return -1; + } + + if (sta->flags & WLAN_STA_PREAUTH) { + os_strlcpy(buf, "IEEE 802.11i Pre-Authentication", + sizeof(buf)); + } else { + os_snprintf(buf, sizeof(buf), "CONNECT %d%sMbps %s", + radius_sta_rate(hapd, sta) / 2, + (radius_sta_rate(hapd, sta) & 1) ? ".5" : "", + radius_mode_txt(hapd)); + buf[sizeof(buf) - 1] = '\0'; + } + if (!hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_CONNECT_INFO) && + !radius_msg_add_attr(msg, RADIUS_ATTR_CONNECT_INFO, + (u8 *) buf, os_strlen(buf))) { + wpa_printf(MSG_ERROR, "Could not add Connect-Info"); + return -1; + } + + if (sta->acct_session_id) { + os_snprintf(buf, sizeof(buf), "%016llX", + (unsigned long long) sta->acct_session_id); + if (!radius_msg_add_attr(msg, RADIUS_ATTR_ACCT_SESSION_ID, + (u8 *) buf, os_strlen(buf))) { + wpa_printf(MSG_ERROR, "Could not add Acct-Session-Id"); + return -1; + } + } + + if ((hapd->conf->wpa & 2) && + !hapd->conf->disable_pmksa_caching && + sta->eapol_sm && sta->eapol_sm->acct_multi_session_id) { + os_snprintf(buf, sizeof(buf), "%016llX", + (unsigned long long) + sta->eapol_sm->acct_multi_session_id); + if (!radius_msg_add_attr( + msg, RADIUS_ATTR_ACCT_MULTI_SESSION_ID, + (u8 *) buf, os_strlen(buf))) { + wpa_printf(MSG_INFO, + "Could not add Acct-Multi-Session-Id"); + return -1; + } + } + +#ifdef CONFIG_IEEE80211R_AP + if (hapd->conf->wpa && wpa_key_mgmt_ft(hapd->conf->wpa_key_mgmt) && + sta->wpa_sm && + (wpa_key_mgmt_ft(wpa_auth_sta_key_mgmt(sta->wpa_sm)) || + sta->auth_alg == WLAN_AUTH_FT) && + !hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_MOBILITY_DOMAIN_ID) && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_MOBILITY_DOMAIN_ID, + WPA_GET_BE16( + hapd->conf->mobility_domain))) { + wpa_printf(MSG_ERROR, "Could not add Mobility-Domain-Id"); + return -1; + } +#endif /* CONFIG_IEEE80211R_AP */ + + if ((hapd->conf->wpa || hapd->conf->osen) && sta->wpa_sm && + add_common_radius_sta_attr_rsn(hapd, req_attr, sta, msg) < 0) + return -1; + + return 0; +} + + +int add_common_radius_attr(struct hostapd_data *hapd, + struct hostapd_radius_attr *req_attr, + struct sta_info *sta, + struct radius_msg *msg) +{ + char buf[128]; + struct hostapd_radius_attr *attr; + int len; + + if (!hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_NAS_IP_ADDRESS) && + hapd->conf->own_ip_addr.af == AF_INET && + !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS, + (u8 *) &hapd->conf->own_ip_addr.u.v4, 4)) { + wpa_printf(MSG_ERROR, "Could not add NAS-IP-Address"); + return -1; + } + +#ifdef CONFIG_IPV6 + if (!hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_NAS_IPV6_ADDRESS) && + hapd->conf->own_ip_addr.af == AF_INET6 && + !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS, + (u8 *) &hapd->conf->own_ip_addr.u.v6, 16)) { + wpa_printf(MSG_ERROR, "Could not add NAS-IPv6-Address"); + return -1; + } +#endif /* CONFIG_IPV6 */ + + if (!hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_NAS_IDENTIFIER) && + hapd->conf->nas_identifier && + !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IDENTIFIER, + (u8 *) hapd->conf->nas_identifier, + os_strlen(hapd->conf->nas_identifier))) { + wpa_printf(MSG_ERROR, "Could not add NAS-Identifier"); + return -1; + } + + len = os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT ":", + MAC2STR(hapd->own_addr)); + os_memcpy(&buf[len], hapd->conf->ssid.ssid, + hapd->conf->ssid.ssid_len); + len += hapd->conf->ssid.ssid_len; + if (!hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_CALLED_STATION_ID) && + !radius_msg_add_attr(msg, RADIUS_ATTR_CALLED_STATION_ID, + (u8 *) buf, len)) { + wpa_printf(MSG_ERROR, "Could not add Called-Station-Id"); + return -1; + } + + if (!hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_NAS_PORT_TYPE) && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT_TYPE, + RADIUS_NAS_PORT_TYPE_IEEE_802_11)) { + wpa_printf(MSG_ERROR, "Could not add NAS-Port-Type"); + return -1; + } + +#ifdef CONFIG_INTERWORKING + if (hapd->conf->interworking && + !is_zero_ether_addr(hapd->conf->hessid)) { + os_snprintf(buf, sizeof(buf), RADIUS_802_1X_ADDR_FORMAT, + MAC2STR(hapd->conf->hessid)); + buf[sizeof(buf) - 1] = '\0'; + if (!hostapd_config_get_radius_attr(req_attr, + RADIUS_ATTR_WLAN_HESSID) && + !radius_msg_add_attr(msg, RADIUS_ATTR_WLAN_HESSID, + (u8 *) buf, os_strlen(buf))) { + wpa_printf(MSG_ERROR, "Could not add WLAN-HESSID"); + return -1; + } + } +#endif /* CONFIG_INTERWORKING */ + + if (sta && add_common_radius_sta_attr(hapd, req_attr, sta, msg) < 0) + return -1; + + for (attr = req_attr; attr; attr = attr->next) { + if (!radius_msg_add_attr(msg, attr->type, + wpabuf_head(attr->val), + wpabuf_len(attr->val))) { + wpa_printf(MSG_ERROR, "Could not add RADIUS attribute"); + return -1; + } + } + + return 0; +} + + +int add_sqlite_radius_attr(struct hostapd_data *hapd, struct sta_info *sta, + struct radius_msg *msg, int acct) +{ +#ifdef CONFIG_SQLITE + const char *attrtxt; + char addrtxt[3 * ETH_ALEN]; + char *sql; + sqlite3_stmt *stmt = NULL; + + if (!hapd->rad_attr_db) + return 0; + + os_snprintf(addrtxt, sizeof(addrtxt), MACSTR, MAC2STR(sta->addr)); + + sql = "SELECT attr FROM radius_attributes WHERE sta=? AND (reqtype=? OR reqtype IS NULL);"; + if (sqlite3_prepare_v2(hapd->rad_attr_db, sql, os_strlen(sql), &stmt, + NULL) != SQLITE_OK) { + wpa_printf(MSG_ERROR, "DB: Failed to prepare SQL statement: %s", + sqlite3_errmsg(hapd->rad_attr_db)); + return -1; + } + sqlite3_bind_text(stmt, 1, addrtxt, os_strlen(addrtxt), SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, acct ? "acct" : "auth", 4, SQLITE_STATIC); + while (sqlite3_step(stmt) == SQLITE_ROW) { + struct hostapd_radius_attr *attr; + struct radius_attr_hdr *hdr; + + attrtxt = (const char *) sqlite3_column_text(stmt, 0); + attr = hostapd_parse_radius_attr(attrtxt); + if (!attr) { + wpa_printf(MSG_ERROR, + "Skipping invalid attribute from SQL: %s", + attrtxt); + continue; + } + wpa_printf(MSG_DEBUG, "Adding RADIUS attribute from SQL: %s", + attrtxt); + hdr = radius_msg_add_attr(msg, attr->type, + wpabuf_head(attr->val), + wpabuf_len(attr->val)); + hostapd_config_free_radius_attr(attr); + if (!hdr) { + wpa_printf(MSG_ERROR, + "Could not add RADIUS attribute from SQL"); + continue; + } + } + + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + sqlite3_finalize(stmt); +#endif /* CONFIG_SQLITE */ + + return 0; +} + + +void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *eap, size_t len) +{ + struct radius_msg *msg; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (!sm) + return; + + ieee802_1x_learn_identity(hapd, sm, eap, len); + + wpa_printf(MSG_DEBUG, "Encapsulating EAP message into a RADIUS packet"); + + sm->radius_identifier = radius_client_get_id(hapd->radius); + msg = radius_msg_new(RADIUS_CODE_ACCESS_REQUEST, + sm->radius_identifier); + if (!msg) { + wpa_printf(MSG_INFO, "Could not create new RADIUS packet"); + return; + } + + if (radius_msg_make_authenticator(msg) < 0) { + wpa_printf(MSG_INFO, "Could not make Request Authenticator"); + goto fail; + } + + if (!radius_msg_add_msg_auth(msg)) + goto fail; + + if (sm->identity && + !radius_msg_add_attr(msg, RADIUS_ATTR_USER_NAME, + sm->identity, sm->identity_len)) { + wpa_printf(MSG_INFO, "Could not add User-Name"); + goto fail; + } + + if (add_common_radius_attr(hapd, hapd->conf->radius_auth_req_attr, sta, + msg) < 0) + goto fail; + + if (sta && add_sqlite_radius_attr(hapd, sta, msg, 0) < 0) + goto fail; + + /* TODO: should probably check MTU from driver config; 2304 is max for + * IEEE 802.11, but use 1400 to avoid problems with too large packets + */ + if (!hostapd_config_get_radius_attr(hapd->conf->radius_auth_req_attr, + RADIUS_ATTR_FRAMED_MTU) && + !radius_msg_add_attr_int32(msg, RADIUS_ATTR_FRAMED_MTU, 1400)) { + wpa_printf(MSG_INFO, "Could not add Framed-MTU"); + goto fail; + } + + if (!radius_msg_add_eap(msg, eap, len)) { + wpa_printf(MSG_INFO, "Could not add EAP-Message"); + goto fail; + } + + /* State attribute must be copied if and only if this packet is + * Access-Request reply to the previous Access-Challenge */ + if (sm->last_recv_radius && + radius_msg_get_hdr(sm->last_recv_radius)->code == + RADIUS_CODE_ACCESS_CHALLENGE) { + int res = radius_msg_copy_attr(msg, sm->last_recv_radius, + RADIUS_ATTR_STATE); + if (res < 0) { + wpa_printf(MSG_INFO, + "Could not copy State attribute from previous Access-Challenge"); + goto fail; + } + if (res > 0) + wpa_printf(MSG_DEBUG, "Copied RADIUS State Attribute"); + } + + if (hapd->conf->radius_request_cui) { + const u8 *cui; + size_t cui_len; + /* Add previously learned CUI or nul CUI to request CUI */ + if (sm->radius_cui) { + cui = wpabuf_head(sm->radius_cui); + cui_len = wpabuf_len(sm->radius_cui); + } else { + cui = (const u8 *) "\0"; + cui_len = 1; + } + if (!radius_msg_add_attr(msg, + RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, + cui, cui_len)) { + wpa_printf(MSG_ERROR, "Could not add CUI"); + goto fail; + } + } + +#ifdef CONFIG_HS20 + if (hapd->conf->hs20) { + u8 ver = hapd->conf->hs20_release - 1; + + if (!radius_msg_add_wfa( + msg, RADIUS_VENDOR_ATTR_WFA_HS20_AP_VERSION, + &ver, 1)) { + wpa_printf(MSG_ERROR, + "Could not add HS 2.0 AP version"); + goto fail; + } + + if (sta->hs20_ie && wpabuf_len(sta->hs20_ie) > 0) { + const u8 *pos; + u8 buf[3]; + u16 id; + + pos = wpabuf_head_u8(sta->hs20_ie); + buf[0] = (*pos) >> 4; + if (((*pos) & HS20_PPS_MO_ID_PRESENT) && + wpabuf_len(sta->hs20_ie) >= 3) + id = WPA_GET_LE16(pos + 1); + else + id = 0; + WPA_PUT_BE16(buf + 1, id); + if (!radius_msg_add_wfa( + msg, + RADIUS_VENDOR_ATTR_WFA_HS20_STA_VERSION, + buf, sizeof(buf))) { + wpa_printf(MSG_ERROR, + "Could not add HS 2.0 STA version"); + goto fail; + } + } + + if (sta->roaming_consortium && + !radius_msg_add_wfa( + msg, RADIUS_VENDOR_ATTR_WFA_HS20_ROAMING_CONSORTIUM, + wpabuf_head(sta->roaming_consortium), + wpabuf_len(sta->roaming_consortium))) { + wpa_printf(MSG_ERROR, + "Could not add HS 2.0 Roaming Consortium"); + goto fail; + } + + if (hapd->conf->t_c_filename) { + be32 timestamp; + + if (!radius_msg_add_wfa( + msg, + RADIUS_VENDOR_ATTR_WFA_HS20_T_C_FILENAME, + (const u8 *) hapd->conf->t_c_filename, + os_strlen(hapd->conf->t_c_filename))) { + wpa_printf(MSG_ERROR, + "Could not add HS 2.0 T&C Filename"); + goto fail; + } + + timestamp = host_to_be32(hapd->conf->t_c_timestamp); + if (!radius_msg_add_wfa( + msg, + RADIUS_VENDOR_ATTR_WFA_HS20_TIMESTAMP, + (const u8 *) ×tamp, + sizeof(timestamp))) { + wpa_printf(MSG_ERROR, + "Could not add HS 2.0 Timestamp"); + goto fail; + } + } + } +#endif /* CONFIG_HS20 */ + + if (radius_client_send(hapd->radius, msg, RADIUS_AUTH, sta->addr) < 0) + goto fail; + + return; + + fail: + radius_msg_free(msg); +} +#endif /* CONFIG_NO_RADIUS */ + + +static void handle_eap_response(struct hostapd_data *hapd, + struct sta_info *sta, struct eap_hdr *eap, + size_t len) +{ + u8 type, *data; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (!sm) + return; + + data = (u8 *) (eap + 1); + + if (len < sizeof(*eap) + 1) { + wpa_printf(MSG_INFO, "%s: too short response data", __func__); + return; + } + + sm->eap_type_supp = type = data[0]; + + hostapd_logger(hapd, sm->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, "received EAP packet (code=%d " + "id=%d len=%d) from STA: EAP Response-%s (%d)", + eap->code, eap->identifier, be_to_host16(eap->length), + eap_server_get_name(0, type), type); + + sm->dot1xAuthEapolRespFramesRx++; + + wpabuf_free(sm->eap_if->eapRespData); + sm->eap_if->eapRespData = wpabuf_alloc_copy(eap, len); + sm->eapolEap = true; +} + + +static void handle_eap_initiate(struct hostapd_data *hapd, + struct sta_info *sta, struct eap_hdr *eap, + size_t len) +{ +#ifdef CONFIG_ERP + u8 type, *data; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (!sm) + return; + + if (len < sizeof(*eap) + 1) { + wpa_printf(MSG_INFO, "%s: too short response data", __func__); + return; + } + + data = (u8 *) (eap + 1); + type = data[0]; + + hostapd_logger(hapd, sm->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, + "received EAP packet (code=%d id=%d len=%d) from STA: EAP Initiate type %u", + eap->code, eap->identifier, be_to_host16(eap->length), + type); + + wpabuf_free(sm->eap_if->eapRespData); + sm->eap_if->eapRespData = wpabuf_alloc_copy(eap, len); + sm->eapolEap = true; +#endif /* CONFIG_ERP */ +} + + +#ifndef CONFIG_NO_STDOUT_DEBUG +static const char * eap_code_str(u8 code) +{ + switch (code) { + case EAP_CODE_REQUEST: + return "request"; + case EAP_CODE_RESPONSE: + return "response"; + case EAP_CODE_SUCCESS: + return "success"; + case EAP_CODE_FAILURE: + return "failure"; + case EAP_CODE_INITIATE: + return "initiate"; + case EAP_CODE_FINISH: + return "finish"; + default: + return "unknown"; + } +} +#endif /* CONFIG_NO_STDOUT_DEBUG */ + + +/* Process incoming EAP packet from Supplicant */ +static void handle_eap(struct hostapd_data *hapd, struct sta_info *sta, + u8 *buf, size_t len) +{ + struct eap_hdr *eap; + u16 eap_len; + + if (len < sizeof(*eap)) { + wpa_printf(MSG_INFO, " too short EAP packet"); + return; + } + + eap = (struct eap_hdr *) buf; + + eap_len = be_to_host16(eap->length); + wpa_printf(MSG_DEBUG, "EAP: code=%d (%s) identifier=%d length=%d", + eap->code, eap_code_str(eap->code), eap->identifier, + eap_len); + if (eap_len < sizeof(*eap)) { + wpa_printf(MSG_DEBUG, " Invalid EAP length"); + return; + } else if (eap_len > len) { + wpa_printf(MSG_DEBUG, + " Too short frame to contain this EAP packet"); + return; + } else if (eap_len < len) { + wpa_printf(MSG_DEBUG, + " Ignoring %lu extra bytes after EAP packet", + (unsigned long) len - eap_len); + } + + switch (eap->code) { + case EAP_CODE_RESPONSE: + handle_eap_response(hapd, sta, eap, eap_len); + break; + case EAP_CODE_INITIATE: + handle_eap_initiate(hapd, sta, eap, eap_len); + break; + } +} + + +struct eapol_state_machine * +ieee802_1x_alloc_eapol_sm(struct hostapd_data *hapd, struct sta_info *sta) +{ + int flags = 0; + + if (sta->flags & WLAN_STA_PREAUTH) + flags |= EAPOL_SM_PREAUTH; + if (sta->wpa_sm) { + flags |= EAPOL_SM_USES_WPA; + if (wpa_auth_sta_get_pmksa(sta->wpa_sm)) + flags |= EAPOL_SM_FROM_PMKSA_CACHE; + } + return eapol_auth_alloc(hapd->eapol_auth, sta->addr, flags, + sta->wps_ie, sta->p2p_ie, sta, + sta->identity, sta->radius_cui); +} + + +static void ieee802_1x_save_eapol(struct sta_info *sta, const u8 *buf, + size_t len, enum frame_encryption encrypted) +{ + if (sta->pending_eapol_rx) { + wpabuf_free(sta->pending_eapol_rx->buf); + } else { + sta->pending_eapol_rx = + os_malloc(sizeof(*sta->pending_eapol_rx)); + if (!sta->pending_eapol_rx) + return; + } + + sta->pending_eapol_rx->buf = wpabuf_alloc_copy(buf, len); + if (!sta->pending_eapol_rx->buf) { + os_free(sta->pending_eapol_rx); + sta->pending_eapol_rx = NULL; + return; + } + + sta->pending_eapol_rx->encrypted = encrypted; + os_get_reltime(&sta->pending_eapol_rx->rx_time); +} + + +static bool ieee802_1x_check_encryption(struct sta_info *sta, + enum frame_encryption encrypted, + u8 type) +{ + if (encrypted != FRAME_NOT_ENCRYPTED) + return true; + if (type != IEEE802_1X_TYPE_EAP_PACKET && + type != IEEE802_1X_TYPE_EAPOL_START && + type != IEEE802_1X_TYPE_EAPOL_LOGOFF) + return true; + if (!(sta->flags & WLAN_STA_MFP)) + return true; + return !wpa_auth_pairwise_set(sta->wpa_sm); +} + + +/** + * ieee802_1x_receive - Process the EAPOL frames from the Supplicant + * @hapd: hostapd BSS data + * @sa: Source address (sender of the EAPOL frame) + * @buf: EAPOL frame + * @len: Length of buf in octets + * @encrypted: Whether the frame was encrypted + * + * This function is called for each incoming EAPOL frame from the interface + */ +void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, + size_t len, enum frame_encryption encrypted) +{ + struct sta_info *sta; + struct ieee802_1x_hdr *hdr; + struct ieee802_1x_eapol_key *key; + u16 datalen; + struct rsn_pmksa_cache_entry *pmksa; + int key_mgmt; + + if (!hapd->conf->ieee802_1x && !hapd->conf->wpa && !hapd->conf->osen && + !hapd->conf->wps_state) + return; + + wpa_printf(MSG_DEBUG, "IEEE 802.1X: %lu bytes from " MACSTR + " (encrypted=%d)", + (unsigned long) len, MAC2STR(sa), encrypted); + sta = ap_get_sta(hapd, sa); + if (!sta || (!(sta->flags & (WLAN_STA_ASSOC | WLAN_STA_PREAUTH)) && + !(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_WIRED))) { + wpa_printf(MSG_DEBUG, + "IEEE 802.1X data frame from not associated/Pre-authenticating STA"); + + if (sta && (sta->flags & WLAN_STA_AUTH)) { + wpa_printf(MSG_DEBUG, "Saving EAPOL frame from " MACSTR + " for later use", MAC2STR(sta->addr)); + ieee802_1x_save_eapol(sta, buf, len, encrypted); + } + + return; + } + + if (len < sizeof(*hdr)) { + wpa_printf(MSG_INFO, " too short IEEE 802.1X packet"); + return; + } + + hdr = (struct ieee802_1x_hdr *) buf; + datalen = be_to_host16(hdr->length); + wpa_printf(MSG_DEBUG, " IEEE 802.1X: version=%d type=%d length=%d", + hdr->version, hdr->type, datalen); + + if (len - sizeof(*hdr) < datalen) { + wpa_printf(MSG_INFO, + " frame too short for this IEEE 802.1X packet"); + if (sta->eapol_sm) + sta->eapol_sm->dot1xAuthEapLengthErrorFramesRx++; + return; + } + if (len - sizeof(*hdr) > datalen) { + wpa_printf(MSG_DEBUG, + " ignoring %lu extra octets after IEEE 802.1X packet", + (unsigned long) len - sizeof(*hdr) - datalen); + } + + if (sta->eapol_sm) { + sta->eapol_sm->dot1xAuthLastEapolFrameVersion = hdr->version; + sta->eapol_sm->dot1xAuthEapolFramesRx++; + } + + key = (struct ieee802_1x_eapol_key *) (hdr + 1); + if (datalen >= sizeof(struct ieee802_1x_eapol_key) && + hdr->type == IEEE802_1X_TYPE_EAPOL_KEY && + (key->type == EAPOL_KEY_TYPE_WPA || + key->type == EAPOL_KEY_TYPE_RSN)) { + wpa_receive(hapd->wpa_auth, sta->wpa_sm, (u8 *) hdr, + sizeof(*hdr) + datalen); + return; + } + + if (!hapd->conf->ieee802_1x && !hapd->conf->osen && + !(sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS))) { + wpa_printf(MSG_DEBUG, + "IEEE 802.1X: Ignore EAPOL message - 802.1X not enabled and WPS not used"); + return; + } + + key_mgmt = wpa_auth_sta_key_mgmt(sta->wpa_sm); + if (key_mgmt != -1 && + (wpa_key_mgmt_wpa_psk(key_mgmt) || key_mgmt == WPA_KEY_MGMT_OWE || + key_mgmt == WPA_KEY_MGMT_DPP)) { + wpa_printf(MSG_DEBUG, + "IEEE 802.1X: Ignore EAPOL message - STA is using PSK"); + return; + } + + if (!ieee802_1x_check_encryption(sta, encrypted, hdr->type)) { + wpa_printf(MSG_DEBUG, + "IEEE 802.1X: Discard unencrypted EAPOL message - encryption was expected"); + return; + } + + if (!sta->eapol_sm) { + sta->eapol_sm = ieee802_1x_alloc_eapol_sm(hapd, sta); + if (!sta->eapol_sm) + return; + +#ifdef CONFIG_WPS + if (!hapd->conf->ieee802_1x && hapd->conf->wps_state) { + u32 wflags = sta->flags & (WLAN_STA_WPS | + WLAN_STA_WPS2 | + WLAN_STA_MAYBE_WPS); + if (wflags == WLAN_STA_MAYBE_WPS || + wflags == (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS)) { + /* + * Delay EAPOL frame transmission until a + * possible WPS STA initiates the handshake + * with EAPOL-Start. Only allow the wait to be + * skipped if the STA is known to support WPS + * 2.0. + */ + wpa_printf(MSG_DEBUG, + "WPS: Do not start EAPOL until EAPOL-Start is received"); + sta->eapol_sm->flags |= EAPOL_SM_WAIT_START; + } + } +#endif /* CONFIG_WPS */ + + sta->eapol_sm->eap_if->portEnabled = true; + } + + /* since we support version 1, we can ignore version field and proceed + * as specified in version 1 standard [IEEE Std 802.1X-2001, 7.5.5] */ + /* TODO: actually, we are not version 1 anymore.. However, Version 2 + * does not change frame contents, so should be ok to process frames + * more or less identically. Some changes might be needed for + * verification of fields. */ + + switch (hdr->type) { + case IEEE802_1X_TYPE_EAP_PACKET: + handle_eap(hapd, sta, (u8 *) (hdr + 1), datalen); + break; + + case IEEE802_1X_TYPE_EAPOL_START: + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, + "received EAPOL-Start from STA"); + sta->eapol_sm->flags &= ~EAPOL_SM_WAIT_START; + pmksa = wpa_auth_sta_get_pmksa(sta->wpa_sm); + if (pmksa) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_WPA, + HOSTAPD_LEVEL_DEBUG, + "cached PMKSA available - ignore it since STA sent EAPOL-Start"); + wpa_auth_sta_clear_pmksa(sta->wpa_sm, pmksa); + } + sta->eapol_sm->eapolStart = true; + sta->eapol_sm->dot1xAuthEapolStartFramesRx++; + eap_server_clear_identity(sta->eapol_sm->eap); + wpa_auth_sm_event(sta->wpa_sm, WPA_REAUTH_EAPOL); + break; + + case IEEE802_1X_TYPE_EAPOL_LOGOFF: + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, + "received EAPOL-Logoff from STA"); + sta->acct_terminate_cause = + RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST; + accounting_sta_stop(hapd, sta); + sta->eapol_sm->eapolLogoff = true; + sta->eapol_sm->dot1xAuthEapolLogoffFramesRx++; + eap_server_clear_identity(sta->eapol_sm->eap); + break; + + case IEEE802_1X_TYPE_EAPOL_KEY: + wpa_printf(MSG_DEBUG, " EAPOL-Key"); + if (!ap_sta_is_authorized(sta)) { + wpa_printf(MSG_DEBUG, + " Dropped key data from unauthorized Supplicant"); + break; + } + break; + + case IEEE802_1X_TYPE_EAPOL_ENCAPSULATED_ASF_ALERT: + wpa_printf(MSG_DEBUG, " EAPOL-Encapsulated-ASF-Alert"); + /* TODO: implement support for this; show data */ + break; + +#ifdef CONFIG_MACSEC + case IEEE802_1X_TYPE_EAPOL_MKA: + wpa_printf(MSG_EXCESSIVE, + "EAPOL type %d will be handled by MKA", hdr->type); + break; +#endif /* CONFIG_MACSEC */ + + default: + wpa_printf(MSG_DEBUG, " unknown IEEE 802.1X packet type"); + sta->eapol_sm->dot1xAuthInvalidEapolFramesRx++; + break; + } + + eapol_auth_step(sta->eapol_sm); +} + + +/** + * ieee802_1x_new_station - Start IEEE 802.1X authentication + * @hapd: hostapd BSS data + * @sta: The station + * + * This function is called to start IEEE 802.1X authentication when a new + * station completes IEEE 802.11 association. + */ +void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct rsn_pmksa_cache_entry *pmksa; + int reassoc = 1; + int force_1x = 0; + int key_mgmt; + +#ifdef CONFIG_WPS + if (hapd->conf->wps_state && + ((hapd->conf->wpa && (sta->flags & WLAN_STA_MAYBE_WPS)) || + (sta->flags & WLAN_STA_WPS))) { + /* + * Need to enable IEEE 802.1X/EAPOL state machines for possible + * WPS handshake even if IEEE 802.1X/EAPOL is not used for + * authentication in this BSS. + */ + force_1x = 1; + } +#endif /* CONFIG_WPS */ + + if (!force_1x && !hapd->conf->ieee802_1x && !hapd->conf->osen) { + wpa_printf(MSG_DEBUG, + "IEEE 802.1X: Ignore STA - 802.1X not enabled or forced for WPS"); + /* + * Clear any possible EAPOL authenticator state to support + * reassociation change from WPS to PSK. + */ + ieee802_1x_free_station(hapd, sta); + return; + } + + key_mgmt = wpa_auth_sta_key_mgmt(sta->wpa_sm); + if (key_mgmt != -1 && + (wpa_key_mgmt_wpa_psk(key_mgmt) || key_mgmt == WPA_KEY_MGMT_OWE || + key_mgmt == WPA_KEY_MGMT_DPP)) { + wpa_printf(MSG_DEBUG, "IEEE 802.1X: Ignore STA - using PSK"); + /* + * Clear any possible EAPOL authenticator state to support + * reassociation change from WPA-EAP to PSK. + */ + ieee802_1x_free_station(hapd, sta); + return; + } + + if (!sta->eapol_sm) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, "start authentication"); + sta->eapol_sm = ieee802_1x_alloc_eapol_sm(hapd, sta); + if (!sta->eapol_sm) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_INFO, + "failed to allocate state machine"); + return; + } + reassoc = 0; + } + +#ifdef CONFIG_WPS + sta->eapol_sm->flags &= ~EAPOL_SM_WAIT_START; + if (!hapd->conf->ieee802_1x && hapd->conf->wps_state && + !(sta->flags & WLAN_STA_WPS2)) { + /* + * Delay EAPOL frame transmission until a possible WPS STA + * initiates the handshake with EAPOL-Start. Only allow the + * wait to be skipped if the STA is known to support WPS 2.0. + */ + wpa_printf(MSG_DEBUG, + "WPS: Do not start EAPOL until EAPOL-Start is received"); + sta->eapol_sm->flags |= EAPOL_SM_WAIT_START; + } +#endif /* CONFIG_WPS */ + + sta->eapol_sm->eap_if->portEnabled = true; + +#ifdef CONFIG_IEEE80211R_AP + if (sta->auth_alg == WLAN_AUTH_FT) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, + "PMK from FT - skip IEEE 802.1X/EAP"); + /* Setup EAPOL state machines to already authenticated state + * because of existing FT information from R0KH. */ + sta->eapol_sm->keyRun = true; + sta->eapol_sm->eap_if->eapKeyAvailable = true; + sta->eapol_sm->auth_pae_state = AUTH_PAE_AUTHENTICATING; + sta->eapol_sm->be_auth_state = BE_AUTH_SUCCESS; + sta->eapol_sm->authSuccess = true; + sta->eapol_sm->authFail = false; + sta->eapol_sm->portValid = true; + if (sta->eapol_sm->eap) + eap_sm_notify_cached(sta->eapol_sm->eap); + ap_sta_bind_vlan(hapd, sta); + return; + } +#endif /* CONFIG_IEEE80211R_AP */ + +#ifdef CONFIG_FILS + if (sta->auth_alg == WLAN_AUTH_FILS_SK || + sta->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sta->auth_alg == WLAN_AUTH_FILS_PK) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, + "PMK from FILS - skip IEEE 802.1X/EAP"); + /* Setup EAPOL state machines to already authenticated state + * because of existing FILS information. */ + sta->eapol_sm->keyRun = true; + sta->eapol_sm->eap_if->eapKeyAvailable = true; + sta->eapol_sm->auth_pae_state = AUTH_PAE_AUTHENTICATING; + sta->eapol_sm->be_auth_state = BE_AUTH_SUCCESS; + sta->eapol_sm->authSuccess = true; + sta->eapol_sm->authFail = false; + sta->eapol_sm->portValid = true; + if (sta->eapol_sm->eap) + eap_sm_notify_cached(sta->eapol_sm->eap); + wpa_auth_set_ptk_rekey_timer(sta->wpa_sm); + return; + } +#endif /* CONFIG_FILS */ + + pmksa = wpa_auth_sta_get_pmksa(sta->wpa_sm); + if (pmksa) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, + "PMK from PMKSA cache - skip IEEE 802.1X/EAP"); + /* Setup EAPOL state machines to already authenticated state + * because of existing PMKSA information in the cache. */ + sta->eapol_sm->keyRun = true; + sta->eapol_sm->eap_if->eapKeyAvailable = true; + sta->eapol_sm->auth_pae_state = AUTH_PAE_AUTHENTICATING; + sta->eapol_sm->be_auth_state = BE_AUTH_SUCCESS; + sta->eapol_sm->authSuccess = true; + sta->eapol_sm->authFail = false; + if (sta->eapol_sm->eap) + eap_sm_notify_cached(sta->eapol_sm->eap); + pmksa_cache_to_eapol_data(hapd, pmksa, sta->eapol_sm); + ap_sta_bind_vlan(hapd, sta); + } else { + if (reassoc) { + /* + * Force EAPOL state machines to start + * re-authentication without having to wait for the + * Supplicant to send EAPOL-Start. + */ + sta->eapol_sm->reAuthenticate = true; + } + eapol_auth_step(sta->eapol_sm); + } +} + + +void ieee802_1x_free_station(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct eapol_state_machine *sm = sta->eapol_sm; + +#ifdef CONFIG_HS20 + eloop_cancel_timeout(ieee802_1x_wnm_notif_send, hapd, sta); +#endif /* CONFIG_HS20 */ + + if (sta->pending_eapol_rx) { + wpabuf_free(sta->pending_eapol_rx->buf); + os_free(sta->pending_eapol_rx); + sta->pending_eapol_rx = NULL; + } + + if (!sm) + return; + + sta->eapol_sm = NULL; + +#ifndef CONFIG_NO_RADIUS + radius_msg_free(sm->last_recv_radius); + radius_free_class(&sm->radius_class); +#endif /* CONFIG_NO_RADIUS */ + + eapol_auth_free(sm); +} + + +#ifndef CONFIG_NO_RADIUS +static void ieee802_1x_decapsulate_radius(struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct wpabuf *eap; + const struct eap_hdr *hdr; + int eap_type = -1; + char buf[64]; + struct radius_msg *msg; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (!sm || !sm->last_recv_radius) { + if (sm) + sm->eap_if->aaaEapNoReq = true; + return; + } + + msg = sm->last_recv_radius; + + eap = radius_msg_get_eap(msg); + if (!eap) { + /* RFC 3579, Chap. 2.6.3: + * RADIUS server SHOULD NOT send Access-Reject/no EAP-Message + * attribute */ + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_WARNING, + "could not extract EAP-Message from RADIUS message"); + sm->eap_if->aaaEapNoReq = true; + return; + } + + if (wpabuf_len(eap) < sizeof(*hdr)) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_WARNING, + "too short EAP packet received from authentication server"); + wpabuf_free(eap); + sm->eap_if->aaaEapNoReq = true; + return; + } + + if (wpabuf_len(eap) > sizeof(*hdr)) + eap_type = (wpabuf_head_u8(eap))[sizeof(*hdr)]; + + hdr = wpabuf_head(eap); + switch (hdr->code) { + case EAP_CODE_REQUEST: + if (eap_type >= 0) + sm->eap_type_authsrv = eap_type; + os_snprintf(buf, sizeof(buf), "EAP-Request-%s (%d)", + eap_server_get_name(0, eap_type), eap_type); + break; + case EAP_CODE_RESPONSE: + os_snprintf(buf, sizeof(buf), "EAP Response-%s (%d)", + eap_server_get_name(0, eap_type), eap_type); + break; + case EAP_CODE_SUCCESS: + os_strlcpy(buf, "EAP Success", sizeof(buf)); + break; + case EAP_CODE_FAILURE: + os_strlcpy(buf, "EAP Failure", sizeof(buf)); + break; + default: + os_strlcpy(buf, "unknown EAP code", sizeof(buf)); + break; + } + buf[sizeof(buf) - 1] = '\0'; + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, + "decapsulated EAP packet (code=%d id=%d len=%d) from RADIUS server: %s", + hdr->code, hdr->identifier, be_to_host16(hdr->length), + buf); + sm->eap_if->aaaEapReq = true; + + wpabuf_free(sm->eap_if->aaaEapReqData); + sm->eap_if->aaaEapReqData = eap; +} + + +static void ieee802_1x_get_keys(struct hostapd_data *hapd, + struct sta_info *sta, struct radius_msg *msg, + struct radius_msg *req, + const u8 *shared_secret, + size_t shared_secret_len) +{ + struct radius_ms_mppe_keys *keys; + u8 *buf; + size_t len; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (!sm) + return; + + keys = radius_msg_get_ms_keys(msg, req, shared_secret, + shared_secret_len); + + if (keys && keys->send && keys->recv) { + len = keys->send_len + keys->recv_len; + wpa_hexdump_key(MSG_DEBUG, "MS-MPPE-Send-Key", + keys->send, keys->send_len); + wpa_hexdump_key(MSG_DEBUG, "MS-MPPE-Recv-Key", + keys->recv, keys->recv_len); + + os_free(sm->eap_if->aaaEapKeyData); + sm->eap_if->aaaEapKeyData = os_malloc(len); + if (sm->eap_if->aaaEapKeyData) { + os_memcpy(sm->eap_if->aaaEapKeyData, keys->recv, + keys->recv_len); + os_memcpy(sm->eap_if->aaaEapKeyData + keys->recv_len, + keys->send, keys->send_len); + sm->eap_if->aaaEapKeyDataLen = len; + sm->eap_if->aaaEapKeyAvailable = true; + } + } else { + wpa_printf(MSG_DEBUG, + "MS-MPPE: 1x_get_keys, could not get keys: %p send: %p recv: %p", + keys, keys ? keys->send : NULL, + keys ? keys->recv : NULL); + } + + if (keys) { + os_free(keys->send); + os_free(keys->recv); + os_free(keys); + } + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_EAP_KEY_NAME, &buf, &len, + NULL) == 0) { + os_free(sm->eap_if->eapSessionId); + sm->eap_if->eapSessionId = os_memdup(buf, len); + if (sm->eap_if->eapSessionId) { + sm->eap_if->eapSessionIdLen = len; + wpa_hexdump(MSG_DEBUG, "EAP-Key Name", + sm->eap_if->eapSessionId, + sm->eap_if->eapSessionIdLen); + } + } else { + sm->eap_if->eapSessionIdLen = 0; + } +} + + +static void ieee802_1x_store_radius_class(struct hostapd_data *hapd, + struct sta_info *sta, + struct radius_msg *msg) +{ + u8 *attr_class; + size_t class_len; + struct eapol_state_machine *sm = sta->eapol_sm; + int count, i; + struct radius_attr_data *nclass; + size_t nclass_count; + + if (!hapd->conf->radius->acct_server || !hapd->radius || !sm) + return; + + radius_free_class(&sm->radius_class); + count = radius_msg_count_attr(msg, RADIUS_ATTR_CLASS, 1); + if (count <= 0) + return; + + nclass = os_calloc(count, sizeof(struct radius_attr_data)); + if (!nclass) + return; + + nclass_count = 0; + + attr_class = NULL; + for (i = 0; i < count; i++) { + do { + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CLASS, + &attr_class, &class_len, + attr_class) < 0) { + i = count; + break; + } + } while (class_len < 1); + + nclass[nclass_count].data = os_memdup(attr_class, class_len); + if (!nclass[nclass_count].data) + break; + + nclass[nclass_count].len = class_len; + nclass_count++; + } + + sm->radius_class.attr = nclass; + sm->radius_class.count = nclass_count; + wpa_printf(MSG_DEBUG, + "IEEE 802.1X: Stored %lu RADIUS Class attributes for " + MACSTR, + (unsigned long) sm->radius_class.count, + MAC2STR(sta->addr)); +} + + +/* Update sta->identity based on User-Name attribute in Access-Accept */ +static void ieee802_1x_update_sta_identity(struct hostapd_data *hapd, + struct sta_info *sta, + struct radius_msg *msg) +{ + u8 *buf, *identity; + size_t len; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (!sm) + return; + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME, &buf, &len, + NULL) < 0) + return; + + identity = (u8 *) dup_binstr(buf, len); + if (!identity) + return; + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, + "old identity '%s' updated with User-Name from Access-Accept '%s'", + sm->identity ? (char *) sm->identity : "N/A", + (char *) identity); + + os_free(sm->identity); + sm->identity = identity; + sm->identity_len = len; +} + + +/* Update CUI based on Chargeable-User-Identity attribute in Access-Accept */ +static void ieee802_1x_update_sta_cui(struct hostapd_data *hapd, + struct sta_info *sta, + struct radius_msg *msg) +{ + struct eapol_state_machine *sm = sta->eapol_sm; + struct wpabuf *cui; + u8 *buf; + size_t len; + + if (!sm) + return; + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, + &buf, &len, NULL) < 0) + return; + + cui = wpabuf_alloc_copy(buf, len); + if (!cui) + return; + + wpabuf_free(sm->radius_cui); + sm->radius_cui = cui; +} + + +#ifdef CONFIG_HS20 + +static void ieee802_1x_hs20_sub_rem(struct sta_info *sta, u8 *pos, size_t len) +{ + sta->remediation = 1; + os_free(sta->remediation_url); + if (len > 2) { + sta->remediation_url = os_malloc(len); + if (!sta->remediation_url) + return; + sta->remediation_method = pos[0]; + os_memcpy(sta->remediation_url, pos + 1, len - 1); + sta->remediation_url[len - 1] = '\0'; + wpa_printf(MSG_DEBUG, + "HS 2.0: Subscription remediation needed for " + MACSTR " - server method %u URL %s", + MAC2STR(sta->addr), sta->remediation_method, + sta->remediation_url); + } else { + sta->remediation_url = NULL; + wpa_printf(MSG_DEBUG, + "HS 2.0: Subscription remediation needed for " + MACSTR, MAC2STR(sta->addr)); + } + /* TODO: assign the STA into remediation VLAN or add filtering */ +} + + +static void ieee802_1x_hs20_deauth_req(struct hostapd_data *hapd, + struct sta_info *sta, const u8 *pos, + size_t len) +{ + size_t url_len; + unsigned int timeout; + + if (len < 3) + return; /* Malformed information */ + url_len = len - 3; + sta->hs20_deauth_requested = 1; + sta->hs20_deauth_on_ack = url_len == 0; + wpa_printf(MSG_DEBUG, + "HS 2.0: Deauthentication request - Code %u Re-auth Delay %u URL length %zu", + *pos, WPA_GET_LE16(pos + 1), url_len); + wpabuf_free(sta->hs20_deauth_req); + sta->hs20_deauth_req = wpabuf_alloc(len + 1); + if (sta->hs20_deauth_req) { + wpabuf_put_data(sta->hs20_deauth_req, pos, 3); + wpabuf_put_u8(sta->hs20_deauth_req, url_len); + wpabuf_put_data(sta->hs20_deauth_req, pos + 3, url_len); + } + timeout = hapd->conf->hs20_deauth_req_timeout; + /* If there is no URL, no need to provide time to fetch it. Use a short + * timeout here to allow maximum time for completing 4-way handshake and + * WNM-Notification delivery. Acknowledgement of the frame will result + * in cutting this wait further. */ + if (!url_len && timeout > 2) + timeout = 2; + ap_sta_session_timeout(hapd, sta, timeout); +} + + +static void ieee802_1x_hs20_session_info(struct hostapd_data *hapd, + struct sta_info *sta, u8 *pos, + size_t len, int session_timeout) +{ + unsigned int swt; + int warning_time, beacon_int; + + if (len < 1) + return; /* Malformed information */ + os_free(sta->hs20_session_info_url); + sta->hs20_session_info_url = os_malloc(len); + if (!sta->hs20_session_info_url) + return; + swt = pos[0]; + os_memcpy(sta->hs20_session_info_url, pos + 1, len - 1); + sta->hs20_session_info_url[len - 1] = '\0'; + wpa_printf(MSG_DEBUG, + "HS 2.0: Session Information URL='%s' SWT=%u (session_timeout=%d)", + sta->hs20_session_info_url, swt, session_timeout); + if (session_timeout < 0) { + wpa_printf(MSG_DEBUG, + "HS 2.0: No Session-Timeout set - ignore session info URL"); + return; + } + if (swt == 255) + swt = 1; /* Use one minute as the AP selected value */ + + if ((unsigned int) session_timeout < swt * 60) + warning_time = 0; + else + warning_time = session_timeout - swt * 60; + + beacon_int = hapd->iconf->beacon_int; + if (beacon_int < 1) + beacon_int = 100; /* best guess */ + sta->hs20_disassoc_timer = swt * 60 * 1000 / beacon_int * 125 / 128; + if (sta->hs20_disassoc_timer > 65535) + sta->hs20_disassoc_timer = 65535; + + ap_sta_session_warning_timeout(hapd, sta, warning_time); +} + + +static void ieee802_1x_hs20_t_c_filtering(struct hostapd_data *hapd, + struct sta_info *sta, u8 *pos, + size_t len) +{ + if (len < 4) + return; /* Malformed information */ + wpa_printf(MSG_DEBUG, + "HS 2.0: Terms and Conditions filtering %02x %02x %02x %02x", + pos[0], pos[1], pos[2], pos[3]); + hs20_t_c_filtering(hapd, sta, pos[0] & BIT(0)); +} + + +static void ieee802_1x_hs20_t_c_url(struct hostapd_data *hapd, + struct sta_info *sta, u8 *pos, size_t len) +{ + os_free(sta->t_c_url); + sta->t_c_url = os_malloc(len + 1); + if (!sta->t_c_url) + return; + os_memcpy(sta->t_c_url, pos, len); + sta->t_c_url[len] = '\0'; + wpa_printf(MSG_DEBUG, + "HS 2.0: Terms and Conditions URL %s", sta->t_c_url); +} + +#endif /* CONFIG_HS20 */ + + +static void ieee802_1x_check_hs20(struct hostapd_data *hapd, + struct sta_info *sta, + struct radius_msg *msg, + int session_timeout) +{ +#ifdef CONFIG_HS20 + u8 *buf, *pos, *end, type, sublen; + size_t len; + + buf = NULL; + sta->remediation = 0; + sta->hs20_deauth_requested = 0; + sta->hs20_deauth_on_ack = 0; + + for (;;) { + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_VENDOR_SPECIFIC, + &buf, &len, buf) < 0) + break; + if (len < 6) + continue; + pos = buf; + end = buf + len; + if (WPA_GET_BE32(pos) != RADIUS_VENDOR_ID_WFA) + continue; + pos += 4; + + type = *pos++; + sublen = *pos++; + if (sublen < 2) + continue; /* invalid length */ + sublen -= 2; /* skip header */ + if (pos + sublen > end) + continue; /* invalid WFA VSA */ + + switch (type) { + case RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION: + ieee802_1x_hs20_sub_rem(sta, pos, sublen); + break; + case RADIUS_VENDOR_ATTR_WFA_HS20_DEAUTH_REQ: + ieee802_1x_hs20_deauth_req(hapd, sta, pos, sublen); + break; + case RADIUS_VENDOR_ATTR_WFA_HS20_SESSION_INFO_URL: + ieee802_1x_hs20_session_info(hapd, sta, pos, sublen, + session_timeout); + break; + case RADIUS_VENDOR_ATTR_WFA_HS20_T_C_FILTERING: + ieee802_1x_hs20_t_c_filtering(hapd, sta, pos, sublen); + break; + case RADIUS_VENDOR_ATTR_WFA_HS20_T_C_URL: + ieee802_1x_hs20_t_c_url(hapd, sta, pos, sublen); + break; + } + } +#endif /* CONFIG_HS20 */ +} + + +struct sta_id_search { + u8 identifier; + struct eapol_state_machine *sm; +}; + + +static int ieee802_1x_select_radius_identifier(struct hostapd_data *hapd, + struct sta_info *sta, + void *ctx) +{ + struct sta_id_search *id_search = ctx; + struct eapol_state_machine *sm = sta->eapol_sm; + + if (sm && sm->radius_identifier >= 0 && + sm->radius_identifier == id_search->identifier) { + id_search->sm = sm; + return 1; + } + return 0; +} + + +static struct eapol_state_machine * +ieee802_1x_search_radius_identifier(struct hostapd_data *hapd, u8 identifier) +{ + struct sta_id_search id_search; + + id_search.identifier = identifier; + id_search.sm = NULL; + ap_for_each_sta(hapd, ieee802_1x_select_radius_identifier, &id_search); + return id_search.sm; +} + + +#ifndef CONFIG_NO_VLAN +static int ieee802_1x_update_vlan(struct radius_msg *msg, + struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct vlan_description vlan_desc; + + os_memset(&vlan_desc, 0, sizeof(vlan_desc)); + vlan_desc.notempty = !!radius_msg_get_vlanid(msg, &vlan_desc.untagged, + MAX_NUM_TAGGED_VLAN, + vlan_desc.tagged); + + if (vlan_desc.notempty && + !hostapd_vlan_valid(hapd->conf->vlan, &vlan_desc)) { + sta->eapol_sm->authFail = true; + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS, + HOSTAPD_LEVEL_INFO, + "Invalid VLAN %d%s received from RADIUS server", + vlan_desc.untagged, + vlan_desc.tagged[0] ? "+" : ""); + os_memset(&vlan_desc, 0, sizeof(vlan_desc)); + ap_sta_set_vlan(hapd, sta, &vlan_desc); + return -1; + } + + if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_REQUIRED && + !vlan_desc.notempty) { + sta->eapol_sm->authFail = true; + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_INFO, + "authentication server did not include required VLAN ID in Access-Accept"); + return -1; + } + + return ap_sta_set_vlan(hapd, sta, &vlan_desc); +} +#endif /* CONFIG_NO_VLAN */ + + +/** + * ieee802_1x_receive_auth - Process RADIUS frames from Authentication Server + * @msg: RADIUS response message + * @req: RADIUS request message + * @shared_secret: RADIUS shared secret + * @shared_secret_len: Length of shared_secret in octets + * @data: Context data (struct hostapd_data *) + * Returns: Processing status + */ +static RadiusRxResult +ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req, + const u8 *shared_secret, size_t shared_secret_len, + void *data) +{ + struct hostapd_data *hapd = data; + struct sta_info *sta; + u32 session_timeout = 0, termination_action, acct_interim_interval; + int session_timeout_set; + u32 reason_code; + struct eapol_state_machine *sm; + int override_eapReq = 0; + struct radius_hdr *hdr = radius_msg_get_hdr(msg); + + sm = ieee802_1x_search_radius_identifier(hapd, hdr->identifier); + if (!sm) { + wpa_printf(MSG_DEBUG, + "IEEE 802.1X: Could not find matching station for this RADIUS message"); + return RADIUS_RX_UNKNOWN; + } + sta = sm->sta; + + if (radius_msg_verify(msg, shared_secret, shared_secret_len, req, 1)) { + wpa_printf(MSG_INFO, + "Incoming RADIUS packet did not have correct Message-Authenticator - dropped"); + return RADIUS_RX_INVALID_AUTHENTICATOR; + } + + if (hdr->code != RADIUS_CODE_ACCESS_ACCEPT && + hdr->code != RADIUS_CODE_ACCESS_REJECT && + hdr->code != RADIUS_CODE_ACCESS_CHALLENGE) { + wpa_printf(MSG_INFO, "Unknown RADIUS message code"); + return RADIUS_RX_UNKNOWN; + } + + sm->radius_identifier = -1; + wpa_printf(MSG_DEBUG, "RADIUS packet matching with station " MACSTR, + MAC2STR(sta->addr)); + + radius_msg_free(sm->last_recv_radius); + sm->last_recv_radius = msg; + + session_timeout_set = + !radius_msg_get_attr_int32(msg, RADIUS_ATTR_SESSION_TIMEOUT, + &session_timeout); + if (radius_msg_get_attr_int32(msg, RADIUS_ATTR_TERMINATION_ACTION, + &termination_action)) + termination_action = RADIUS_TERMINATION_ACTION_DEFAULT; + + if (hapd->conf->acct_interim_interval == 0 && + hdr->code == RADIUS_CODE_ACCESS_ACCEPT && + radius_msg_get_attr_int32(msg, RADIUS_ATTR_ACCT_INTERIM_INTERVAL, + &acct_interim_interval) == 0) { + if (acct_interim_interval < 60) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_INFO, + "ignored too small Acct-Interim-Interval %d", + acct_interim_interval); + } else + sta->acct_interim_interval = acct_interim_interval; + } + + + switch (hdr->code) { + case RADIUS_CODE_ACCESS_ACCEPT: +#ifndef CONFIG_NO_VLAN + if (hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED && + ieee802_1x_update_vlan(msg, hapd, sta) < 0) + break; + + if (sta->vlan_id > 0) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_RADIUS, + HOSTAPD_LEVEL_INFO, + "VLAN ID %d", sta->vlan_id); + } + + if ((sta->flags & WLAN_STA_ASSOC) && + ap_sta_bind_vlan(hapd, sta) < 0) + break; +#endif /* CONFIG_NO_VLAN */ + + sta->session_timeout_set = !!session_timeout_set; + os_get_reltime(&sta->session_timeout); + sta->session_timeout.sec += session_timeout; + + /* RFC 3580, Ch. 3.17 */ + if (session_timeout_set && termination_action == + RADIUS_TERMINATION_ACTION_RADIUS_REQUEST) + sm->reAuthPeriod = session_timeout; + else if (session_timeout_set) + ap_sta_session_timeout(hapd, sta, session_timeout); + else + ap_sta_no_session_timeout(hapd, sta); + + sm->eap_if->aaaSuccess = true; + override_eapReq = 1; + ieee802_1x_get_keys(hapd, sta, msg, req, shared_secret, + shared_secret_len); + ieee802_1x_store_radius_class(hapd, sta, msg); + ieee802_1x_update_sta_identity(hapd, sta, msg); + ieee802_1x_update_sta_cui(hapd, sta, msg); + ieee802_1x_check_hs20(hapd, sta, msg, + session_timeout_set ? + (int) session_timeout : -1); + break; + case RADIUS_CODE_ACCESS_REJECT: + sm->eap_if->aaaFail = true; + override_eapReq = 1; + if (radius_msg_get_attr_int32(msg, RADIUS_ATTR_WLAN_REASON_CODE, + &reason_code) == 0) { + wpa_printf(MSG_DEBUG, + "RADIUS server indicated WLAN-Reason-Code %u in Access-Reject for " + MACSTR, reason_code, MAC2STR(sta->addr)); + sta->disconnect_reason_code = reason_code; + } + break; + case RADIUS_CODE_ACCESS_CHALLENGE: + sm->eap_if->aaaEapReq = true; + if (session_timeout_set) { + /* RFC 2869, Ch. 2.3.2; RFC 3580, Ch. 3.17 */ + sm->eap_if->aaaMethodTimeout = session_timeout; + hostapd_logger(hapd, sm->addr, + HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, + "using EAP timeout of %d seconds (from RADIUS)", + sm->eap_if->aaaMethodTimeout); + } else { + /* + * Use dynamic retransmission behavior per EAP + * specification. + */ + sm->eap_if->aaaMethodTimeout = 0; + } + break; + } + + ieee802_1x_decapsulate_radius(hapd, sta); + if (override_eapReq) + sm->eap_if->aaaEapReq = false; + +#ifdef CONFIG_FILS +#ifdef NEED_AP_MLME + if (sta->flags & + (WLAN_STA_PENDING_FILS_ERP | WLAN_STA_PENDING_PASN_FILS_ERP)) { + /* TODO: Add a PMKSA entry on success? */ + ieee802_11_finish_fils_auth( + hapd, sta, hdr->code == RADIUS_CODE_ACCESS_ACCEPT, + sm->eap_if->aaaEapReqData, + sm->eap_if->aaaEapKeyData, + sm->eap_if->aaaEapKeyDataLen); + } +#endif /* NEED_AP_MLME */ +#endif /* CONFIG_FILS */ + + eapol_auth_step(sm); + + return RADIUS_RX_QUEUED; +} +#endif /* CONFIG_NO_RADIUS */ + + +void ieee802_1x_abort_auth(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct eapol_state_machine *sm = sta->eapol_sm; + + if (!sm) + return; + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, "aborting authentication"); + +#ifndef CONFIG_NO_RADIUS + radius_msg_free(sm->last_recv_radius); + sm->last_recv_radius = NULL; +#endif /* CONFIG_NO_RADIUS */ + + if (sm->eap_if->eapTimeout) { + /* + * Disconnect the STA since it did not reply to the last EAP + * request and we cannot continue EAP processing (EAP-Failure + * could only be sent if the EAP peer actually replied). + */ + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "EAP Timeout, STA " MACSTR, + MAC2STR(sta->addr)); + + sm->eap_if->portEnabled = false; + ap_sta_disconnect(hapd, sta, sta->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + } +} + + +#ifdef CONFIG_WEP + +static int ieee802_1x_rekey_broadcast(struct hostapd_data *hapd) +{ + struct eapol_authenticator *eapol = hapd->eapol_auth; + + if (hapd->conf->default_wep_key_len < 1) + return 0; + + os_free(eapol->default_wep_key); + eapol->default_wep_key = os_malloc(hapd->conf->default_wep_key_len); + if (!eapol->default_wep_key || + random_get_bytes(eapol->default_wep_key, + hapd->conf->default_wep_key_len)) { + wpa_printf(MSG_INFO, "Could not generate random WEP key"); + os_free(eapol->default_wep_key); + eapol->default_wep_key = NULL; + return -1; + } + + wpa_hexdump_key(MSG_DEBUG, "IEEE 802.1X: New default WEP key", + eapol->default_wep_key, + hapd->conf->default_wep_key_len); + + return 0; +} + + +static int ieee802_1x_sta_key_available(struct hostapd_data *hapd, + struct sta_info *sta, void *ctx) +{ + if (sta->eapol_sm) { + sta->eapol_sm->eap_if->eapKeyAvailable = true; + eapol_auth_step(sta->eapol_sm); + } + return 0; +} + + +static void ieee802_1x_rekey(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct eapol_authenticator *eapol = hapd->eapol_auth; + + if (eapol->default_wep_key_idx >= 3) + eapol->default_wep_key_idx = + hapd->conf->individual_wep_key_len > 0 ? 1 : 0; + else + eapol->default_wep_key_idx++; + + wpa_printf(MSG_DEBUG, "IEEE 802.1X: New default WEP key index %d", + eapol->default_wep_key_idx); + + if (ieee802_1x_rekey_broadcast(hapd)) { + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_WARNING, + "failed to generate a new broadcast key"); + os_free(eapol->default_wep_key); + eapol->default_wep_key = NULL; + return; + } + + /* TODO: Could setup key for RX here, but change default TX keyid only + * after new broadcast key has been sent to all stations. */ + if (hostapd_drv_set_key(hapd->conf->iface, hapd, WPA_ALG_WEP, + broadcast_ether_addr, + eapol->default_wep_key_idx, 0, 1, NULL, 0, + eapol->default_wep_key, + hapd->conf->default_wep_key_len, + KEY_FLAG_GROUP_RX_TX_DEFAULT)) { + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_WARNING, + "failed to configure a new broadcast key"); + os_free(eapol->default_wep_key); + eapol->default_wep_key = NULL; + return; + } + + ap_for_each_sta(hapd, ieee802_1x_sta_key_available, NULL); + + if (hapd->conf->wep_rekeying_period > 0) { + eloop_register_timeout(hapd->conf->wep_rekeying_period, 0, + ieee802_1x_rekey, hapd, NULL); + } +} + +#endif /* CONFIG_WEP */ + + +static void ieee802_1x_eapol_send(void *ctx, void *sta_ctx, u8 type, + const u8 *data, size_t datalen) +{ +#ifdef CONFIG_WPS + struct sta_info *sta = sta_ctx; + + if ((sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS)) == + WLAN_STA_MAYBE_WPS) { + const u8 *identity; + size_t identity_len; + struct eapol_state_machine *sm = sta->eapol_sm; + + identity = eap_get_identity(sm->eap, &identity_len); + if (identity && + ((identity_len == WSC_ID_ENROLLEE_LEN && + os_memcmp(identity, WSC_ID_ENROLLEE, + WSC_ID_ENROLLEE_LEN) == 0) || + (identity_len == WSC_ID_REGISTRAR_LEN && + os_memcmp(identity, WSC_ID_REGISTRAR, + WSC_ID_REGISTRAR_LEN) == 0))) { + wpa_printf(MSG_DEBUG, + "WPS: WLAN_STA_MAYBE_WPS -> WLAN_STA_WPS"); + sta->flags |= WLAN_STA_WPS; + } + } +#endif /* CONFIG_WPS */ + + ieee802_1x_send(ctx, sta_ctx, type, data, datalen); +} + + +static void ieee802_1x_aaa_send(void *ctx, void *sta_ctx, + const u8 *data, size_t datalen) +{ +#ifndef CONFIG_NO_RADIUS + struct hostapd_data *hapd = ctx; + struct sta_info *sta = sta_ctx; + + ieee802_1x_encapsulate_radius(hapd, sta, data, datalen); +#endif /* CONFIG_NO_RADIUS */ +} + + +static bool _ieee802_1x_finished(void *ctx, void *sta_ctx, int success, + int preauth, int remediation, bool logoff) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta = sta_ctx; + + if (preauth) { + rsn_preauth_finished(hapd, sta, success); + return false; + } + + return ieee802_1x_finished(hapd, sta, success, remediation, logoff); +} + + +static int ieee802_1x_get_eap_user(void *ctx, const u8 *identity, + size_t identity_len, int phase2, + struct eap_user *user) +{ + struct hostapd_data *hapd = ctx; + const struct hostapd_eap_user *eap_user; + int i; + int rv = -1; + + eap_user = hostapd_get_eap_user(hapd, identity, identity_len, phase2); + if (!eap_user) + goto out; + + os_memset(user, 0, sizeof(*user)); + user->phase2 = phase2; + for (i = 0; i < EAP_MAX_METHODS; i++) { + user->methods[i].vendor = eap_user->methods[i].vendor; + user->methods[i].method = eap_user->methods[i].method; + } + + if (eap_user->password) { + user->password = os_memdup(eap_user->password, + eap_user->password_len); + if (!user->password) + goto out; + user->password_len = eap_user->password_len; + user->password_hash = eap_user->password_hash; + if (eap_user->salt && eap_user->salt_len) { + user->salt = os_memdup(eap_user->salt, + eap_user->salt_len); + if (!user->salt) + goto out; + user->salt_len = eap_user->salt_len; + } + } + user->force_version = eap_user->force_version; + user->macacl = eap_user->macacl; + user->ttls_auth = eap_user->ttls_auth; + user->remediation = eap_user->remediation; + rv = 0; + +out: + if (rv) + wpa_printf(MSG_DEBUG, "%s: Failed to find user", __func__); + + return rv; +} + + +static int ieee802_1x_sta_entry_alive(void *ctx, const u8 *addr) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + sta = ap_get_sta(hapd, addr); + if (!sta || !sta->eapol_sm) + return 0; + return 1; +} + + +static void ieee802_1x_logger(void *ctx, const u8 *addr, + eapol_logger_level level, const char *txt) +{ +#ifndef CONFIG_NO_HOSTAPD_LOGGER + struct hostapd_data *hapd = ctx; + int hlevel; + + switch (level) { + case EAPOL_LOGGER_WARNING: + hlevel = HOSTAPD_LEVEL_WARNING; + break; + case EAPOL_LOGGER_INFO: + hlevel = HOSTAPD_LEVEL_INFO; + break; + case EAPOL_LOGGER_DEBUG: + default: + hlevel = HOSTAPD_LEVEL_DEBUG; + break; + } + + hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE8021X, hlevel, "%s", + txt); +#endif /* CONFIG_NO_HOSTAPD_LOGGER */ +} + + +static void ieee802_1x_set_port_authorized(void *ctx, void *sta_ctx, + int authorized) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta = sta_ctx; + + ieee802_1x_set_sta_authorized(hapd, sta, authorized); +} + + +static void _ieee802_1x_abort_auth(void *ctx, void *sta_ctx) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta = sta_ctx; + + ieee802_1x_abort_auth(hapd, sta); +} + + +#ifdef CONFIG_WEP +static void _ieee802_1x_tx_key(void *ctx, void *sta_ctx) +{ +#ifndef CONFIG_FIPS +#ifndef CONFIG_NO_RC4 + struct hostapd_data *hapd = ctx; + struct sta_info *sta = sta_ctx; + + ieee802_1x_tx_key(hapd, sta); +#endif /* CONFIG_NO_RC4 */ +#endif /* CONFIG_FIPS */ +} +#endif /* CONFIG_WEP */ + + +static void ieee802_1x_eapol_event(void *ctx, void *sta_ctx, + enum eapol_event type) +{ + /* struct hostapd_data *hapd = ctx; */ + struct sta_info *sta = sta_ctx; + + switch (type) { + case EAPOL_AUTH_SM_CHANGE: + wpa_auth_sm_notify(sta->wpa_sm); + break; + case EAPOL_AUTH_REAUTHENTICATE: + wpa_auth_sm_event(sta->wpa_sm, WPA_REAUTH_EAPOL); + break; + } +} + + +#ifdef CONFIG_ERP + +static struct eap_server_erp_key * +ieee802_1x_erp_get_key(void *ctx, const char *keyname) +{ + struct hostapd_data *hapd = ctx; + struct eap_server_erp_key *erp; + + dl_list_for_each(erp, &hapd->erp_keys, struct eap_server_erp_key, + list) { + if (os_strcmp(erp->keyname_nai, keyname) == 0) + return erp; + } + + return NULL; +} + + +static int ieee802_1x_erp_add_key(void *ctx, struct eap_server_erp_key *erp) +{ + struct hostapd_data *hapd = ctx; + + dl_list_add(&hapd->erp_keys, &erp->list); + return 0; +} + +#endif /* CONFIG_ERP */ + + +int ieee802_1x_init(struct hostapd_data *hapd) +{ + struct eapol_auth_config conf; + struct eapol_auth_cb cb; + +#ifdef CONFIG_IEEE80211BE + if (!hostapd_mld_is_first_bss(hapd)) { + struct hostapd_data *first; + + first = hostapd_mld_get_first_bss(hapd); + if (!first) + return -1; + + if (!first->eapol_auth) { + wpa_printf(MSG_DEBUG, + "MLD: First BSS IEEE 802.1X state machine does not exist. Init on its behalf"); + + if (ieee802_1x_init(first)) + return -1; + } + + wpa_printf(MSG_DEBUG, + "MLD: Using IEEE 802.1X state machine of the first BSS"); + + hapd->eapol_auth = first->eapol_auth; + return 0; + } +#endif /* CONFIG_IEEE80211BE */ + + dl_list_init(&hapd->erp_keys); + + os_memset(&conf, 0, sizeof(conf)); + conf.eap_cfg = hapd->eap_cfg; + conf.ctx = hapd; + conf.eap_reauth_period = hapd->conf->eap_reauth_period; + conf.wpa = hapd->conf->wpa; +#ifdef CONFIG_WEP + conf.individual_wep_key_len = hapd->conf->individual_wep_key_len; +#endif /* CONFIG_WEP */ + conf.eap_req_id_text = hapd->conf->eap_req_id_text; + conf.eap_req_id_text_len = hapd->conf->eap_req_id_text_len; + conf.erp_send_reauth_start = hapd->conf->erp_send_reauth_start; + conf.erp_domain = hapd->conf->erp_domain; +#ifdef CONFIG_TESTING_OPTIONS + conf.eap_skip_prot_success = hapd->conf->eap_skip_prot_success; +#endif /* CONFIG_TESTING_OPTIONS */ + + os_memset(&cb, 0, sizeof(cb)); + cb.eapol_send = ieee802_1x_eapol_send; + cb.aaa_send = ieee802_1x_aaa_send; + cb.finished = _ieee802_1x_finished; + cb.get_eap_user = ieee802_1x_get_eap_user; + cb.sta_entry_alive = ieee802_1x_sta_entry_alive; + cb.logger = ieee802_1x_logger; + cb.set_port_authorized = ieee802_1x_set_port_authorized; + cb.abort_auth = _ieee802_1x_abort_auth; +#ifdef CONFIG_WEP + cb.tx_key = _ieee802_1x_tx_key; +#endif /* CONFIG_WEP */ + cb.eapol_event = ieee802_1x_eapol_event; +#ifdef CONFIG_ERP + cb.erp_get_key = ieee802_1x_erp_get_key; + cb.erp_add_key = ieee802_1x_erp_add_key; +#endif /* CONFIG_ERP */ + + hapd->eapol_auth = eapol_auth_init(&conf, &cb); + if (!hapd->eapol_auth) + return -1; + + if ((hapd->conf->ieee802_1x || hapd->conf->wpa) && + hostapd_set_drv_ieee8021x(hapd, hapd->conf->iface, 1)) + return -1; + +#ifndef CONFIG_NO_RADIUS + if (radius_client_register(hapd->radius, RADIUS_AUTH, + ieee802_1x_receive_auth, hapd)) + return -1; +#endif /* CONFIG_NO_RADIUS */ + +#ifdef CONFIG_WEP + if (hapd->conf->default_wep_key_len) { + int i; + + for (i = 0; i < 4; i++) + hostapd_drv_set_key(hapd->conf->iface, hapd, + WPA_ALG_NONE, NULL, i, 0, 0, NULL, + 0, NULL, 0, KEY_FLAG_GROUP); + + ieee802_1x_rekey(hapd, NULL); + + if (!hapd->eapol_auth->default_wep_key) + return -1; + } +#endif /* CONFIG_WEP */ + + return 0; +} + + +void ieee802_1x_erp_flush(struct hostapd_data *hapd) +{ + struct eap_server_erp_key *erp; + + while ((erp = dl_list_first(&hapd->erp_keys, struct eap_server_erp_key, + list)) != NULL) { + dl_list_del(&erp->list); + bin_clear_free(erp, sizeof(*erp)); + } +} + + +void ieee802_1x_deinit(struct hostapd_data *hapd) +{ +#ifdef CONFIG_IEEE80211BE + if (!hostapd_mld_is_first_bss(hapd)) { + wpa_printf(MSG_DEBUG, + "MLD: Deinit IEEE 802.1X state machine of a non-first BSS"); + + hapd->eapol_auth = NULL; + return; + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_WEP + eloop_cancel_timeout(ieee802_1x_rekey, hapd, NULL); +#endif /* CONFIG_WEP */ + + if (hapd->driver && hapd->drv_priv && + (hapd->conf->ieee802_1x || hapd->conf->wpa)) + hostapd_set_drv_ieee8021x(hapd, hapd->conf->iface, 0); + + eapol_auth_deinit(hapd->eapol_auth); + hapd->eapol_auth = NULL; + + ieee802_1x_erp_flush(hapd); +} + + +int ieee802_1x_tx_status(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *buf, size_t len, int ack) +{ + struct ieee80211_hdr *hdr; + u8 *pos; + const unsigned char rfc1042_hdr[ETH_ALEN] = + { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 }; + + if (!sta) + return -1; + if (len < sizeof(*hdr) + sizeof(rfc1042_hdr) + 2) + return 0; + + hdr = (struct ieee80211_hdr *) buf; + pos = (u8 *) (hdr + 1); + if (os_memcmp(pos, rfc1042_hdr, sizeof(rfc1042_hdr)) != 0) + return 0; + pos += sizeof(rfc1042_hdr); + if (WPA_GET_BE16(pos) != ETH_P_PAE) + return 0; + pos += 2; + + return ieee802_1x_eapol_tx_status(hapd, sta, pos, buf + len - pos, + ack); +} + + +int ieee802_1x_eapol_tx_status(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *buf, int len, int ack) +{ + const struct ieee802_1x_hdr *xhdr = + (const struct ieee802_1x_hdr *) buf; + const u8 *pos = buf + sizeof(*xhdr); + struct ieee802_1x_eapol_key *key; + + if (len < (int) sizeof(*xhdr)) + return 0; + wpa_printf(MSG_DEBUG, "IEEE 802.1X: " MACSTR + " TX status - version=%d type=%d length=%d - ack=%d", + MAC2STR(sta->addr), xhdr->version, xhdr->type, + be_to_host16(xhdr->length), ack); + +#ifdef CONFIG_WPS + if (xhdr->type == IEEE802_1X_TYPE_EAP_PACKET && ack && + (sta->flags & WLAN_STA_WPS) && + ap_sta_pending_delayed_1x_auth_fail_disconnect(hapd, sta)) { + wpa_printf(MSG_DEBUG, + "WPS: Indicate EAP completion on ACK for EAP-Failure"); + hostapd_wps_eap_completed(hapd); + } +#endif /* CONFIG_WPS */ + + if (xhdr->type != IEEE802_1X_TYPE_EAPOL_KEY) + return 0; + + if (pos + sizeof(struct wpa_eapol_key) <= buf + len) { + const struct wpa_eapol_key *wpa; + + wpa = (const struct wpa_eapol_key *) pos; + if (wpa->type == EAPOL_KEY_TYPE_RSN || + wpa->type == EAPOL_KEY_TYPE_WPA) + wpa_auth_eapol_key_tx_status(hapd->wpa_auth, + sta->wpa_sm, ack); + } + + /* EAPOL EAP-Packet packets are eventually re-sent by either Supplicant + * or Authenticator state machines, but EAPOL-Key packets are not + * retransmitted in case of failure. Try to re-send failed EAPOL-Key + * packets couple of times because otherwise STA keys become + * unsynchronized with AP. */ + if (!ack && pos + sizeof(*key) <= buf + len) { + key = (struct ieee802_1x_eapol_key *) pos; + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X, + HOSTAPD_LEVEL_DEBUG, + "did not Ack EAPOL-Key frame (%scast index=%d)", + key->key_index & BIT(7) ? "uni" : "broad", + key->key_index & ~BIT(7)); + /* TODO: re-send EAPOL-Key couple of times (with short delay + * between them?). If all attempt fail, report error and + * deauthenticate STA so that it will get new keys when + * authenticating again (e.g., after returning in range). + * Separate limit/transmit state needed both for unicast and + * broadcast keys(?) */ + } + /* TODO: could move unicast key configuration from ieee802_1x_tx_key() + * to here and change the key only if the EAPOL-Key packet was Acked. + */ + + return 1; +} + + +u8 * ieee802_1x_get_identity(struct eapol_state_machine *sm, size_t *len) +{ + if (!sm || !sm->identity) + return NULL; + + *len = sm->identity_len; + return sm->identity; +} + + +u8 * ieee802_1x_get_radius_class(struct eapol_state_machine *sm, size_t *len, + int idx) +{ + if (!sm || !sm->radius_class.attr || + idx >= (int) sm->radius_class.count) + return NULL; + + *len = sm->radius_class.attr[idx].len; + return sm->radius_class.attr[idx].data; +} + + +struct wpabuf * ieee802_1x_get_radius_cui(struct eapol_state_machine *sm) +{ + if (!sm) + return NULL; + return sm->radius_cui; +} + + +const u8 * ieee802_1x_get_key(struct eapol_state_machine *sm, size_t *len) +{ + *len = 0; + if (!sm) + return NULL; + + *len = sm->eap_if->eapKeyDataLen; + return sm->eap_if->eapKeyData; +} + + +#ifdef CONFIG_MACSEC +const u8 * ieee802_1x_get_session_id(struct eapol_state_machine *sm, + size_t *len) +{ + *len = 0; + if (!sm || !sm->eap_if) + return NULL; + + *len = sm->eap_if->eapSessionIdLen; + return sm->eap_if->eapSessionId; +} +#endif /* CONFIG_MACSEC */ + + +void ieee802_1x_notify_port_enabled(struct eapol_state_machine *sm, + bool enabled) +{ + if (!sm) + return; + sm->eap_if->portEnabled = enabled; + eapol_auth_step(sm); +} + + +void ieee802_1x_notify_port_valid(struct eapol_state_machine *sm, bool valid) +{ + if (!sm) + return; + sm->portValid = valid; + eapol_auth_step(sm); +} + + +void ieee802_1x_notify_pre_auth(struct eapol_state_machine *sm, bool pre_auth) +{ + if (!sm) + return; + if (pre_auth) + sm->flags |= EAPOL_SM_PREAUTH; + else + sm->flags &= ~EAPOL_SM_PREAUTH; +} + + +static const char * bool_txt(bool val) +{ + return val ? "TRUE" : "FALSE"; +} + + +int ieee802_1x_get_mib(struct hostapd_data *hapd, char *buf, size_t buflen) +{ + /* TODO */ + return 0; +} + + +int ieee802_1x_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, + char *buf, size_t buflen) +{ + int len = 0, ret; + struct eapol_state_machine *sm = sta->eapol_sm; + struct os_reltime diff; + const char *name1; + const char *name2; + char *identity_buf = NULL; + + if (!sm) + return 0; + + ret = os_snprintf(buf + len, buflen - len, + "dot1xPaePortNumber=%d\n" + "dot1xPaePortProtocolVersion=%d\n" + "dot1xPaePortCapabilities=1\n" + "dot1xPaePortInitialize=%d\n" + "dot1xPaePortReauthenticate=FALSE\n", + sta->aid, + EAPOL_VERSION, + sm->initialize); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* dot1xAuthConfigTable */ + ret = os_snprintf(buf + len, buflen - len, + "dot1xAuthPaeState=%d\n" + "dot1xAuthBackendAuthState=%d\n" + "dot1xAuthAdminControlledDirections=%d\n" + "dot1xAuthOperControlledDirections=%d\n" + "dot1xAuthAuthControlledPortStatus=%d\n" + "dot1xAuthAuthControlledPortControl=%d\n" + "dot1xAuthQuietPeriod=%u\n" + "dot1xAuthServerTimeout=%u\n" + "dot1xAuthReAuthPeriod=%u\n" + "dot1xAuthReAuthEnabled=%s\n" + "dot1xAuthKeyTxEnabled=%s\n", + sm->auth_pae_state + 1, + sm->be_auth_state + 1, + sm->adminControlledDirections, + sm->operControlledDirections, + sm->authPortStatus, + sm->portControl, + sm->quietPeriod, + sm->serverTimeout, + sm->reAuthPeriod, + bool_txt(sm->reAuthEnabled), + bool_txt(sm->keyTxEnabled)); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* dot1xAuthStatsTable */ + ret = os_snprintf(buf + len, buflen - len, + "dot1xAuthEapolFramesRx=%u\n" + "dot1xAuthEapolFramesTx=%u\n" + "dot1xAuthEapolStartFramesRx=%u\n" + "dot1xAuthEapolLogoffFramesRx=%u\n" + "dot1xAuthEapolRespIdFramesRx=%u\n" + "dot1xAuthEapolRespFramesRx=%u\n" + "dot1xAuthEapolReqIdFramesTx=%u\n" + "dot1xAuthEapolReqFramesTx=%u\n" + "dot1xAuthInvalidEapolFramesRx=%u\n" + "dot1xAuthEapLengthErrorFramesRx=%u\n" + "dot1xAuthLastEapolFrameVersion=%u\n" + "dot1xAuthLastEapolFrameSource=" MACSTR "\n", + sm->dot1xAuthEapolFramesRx, + sm->dot1xAuthEapolFramesTx, + sm->dot1xAuthEapolStartFramesRx, + sm->dot1xAuthEapolLogoffFramesRx, + sm->dot1xAuthEapolRespIdFramesRx, + sm->dot1xAuthEapolRespFramesRx, + sm->dot1xAuthEapolReqIdFramesTx, + sm->dot1xAuthEapolReqFramesTx, + sm->dot1xAuthInvalidEapolFramesRx, + sm->dot1xAuthEapLengthErrorFramesRx, + sm->dot1xAuthLastEapolFrameVersion, + MAC2STR(sm->addr)); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* dot1xAuthDiagTable */ + ret = os_snprintf(buf + len, buflen - len, + "dot1xAuthEntersConnecting=%u\n" + "dot1xAuthEapLogoffsWhileConnecting=%u\n" + "dot1xAuthEntersAuthenticating=%u\n" + "dot1xAuthAuthSuccessesWhileAuthenticating=%u\n" + "dot1xAuthAuthTimeoutsWhileAuthenticating=%u\n" + "dot1xAuthAuthFailWhileAuthenticating=%u\n" + "dot1xAuthAuthEapStartsWhileAuthenticating=%u\n" + "dot1xAuthAuthEapLogoffWhileAuthenticating=%u\n" + "dot1xAuthAuthReauthsWhileAuthenticated=%u\n" + "dot1xAuthAuthEapStartsWhileAuthenticated=%u\n" + "dot1xAuthAuthEapLogoffWhileAuthenticated=%u\n" + "dot1xAuthBackendResponses=%u\n" + "dot1xAuthBackendAccessChallenges=%u\n" + "dot1xAuthBackendOtherRequestsToSupplicant=%u\n" + "dot1xAuthBackendAuthSuccesses=%u\n" + "dot1xAuthBackendAuthFails=%u\n", + sm->authEntersConnecting, + sm->authEapLogoffsWhileConnecting, + sm->authEntersAuthenticating, + sm->authAuthSuccessesWhileAuthenticating, + sm->authAuthTimeoutsWhileAuthenticating, + sm->authAuthFailWhileAuthenticating, + sm->authAuthEapStartsWhileAuthenticating, + sm->authAuthEapLogoffWhileAuthenticating, + sm->authAuthReauthsWhileAuthenticated, + sm->authAuthEapStartsWhileAuthenticated, + sm->authAuthEapLogoffWhileAuthenticated, + sm->backendResponses, + sm->backendAccessChallenges, + sm->backendOtherRequestsToSupplicant, + sm->backendAuthSuccesses, + sm->backendAuthFails); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* dot1xAuthSessionStatsTable */ + os_reltime_age(&sta->acct_session_start, &diff); + if (sm->eap && !sm->identity) { + const u8 *id; + size_t id_len; + + id = eap_get_identity(sm->eap, &id_len); + if (id) + identity_buf = dup_binstr(id, id_len); + } + ret = os_snprintf(buf + len, buflen - len, + /* TODO: dot1xAuthSessionOctetsRx */ + /* TODO: dot1xAuthSessionOctetsTx */ + /* TODO: dot1xAuthSessionFramesRx */ + /* TODO: dot1xAuthSessionFramesTx */ + "dot1xAuthSessionId=%016llX\n" + "dot1xAuthSessionAuthenticMethod=%d\n" + "dot1xAuthSessionTime=%u\n" + "dot1xAuthSessionTerminateCause=999\n" + "dot1xAuthSessionUserName=%s\n", + (unsigned long long) sta->acct_session_id, + (wpa_key_mgmt_wpa_ieee8021x( + wpa_auth_sta_key_mgmt(sta->wpa_sm))) ? + 1 : 2, + (unsigned int) diff.sec, + sm->identity ? (char *) sm->identity : + (identity_buf ? identity_buf : "N/A")); + os_free(identity_buf); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + if (sm->acct_multi_session_id) { + ret = os_snprintf(buf + len, buflen - len, + "authMultiSessionId=%016llX\n", + (unsigned long long) + sm->acct_multi_session_id); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + } + + name1 = eap_server_get_name(0, sm->eap_type_authsrv); + name2 = eap_server_get_name(0, sm->eap_type_supp); + ret = os_snprintf(buf + len, buflen - len, + "last_eap_type_as=%d (%s)\n" + "last_eap_type_sta=%d (%s)\n", + sm->eap_type_authsrv, name1, + sm->eap_type_supp, name2); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + return len; +} + + +#ifdef CONFIG_HS20 +static void ieee802_1x_wnm_notif_send(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + + if (sta->remediation) { + wpa_printf(MSG_DEBUG, "HS 2.0: Send WNM-Notification to " + MACSTR " to indicate Subscription Remediation", + MAC2STR(sta->addr)); + hs20_send_wnm_notification(hapd, sta->addr, + sta->remediation_method, + sta->remediation_url); + os_free(sta->remediation_url); + sta->remediation_url = NULL; + } + + if (sta->hs20_deauth_req) { + wpa_printf(MSG_DEBUG, "HS 2.0: Send WNM-Notification to " + MACSTR " to indicate imminent deauthentication", + MAC2STR(sta->addr)); + hs20_send_wnm_notification_deauth_req(hapd, sta->addr, + sta->hs20_deauth_req); + } + + if (sta->hs20_t_c_filtering) { + wpa_printf(MSG_DEBUG, "HS 2.0: Send WNM-Notification to " + MACSTR " to indicate Terms and Conditions filtering", + MAC2STR(sta->addr)); + hs20_send_wnm_notification_t_c(hapd, sta->addr, sta->t_c_url); + os_free(sta->t_c_url); + sta->t_c_url = NULL; + } +} +#endif /* CONFIG_HS20 */ + + +static bool ieee802_1x_finished(struct hostapd_data *hapd, + struct sta_info *sta, int success, + int remediation, bool logoff) +{ + const u8 *key; + size_t len; + /* TODO: get PMKLifetime from WPA parameters */ + static const int dot11RSNAConfigPMKLifetime = 43200; + unsigned int session_timeout; + struct os_reltime now, remaining; + +#ifdef CONFIG_HS20 + if (remediation && !sta->remediation) { + sta->remediation = 1; + os_free(sta->remediation_url); + sta->remediation_url = + os_strdup(hapd->conf->subscr_remediation_url); + sta->remediation_method = 1; /* SOAP-XML SPP */ + } + + if (success && (sta->remediation || sta->hs20_deauth_req || + sta->hs20_t_c_filtering)) { + wpa_printf(MSG_DEBUG, "HS 2.0: Schedule WNM-Notification to " + MACSTR " in 100 ms", MAC2STR(sta->addr)); + eloop_cancel_timeout(ieee802_1x_wnm_notif_send, hapd, sta); + eloop_register_timeout(0, 100000, ieee802_1x_wnm_notif_send, + hapd, sta); + } +#endif /* CONFIG_HS20 */ + +#ifdef CONFIG_MACSEC + ieee802_1x_notify_create_actor_hapd(hapd, sta); +#endif /* CONFIG_MACSEC */ + + key = ieee802_1x_get_key(sta->eapol_sm, &len); + if (sta->session_timeout_set) { + os_get_reltime(&now); + os_reltime_sub(&sta->session_timeout, &now, &remaining); + session_timeout = (remaining.sec > 0) ? remaining.sec : 1; + } else { + session_timeout = dot11RSNAConfigPMKLifetime; + } + if (success && key && len >= PMK_LEN && !sta->remediation && + !sta->hs20_deauth_requested && + wpa_auth_pmksa_add(sta->wpa_sm, key, len, session_timeout, + sta->eapol_sm) == 0) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_WPA, + HOSTAPD_LEVEL_DEBUG, + "Added PMKSA cache entry (IEEE 802.1X)"); + } + + if (!success) { + /* + * Many devices require deauthentication after WPS provisioning + * and some may not be be able to do that themselves, so + * disconnect the client here. In addition, this may also + * benefit IEEE 802.1X/EAPOL authentication cases, too since + * the EAPOL PAE state machine would remain in HELD state for + * considerable amount of time and some EAP methods, like + * EAP-FAST with anonymous provisioning, may require another + * EAPOL authentication to be started to complete connection. + */ + ap_sta_delayed_1x_auth_fail_disconnect(hapd, sta, + logoff ? 0 : 10); + if (logoff && sta->wpa_sm) + return true; + } + + return false; +} diff --git a/src/ap/ieee802_1x.h b/src/ap/ieee802_1x.h new file mode 100644 index 0000000..1469351 --- /dev/null +++ b/src/ap/ieee802_1x.h @@ -0,0 +1,69 @@ +/* + * hostapd / IEEE 802.1X-2004 Authenticator + * Copyright (c) 2002-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef IEEE802_1X_H +#define IEEE802_1X_H + +struct hostapd_data; +struct sta_info; +struct eapol_state_machine; +struct hostapd_config; +struct hostapd_bss_config; +struct hostapd_radius_attr; +struct radius_msg; + + +void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, + size_t len, enum frame_encryption encrypted); +void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta); +void ieee802_1x_free_station(struct hostapd_data *hapd, struct sta_info *sta); + +void ieee802_1x_abort_auth(struct hostapd_data *hapd, struct sta_info *sta); +void ieee802_1x_set_sta_authorized(struct hostapd_data *hapd, + struct sta_info *sta, int authorized); +void ieee802_1x_dump_state(FILE *f, const char *prefix, struct sta_info *sta); +int ieee802_1x_init(struct hostapd_data *hapd); +void ieee802_1x_erp_flush(struct hostapd_data *hapd); +void ieee802_1x_deinit(struct hostapd_data *hapd); +int ieee802_1x_tx_status(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *buf, size_t len, int ack); +int ieee802_1x_eapol_tx_status(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *data, int len, int ack); +u8 * ieee802_1x_get_identity(struct eapol_state_machine *sm, size_t *len); +u8 * ieee802_1x_get_radius_class(struct eapol_state_machine *sm, size_t *len, + int idx); +struct wpabuf * ieee802_1x_get_radius_cui(struct eapol_state_machine *sm); +const u8 * ieee802_1x_get_key(struct eapol_state_machine *sm, size_t *len); +const u8 * ieee802_1x_get_session_id(struct eapol_state_machine *sm, + size_t *len); +void ieee802_1x_notify_port_enabled(struct eapol_state_machine *sm, + bool enabled); +void ieee802_1x_notify_port_valid(struct eapol_state_machine *sm, bool valid); +void ieee802_1x_notify_pre_auth(struct eapol_state_machine *sm, bool pre_auth); +int ieee802_1x_get_mib(struct hostapd_data *hapd, char *buf, size_t buflen); +int ieee802_1x_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, + char *buf, size_t buflen); +void hostapd_get_ntp_timestamp(u8 *buf); +char *eap_type_text(u8 type); + +const char *radius_mode_txt(struct hostapd_data *hapd); +int radius_sta_rate(struct hostapd_data *hapd, struct sta_info *sta); + +int add_common_radius_attr(struct hostapd_data *hapd, + struct hostapd_radius_attr *req_attr, + struct sta_info *sta, + struct radius_msg *msg); +int add_sqlite_radius_attr(struct hostapd_data *hapd, struct sta_info *sta, + struct radius_msg *msg, int acct); +void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *eap, size_t len); +struct eapol_state_machine * +ieee802_1x_alloc_eapol_sm(struct hostapd_data *hapd, struct sta_info *sta); + +#endif /* IEEE802_1X_H */ diff --git a/src/ap/mbo_ap.c b/src/ap/mbo_ap.c new file mode 100644 index 0000000..43b0bf1 --- /dev/null +++ b/src/ap/mbo_ap.c @@ -0,0 +1,244 @@ +/* + * hostapd - MBO + * Copyright (c) 2016, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "hostapd.h" +#include "sta_info.h" +#include "mbo_ap.h" + + +void mbo_ap_sta_free(struct sta_info *sta) +{ + struct mbo_non_pref_chan_info *info, *prev; + + info = sta->non_pref_chan; + sta->non_pref_chan = NULL; + while (info) { + prev = info; + info = info->next; + os_free(prev); + } +} + + +static void mbo_ap_parse_non_pref_chan(struct sta_info *sta, + const u8 *buf, size_t len) +{ + struct mbo_non_pref_chan_info *info, *tmp; + char channels[200], *pos, *end; + size_t num_chan, i; + int ret; + + if (len <= 3) + return; /* Not enough room for any channels */ + + num_chan = len - 3; + info = os_zalloc(sizeof(*info) + num_chan); + if (!info) + return; + info->op_class = buf[0]; + info->pref = buf[len - 2]; + info->reason_code = buf[len - 1]; + info->num_channels = num_chan; + buf++; + os_memcpy(info->channels, buf, num_chan); + if (!sta->non_pref_chan) { + sta->non_pref_chan = info; + } else { + tmp = sta->non_pref_chan; + while (tmp->next) + tmp = tmp->next; + tmp->next = info; + } + + pos = channels; + end = pos + sizeof(channels); + *pos = '\0'; + for (i = 0; i < num_chan; i++) { + ret = os_snprintf(pos, end - pos, "%s%u", + i == 0 ? "" : " ", buf[i]); + if (os_snprintf_error(end - pos, ret)) { + *pos = '\0'; + break; + } + pos += ret; + } + + wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR + " non-preferred channel list (op class %u, pref %u, reason code %u, channels %s)", + MAC2STR(sta->addr), info->op_class, info->pref, + info->reason_code, channels); +} + + +void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta, + struct ieee802_11_elems *elems) +{ + const u8 *pos, *attr, *end; + size_t len; + + if (!hapd->conf->mbo_enabled || !elems->mbo) + return; + + pos = elems->mbo + 4; + len = elems->mbo_len - 4; + wpa_hexdump(MSG_DEBUG, "MBO: Association Request attributes", pos, len); + + attr = get_ie(pos, len, MBO_ATTR_ID_CELL_DATA_CAPA); + if (attr && attr[1] >= 1) + sta->cell_capa = attr[2]; + + mbo_ap_sta_free(sta); + end = pos + len; + while (end - pos > 1) { + u8 ie_len = pos[1]; + + if (2 + ie_len > end - pos) + break; + + if (pos[0] == MBO_ATTR_ID_NON_PREF_CHAN_REPORT) + mbo_ap_parse_non_pref_chan(sta, pos + 2, ie_len); + pos += 2 + pos[1]; + } +} + + +int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen) +{ + char *pos = buf, *end = buf + buflen; + int ret; + struct mbo_non_pref_chan_info *info; + u8 i; + unsigned int count = 0; + + if (!sta->cell_capa) + return 0; + + ret = os_snprintf(pos, end - pos, "mbo_cell_capa=%u\n", sta->cell_capa); + if (os_snprintf_error(end - pos, ret)) + return pos - buf; + pos += ret; + + for (info = sta->non_pref_chan; info; info = info->next) { + char *pos2 = pos; + + ret = os_snprintf(pos2, end - pos2, + "non_pref_chan[%u]=%u:%u:%u:", + count, info->op_class, info->pref, + info->reason_code); + count++; + if (os_snprintf_error(end - pos2, ret)) + break; + pos2 += ret; + + for (i = 0; i < info->num_channels; i++) { + ret = os_snprintf(pos2, end - pos2, "%u%s", + info->channels[i], + i + 1 < info->num_channels ? + "," : ""); + if (os_snprintf_error(end - pos2, ret)) { + pos2 = NULL; + break; + } + pos2 += ret; + } + + if (!pos2) + break; + ret = os_snprintf(pos2, end - pos2, "\n"); + if (os_snprintf_error(end - pos2, ret)) + break; + pos2 += ret; + pos = pos2; + } + + return pos - buf; +} + + +static void mbo_ap_wnm_notif_req_cell_capa(struct sta_info *sta, + const u8 *buf, size_t len) +{ + if (len < 1) + return; + wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR + " updated cellular data capability: %u", + MAC2STR(sta->addr), buf[0]); + sta->cell_capa = buf[0]; +} + + +static void mbo_ap_wnm_notif_req_elem(struct sta_info *sta, u8 type, + const u8 *buf, size_t len, + int *first_non_pref_chan) +{ + switch (type) { + case WFA_WNM_NOTIF_SUBELEM_NON_PREF_CHAN_REPORT: + if (*first_non_pref_chan) { + /* + * Need to free the previously stored entries now to + * allow the update to replace all entries. + */ + *first_non_pref_chan = 0; + mbo_ap_sta_free(sta); + } + mbo_ap_parse_non_pref_chan(sta, buf, len); + break; + case WFA_WNM_NOTIF_SUBELEM_CELL_DATA_CAPA: + mbo_ap_wnm_notif_req_cell_capa(sta, buf, len); + break; + default: + wpa_printf(MSG_DEBUG, + "MBO: Ignore unknown WNM Notification WFA subelement %u", + type); + break; + } +} + + +void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr, + const u8 *buf, size_t len) +{ + const u8 *pos, *end; + u8 ie_len; + struct sta_info *sta; + int first_non_pref_chan = 1; + + if (!hapd->conf->mbo_enabled) + return; + + sta = ap_get_sta(hapd, addr); + if (!sta) + return; + + pos = buf; + end = buf + len; + + while (end - pos > 1) { + ie_len = pos[1]; + + if (2 + ie_len > end - pos) + break; + + if (pos[0] == WLAN_EID_VENDOR_SPECIFIC && + ie_len >= 4 && WPA_GET_BE24(pos + 2) == OUI_WFA) + mbo_ap_wnm_notif_req_elem(sta, pos[5], + pos + 6, ie_len - 4, + &first_non_pref_chan); + else + wpa_printf(MSG_DEBUG, + "MBO: Ignore unknown WNM Notification element %u (len=%u)", + pos[0], pos[1]); + + pos += 2 + pos[1]; + } +} diff --git a/src/ap/mbo_ap.h b/src/ap/mbo_ap.h new file mode 100644 index 0000000..9f37f28 --- /dev/null +++ b/src/ap/mbo_ap.h @@ -0,0 +1,51 @@ +/* + * MBO related functions and structures + * Copyright (c) 2016, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef MBO_AP_H +#define MBO_AP_H + +struct hostapd_data; +struct sta_info; +struct ieee802_11_elems; + +#ifdef CONFIG_MBO + +void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta, + struct ieee802_11_elems *elems); +int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen); +void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr, + const u8 *buf, size_t len); +void mbo_ap_sta_free(struct sta_info *sta); + +#else /* CONFIG_MBO */ + +static inline void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, + struct sta_info *sta, + struct ieee802_11_elems *elems) +{ +} + +static inline int mbo_ap_get_info(struct sta_info *sta, char *buf, + size_t buflen) +{ + return 0; +} + +static inline void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, + const u8 *addr, + const u8 *buf, size_t len) +{ +} + +static inline void mbo_ap_sta_free(struct sta_info *sta) +{ +} + +#endif /* CONFIG_MBO */ + +#endif /* MBO_AP_H */ diff --git a/src/ap/nan_usd_ap.c b/src/ap/nan_usd_ap.c new file mode 100644 index 0000000..52a967a --- /dev/null +++ b/src/ap/nan_usd_ap.c @@ -0,0 +1,267 @@ +/* + * NAN unsynchronized service discovery (USD) + * Copyright (c) 2024, Qualcomm Innovation Center, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/wpa_ctrl.h" +#include "common/nan_de.h" +#include "hostapd.h" +#include "ap_drv_ops.h" +#include "nan_usd_ap.h" + + +static int hostapd_nan_de_tx(void *ctx, unsigned int freq, + unsigned int wait_time, + const u8 *dst, const u8 *src, const u8 *bssid, + const struct wpabuf *buf) +{ + struct hostapd_data *hapd = ctx; + + wpa_printf(MSG_DEBUG, "NAN: TX NAN SDF A1=" MACSTR " A2=" MACSTR + " A3=" MACSTR " len=%zu", + MAC2STR(dst), MAC2STR(src), MAC2STR(bssid), + wpabuf_len(buf)); + + /* TODO: Force use of OFDM */ + return hostapd_drv_send_action(hapd, hapd->iface->freq, 0, dst, + wpabuf_head(buf), wpabuf_len(buf)); +} + + +static int hostapd_nan_de_listen(void *ctx, unsigned int freq, + unsigned int duration) +{ + return 0; +} + + +static void +hostapd_nan_de_discovery_result(void *ctx, int subscribe_id, + enum nan_service_protocol_type srv_proto_type, + const u8 *ssi, size_t ssi_len, + int peer_publish_id, const u8 *peer_addr, + bool fsd, bool fsd_gas) +{ + struct hostapd_data *hapd = ctx; + char *ssi_hex; + + ssi_hex = os_zalloc(2 * ssi_len + 1); + if (!ssi_hex) + return; + if (ssi) + wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len); + wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_DISCOVERY_RESULT + "subscribe_id=%d publish_id=%d address=" MACSTR + " fsd=%d fsd_gas=%d srv_proto_type=%u ssi=%s", + subscribe_id, peer_publish_id, MAC2STR(peer_addr), + fsd, fsd_gas, srv_proto_type, ssi_hex); + os_free(ssi_hex); +} + + +static void +hostapd_nan_de_replied(void *ctx, int publish_id, const u8 *peer_addr, + int peer_subscribe_id, + enum nan_service_protocol_type srv_proto_type, + const u8 *ssi, size_t ssi_len) +{ + struct hostapd_data *hapd = ctx; + char *ssi_hex; + + ssi_hex = os_zalloc(2 * ssi_len + 1); + if (!ssi_hex) + return; + if (ssi) + wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len); + wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_REPLIED + "publish_id=%d address=" MACSTR + " subscribe_id=%d srv_proto_type=%u ssi=%s", + publish_id, MAC2STR(peer_addr), peer_subscribe_id, + srv_proto_type, ssi_hex); + os_free(ssi_hex); +} + + +static const char * nan_reason_txt(enum nan_de_reason reason) +{ + switch (reason) { + case NAN_DE_REASON_TIMEOUT: + return "timeout"; + case NAN_DE_REASON_USER_REQUEST: + return "user-request"; + case NAN_DE_REASON_FAILURE: + return "failure"; + } + + return "unknown"; +} + + +static void hostapd_nan_de_publish_terminated(void *ctx, int publish_id, + enum nan_de_reason reason) +{ + struct hostapd_data *hapd = ctx; + + wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_PUBLISH_TERMINATED + "publish_id=%d reason=%s", + publish_id, nan_reason_txt(reason)); +} + + +static void hostapd_nan_de_subscribe_terminated(void *ctx, int subscribe_id, + enum nan_de_reason reason) +{ + struct hostapd_data *hapd = ctx; + + wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_SUBSCRIBE_TERMINATED + "subscribe_id=%d reason=%s", + subscribe_id, nan_reason_txt(reason)); +} + + +static void hostapd_nan_de_receive(void *ctx, int id, int peer_instance_id, + const u8 *ssi, size_t ssi_len, + const u8 *peer_addr) +{ + struct hostapd_data *hapd = ctx; + char *ssi_hex; + + ssi_hex = os_zalloc(2 * ssi_len + 1); + if (!ssi_hex) + return; + if (ssi) + wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len); + wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_RECEIVE + "id=%d peer_instance_id=%d address=" MACSTR " ssi=%s", + id, peer_instance_id, MAC2STR(peer_addr), ssi_hex); + os_free(ssi_hex); +} + + +int hostapd_nan_usd_init(struct hostapd_data *hapd) +{ + struct nan_callbacks cb; + + os_memset(&cb, 0, sizeof(cb)); + cb.ctx = hapd; + cb.tx = hostapd_nan_de_tx; + cb.listen = hostapd_nan_de_listen; + cb.discovery_result = hostapd_nan_de_discovery_result; + cb.replied = hostapd_nan_de_replied; + cb.publish_terminated = hostapd_nan_de_publish_terminated; + cb.subscribe_terminated = hostapd_nan_de_subscribe_terminated; + cb.receive = hostapd_nan_de_receive; + + hapd->nan_de = nan_de_init(hapd->own_addr, true, &cb); + if (!hapd->nan_de) + return -1; + return 0; +} + + +void hostapd_nan_usd_deinit(struct hostapd_data *hapd) +{ + nan_de_deinit(hapd->nan_de); + hapd->nan_de = NULL; +} + + +void hostapd_nan_usd_rx_sdf(struct hostapd_data *hapd, const u8 *src, + unsigned int freq, const u8 *buf, size_t len) +{ + if (!hapd->nan_de) + return; + nan_de_rx_sdf(hapd->nan_de, src, freq, buf, len); +} + + +void hostapd_nan_usd_flush(struct hostapd_data *hapd) +{ + if (!hapd->nan_de) + return; + nan_de_flush(hapd->nan_de); +} + + +int hostapd_nan_usd_publish(struct hostapd_data *hapd, const char *service_name, + enum nan_service_protocol_type srv_proto_type, + const struct wpabuf *ssi, + struct nan_publish_params *params) +{ + int publish_id; + struct wpabuf *elems = NULL; + + if (!hapd->nan_de) + return -1; + + publish_id = nan_de_publish(hapd->nan_de, service_name, srv_proto_type, + ssi, elems, params); + wpabuf_free(elems); + return publish_id; +} + + +void hostapd_nan_usd_cancel_publish(struct hostapd_data *hapd, int publish_id) +{ + if (!hapd->nan_de) + return; + nan_de_cancel_publish(hapd->nan_de, publish_id); +} + + +int hostapd_nan_usd_update_publish(struct hostapd_data *hapd, int publish_id, + const struct wpabuf *ssi) +{ + int ret; + + if (!hapd->nan_de) + return -1; + ret = nan_de_update_publish(hapd->nan_de, publish_id, ssi); + return ret; +} + + +int hostapd_nan_usd_subscribe(struct hostapd_data *hapd, + const char *service_name, + enum nan_service_protocol_type srv_proto_type, + const struct wpabuf *ssi, + struct nan_subscribe_params *params) +{ + int subscribe_id; + struct wpabuf *elems = NULL; + + if (!hapd->nan_de) + return -1; + + subscribe_id = nan_de_subscribe(hapd->nan_de, service_name, + srv_proto_type, ssi, elems, params); + wpabuf_free(elems); + return subscribe_id; +} + + +void hostapd_nan_usd_cancel_subscribe(struct hostapd_data *hapd, + int subscribe_id) +{ + if (!hapd->nan_de) + return; + nan_de_cancel_subscribe(hapd->nan_de, subscribe_id); +} + + +int hostapd_nan_usd_transmit(struct hostapd_data *hapd, int handle, + const struct wpabuf *ssi, + const struct wpabuf *elems, + const u8 *peer_addr, u8 req_instance_id) +{ + if (!hapd->nan_de) + return -1; + return nan_de_transmit(hapd->nan_de, handle, ssi, elems, peer_addr, + req_instance_id); +} diff --git a/src/ap/nan_usd_ap.h b/src/ap/nan_usd_ap.h new file mode 100644 index 0000000..58ff5fc --- /dev/null +++ b/src/ap/nan_usd_ap.h @@ -0,0 +1,46 @@ +/* + * NAN unsynchronized service discovery (USD) + * Copyright (c) 2024, Qualcomm Innovation Center, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef NAN_USD_AP_H +#define NAN_USD_AP_H + +struct nan_subscribe_params; +struct nan_publish_params; +enum nan_service_protocol_type; + +int hostapd_nan_usd_init(struct hostapd_data *hapd); +void hostapd_nan_usd_deinit(struct hostapd_data *hapd); +void hostapd_nan_usd_rx_sdf(struct hostapd_data *hapd, const u8 *src, + unsigned int freq, const u8 *buf, size_t len); +void hostapd_nan_usd_flush(struct hostapd_data *hapd); +int hostapd_nan_usd_publish(struct hostapd_data *hapd, const char *service_name, + enum nan_service_protocol_type srv_proto_type, + const struct wpabuf *ssi, + struct nan_publish_params *params); +void hostapd_nan_usd_cancel_publish(struct hostapd_data *hapd, int publish_id); +int hostapd_nan_usd_update_publish(struct hostapd_data *hapd, int publish_id, + const struct wpabuf *ssi); +int hostapd_nan_usd_subscribe(struct hostapd_data *hapd, + const char *service_name, + enum nan_service_protocol_type srv_proto_type, + const struct wpabuf *ssi, + struct nan_subscribe_params *params); +void hostapd_nan_usd_cancel_subscribe(struct hostapd_data *hapd, + int subscribe_id); +int hostapd_nan_usd_transmit(struct hostapd_data *hapd, int handle, + const struct wpabuf *ssi, + const struct wpabuf *elems, + const u8 *peer_addr, u8 req_instance_id); +void hostapd_nan_usd_remain_on_channel_cb(struct hostapd_data *hapd, + unsigned int freq, + unsigned int duration); +void hostapd_nan_usd_cancel_remain_on_channel_cb(struct hostapd_data *hapd, + unsigned int freq); +void hostapd_nan_usd_tx_wait_expire(struct hostapd_data *hapd); + +#endif /* NAN_USD_AP_H */ diff --git a/src/ap/ndisc_snoop.c b/src/ap/ndisc_snoop.c new file mode 100644 index 0000000..bc1eb62 --- /dev/null +++ b/src/ap/ndisc_snoop.c @@ -0,0 +1,189 @@ +/* + * Neighbor Discovery snooping for Proxy ARP + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include +#include + +#include "utils/common.h" +#include "l2_packet/l2_packet.h" +#include "hostapd.h" +#include "sta_info.h" +#include "ap_drv_ops.h" +#include "list.h" +#include "x_snoop.h" +#include "ndisc_snoop.h" + +struct ip6addr { + struct in6_addr addr; + struct dl_list list; +}; + +struct icmpv6_ndmsg { + struct ip6_hdr ipv6h; + struct icmp6_hdr icmp6h; + struct in6_addr target_addr; + u8 opt_type; + u8 len; + u8 opt_lladdr[0]; +} STRUCT_PACKED; + +#define ROUTER_ADVERTISEMENT 134 +#define NEIGHBOR_SOLICITATION 135 +#define NEIGHBOR_ADVERTISEMENT 136 +#define SOURCE_LL_ADDR 1 + +static int sta_ip6addr_add(struct sta_info *sta, struct in6_addr *addr) +{ + struct ip6addr *ip6addr; + + ip6addr = os_zalloc(sizeof(*ip6addr)); + if (!ip6addr) + return -1; + + os_memcpy(&ip6addr->addr, addr, sizeof(*addr)); + + dl_list_add_tail(&sta->ip6addr, &ip6addr->list); + + return 0; +} + + +void sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct ip6addr *ip6addr, *prev; + + dl_list_for_each_safe(ip6addr, prev, &sta->ip6addr, struct ip6addr, + list) { + hostapd_drv_br_delete_ip_neigh(hapd, 6, (u8 *) &ip6addr->addr); + dl_list_del(&ip6addr->list); + os_free(ip6addr); + } +} + + +static int sta_has_ip6addr(struct sta_info *sta, struct in6_addr *addr) +{ + struct ip6addr *ip6addr; + + dl_list_for_each(ip6addr, &sta->ip6addr, struct ip6addr, list) { + if (ip6addr->addr.s6_addr32[0] == addr->s6_addr32[0] && + ip6addr->addr.s6_addr32[1] == addr->s6_addr32[1] && + ip6addr->addr.s6_addr32[2] == addr->s6_addr32[2] && + ip6addr->addr.s6_addr32[3] == addr->s6_addr32[3]) + return 1; + } + + return 0; +} + + +static void ucast_to_stas(struct hostapd_data *hapd, const u8 *buf, size_t len) +{ + struct sta_info *sta; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (!(sta->flags & WLAN_STA_AUTHORIZED)) + continue; + x_snoop_mcast_to_ucast_convert_send(hapd, sta, (u8 *) buf, len); + } +} + + +static void handle_ndisc(void *ctx, const u8 *src_addr, const u8 *buf, + size_t len) +{ + struct hostapd_data *hapd = ctx; + struct icmpv6_ndmsg *msg; + struct in6_addr saddr; + struct sta_info *sta; + int res; + char addrtxt[INET6_ADDRSTRLEN + 1]; + + if (len < ETH_HLEN + sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr)) + return; + msg = (struct icmpv6_ndmsg *) &buf[ETH_HLEN]; + switch (msg->icmp6h.icmp6_type) { + case NEIGHBOR_SOLICITATION: + if (len < ETH_HLEN + sizeof(*msg)) + return; + if (msg->opt_type != SOURCE_LL_ADDR) + return; + + /* + * IPv6 header may not be 32-bit aligned in the buffer, so use + * a local copy to avoid unaligned reads. + */ + os_memcpy(&saddr, &msg->ipv6h.ip6_src, sizeof(saddr)); + if (!(saddr.s6_addr32[0] == 0 && saddr.s6_addr32[1] == 0 && + saddr.s6_addr32[2] == 0 && saddr.s6_addr32[3] == 0)) { + if (len < ETH_HLEN + sizeof(*msg) + ETH_ALEN) + return; + sta = ap_get_sta(hapd, msg->opt_lladdr); + if (!sta) + return; + + if (sta_has_ip6addr(sta, &saddr)) + return; + + if (inet_ntop(AF_INET6, &saddr, addrtxt, + sizeof(addrtxt)) == NULL) + addrtxt[0] = '\0'; + wpa_printf(MSG_DEBUG, "ndisc_snoop: Learned new IPv6 address %s for " + MACSTR, addrtxt, MAC2STR(sta->addr)); + hostapd_drv_br_delete_ip_neigh(hapd, 6, (u8 *) &saddr); + res = hostapd_drv_br_add_ip_neigh(hapd, 6, + (u8 *) &saddr, + 128, sta->addr); + if (res) { + wpa_printf(MSG_ERROR, + "ndisc_snoop: Adding ip neigh failed: %d", + res); + return; + } + + if (sta_ip6addr_add(sta, &saddr)) + return; + } + break; +#ifdef CONFIG_HS20 + case ROUTER_ADVERTISEMENT: + if (hapd->conf->disable_dgaf) + ucast_to_stas(hapd, buf, len); + break; +#endif /* CONFIG_HS20 */ + case NEIGHBOR_ADVERTISEMENT: + if (hapd->conf->na_mcast_to_ucast) + ucast_to_stas(hapd, buf, len); + break; + default: + break; + } +} + + +int ndisc_snoop_init(struct hostapd_data *hapd) +{ + hapd->sock_ndisc = x_snoop_get_l2_packet(hapd, handle_ndisc, + L2_PACKET_FILTER_NDISC); + if (hapd->sock_ndisc == NULL) { + wpa_printf(MSG_DEBUG, + "ndisc_snoop: Failed to initialize L2 packet processing for NDISC packets: %s", + strerror(errno)); + return -1; + } + + return 0; +} + + +void ndisc_snoop_deinit(struct hostapd_data *hapd) +{ + l2_packet_deinit(hapd->sock_ndisc); + hapd->sock_ndisc = NULL; +} diff --git a/src/ap/ndisc_snoop.h b/src/ap/ndisc_snoop.h new file mode 100644 index 0000000..3cc9a55 --- /dev/null +++ b/src/ap/ndisc_snoop.h @@ -0,0 +1,36 @@ +/* + * Neighbor Discovery snooping for Proxy ARP + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef NDISC_SNOOP_H +#define NDISC_SNOOP_H + +#if defined(CONFIG_PROXYARP) && defined(CONFIG_IPV6) + +int ndisc_snoop_init(struct hostapd_data *hapd); +void ndisc_snoop_deinit(struct hostapd_data *hapd); +void sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta); + +#else /* CONFIG_PROXYARP && CONFIG_IPV6 */ + +static inline int ndisc_snoop_init(struct hostapd_data *hapd) +{ + return 0; +} + +static inline void ndisc_snoop_deinit(struct hostapd_data *hapd) +{ +} + +static inline void sta_ip6addr_del(struct hostapd_data *hapd, + struct sta_info *sta) +{ +} + +#endif /* CONFIG_PROXYARP && CONFIG_IPV6 */ + +#endif /* NDISC_SNOOP_H */ diff --git a/src/ap/neighbor_db.c b/src/ap/neighbor_db.c new file mode 100644 index 0000000..f7a7d83 --- /dev/null +++ b/src/ap/neighbor_db.c @@ -0,0 +1,366 @@ +/* + * hostapd / Neighboring APs DB + * Copyright(c) 2013 - 2016 Intel Mobile Communications GmbH. + * Copyright(c) 2011 - 2016 Intel Corporation. All rights reserved. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/crc32.h" +#include "hostapd.h" +#include "ieee802_11.h" +#include "neighbor_db.h" + + +struct hostapd_neighbor_entry * +hostapd_neighbor_get(struct hostapd_data *hapd, const u8 *bssid, + const struct wpa_ssid_value *ssid) +{ + struct hostapd_neighbor_entry *nr; + + dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry, + list) { + if (ether_addr_equal(bssid, nr->bssid) && + (!ssid || + (ssid->ssid_len == nr->ssid.ssid_len && + os_memcmp(ssid->ssid, nr->ssid.ssid, + ssid->ssid_len) == 0))) + return nr; + } + return NULL; +} + + +int hostapd_neighbor_show(struct hostapd_data *hapd, char *buf, size_t buflen) +{ + struct hostapd_neighbor_entry *nr; + char *pos, *end; + + pos = buf; + end = buf + buflen; + + dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry, + list) { + int ret; + char nrie[2 * 255 + 1]; + char lci[2 * 255 + 1]; + char civic[2 * 255 + 1]; + char ssid[SSID_MAX_LEN * 2 + 1]; + + ssid[0] = '\0'; + wpa_snprintf_hex(ssid, sizeof(ssid), nr->ssid.ssid, + nr->ssid.ssid_len); + + nrie[0] = '\0'; + if (nr->nr) + wpa_snprintf_hex(nrie, sizeof(nrie), + wpabuf_head(nr->nr), + wpabuf_len(nr->nr)); + + lci[0] = '\0'; + if (nr->lci) + wpa_snprintf_hex(lci, sizeof(lci), + wpabuf_head(nr->lci), + wpabuf_len(nr->lci)); + + civic[0] = '\0'; + if (nr->civic) + wpa_snprintf_hex(civic, sizeof(civic), + wpabuf_head(nr->civic), + wpabuf_len(nr->civic)); + + ret = os_snprintf(pos, end - pos, MACSTR + " ssid=%s%s%s%s%s%s%s%s\n", + MAC2STR(nr->bssid), ssid, + nr->nr ? " nr=" : "", nrie, + nr->lci ? " lci=" : "", lci, + nr->civic ? " civic=" : "", civic, + nr->stationary ? " stat" : ""); + if (os_snprintf_error(end - pos, ret)) + break; + pos += ret; + } + + return pos - buf; +} + + +static void hostapd_neighbor_clear_entry(struct hostapd_neighbor_entry *nr) +{ + wpabuf_free(nr->nr); + nr->nr = NULL; + wpabuf_free(nr->lci); + nr->lci = NULL; + wpabuf_free(nr->civic); + nr->civic = NULL; + os_memset(nr->bssid, 0, sizeof(nr->bssid)); + os_memset(&nr->ssid, 0, sizeof(nr->ssid)); + os_memset(&nr->lci_date, 0, sizeof(nr->lci_date)); + nr->stationary = 0; + nr->short_ssid = 0; + nr->bss_parameters = 0; +} + + +static struct hostapd_neighbor_entry * +hostapd_neighbor_add(struct hostapd_data *hapd) +{ + struct hostapd_neighbor_entry *nr; + + nr = os_zalloc(sizeof(struct hostapd_neighbor_entry)); + if (!nr) + return NULL; + + dl_list_add(&hapd->nr_db, &nr->list); + + return nr; +} + + +int hostapd_neighbor_set(struct hostapd_data *hapd, const u8 *bssid, + const struct wpa_ssid_value *ssid, + const struct wpabuf *nr, const struct wpabuf *lci, + const struct wpabuf *civic, int stationary, + u8 bss_parameters) +{ + struct hostapd_neighbor_entry *entry; + + entry = hostapd_neighbor_get(hapd, bssid, ssid); + if (!entry) + entry = hostapd_neighbor_add(hapd); + if (!entry) + return -1; + + hostapd_neighbor_clear_entry(entry); + + os_memcpy(entry->bssid, bssid, ETH_ALEN); + os_memcpy(&entry->ssid, ssid, sizeof(entry->ssid)); + entry->short_ssid = ieee80211_crc32(ssid->ssid, ssid->ssid_len); + + entry->nr = wpabuf_dup(nr); + if (!entry->nr) + goto fail; + + if (lci && wpabuf_len(lci)) { + entry->lci = wpabuf_dup(lci); + if (!entry->lci || os_get_time(&entry->lci_date)) + goto fail; + } + + if (civic && wpabuf_len(civic)) { + entry->civic = wpabuf_dup(civic); + if (!entry->civic) + goto fail; + } + + entry->stationary = stationary; + entry->bss_parameters = bss_parameters; + + return 0; + +fail: + hostapd_neighbor_remove(hapd, bssid, ssid); + return -1; +} + + +static void hostapd_neighbor_free(struct hostapd_neighbor_entry *nr) +{ + hostapd_neighbor_clear_entry(nr); + dl_list_del(&nr->list); + os_free(nr); +} + + +int hostapd_neighbor_remove(struct hostapd_data *hapd, const u8 *bssid, + const struct wpa_ssid_value *ssid) +{ + struct hostapd_neighbor_entry *nr; + + nr = hostapd_neighbor_get(hapd, bssid, ssid); + if (!nr) + return -1; + + hostapd_neighbor_free(nr); + + return 0; +} + + +void hostapd_free_neighbor_db(struct hostapd_data *hapd) +{ + struct hostapd_neighbor_entry *nr, *prev; + + dl_list_for_each_safe(nr, prev, &hapd->nr_db, + struct hostapd_neighbor_entry, list) { + hostapd_neighbor_free(nr); + } +} + + +#ifdef NEED_AP_MLME +static enum nr_chan_width hostapd_get_nr_chan_width(struct hostapd_data *hapd, + int ht, int vht, int he) +{ + enum oper_chan_width oper_chwidth; + + oper_chwidth = hostapd_get_oper_chwidth(hapd->iconf); + + if (!ht && !vht && !he) + return NR_CHAN_WIDTH_20; + if (!hapd->iconf->secondary_channel) + return NR_CHAN_WIDTH_20; + if ((!vht && !he) || oper_chwidth == CONF_OPER_CHWIDTH_USE_HT) + return NR_CHAN_WIDTH_40; + if (oper_chwidth == CONF_OPER_CHWIDTH_80MHZ) + return NR_CHAN_WIDTH_80; + if (oper_chwidth == CONF_OPER_CHWIDTH_160MHZ) + return NR_CHAN_WIDTH_160; + if (oper_chwidth == CONF_OPER_CHWIDTH_80P80MHZ) + return NR_CHAN_WIDTH_80P80; + return NR_CHAN_WIDTH_20; +} +#endif /* NEED_AP_MLME */ + + +void hostapd_neighbor_set_own_report(struct hostapd_data *hapd) +{ +#ifdef NEED_AP_MLME + u16 capab = hostapd_own_capab_info(hapd); + int ht = hapd->iconf->ieee80211n && !hapd->conf->disable_11n; + int vht = hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac; + int he = hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax; + bool eht = he && hapd->iconf->ieee80211be && !hapd->conf->disable_11be; + struct wpa_ssid_value ssid; + u8 channel, op_class; + u8 center_freq1_idx = 0, center_freq2_idx = 0; + enum nr_chan_width width; + u32 bssid_info; + struct wpabuf *nr; + + if (!(hapd->conf->radio_measurements[0] & + WLAN_RRM_CAPS_NEIGHBOR_REPORT)) + return; + + bssid_info = 3; /* AP is reachable */ + bssid_info |= NEI_REP_BSSID_INFO_SECURITY; /* "same as the AP" */ + bssid_info |= NEI_REP_BSSID_INFO_KEY_SCOPE; /* "same as the AP" */ + + if (capab & WLAN_CAPABILITY_SPECTRUM_MGMT) + bssid_info |= NEI_REP_BSSID_INFO_SPECTRUM_MGMT; + + bssid_info |= NEI_REP_BSSID_INFO_RM; /* RRM is supported */ + + if (hapd->conf->wmm_enabled) { + bssid_info |= NEI_REP_BSSID_INFO_QOS; + + if (hapd->conf->wmm_uapsd && + (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_UAPSD)) + bssid_info |= NEI_REP_BSSID_INFO_APSD; + } + + if (ht) { + bssid_info |= NEI_REP_BSSID_INFO_HT | + NEI_REP_BSSID_INFO_DELAYED_BA; + + /* VHT bit added in IEEE P802.11-REVmc/D4.3 */ + if (vht) + bssid_info |= NEI_REP_BSSID_INFO_VHT; + } + + if (he) + bssid_info |= NEI_REP_BSSID_INFO_HE; + if (eht) + bssid_info |= NEI_REP_BSSID_INFO_EHT; + /* TODO: Set NEI_REP_BSSID_INFO_MOBILITY_DOMAIN if MDE is set */ + + if (ieee80211_freq_to_channel_ext(hapd->iface->freq, + hapd->iconf->secondary_channel, + hostapd_get_oper_chwidth(hapd->iconf), + &op_class, &channel) == + NUM_HOSTAPD_MODES) + return; + width = hostapd_get_nr_chan_width(hapd, ht, vht, he); + if (vht) { + center_freq1_idx = hostapd_get_oper_centr_freq_seg0_idx( + hapd->iconf); + if (width == NR_CHAN_WIDTH_80P80) + center_freq2_idx = + hostapd_get_oper_centr_freq_seg1_idx( + hapd->iconf); + } else if (ht) { + ieee80211_freq_to_chan(hapd->iface->freq + + 10 * hapd->iconf->secondary_channel, + ¢er_freq1_idx); + } + + ssid.ssid_len = hapd->conf->ssid.ssid_len; + os_memcpy(ssid.ssid, hapd->conf->ssid.ssid, ssid.ssid_len); + + /* + * Neighbor Report element size = BSSID + BSSID info + op_class + chan + + * phy type + wide bandwidth channel subelement. + */ + nr = wpabuf_alloc(ETH_ALEN + 4 + 1 + 1 + 1 + 5); + if (!nr) + return; + + wpabuf_put_data(nr, hapd->own_addr, ETH_ALEN); + wpabuf_put_le32(nr, bssid_info); + wpabuf_put_u8(nr, op_class); + wpabuf_put_u8(nr, channel); + wpabuf_put_u8(nr, ieee80211_get_phy_type(hapd->iface->freq, ht, vht)); + + /* + * Wide Bandwidth Channel subelement may be needed to allow the + * receiving STA to send packets to the AP. See IEEE P802.11-REVmc/D5.0 + * Figure 9-301. + */ + wpabuf_put_u8(nr, WNM_NEIGHBOR_WIDE_BW_CHAN); + wpabuf_put_u8(nr, 3); + wpabuf_put_u8(nr, width); + wpabuf_put_u8(nr, center_freq1_idx); + wpabuf_put_u8(nr, center_freq2_idx); + + hostapd_neighbor_set(hapd, hapd->own_addr, &ssid, nr, hapd->iconf->lci, + hapd->iconf->civic, hapd->iconf->stationary_ap, 0); + + wpabuf_free(nr); +#endif /* NEED_AP_MLME */ +} + + +static struct hostapd_neighbor_entry * +hostapd_neighbor_get_diff_short_ssid(struct hostapd_data *hapd, const u8 *bssid) +{ + struct hostapd_neighbor_entry *nr; + + dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry, + list) { + if (ether_addr_equal(bssid, nr->bssid) && + nr->short_ssid != hapd->conf->ssid.short_ssid) + return nr; + } + return NULL; +} + + +int hostapd_neighbor_sync_own_report(struct hostapd_data *hapd) +{ + struct hostapd_neighbor_entry *nr; + + nr = hostapd_neighbor_get_diff_short_ssid(hapd, hapd->own_addr); + if (!nr) + return -1; + + /* Clear old entry due to SSID change */ + hostapd_neighbor_free(nr); + + hostapd_neighbor_set_own_report(hapd); + + return 0; +} diff --git a/src/ap/neighbor_db.h b/src/ap/neighbor_db.h new file mode 100644 index 0000000..53f7142 --- /dev/null +++ b/src/ap/neighbor_db.h @@ -0,0 +1,28 @@ +/* + * hostapd / Neighboring APs DB + * Copyright(c) 2013 - 2016 Intel Mobile Communications GmbH. + * Copyright(c) 2011 - 2016 Intel Corporation. All rights reserved. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef NEIGHBOR_DB_H +#define NEIGHBOR_DB_H + +struct hostapd_neighbor_entry * +hostapd_neighbor_get(struct hostapd_data *hapd, const u8 *bssid, + const struct wpa_ssid_value *ssid); +int hostapd_neighbor_show(struct hostapd_data *hapd, char *buf, size_t buflen); +int hostapd_neighbor_set(struct hostapd_data *hapd, const u8 *bssid, + const struct wpa_ssid_value *ssid, + const struct wpabuf *nr, const struct wpabuf *lci, + const struct wpabuf *civic, int stationary, + u8 bss_parameters); +void hostapd_neighbor_set_own_report(struct hostapd_data *hapd); +int hostapd_neighbor_sync_own_report(struct hostapd_data *hapd); +int hostapd_neighbor_remove(struct hostapd_data *hapd, const u8 *bssid, + const struct wpa_ssid_value *ssid); +void hostapd_free_neighbor_db(struct hostapd_data *hapd); + +#endif /* NEIGHBOR_DB_H */ diff --git a/src/ap/p2p_hostapd.c b/src/ap/p2p_hostapd.c new file mode 100644 index 0000000..9be640c --- /dev/null +++ b/src/ap/p2p_hostapd.c @@ -0,0 +1,113 @@ +/* + * hostapd / P2P integration + * Copyright (c) 2009-2010, Atheros Communications + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "p2p/p2p.h" +#include "hostapd.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "sta_info.h" +#include "p2p_hostapd.h" + + +#ifdef CONFIG_P2P + +int hostapd_p2p_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, + char *buf, size_t buflen) +{ + if (sta->p2p_ie == NULL) + return 0; + + return p2p_ie_text(sta->p2p_ie, buf, buf + buflen); +} + + +int hostapd_p2p_set_noa(struct hostapd_data *hapd, u8 count, int start, + int duration) +{ + wpa_printf(MSG_DEBUG, "P2P: Set NoA parameters: count=%u start=%d " + "duration=%d", count, start, duration); + + if (count == 0) { + hapd->noa_enabled = 0; + hapd->noa_start = 0; + hapd->noa_duration = 0; + } + + if (count != 255) { + wpa_printf(MSG_DEBUG, "P2P: Non-periodic NoA - set " + "NoA parameters"); + return hostapd_driver_set_noa(hapd, count, start, duration); + } + + hapd->noa_enabled = 1; + hapd->noa_start = start; + hapd->noa_duration = duration; + + if (hapd->num_sta_no_p2p == 0) { + wpa_printf(MSG_DEBUG, "P2P: No legacy STAs connected - update " + "periodic NoA parameters"); + return hostapd_driver_set_noa(hapd, count, start, duration); + } + + wpa_printf(MSG_DEBUG, "P2P: Legacy STA(s) connected - do not enable " + "periodic NoA"); + + return 0; +} + + +void hostapd_p2p_non_p2p_sta_connected(struct hostapd_data *hapd) +{ + wpa_printf(MSG_DEBUG, "P2P: First non-P2P device connected"); + + if (hapd->noa_enabled) { + wpa_printf(MSG_DEBUG, "P2P: Disable periodic NoA"); + hostapd_driver_set_noa(hapd, 0, 0, 0); + } +} + + +void hostapd_p2p_non_p2p_sta_disconnected(struct hostapd_data *hapd) +{ + wpa_printf(MSG_DEBUG, "P2P: Last non-P2P device disconnected"); + + if (hapd->noa_enabled) { + wpa_printf(MSG_DEBUG, "P2P: Enable periodic NoA"); + hostapd_driver_set_noa(hapd, 255, hapd->noa_start, + hapd->noa_duration); + } +} + +#endif /* CONFIG_P2P */ + + +#ifdef CONFIG_P2P_MANAGER +u8 * hostapd_eid_p2p_manage(struct hostapd_data *hapd, u8 *eid) +{ + u8 bitmap; + *eid++ = WLAN_EID_VENDOR_SPECIFIC; + *eid++ = 4 + 3 + 1; + WPA_PUT_BE32(eid, P2P_IE_VENDOR_TYPE); + eid += 4; + + *eid++ = P2P_ATTR_MANAGEABILITY; + WPA_PUT_LE16(eid, 1); + eid += 2; + bitmap = P2P_MAN_DEVICE_MANAGEMENT; + if (hapd->conf->p2p & P2P_ALLOW_CROSS_CONNECTION) + bitmap |= P2P_MAN_CROSS_CONNECTION_PERMITTED; + bitmap |= P2P_MAN_COEXISTENCE_OPTIONAL; + *eid++ = bitmap; + + return eid; +} +#endif /* CONFIG_P2P_MANAGER */ diff --git a/src/ap/p2p_hostapd.h b/src/ap/p2p_hostapd.h new file mode 100644 index 0000000..0e3921c --- /dev/null +++ b/src/ap/p2p_hostapd.h @@ -0,0 +1,35 @@ +/* + * hostapd / P2P integration + * Copyright (c) 2009-2010, Atheros Communications + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef P2P_HOSTAPD_H +#define P2P_HOSTAPD_H + +#ifdef CONFIG_P2P + +int hostapd_p2p_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, + char *buf, size_t buflen); +int hostapd_p2p_set_noa(struct hostapd_data *hapd, u8 count, int start, + int duration); +void hostapd_p2p_non_p2p_sta_connected(struct hostapd_data *hapd); +void hostapd_p2p_non_p2p_sta_disconnected(struct hostapd_data *hapd); + + +#else /* CONFIG_P2P */ + +static inline int hostapd_p2p_get_mib_sta(struct hostapd_data *hapd, + struct sta_info *sta, + char *buf, size_t buflen) +{ + return 0; +} + +#endif /* CONFIG_P2P */ + +u8 * hostapd_eid_p2p_manage(struct hostapd_data *hapd, u8 *eid); + +#endif /* P2P_HOSTAPD_H */ diff --git a/src/ap/pmksa_cache_auth.c b/src/ap/pmksa_cache_auth.c new file mode 100644 index 0000000..2fce838 --- /dev/null +++ b/src/ap/pmksa_cache_auth.c @@ -0,0 +1,751 @@ +/* + * hostapd - PMKSA cache for IEEE 802.11i RSN + * Copyright (c) 2004-2008, 2012-2015, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "eapol_auth/eapol_auth_sm_i.h" +#include "radius/radius_das.h" +#include "sta_info.h" +#include "ap_config.h" +#include "pmksa_cache_auth.h" + + +static const int pmksa_cache_max_entries = 1024; +static const int dot11RSNAConfigPMKLifetime = 43200; + +struct rsn_pmksa_cache { +#define PMKID_HASH_SIZE 128 +#define PMKID_HASH(pmkid) (unsigned int) ((pmkid)[0] & 0x7f) + struct rsn_pmksa_cache_entry *pmkid[PMKID_HASH_SIZE]; + struct rsn_pmksa_cache_entry *pmksa; + int pmksa_count; + + void (*free_cb)(struct rsn_pmksa_cache_entry *entry, void *ctx); + void *ctx; +}; + + +static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa); + + +static void _pmksa_cache_free_entry(struct rsn_pmksa_cache_entry *entry) +{ + os_free(entry->vlan_desc); + os_free(entry->identity); + os_free(entry->dpp_pkhash); + wpabuf_free(entry->cui); +#ifndef CONFIG_NO_RADIUS + radius_free_class(&entry->radius_class); +#endif /* CONFIG_NO_RADIUS */ + bin_clear_free(entry, sizeof(*entry)); +} + + +void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa, + struct rsn_pmksa_cache_entry *entry) +{ + struct rsn_pmksa_cache_entry *pos, *prev; + unsigned int hash; + + pmksa->pmksa_count--; + + if (pmksa->free_cb) + pmksa->free_cb(entry, pmksa->ctx); + + /* unlink from hash list */ + hash = PMKID_HASH(entry->pmkid); + pos = pmksa->pmkid[hash]; + prev = NULL; + while (pos) { + if (pos == entry) { + if (prev != NULL) + prev->hnext = entry->hnext; + else + pmksa->pmkid[hash] = entry->hnext; + break; + } + prev = pos; + pos = pos->hnext; + } + + /* unlink from entry list */ + pos = pmksa->pmksa; + prev = NULL; + while (pos) { + if (pos == entry) { + if (prev != NULL) + prev->next = entry->next; + else + pmksa->pmksa = entry->next; + break; + } + prev = pos; + pos = pos->next; + } + + _pmksa_cache_free_entry(entry); +} + + +/** + * pmksa_cache_auth_flush - Flush all PMKSA cache entries + * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() + */ +void pmksa_cache_auth_flush(struct rsn_pmksa_cache *pmksa) +{ + while (pmksa->pmksa) { + wpa_printf(MSG_DEBUG, "RSN: Flush PMKSA cache entry for " + MACSTR, MAC2STR(pmksa->pmksa->spa)); + pmksa_cache_free_entry(pmksa, pmksa->pmksa); + } +} + + +static void pmksa_cache_expire(void *eloop_ctx, void *timeout_ctx) +{ + struct rsn_pmksa_cache *pmksa = eloop_ctx; + struct os_reltime now; + + os_get_reltime(&now); + while (pmksa->pmksa && pmksa->pmksa->expiration <= now.sec) { + wpa_printf(MSG_DEBUG, "RSN: expired PMKSA cache entry for " + MACSTR, MAC2STR(pmksa->pmksa->spa)); + pmksa_cache_free_entry(pmksa, pmksa->pmksa); + } + + pmksa_cache_set_expiration(pmksa); +} + + +static void pmksa_cache_set_expiration(struct rsn_pmksa_cache *pmksa) +{ + int sec; + struct os_reltime now; + + eloop_cancel_timeout(pmksa_cache_expire, pmksa, NULL); + if (pmksa->pmksa == NULL) + return; + os_get_reltime(&now); + sec = pmksa->pmksa->expiration - now.sec; + if (sec < 0) + sec = 0; + eloop_register_timeout(sec + 1, 0, pmksa_cache_expire, pmksa, NULL); +} + + +static void pmksa_cache_from_eapol_data(struct rsn_pmksa_cache_entry *entry, + struct eapol_state_machine *eapol) +{ + struct vlan_description *vlan_desc; + + if (eapol == NULL) + return; + + if (eapol->identity) { + entry->identity = os_malloc(eapol->identity_len); + if (entry->identity) { + entry->identity_len = eapol->identity_len; + os_memcpy(entry->identity, eapol->identity, + eapol->identity_len); + } + } + + if (eapol->radius_cui) + entry->cui = wpabuf_dup(eapol->radius_cui); + +#ifndef CONFIG_NO_RADIUS + radius_copy_class(&entry->radius_class, &eapol->radius_class); +#endif /* CONFIG_NO_RADIUS */ + + entry->eap_type_authsrv = eapol->eap_type_authsrv; + + vlan_desc = ((struct sta_info *) eapol->sta)->vlan_desc; + if (vlan_desc && vlan_desc->notempty) { + entry->vlan_desc = os_zalloc(sizeof(struct vlan_description)); + if (entry->vlan_desc) + *entry->vlan_desc = *vlan_desc; + } else { + entry->vlan_desc = NULL; + } + + entry->acct_multi_session_id = eapol->acct_multi_session_id; +} + + +void pmksa_cache_to_eapol_data(struct hostapd_data *hapd, + struct rsn_pmksa_cache_entry *entry, + struct eapol_state_machine *eapol) +{ + if (entry == NULL || eapol == NULL) + return; + + if (entry->identity) { + os_free(eapol->identity); + eapol->identity = os_malloc(entry->identity_len); + if (eapol->identity) { + eapol->identity_len = entry->identity_len; + os_memcpy(eapol->identity, entry->identity, + entry->identity_len); + } + wpa_hexdump_ascii(MSG_DEBUG, "STA identity from PMKSA", + eapol->identity, eapol->identity_len); + } + + if (entry->cui) { + wpabuf_free(eapol->radius_cui); + eapol->radius_cui = wpabuf_dup(entry->cui); + } + +#ifndef CONFIG_NO_RADIUS + radius_free_class(&eapol->radius_class); + radius_copy_class(&eapol->radius_class, &entry->radius_class); +#endif /* CONFIG_NO_RADIUS */ + if (eapol->radius_class.attr) { + wpa_printf(MSG_DEBUG, "Copied %lu Class attribute(s) from " + "PMKSA", (unsigned long) eapol->radius_class.count); + } + + eapol->eap_type_authsrv = entry->eap_type_authsrv; +#ifndef CONFIG_NO_VLAN + ap_sta_set_vlan(hapd, eapol->sta, entry->vlan_desc); +#endif /* CONFIG_NO_VLAN */ + + eapol->acct_multi_session_id = entry->acct_multi_session_id; +} + + +static void pmksa_cache_link_entry(struct rsn_pmksa_cache *pmksa, + struct rsn_pmksa_cache_entry *entry) +{ + struct rsn_pmksa_cache_entry *pos, *prev; + int hash; + + /* Add the new entry; order by expiration time */ + pos = pmksa->pmksa; + prev = NULL; + while (pos) { + if (pos->expiration > entry->expiration) + break; + prev = pos; + pos = pos->next; + } + if (prev == NULL) { + entry->next = pmksa->pmksa; + pmksa->pmksa = entry; + } else { + entry->next = prev->next; + prev->next = entry; + } + + hash = PMKID_HASH(entry->pmkid); + entry->hnext = pmksa->pmkid[hash]; + pmksa->pmkid[hash] = entry; + + pmksa->pmksa_count++; + if (prev == NULL) + pmksa_cache_set_expiration(pmksa); + wpa_printf(MSG_DEBUG, "RSN: added PMKSA cache entry for " MACSTR, + MAC2STR(entry->spa)); + wpa_hexdump(MSG_DEBUG, "RSN: added PMKID", entry->pmkid, PMKID_LEN); +} + + +/** + * pmksa_cache_auth_add - Add a PMKSA cache entry + * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() + * @pmk: The new pairwise master key + * @pmk_len: PMK length in bytes, usually PMK_LEN (32) + * @pmkid: Calculated PMKID + * @kck: Key confirmation key or %NULL if not yet derived + * @kck_len: KCK length in bytes + * @aa: Authenticator address + * @spa: Supplicant address + * @session_timeout: Session timeout + * @eapol: Pointer to EAPOL state machine data + * @akmp: WPA_KEY_MGMT_* used in key derivation + * Returns: Pointer to the added PMKSA cache entry or %NULL on error + * + * This function create a PMKSA entry for a new PMK and adds it to the PMKSA + * cache. If an old entry is already in the cache for the same Supplicant, + * this entry will be replaced with the new entry. PMKID will be calculated + * based on the PMK. + */ +struct rsn_pmksa_cache_entry * +pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa, + const u8 *pmk, size_t pmk_len, const u8 *pmkid, + const u8 *kck, size_t kck_len, + const u8 *aa, const u8 *spa, int session_timeout, + struct eapol_state_machine *eapol, int akmp) +{ + struct rsn_pmksa_cache_entry *entry; + + entry = pmksa_cache_auth_create_entry(pmk, pmk_len, pmkid, kck, kck_len, + aa, spa, session_timeout, eapol, + akmp); + + if (pmksa_cache_auth_add_entry(pmksa, entry) < 0) + return NULL; + + return entry; +} + + +/** + * pmksa_cache_auth_create_entry - Create a PMKSA cache entry + * @pmk: The new pairwise master key + * @pmk_len: PMK length in bytes, usually PMK_LEN (32) + * @pmkid: Calculated PMKID + * @kck: Key confirmation key or %NULL if not yet derived + * @kck_len: KCK length in bytes + * @aa: Authenticator address + * @spa: Supplicant address + * @session_timeout: Session timeout + * @eapol: Pointer to EAPOL state machine data + * @akmp: WPA_KEY_MGMT_* used in key derivation + * Returns: Pointer to the added PMKSA cache entry or %NULL on error + * + * This function creates a PMKSA entry. + */ +struct rsn_pmksa_cache_entry * +pmksa_cache_auth_create_entry(const u8 *pmk, size_t pmk_len, const u8 *pmkid, + const u8 *kck, size_t kck_len, const u8 *aa, + const u8 *spa, int session_timeout, + struct eapol_state_machine *eapol, int akmp) +{ + struct rsn_pmksa_cache_entry *entry; + struct os_reltime now; + + if (pmk_len > PMK_LEN_MAX) + return NULL; + + if (wpa_key_mgmt_suite_b(akmp) && !kck) + return NULL; + + entry = os_zalloc(sizeof(*entry)); + if (entry == NULL) + return NULL; + os_memcpy(entry->pmk, pmk, pmk_len); + entry->pmk_len = pmk_len; + if (kck && kck_len && kck_len < WPA_KCK_MAX_LEN) { + os_memcpy(entry->kck, kck, kck_len); + entry->kck_len = kck_len; + } + if (pmkid) + os_memcpy(entry->pmkid, pmkid, PMKID_LEN); + else if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192) + rsn_pmkid_suite_b_192(kck, kck_len, aa, spa, entry->pmkid); + else if (wpa_key_mgmt_suite_b(akmp)) + rsn_pmkid_suite_b(kck, kck_len, aa, spa, entry->pmkid); + else + rsn_pmkid(pmk, pmk_len, aa, spa, entry->pmkid, akmp); + os_get_reltime(&now); + entry->expiration = now.sec; + if (session_timeout > 0) + entry->expiration += session_timeout; + else + entry->expiration += dot11RSNAConfigPMKLifetime; + entry->akmp = akmp; + os_memcpy(entry->spa, spa, ETH_ALEN); + pmksa_cache_from_eapol_data(entry, eapol); + + return entry; +} + + +/** + * pmksa_cache_auth_add_entry - Add a PMKSA cache entry + * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() + * @entry: Pointer to PMKSA cache entry + * + * This function adds PMKSA cache entry to the PMKSA cache. If an old entry is + * already in the cache for the same Supplicant, this entry will be replaced + * with the new entry. PMKID will be calculated based on the PMK. + */ +int pmksa_cache_auth_add_entry(struct rsn_pmksa_cache *pmksa, + struct rsn_pmksa_cache_entry *entry) +{ + struct rsn_pmksa_cache_entry *pos; + + if (entry == NULL) + return -1; + + /* Replace an old entry for the same STA (if found) with the new entry + */ + pos = pmksa_cache_auth_get(pmksa, entry->spa, NULL); + if (pos) + pmksa_cache_free_entry(pmksa, pos); + + if (pmksa->pmksa_count >= pmksa_cache_max_entries && pmksa->pmksa) { + /* Remove the oldest entry to make room for the new entry */ + wpa_printf(MSG_DEBUG, "RSN: removed the oldest PMKSA cache " + "entry (for " MACSTR ") to make room for new one", + MAC2STR(pmksa->pmksa->spa)); + pmksa_cache_free_entry(pmksa, pmksa->pmksa); + } + + pmksa_cache_link_entry(pmksa, entry); + + return 0; +} + + +struct rsn_pmksa_cache_entry * +pmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa, + const struct rsn_pmksa_cache_entry *old_entry, + const u8 *aa, const u8 *pmkid) +{ + struct rsn_pmksa_cache_entry *entry; + + entry = os_zalloc(sizeof(*entry)); + if (entry == NULL) + return NULL; + os_memcpy(entry->pmkid, pmkid, PMKID_LEN); + os_memcpy(entry->pmk, old_entry->pmk, old_entry->pmk_len); + entry->pmk_len = old_entry->pmk_len; + entry->expiration = old_entry->expiration; + entry->akmp = old_entry->akmp; + os_memcpy(entry->spa, old_entry->spa, ETH_ALEN); + entry->opportunistic = 1; + if (old_entry->identity) { + entry->identity = os_malloc(old_entry->identity_len); + if (entry->identity) { + entry->identity_len = old_entry->identity_len; + os_memcpy(entry->identity, old_entry->identity, + old_entry->identity_len); + } + } + if (old_entry->cui) + entry->cui = wpabuf_dup(old_entry->cui); +#ifndef CONFIG_NO_RADIUS + radius_copy_class(&entry->radius_class, &old_entry->radius_class); +#endif /* CONFIG_NO_RADIUS */ + entry->eap_type_authsrv = old_entry->eap_type_authsrv; + if (old_entry->vlan_desc) { + entry->vlan_desc = os_zalloc(sizeof(struct vlan_description)); + if (entry->vlan_desc) + *entry->vlan_desc = *old_entry->vlan_desc; + } else { + entry->vlan_desc = NULL; + } + entry->opportunistic = 1; + + pmksa_cache_link_entry(pmksa, entry); + + return entry; +} + + +/** + * pmksa_cache_auth_deinit - Free all entries in PMKSA cache + * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() + */ +void pmksa_cache_auth_deinit(struct rsn_pmksa_cache *pmksa) +{ + struct rsn_pmksa_cache_entry *entry, *prev; + int i; + + if (pmksa == NULL) + return; + + entry = pmksa->pmksa; + while (entry) { + prev = entry; + entry = entry->next; + _pmksa_cache_free_entry(prev); + } + eloop_cancel_timeout(pmksa_cache_expire, pmksa, NULL); + pmksa->pmksa_count = 0; + pmksa->pmksa = NULL; + for (i = 0; i < PMKID_HASH_SIZE; i++) + pmksa->pmkid[i] = NULL; + os_free(pmksa); +} + + +/** + * pmksa_cache_auth_get - Fetch a PMKSA cache entry + * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() + * @spa: Supplicant address or %NULL to match any + * @pmkid: PMKID or %NULL to match any + * Returns: Pointer to PMKSA cache entry or %NULL if no match was found + */ +struct rsn_pmksa_cache_entry * +pmksa_cache_auth_get(struct rsn_pmksa_cache *pmksa, + const u8 *spa, const u8 *pmkid) +{ + struct rsn_pmksa_cache_entry *entry; + + if (pmkid) { + for (entry = pmksa->pmkid[PMKID_HASH(pmkid)]; entry; + entry = entry->hnext) { + if ((spa == NULL || + ether_addr_equal(entry->spa, spa)) && + os_memcmp(entry->pmkid, pmkid, PMKID_LEN) == 0) + return entry; + } + } else { + for (entry = pmksa->pmksa; entry; entry = entry->next) { + if (spa == NULL || + ether_addr_equal(entry->spa, spa)) + return entry; + } + } + + return NULL; +} + + +/** + * pmksa_cache_get_okc - Fetch a PMKSA cache entry using OKC + * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() + * @aa: Authenticator address + * @spa: Supplicant address + * @pmkid: PMKID + * Returns: Pointer to PMKSA cache entry or %NULL if no match was found + * + * Use opportunistic key caching (OKC) to find a PMK for a supplicant. + */ +struct rsn_pmksa_cache_entry * pmksa_cache_get_okc( + struct rsn_pmksa_cache *pmksa, const u8 *aa, const u8 *spa, + const u8 *pmkid) +{ + struct rsn_pmksa_cache_entry *entry; + u8 new_pmkid[PMKID_LEN]; + + for (entry = pmksa->pmksa; entry; entry = entry->next) { + if (!ether_addr_equal(entry->spa, spa)) + continue; + if (wpa_key_mgmt_sae(entry->akmp) || + wpa_key_mgmt_fils(entry->akmp)) { + if (os_memcmp(entry->pmkid, pmkid, PMKID_LEN) == 0) + return entry; + continue; + } + if (entry->akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 && + entry->kck_len > 0) + rsn_pmkid_suite_b_192(entry->kck, entry->kck_len, + aa, spa, new_pmkid); + else if (wpa_key_mgmt_suite_b(entry->akmp) && + entry->kck_len > 0) + rsn_pmkid_suite_b(entry->kck, entry->kck_len, aa, spa, + new_pmkid); + else + rsn_pmkid(entry->pmk, entry->pmk_len, aa, spa, + new_pmkid, entry->akmp); + if (os_memcmp(new_pmkid, pmkid, PMKID_LEN) == 0) + return entry; + } + return NULL; +} + + +/** + * pmksa_cache_auth_init - Initialize PMKSA cache + * @free_cb: Callback function to be called when a PMKSA cache entry is freed + * @ctx: Context pointer for free_cb function + * Returns: Pointer to PMKSA cache data or %NULL on failure + */ +struct rsn_pmksa_cache * +pmksa_cache_auth_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry, + void *ctx), void *ctx) +{ + struct rsn_pmksa_cache *pmksa; + + pmksa = os_zalloc(sizeof(*pmksa)); + if (pmksa) { + pmksa->free_cb = free_cb; + pmksa->ctx = ctx; + } + + return pmksa; +} + + +static int das_attr_match(struct rsn_pmksa_cache_entry *entry, + struct radius_das_attrs *attr) +{ + int match = 0; + + if (attr->sta_addr) { + if (!ether_addr_equal(attr->sta_addr, entry->spa)) + return 0; + match++; + } + + if (attr->acct_multi_session_id) { + char buf[20]; + + if (attr->acct_multi_session_id_len != 16) + return 0; + os_snprintf(buf, sizeof(buf), "%016llX", + (unsigned long long) entry->acct_multi_session_id); + if (os_memcmp(attr->acct_multi_session_id, buf, 16) != 0) + return 0; + match++; + } + + if (attr->cui) { + if (!entry->cui || + attr->cui_len != wpabuf_len(entry->cui) || + os_memcmp(attr->cui, wpabuf_head(entry->cui), + attr->cui_len) != 0) + return 0; + match++; + } + + if (attr->user_name) { + if (!entry->identity || + attr->user_name_len != entry->identity_len || + os_memcmp(attr->user_name, entry->identity, + attr->user_name_len) != 0) + return 0; + match++; + } + + return match; +} + + +int pmksa_cache_auth_radius_das_disconnect(struct rsn_pmksa_cache *pmksa, + struct radius_das_attrs *attr) +{ + int found = 0; + struct rsn_pmksa_cache_entry *entry, *prev; + + if (attr->acct_session_id) + return -1; + + entry = pmksa->pmksa; + while (entry) { + if (das_attr_match(entry, attr)) { + found++; + prev = entry; + entry = entry->next; + pmksa_cache_free_entry(pmksa, prev); + continue; + } + entry = entry->next; + } + + return found ? 0 : -1; +} + + +/** + * pmksa_cache_auth_list - Dump text list of entries in PMKSA cache + * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() + * @buf: Buffer for the list + * @len: Length of the buffer + * Returns: Number of bytes written to buffer + * + * This function is used to generate a text format representation of the + * current PMKSA cache contents for the ctrl_iface PMKSA command. + */ +int pmksa_cache_auth_list(struct rsn_pmksa_cache *pmksa, char *buf, size_t len) +{ + int i, ret; + char *pos = buf; + struct rsn_pmksa_cache_entry *entry; + struct os_reltime now; + + os_get_reltime(&now); + ret = os_snprintf(pos, buf + len - pos, + "Index / SPA / PMKID / expiration (in seconds) / opportunistic\n"); + if (os_snprintf_error(buf + len - pos, ret)) + return pos - buf; + pos += ret; + i = 0; + entry = pmksa->pmksa; + while (entry) { + ret = os_snprintf(pos, buf + len - pos, "%d " MACSTR " ", + i, MAC2STR(entry->spa)); + if (os_snprintf_error(buf + len - pos, ret)) + return pos - buf; + pos += ret; + pos += wpa_snprintf_hex(pos, buf + len - pos, entry->pmkid, + PMKID_LEN); + ret = os_snprintf(pos, buf + len - pos, " %d %d\n", + (int) (entry->expiration - now.sec), + entry->opportunistic); + if (os_snprintf_error(buf + len - pos, ret)) + return pos - buf; + pos += ret; + entry = entry->next; + } + return pos - buf; +} + + +#ifdef CONFIG_PMKSA_CACHE_EXTERNAL +#ifdef CONFIG_MESH + +/** + * pmksa_cache_auth_list_mesh - Dump text list of entries in PMKSA cache + * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init() + * @addr: MAC address of the peer (NULL means any) + * @buf: Buffer for the list + * @len: Length of the buffer + * Returns: Number of bytes written to buffer + * + * This function is used to generate a text format representation of the + * current PMKSA cache contents for the ctrl_iface PMKSA_GET command to store + * in external storage. + */ +int pmksa_cache_auth_list_mesh(struct rsn_pmksa_cache *pmksa, const u8 *addr, + char *buf, size_t len) +{ + int ret; + char *pos, *end; + struct rsn_pmksa_cache_entry *entry; + struct os_reltime now; + + pos = buf; + end = buf + len; + os_get_reltime(&now); + + + /* + * Entry format: + * + */ + for (entry = pmksa->pmksa; entry; entry = entry->next) { + if (addr && !ether_addr_equal(entry->spa, addr)) + continue; + + ret = os_snprintf(pos, end - pos, MACSTR " ", + MAC2STR(entry->spa)); + if (os_snprintf_error(end - pos, ret)) + return 0; + pos += ret; + + pos += wpa_snprintf_hex(pos, end - pos, entry->pmkid, + PMKID_LEN); + + ret = os_snprintf(pos, end - pos, " "); + if (os_snprintf_error(end - pos, ret)) + return 0; + pos += ret; + + pos += wpa_snprintf_hex(pos, end - pos, entry->pmk, + entry->pmk_len); + + ret = os_snprintf(pos, end - pos, " %d\n", + (int) (entry->expiration - now.sec)); + if (os_snprintf_error(end - pos, ret)) + return 0; + pos += ret; + } + + return pos - buf; +} + +#endif /* CONFIG_MESH */ +#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */ diff --git a/src/ap/pmksa_cache_auth.h b/src/ap/pmksa_cache_auth.h new file mode 100644 index 0000000..e38e7ec --- /dev/null +++ b/src/ap/pmksa_cache_auth.h @@ -0,0 +1,83 @@ +/* + * hostapd - PMKSA cache for IEEE 802.11i RSN + * Copyright (c) 2004-2008, 2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef PMKSA_CACHE_H +#define PMKSA_CACHE_H + +#include "radius/radius.h" + +/** + * struct rsn_pmksa_cache_entry - PMKSA cache entry + */ +struct rsn_pmksa_cache_entry { + struct rsn_pmksa_cache_entry *next, *hnext; + u8 pmkid[PMKID_LEN]; + u8 pmk[PMK_LEN_MAX]; + size_t pmk_len; + u8 kck[WPA_KCK_MAX_LEN]; + size_t kck_len; + os_time_t expiration; + int akmp; /* WPA_KEY_MGMT_* */ + u8 spa[ETH_ALEN]; + + u8 *dpp_pkhash; /* SHA256_MAC_LEN octet hash value of DPP Connector + * public key */ + u8 *identity; + size_t identity_len; + struct wpabuf *cui; + struct radius_class_data radius_class; + u8 eap_type_authsrv; + struct vlan_description *vlan_desc; + int opportunistic; + + u64 acct_multi_session_id; +}; + +struct rsn_pmksa_cache; +struct radius_das_attrs; + +struct rsn_pmksa_cache * +pmksa_cache_auth_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry, + void *ctx), void *ctx); +void pmksa_cache_auth_deinit(struct rsn_pmksa_cache *pmksa); +struct rsn_pmksa_cache_entry * +pmksa_cache_auth_get(struct rsn_pmksa_cache *pmksa, + const u8 *spa, const u8 *pmkid); +struct rsn_pmksa_cache_entry * pmksa_cache_get_okc( + struct rsn_pmksa_cache *pmksa, const u8 *spa, const u8 *aa, + const u8 *pmkid); +struct rsn_pmksa_cache_entry * +pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa, + const u8 *pmk, size_t pmk_len, const u8 *pmkid, + const u8 *kck, size_t kck_len, + const u8 *aa, const u8 *spa, int session_timeout, + struct eapol_state_machine *eapol, int akmp); +struct rsn_pmksa_cache_entry * +pmksa_cache_auth_create_entry(const u8 *pmk, size_t pmk_len, const u8 *pmkid, + const u8 *kck, size_t kck_len, const u8 *aa, + const u8 *spa, int session_timeout, + struct eapol_state_machine *eapol, int akmp); +int pmksa_cache_auth_add_entry(struct rsn_pmksa_cache *pmksa, + struct rsn_pmksa_cache_entry *entry); +struct rsn_pmksa_cache_entry * +pmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa, + const struct rsn_pmksa_cache_entry *old_entry, + const u8 *aa, const u8 *pmkid); +void pmksa_cache_to_eapol_data(struct hostapd_data *hapd, + struct rsn_pmksa_cache_entry *entry, + struct eapol_state_machine *eapol); +void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa, + struct rsn_pmksa_cache_entry *entry); +int pmksa_cache_auth_radius_das_disconnect(struct rsn_pmksa_cache *pmksa, + struct radius_das_attrs *attr); +int pmksa_cache_auth_list(struct rsn_pmksa_cache *pmksa, char *buf, size_t len); +void pmksa_cache_auth_flush(struct rsn_pmksa_cache *pmksa); +int pmksa_cache_auth_list_mesh(struct rsn_pmksa_cache *pmksa, const u8 *addr, + char *buf, size_t len); + +#endif /* PMKSA_CACHE_H */ diff --git a/src/ap/preauth_auth.c b/src/ap/preauth_auth.c new file mode 100644 index 0000000..cb225c6 --- /dev/null +++ b/src/ap/preauth_auth.c @@ -0,0 +1,273 @@ +/* + * hostapd - Authenticator for IEEE 802.11i RSN pre-authentication + * Copyright (c) 2004-2007, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#ifdef CONFIG_RSN_PREAUTH + +#include "utils/common.h" +#include "utils/eloop.h" +#include "l2_packet/l2_packet.h" +#include "common/wpa_common.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "eapol_auth/eapol_auth_sm_i.h" +#include "hostapd.h" +#include "ap_config.h" +#include "ieee802_1x.h" +#include "sta_info.h" +#include "wpa_auth.h" +#include "preauth_auth.h" + +#ifndef ETH_P_PREAUTH +#define ETH_P_PREAUTH 0x88C7 /* IEEE 802.11i pre-authentication */ +#endif /* ETH_P_PREAUTH */ + +static const int dot11RSNAConfigPMKLifetime = 43200; + +struct rsn_preauth_interface { + struct rsn_preauth_interface *next; + struct hostapd_data *hapd; + struct l2_packet_data *l2; + char *ifname; + int ifindex; +}; + + +static void rsn_preauth_receive(void *ctx, const u8 *src_addr, + const u8 *buf, size_t len) +{ + struct rsn_preauth_interface *piface = ctx; + struct hostapd_data *hapd = piface->hapd; + struct ieee802_1x_hdr *hdr; + struct sta_info *sta; + struct l2_ethhdr *ethhdr; + + wpa_printf(MSG_DEBUG, "RSN: receive pre-auth packet " + "from interface '%s'", piface->ifname); + if (len < sizeof(*ethhdr) + sizeof(*hdr)) { + wpa_printf(MSG_DEBUG, "RSN: too short pre-auth packet " + "(len=%lu)", (unsigned long) len); + return; + } + + ethhdr = (struct l2_ethhdr *) buf; + hdr = (struct ieee802_1x_hdr *) (ethhdr + 1); + + if (!ether_addr_equal(ethhdr->h_dest, hapd->own_addr)) { + wpa_printf(MSG_DEBUG, "RSN: pre-auth for foreign address " + MACSTR, MAC2STR(ethhdr->h_dest)); + return; + } + + sta = ap_get_sta(hapd, ethhdr->h_source); + if (sta && (sta->flags & WLAN_STA_ASSOC)) { + wpa_printf(MSG_DEBUG, "RSN: pre-auth for already association " + "STA " MACSTR, MAC2STR(sta->addr)); + return; + } + if (!sta && hdr->type == IEEE802_1X_TYPE_EAPOL_START) { + sta = ap_sta_add(hapd, ethhdr->h_source); + if (sta == NULL) + return; + sta->flags = WLAN_STA_PREAUTH; + + ieee802_1x_new_station(hapd, sta); + if (sta->eapol_sm == NULL) { + ap_free_sta(hapd, sta); + sta = NULL; + } else { + sta->eapol_sm->radius_identifier = -1; + sta->eapol_sm->portValid = true; + sta->eapol_sm->flags |= EAPOL_SM_PREAUTH; + } + } + if (sta == NULL) + return; + sta->preauth_iface = piface; + ieee802_1x_receive(hapd, ethhdr->h_source, (u8 *) (ethhdr + 1), + len - sizeof(*ethhdr), FRAME_ENCRYPTION_UNKNOWN); +} + + +static int rsn_preauth_iface_add(struct hostapd_data *hapd, const char *ifname) +{ + struct rsn_preauth_interface *piface; + + wpa_printf(MSG_DEBUG, "RSN pre-auth interface '%s'", ifname); + + piface = os_zalloc(sizeof(*piface)); + if (piface == NULL) + return -1; + piface->hapd = hapd; + + piface->ifname = os_strdup(ifname); + if (piface->ifname == NULL) { + goto fail1; + } + + piface->l2 = l2_packet_init(piface->ifname, NULL, ETH_P_PREAUTH, + rsn_preauth_receive, piface, 1); + if (piface->l2 == NULL) { + wpa_printf(MSG_ERROR, "Failed to open register layer 2 access " + "to ETH_P_PREAUTH"); + goto fail2; + } + + piface->next = hapd->preauth_iface; + hapd->preauth_iface = piface; + return 0; + +fail2: + os_free(piface->ifname); +fail1: + os_free(piface); + return -1; +} + + +void rsn_preauth_iface_deinit(struct hostapd_data *hapd) +{ + struct rsn_preauth_interface *piface, *prev; + + piface = hapd->preauth_iface; + hapd->preauth_iface = NULL; + while (piface) { + prev = piface; + piface = piface->next; + l2_packet_deinit(prev->l2); + os_free(prev->ifname); + os_free(prev); + } +} + + +int rsn_preauth_iface_init(struct hostapd_data *hapd) +{ + char *tmp, *start, *end; + + if (hapd->conf->rsn_preauth_interfaces == NULL) + return 0; + + tmp = os_strdup(hapd->conf->rsn_preauth_interfaces); + if (tmp == NULL) + return -1; + start = tmp; + for (;;) { + while (*start == ' ') + start++; + if (*start == '\0') + break; + end = os_strchr(start, ' '); + if (end) + *end = '\0'; + + if (rsn_preauth_iface_add(hapd, start)) { + rsn_preauth_iface_deinit(hapd); + os_free(tmp); + return -1; + } + + if (end) + start = end + 1; + else + break; + } + os_free(tmp); + return 0; +} + + +static void rsn_preauth_finished_cb(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + wpa_printf(MSG_DEBUG, "RSN: Removing pre-authentication STA entry for " + MACSTR, MAC2STR(sta->addr)); + ap_free_sta(hapd, sta); +} + + +void rsn_preauth_finished(struct hostapd_data *hapd, struct sta_info *sta, + int success) +{ + const u8 *key; + size_t len; + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_WPA, + HOSTAPD_LEVEL_INFO, "pre-authentication %s", + success ? "succeeded" : "failed"); + + key = ieee802_1x_get_key(sta->eapol_sm, &len); + if (len > PMK_LEN) + len = PMK_LEN; + if (success && key) { + if (wpa_auth_pmksa_add_preauth(hapd->wpa_auth, key, len, + sta->addr, + dot11RSNAConfigPMKLifetime, + sta->eapol_sm) == 0) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_WPA, + HOSTAPD_LEVEL_DEBUG, + "added PMKSA cache entry (pre-auth)"); + } else { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_WPA, + HOSTAPD_LEVEL_DEBUG, + "failed to add PMKSA cache entry " + "(pre-auth)"); + } + } + + /* + * Finish STA entry removal from timeout in order to avoid freeing + * STA data before the caller has finished processing. + */ + eloop_register_timeout(0, 0, rsn_preauth_finished_cb, hapd, sta); +} + + +void rsn_preauth_send(struct hostapd_data *hapd, struct sta_info *sta, + u8 *buf, size_t len) +{ + struct rsn_preauth_interface *piface; + struct l2_ethhdr *ethhdr; + + piface = hapd->preauth_iface; + while (piface) { + if (piface == sta->preauth_iface) + break; + piface = piface->next; + } + + if (piface == NULL) { + wpa_printf(MSG_DEBUG, "RSN: Could not find pre-authentication " + "interface for " MACSTR, MAC2STR(sta->addr)); + return; + } + + ethhdr = os_malloc(sizeof(*ethhdr) + len); + if (ethhdr == NULL) + return; + + os_memcpy(ethhdr->h_dest, sta->addr, ETH_ALEN); + os_memcpy(ethhdr->h_source, hapd->own_addr, ETH_ALEN); + ethhdr->h_proto = host_to_be16(ETH_P_PREAUTH); + os_memcpy(ethhdr + 1, buf, len); + + if (l2_packet_send(piface->l2, sta->addr, ETH_P_PREAUTH, (u8 *) ethhdr, + sizeof(*ethhdr) + len) < 0) { + wpa_printf(MSG_ERROR, "Failed to send preauth packet using " + "l2_packet_send\n"); + } + os_free(ethhdr); +} + + +void rsn_preauth_free_station(struct hostapd_data *hapd, struct sta_info *sta) +{ + eloop_cancel_timeout(rsn_preauth_finished_cb, hapd, sta); +} + +#endif /* CONFIG_RSN_PREAUTH */ diff --git a/src/ap/preauth_auth.h b/src/ap/preauth_auth.h new file mode 100644 index 0000000..69fb356 --- /dev/null +++ b/src/ap/preauth_auth.h @@ -0,0 +1,52 @@ +/* + * hostapd - Authenticator for IEEE 802.11i RSN pre-authentication + * Copyright (c) 2004-2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef PREAUTH_H +#define PREAUTH_H + +#ifdef CONFIG_RSN_PREAUTH + +int rsn_preauth_iface_init(struct hostapd_data *hapd); +void rsn_preauth_iface_deinit(struct hostapd_data *hapd); +void rsn_preauth_finished(struct hostapd_data *hapd, struct sta_info *sta, + int success); +void rsn_preauth_send(struct hostapd_data *hapd, struct sta_info *sta, + u8 *buf, size_t len); +void rsn_preauth_free_station(struct hostapd_data *hapd, struct sta_info *sta); + +#else /* CONFIG_RSN_PREAUTH */ + +static inline int rsn_preauth_iface_init(struct hostapd_data *hapd) +{ + return 0; +} + +static inline void rsn_preauth_iface_deinit(struct hostapd_data *hapd) +{ +} + +static inline void rsn_preauth_finished(struct hostapd_data *hapd, + struct sta_info *sta, + int success) +{ +} + +static inline void rsn_preauth_send(struct hostapd_data *hapd, + struct sta_info *sta, + u8 *buf, size_t len) +{ +} + +static inline void rsn_preauth_free_station(struct hostapd_data *hapd, + struct sta_info *sta) +{ +} + +#endif /* CONFIG_RSN_PREAUTH */ + +#endif /* PREAUTH_H */ diff --git a/src/ap/rrm.c b/src/ap/rrm.c new file mode 100644 index 0000000..fbcddf3 --- /dev/null +++ b/src/ap/rrm.c @@ -0,0 +1,795 @@ +/* + * hostapd / Radio Measurement (RRM) + * Copyright(c) 2013 - 2016 Intel Mobile Communications GmbH. + * Copyright(c) 2011 - 2016 Intel Corporation. All rights reserved. + * Copyright (c) 2016-2017, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/wpa_ctrl.h" +#include "hostapd.h" +#include "ap_drv_ops.h" +#include "sta_info.h" +#include "eloop.h" +#include "neighbor_db.h" +#include "rrm.h" + +#define HOSTAPD_RRM_REQUEST_TIMEOUT 5 + + +static void hostapd_lci_rep_timeout_handler(void *eloop_data, void *user_ctx) +{ + struct hostapd_data *hapd = eloop_data; + + wpa_printf(MSG_DEBUG, "RRM: LCI request (token %u) timed out", + hapd->lci_req_token); + hapd->lci_req_active = 0; +} + + +static void hostapd_handle_lci_report(struct hostapd_data *hapd, u8 token, + const u8 *pos, size_t len) +{ + if (!hapd->lci_req_active || hapd->lci_req_token != token) { + wpa_printf(MSG_DEBUG, "Unexpected LCI report, token %u", token); + return; + } + + hapd->lci_req_active = 0; + eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd, NULL); + wpa_printf(MSG_DEBUG, "LCI report token %u len %zu", token, len); +} + + +static void hostapd_range_rep_timeout_handler(void *eloop_data, void *user_ctx) +{ + struct hostapd_data *hapd = eloop_data; + + wpa_printf(MSG_DEBUG, "RRM: Range request (token %u) timed out", + hapd->range_req_token); + hapd->range_req_active = 0; +} + + +static void hostapd_handle_range_report(struct hostapd_data *hapd, u8 token, + const u8 *pos, size_t len) +{ + if (!hapd->range_req_active || hapd->range_req_token != token) { + wpa_printf(MSG_DEBUG, "Unexpected range report, token %u", + token); + return; + } + + hapd->range_req_active = 0; + eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, NULL); + wpa_printf(MSG_DEBUG, "Range report token %u len %zu", token, len); +} + + +static void hostapd_handle_beacon_report(struct hostapd_data *hapd, + const u8 *addr, u8 token, u8 rep_mode, + const u8 *pos, size_t len) +{ + char report[2 * 255 + 1]; + + wpa_printf(MSG_DEBUG, "Beacon report token %u len %zu from " MACSTR, + token, len, MAC2STR(addr)); + /* Skip to the beginning of the Beacon report */ + if (len < 3) + return; + pos += 3; + len -= 3; + report[0] = '\0'; + if (wpa_snprintf_hex(report, sizeof(report), pos, len) < 0) + return; + wpa_msg(hapd->msg_ctx, MSG_INFO, BEACON_RESP_RX MACSTR " %u %02x %s", + MAC2STR(addr), token, rep_mode, report); +} + + +static void hostapd_handle_radio_msmt_report(struct hostapd_data *hapd, + const u8 *buf, size_t len) +{ + const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf; + const u8 *pos, *ie, *end; + u8 token, rep_mode; + + end = buf + len; + token = mgmt->u.action.u.rrm.dialog_token; + pos = mgmt->u.action.u.rrm.variable; + + while ((ie = get_ie(pos, end - pos, WLAN_EID_MEASURE_REPORT))) { + if (ie[1] < 3) { + wpa_printf(MSG_DEBUG, "Bad Measurement Report element"); + break; + } + + rep_mode = ie[3]; + wpa_printf(MSG_DEBUG, "Measurement report mode 0x%x type %u", + rep_mode, ie[4]); + + switch (ie[4]) { + case MEASURE_TYPE_LCI: + hostapd_handle_lci_report(hapd, token, ie + 2, ie[1]); + break; + case MEASURE_TYPE_FTM_RANGE: + hostapd_handle_range_report(hapd, token, ie + 2, ie[1]); + break; + case MEASURE_TYPE_BEACON: + hostapd_handle_beacon_report(hapd, mgmt->sa, token, + rep_mode, ie + 2, ie[1]); + break; + default: + wpa_printf(MSG_DEBUG, + "Measurement report type %u is not supported", + ie[4]); + break; + } + + pos = ie + ie[1] + 2; + } +} + + +static u16 hostapd_parse_location_lci_req_age(const u8 *buf, size_t len) +{ + const u8 *subelem; + + /* Range Request element + Location Subject + Maximum Age subelement */ + if (len < 3 + 1 + 4) + return 0; + + /* Subelements are arranged as IEs */ + subelem = get_ie(buf + 4, len - 4, LCI_REQ_SUBELEM_MAX_AGE); + if (subelem && subelem[1] == 2) + return WPA_GET_LE16(subelem + 2); + + return 0; +} + + +static int hostapd_check_lci_age(struct hostapd_neighbor_entry *nr, u16 max_age) +{ + struct os_time curr, diff; + unsigned long diff_l; + + if (nr->stationary || max_age == 0xffff) + return 1; + + if (!max_age) + return 0; + + if (os_get_time(&curr)) + return 0; + + os_time_sub(&curr, &nr->lci_date, &diff); + + /* avoid overflow */ + if (diff.sec > 0xffff) + return 0; + + /* LCI age is calculated in 10th of a second units. */ + diff_l = diff.sec * 10 + diff.usec / 100000; + + return max_age > diff_l; +} + + +static size_t hostapd_neighbor_report_len(struct wpabuf *buf, + struct hostapd_neighbor_entry *nr, + int send_lci, int send_civic) +{ + size_t len = 2 + wpabuf_len(nr->nr); + + if (send_lci && nr->lci) + len += 2 + wpabuf_len(nr->lci); + + if (send_civic && nr->civic) + len += 2 + wpabuf_len(nr->civic); + + return len; +} + + +static void hostapd_send_nei_report_resp(struct hostapd_data *hapd, + const u8 *addr, u8 dialog_token, + struct wpa_ssid_value *ssid, u8 lci, + u8 civic, u16 lci_max_age) +{ + struct hostapd_neighbor_entry *nr; + struct wpabuf *buf; + u8 *msmt_token; + + /* + * The number and length of the Neighbor Report elements in a Neighbor + * Report frame is limited by the maximum allowed MMPDU size; + 3 bytes + * of RRM header. + */ + buf = wpabuf_alloc(3 + IEEE80211_MAX_MMPDU_SIZE); + if (!buf) + return; + + wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT); + wpabuf_put_u8(buf, WLAN_RRM_NEIGHBOR_REPORT_RESPONSE); + wpabuf_put_u8(buf, dialog_token); + + dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry, + list) { + int send_lci; + size_t len; + + if (ssid->ssid_len != nr->ssid.ssid_len || + os_memcmp(ssid->ssid, nr->ssid.ssid, ssid->ssid_len) != 0) + continue; + + send_lci = (lci != 0) && hostapd_check_lci_age(nr, lci_max_age); + len = hostapd_neighbor_report_len(buf, nr, send_lci, civic); + + if (len - 2 > 0xff) { + wpa_printf(MSG_DEBUG, + "NR entry for " MACSTR " exceeds 0xFF bytes", + MAC2STR(nr->bssid)); + continue; + } + + if (len > wpabuf_tailroom(buf)) + break; + + wpabuf_put_u8(buf, WLAN_EID_NEIGHBOR_REPORT); + wpabuf_put_u8(buf, len - 2); + wpabuf_put_buf(buf, nr->nr); + + if (send_lci && nr->lci) { + wpabuf_put_u8(buf, WLAN_EID_MEASURE_REPORT); + wpabuf_put_u8(buf, wpabuf_len(nr->lci)); + /* + * Override measurement token - the first byte of the + * Measurement Report element. + */ + msmt_token = wpabuf_put(buf, 0); + wpabuf_put_buf(buf, nr->lci); + *msmt_token = lci; + } + + if (civic && nr->civic) { + wpabuf_put_u8(buf, WLAN_EID_MEASURE_REPORT); + wpabuf_put_u8(buf, wpabuf_len(nr->civic)); + /* + * Override measurement token - the first byte of the + * Measurement Report element. + */ + msmt_token = wpabuf_put(buf, 0); + wpabuf_put_buf(buf, nr->civic); + *msmt_token = civic; + } + } + + hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr, + wpabuf_head(buf), wpabuf_len(buf)); + wpabuf_free(buf); +} + + +static void hostapd_handle_nei_report_req(struct hostapd_data *hapd, + const u8 *buf, size_t len) +{ + const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf; + const u8 *pos, *ie, *end; + struct wpa_ssid_value ssid = { + .ssid_len = 0 + }; + u8 token; + u8 lci = 0, civic = 0; /* Measurement tokens */ + u16 lci_max_age = 0; + + if (!(hapd->conf->radio_measurements[0] & + WLAN_RRM_CAPS_NEIGHBOR_REPORT)) + return; + + end = buf + len; + + token = mgmt->u.action.u.rrm.dialog_token; + pos = mgmt->u.action.u.rrm.variable; + len = end - pos; + + ie = get_ie(pos, len, WLAN_EID_SSID); + if (ie && ie[1] && ie[1] <= SSID_MAX_LEN) { + ssid.ssid_len = ie[1]; + os_memcpy(ssid.ssid, ie + 2, ssid.ssid_len); + } else { + ssid.ssid_len = hapd->conf->ssid.ssid_len; + os_memcpy(ssid.ssid, hapd->conf->ssid.ssid, ssid.ssid_len); + } + + while ((ie = get_ie(pos, len, WLAN_EID_MEASURE_REQUEST))) { + if (ie[1] < 3) + break; + + wpa_printf(MSG_DEBUG, + "Neighbor report request, measure type %u", + ie[4]); + + switch (ie[4]) { /* Measurement Type */ + case MEASURE_TYPE_LCI: + lci = ie[2]; /* Measurement Token */ + lci_max_age = hostapd_parse_location_lci_req_age(ie + 2, + ie[1]); + break; + case MEASURE_TYPE_LOCATION_CIVIC: + civic = ie[2]; /* Measurement token */ + break; + } + + pos = ie + ie[1] + 2; + len = end - pos; + } + + hostapd_send_nei_report_resp(hapd, mgmt->sa, token, &ssid, lci, civic, + lci_max_age); +} + + +static void hostapd_link_mesr_rep_timeout_handler(void *eloop_data, + void *user_ctx) +{ + struct hostapd_data *hapd = eloop_data; + + wpa_printf(MSG_DEBUG, + "RRM: Link measurement request (token %u) timed out", + hapd->link_measurement_req_token); + hapd->link_mesr_req_active = 0; +} + + +static void hostapd_handle_link_mesr_report(struct hostapd_data *hapd, + const u8 *buf, size_t len) +{ + const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf; + const struct rrm_link_measurement_report *report; + const u8 *pos, *end; + char report_msg[2 * 8 + 1]; + + end = buf + len; + pos = mgmt->u.action.u.rrm.variable; + report = (const struct rrm_link_measurement_report *) (pos - 1); + if (end - (const u8 *) report < (int) sizeof(*report)) + return; + + if (!hapd->link_mesr_req_active || + (hapd->link_measurement_req_token != report->dialog_token)) { + wpa_printf(MSG_INFO, + "Unexpected Link measurement report, token %u", + report->dialog_token); + return; + } + + hapd->link_mesr_req_active = 0; + eloop_cancel_timeout(hostapd_link_mesr_rep_timeout_handler, hapd, NULL); + + report_msg[0] = '\0'; + if (wpa_snprintf_hex(report_msg, sizeof(report_msg), + pos, end - pos) < 0) + return; + + wpa_msg(hapd->msg_ctx, MSG_INFO, LINK_MSR_RESP_RX MACSTR " %u %s", + MAC2STR(mgmt->sa), report->dialog_token, report_msg); +} + + +void hostapd_handle_radio_measurement(struct hostapd_data *hapd, + const u8 *buf, size_t len) +{ + const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf; + + /* + * Check for enough bytes: header + (1B)Category + (1B)Action + + * (1B)Dialog Token. + */ + if (len < IEEE80211_HDRLEN + 3) + return; + + wpa_printf(MSG_DEBUG, "Radio measurement frame, action %u from " MACSTR, + mgmt->u.action.u.rrm.action, MAC2STR(mgmt->sa)); + + switch (mgmt->u.action.u.rrm.action) { + case WLAN_RRM_RADIO_MEASUREMENT_REPORT: + hostapd_handle_radio_msmt_report(hapd, buf, len); + break; + case WLAN_RRM_NEIGHBOR_REPORT_REQUEST: + hostapd_handle_nei_report_req(hapd, buf, len); + break; + case WLAN_RRM_LINK_MEASUREMENT_REPORT: + hostapd_handle_link_mesr_report(hapd, buf, len); + break; + default: + wpa_printf(MSG_DEBUG, "RRM action %u is not supported", + mgmt->u.action.u.rrm.action); + break; + } +} + + +int hostapd_send_lci_req(struct hostapd_data *hapd, const u8 *addr) +{ + struct wpabuf *buf; + struct sta_info *sta = ap_get_sta(hapd, addr); + int ret; + + if (!sta || !(sta->flags & WLAN_STA_AUTHORIZED)) { + wpa_printf(MSG_INFO, + "Request LCI: Destination address is not connected"); + return -1; + } + + if (!(sta->rrm_enabled_capa[1] & WLAN_RRM_CAPS_LCI_MEASUREMENT)) { + wpa_printf(MSG_INFO, + "Request LCI: Station does not support LCI in RRM"); + return -1; + } + + if (hapd->lci_req_active) { + wpa_printf(MSG_DEBUG, + "Request LCI: LCI request is already in process, overriding"); + hapd->lci_req_active = 0; + eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd, + NULL); + } + + /* Measurement request (5) + Measurement element with LCI (10) */ + buf = wpabuf_alloc(5 + 10); + if (!buf) + return -1; + + hapd->lci_req_token++; + /* For wraparounds - the token must be nonzero */ + if (!hapd->lci_req_token) + hapd->lci_req_token++; + + wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT); + wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REQUEST); + wpabuf_put_u8(buf, hapd->lci_req_token); + wpabuf_put_le16(buf, 0); /* Number of repetitions */ + + wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST); + wpabuf_put_u8(buf, 3 + 1 + 4); + + wpabuf_put_u8(buf, 1); /* Measurement Token */ + /* + * Parallel and Enable bits are 0, Duration, Request, and Report are + * reserved. + */ + wpabuf_put_u8(buf, 0); + wpabuf_put_u8(buf, MEASURE_TYPE_LCI); + + wpabuf_put_u8(buf, LOCATION_SUBJECT_REMOTE); + + wpabuf_put_u8(buf, LCI_REQ_SUBELEM_MAX_AGE); + wpabuf_put_u8(buf, 2); + wpabuf_put_le16(buf, 0xffff); + + ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr, + wpabuf_head(buf), wpabuf_len(buf)); + wpabuf_free(buf); + if (ret) + return ret; + + hapd->lci_req_active = 1; + + eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0, + hostapd_lci_rep_timeout_handler, hapd, NULL); + + return 0; +} + + +int hostapd_send_range_req(struct hostapd_data *hapd, const u8 *addr, + u16 random_interval, u8 min_ap, + const u8 *responders, unsigned int n_responders) +{ + struct wpabuf *buf; + struct sta_info *sta; + u8 *len; + unsigned int i; + int ret; + + wpa_printf(MSG_DEBUG, "Request range: dest addr " MACSTR + " rand interval %u min AP %u n_responders %u", MAC2STR(addr), + random_interval, min_ap, n_responders); + + if (min_ap == 0 || min_ap > n_responders) { + wpa_printf(MSG_INFO, "Request range: Wrong min AP count"); + return -1; + } + + sta = ap_get_sta(hapd, addr); + if (!sta || !(sta->flags & WLAN_STA_AUTHORIZED)) { + wpa_printf(MSG_INFO, + "Request range: Destination address is not connected"); + return -1; + } + + if (!(sta->rrm_enabled_capa[4] & WLAN_RRM_CAPS_FTM_RANGE_REPORT)) { + wpa_printf(MSG_ERROR, + "Request range: Destination station does not support FTM range report in RRM"); + return -1; + } + + if (hapd->range_req_active) { + wpa_printf(MSG_DEBUG, + "Request range: Range request is already in process; overriding"); + hapd->range_req_active = 0; + eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, + NULL); + } + + /* Action + measurement type + token + reps + EID + len = 7 */ + buf = wpabuf_alloc(7 + 255); + if (!buf) + return -1; + + hapd->range_req_token++; + if (!hapd->range_req_token) /* For wraparounds */ + hapd->range_req_token++; + + /* IEEE P802.11-REVmc/D5.0, 9.6.7.2 */ + wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT); + wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REQUEST); + wpabuf_put_u8(buf, hapd->range_req_token); /* Dialog Token */ + wpabuf_put_le16(buf, 0); /* Number of Repetitions */ + + /* IEEE P802.11-REVmc/D5.0, 9.4.2.21 */ + wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST); + len = wpabuf_put(buf, 1); /* Length will be set later */ + + wpabuf_put_u8(buf, 1); /* Measurement Token */ + /* + * Parallel and Enable bits are 0; Duration, Request, and Report are + * reserved. + */ + wpabuf_put_u8(buf, 0); /* Measurement Request Mode */ + wpabuf_put_u8(buf, MEASURE_TYPE_FTM_RANGE); /* Measurement Type */ + + /* IEEE P802.11-REVmc/D5.0, 9.4.2.21.19 */ + wpabuf_put_le16(buf, random_interval); /* Randomization Interval */ + wpabuf_put_u8(buf, min_ap); /* Minimum AP Count */ + + /* FTM Range Subelements */ + + /* + * Taking the neighbor report part of the range request from neighbor + * database instead of requesting the separate bits of data from the + * user. + */ + for (i = 0; i < n_responders; i++) { + struct hostapd_neighbor_entry *nr; + + nr = hostapd_neighbor_get(hapd, responders + ETH_ALEN * i, + NULL); + if (!nr) { + wpa_printf(MSG_INFO, "Missing neighbor report for " + MACSTR, MAC2STR(responders + ETH_ALEN * i)); + wpabuf_free(buf); + return -1; + } + + if (wpabuf_tailroom(buf) < 2 + wpabuf_len(nr->nr)) { + wpa_printf(MSG_ERROR, "Too long range request"); + wpabuf_free(buf); + return -1; + } + + wpabuf_put_u8(buf, WLAN_EID_NEIGHBOR_REPORT); + wpabuf_put_u8(buf, wpabuf_len(nr->nr)); + wpabuf_put_buf(buf, nr->nr); + } + + /* Action + measurement type + token + reps + EID + len = 7 */ + *len = wpabuf_len(buf) - 7; + + ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr, + wpabuf_head(buf), wpabuf_len(buf)); + wpabuf_free(buf); + if (ret) + return ret; + + hapd->range_req_active = 1; + + eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0, + hostapd_range_rep_timeout_handler, hapd, NULL); + + return 0; +} + + +void hostapd_clean_rrm(struct hostapd_data *hapd) +{ + hostapd_free_neighbor_db(hapd); + eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd, NULL); + hapd->lci_req_active = 0; + eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, NULL); + hapd->range_req_active = 0; + eloop_cancel_timeout(hostapd_link_mesr_rep_timeout_handler, hapd, NULL); +} + + +int hostapd_send_beacon_req(struct hostapd_data *hapd, const u8 *addr, + u8 req_mode, const struct wpabuf *req) +{ + struct wpabuf *buf; + struct sta_info *sta = ap_get_sta(hapd, addr); + int ret; + enum beacon_report_mode mode; + const u8 *pos; + + /* Request data: + * Operating Class (1), Channel Number (1), Randomization Interval (2), + * Measurement Duration (2), Measurement Mode (1), BSSID (6), + * Optional Subelements (variable) + */ + if (wpabuf_len(req) < 13) { + wpa_printf(MSG_INFO, "Beacon request: Too short request data"); + return -1; + } + pos = wpabuf_head(req); + mode = pos[6]; + + if (!sta || !(sta->flags & WLAN_STA_AUTHORIZED)) { + wpa_printf(MSG_INFO, + "Beacon request: " MACSTR " is not connected", + MAC2STR(addr)); + return -1; + } + + switch (mode) { + case BEACON_REPORT_MODE_PASSIVE: + if (!(sta->rrm_enabled_capa[0] & + WLAN_RRM_CAPS_BEACON_REPORT_PASSIVE)) { + wpa_printf(MSG_INFO, + "Beacon request: " MACSTR + " does not support passive beacon report", + MAC2STR(addr)); + return -1; + } + break; + case BEACON_REPORT_MODE_ACTIVE: + if (!(sta->rrm_enabled_capa[0] & + WLAN_RRM_CAPS_BEACON_REPORT_ACTIVE)) { + wpa_printf(MSG_INFO, + "Beacon request: " MACSTR + " does not support active beacon report", + MAC2STR(addr)); + return -1; + } + break; + case BEACON_REPORT_MODE_TABLE: + if (!(sta->rrm_enabled_capa[0] & + WLAN_RRM_CAPS_BEACON_REPORT_TABLE)) { + wpa_printf(MSG_INFO, + "Beacon request: " MACSTR + " does not support table beacon report", + MAC2STR(addr)); + return -1; + } + break; + default: + wpa_printf(MSG_INFO, + "Beacon request: Unknown measurement mode %d", mode); + return -1; + } + + buf = wpabuf_alloc(5 + 2 + 3 + wpabuf_len(req)); + if (!buf) + return -1; + + hapd->beacon_req_token++; + if (!hapd->beacon_req_token) + hapd->beacon_req_token++; + + wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT); + wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REQUEST); + wpabuf_put_u8(buf, hapd->beacon_req_token); + wpabuf_put_le16(buf, 0); /* Number of repetitions */ + + /* Measurement Request element */ + wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST); + wpabuf_put_u8(buf, 3 + wpabuf_len(req)); + wpabuf_put_u8(buf, 1); /* Measurement Token */ + wpabuf_put_u8(buf, req_mode); /* Measurement Request Mode */ + wpabuf_put_u8(buf, MEASURE_TYPE_BEACON); /* Measurement Type */ + wpabuf_put_buf(buf, req); + + ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr, + wpabuf_head(buf), wpabuf_len(buf)); + wpabuf_free(buf); + if (ret < 0) + return ret; + + return hapd->beacon_req_token; +} + + +void hostapd_rrm_beacon_req_tx_status(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len, int ok) +{ + if (len < 24 + 3) + return; + wpa_msg(hapd->msg_ctx, MSG_INFO, BEACON_REQ_TX_STATUS MACSTR + " %u ack=%d", MAC2STR(mgmt->da), + mgmt->u.action.u.rrm.dialog_token, ok); +} + + +int hostapd_send_link_measurement_req(struct hostapd_data *hapd, const u8 *addr) +{ + struct wpabuf *buf; + struct sta_info *sta; + int ret; + + wpa_printf(MSG_DEBUG, "Request Link Measurement: dest addr " MACSTR, + MAC2STR(addr)); + + if (!(hapd->iface->drv_rrm_flags & + WPA_DRIVER_FLAGS_TX_POWER_INSERTION)) { + wpa_printf(MSG_INFO, + "Request Link Measurement: the driver does not support TX power insertion"); + return -1; + } + + sta = ap_get_sta(hapd, addr); + if (!sta || !(sta->flags & WLAN_STA_AUTHORIZED)) { + wpa_printf(MSG_INFO, + "Request Link Measurement: specied STA is not connected"); + return -1; + } + + if (!(sta->rrm_enabled_capa[0] & WLAN_RRM_CAPS_LINK_MEASUREMENT)) { + wpa_printf(MSG_INFO, + "Request Link Measurement: destination STA does not support link measurement"); + return -1; + } + + if (hapd->link_mesr_req_active) { + wpa_printf(MSG_DEBUG, + "Request Link Measurement: request already in process - overriding"); + hapd->link_mesr_req_active = 0; + eloop_cancel_timeout(hostapd_link_mesr_rep_timeout_handler, + hapd, NULL); + } + + /* Action + Action type + token + Tx Power used + Max Tx Power = 5 */ + buf = wpabuf_alloc(5); + if (!buf) + return -1; + + hapd->link_measurement_req_token++; + if (!hapd->link_measurement_req_token) + hapd->link_measurement_req_token++; + + wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT); + wpabuf_put_u8(buf, WLAN_RRM_LINK_MEASUREMENT_REQUEST); + wpabuf_put_u8(buf, hapd->link_measurement_req_token); + /* NOTE: The driver is expected to fill the Tx Power Used and Max Tx + * Power */ + wpabuf_put_u8(buf, 0); + wpabuf_put_u8(buf, 0); + + ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr, + wpabuf_head(buf), wpabuf_len(buf)); + wpabuf_free(buf); + if (ret < 0) + return ret; + + hapd->link_mesr_req_active = 1; + + eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0, + hostapd_link_mesr_rep_timeout_handler, hapd, + NULL); + + return hapd->link_measurement_req_token; +} diff --git a/src/ap/rrm.h b/src/ap/rrm.h new file mode 100644 index 0000000..17751e0 --- /dev/null +++ b/src/ap/rrm.h @@ -0,0 +1,35 @@ +/* + * hostapd / Radio Measurement (RRM) + * Copyright(c) 2013 - 2016 Intel Mobile Communications GmbH. + * Copyright(c) 2011 - 2016 Intel Corporation. All rights reserved. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef RRM_H +#define RRM_H + +/* + * Max measure request length is 255, -6 of the body we have 249 for the + * neighbor report elements. Each neighbor report element is at least 2 + 13 + * bytes, so we can't have more than 16 responders in the request. + */ +#define RRM_RANGE_REQ_MAX_RESPONDERS 16 + +void hostapd_handle_radio_measurement(struct hostapd_data *hapd, + const u8 *buf, size_t len); +int hostapd_send_lci_req(struct hostapd_data *hapd, const u8 *addr); +int hostapd_send_range_req(struct hostapd_data *hapd, const u8 *addr, + u16 random_interval, u8 min_ap, + const u8 *responders, unsigned int n_responders); +void hostapd_clean_rrm(struct hostapd_data *hapd); +int hostapd_send_beacon_req(struct hostapd_data *hapd, const u8 *addr, + u8 req_mode, const struct wpabuf *req); +void hostapd_rrm_beacon_req_tx_status(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + size_t len, int ok); +int hostapd_send_link_measurement_req(struct hostapd_data *hapd, + const u8 *addr); + +#endif /* RRM_H */ diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c new file mode 100644 index 0000000..13613db --- /dev/null +++ b/src/ap/sta_info.c @@ -0,0 +1,1888 @@ +/* + * hostapd / Station table + * Copyright (c) 2002-2017, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/ieee802_11_defs.h" +#include "common/wpa_ctrl.h" +#include "common/sae.h" +#include "common/dpp.h" +#include "radius/radius.h" +#include "radius/radius_client.h" +#include "p2p/p2p.h" +#include "fst/fst.h" +#include "crypto/crypto.h" +#include "hostapd.h" +#include "accounting.h" +#include "ieee802_1x.h" +#include "ieee802_11.h" +#include "ieee802_11_auth.h" +#include "wpa_auth.h" +#include "preauth_auth.h" +#include "ap_config.h" +#include "beacon.h" +#include "ap_mlme.h" +#include "vlan_init.h" +#include "p2p_hostapd.h" +#include "ap_drv_ops.h" +#include "gas_serv.h" +#include "wnm_ap.h" +#include "mbo_ap.h" +#include "ndisc_snoop.h" +#include "sta_info.h" +#include "vlan.h" +#include "wps_hostapd.h" + +static void ap_sta_remove_in_other_bss(struct hostapd_data *hapd, + struct sta_info *sta); +static void ap_handle_session_timer(void *eloop_ctx, void *timeout_ctx); +static void ap_handle_session_warning_timer(void *eloop_ctx, void *timeout_ctx); +static void ap_sta_deauth_cb_timeout(void *eloop_ctx, void *timeout_ctx); +static void ap_sta_disassoc_cb_timeout(void *eloop_ctx, void *timeout_ctx); +static void ap_sa_query_timer(void *eloop_ctx, void *timeout_ctx); +static int ap_sta_remove(struct hostapd_data *hapd, struct sta_info *sta); +static void ap_sta_delayed_1x_auth_fail_cb(void *eloop_ctx, void *timeout_ctx); + +int ap_for_each_sta(struct hostapd_data *hapd, + int (*cb)(struct hostapd_data *hapd, struct sta_info *sta, + void *ctx), + void *ctx) +{ + struct sta_info *sta; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (cb(hapd, sta, ctx)) + return 1; + } + + return 0; +} + + +struct sta_info * ap_get_sta(struct hostapd_data *hapd, const u8 *sta) +{ + struct sta_info *s; + + s = hapd->sta_hash[STA_HASH(sta)]; + while (s != NULL && os_memcmp(s->addr, sta, 6) != 0) + s = s->hnext; + return s; +} + + +#ifdef CONFIG_P2P +struct sta_info * ap_get_sta_p2p(struct hostapd_data *hapd, const u8 *addr) +{ + struct sta_info *sta; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + const u8 *p2p_dev_addr; + + if (sta->p2p_ie == NULL) + continue; + + p2p_dev_addr = p2p_get_go_dev_addr(sta->p2p_ie); + if (p2p_dev_addr == NULL) + continue; + + if (ether_addr_equal(p2p_dev_addr, addr)) + return sta; + } + + return NULL; +} +#endif /* CONFIG_P2P */ + + +static void ap_sta_list_del(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct sta_info *tmp; + + if (hapd->sta_list == sta) { + hapd->sta_list = sta->next; + return; + } + + tmp = hapd->sta_list; + while (tmp != NULL && tmp->next != sta) + tmp = tmp->next; + if (tmp == NULL) { + wpa_printf(MSG_DEBUG, "Could not remove STA " MACSTR " from " + "list.", MAC2STR(sta->addr)); + } else + tmp->next = sta->next; +} + + +void ap_sta_hash_add(struct hostapd_data *hapd, struct sta_info *sta) +{ + sta->hnext = hapd->sta_hash[STA_HASH(sta->addr)]; + hapd->sta_hash[STA_HASH(sta->addr)] = sta; +} + + +static void ap_sta_hash_del(struct hostapd_data *hapd, struct sta_info *sta) +{ + struct sta_info *s; + + s = hapd->sta_hash[STA_HASH(sta->addr)]; + if (s == NULL) return; + if (os_memcmp(s->addr, sta->addr, 6) == 0) { + hapd->sta_hash[STA_HASH(sta->addr)] = s->hnext; + return; + } + + while (s->hnext != NULL && + !ether_addr_equal(s->hnext->addr, sta->addr)) + s = s->hnext; + if (s->hnext != NULL) + s->hnext = s->hnext->hnext; + else + wpa_printf(MSG_DEBUG, "AP: could not remove STA " MACSTR + " from hash table", MAC2STR(sta->addr)); +} + + +void ap_sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta) +{ + sta_ip6addr_del(hapd, sta); +} + + +#ifdef CONFIG_PASN + +void ap_free_sta_pasn(struct hostapd_data *hapd, struct sta_info *sta) +{ + if (sta->pasn) { + wpa_printf(MSG_DEBUG, "PASN: Free PASN context: " MACSTR, + MAC2STR(sta->addr)); + + if (sta->pasn->ecdh) + crypto_ecdh_deinit(sta->pasn->ecdh); + + wpabuf_free(sta->pasn->secret); + sta->pasn->secret = NULL; + +#ifdef CONFIG_SAE + sae_clear_data(&sta->pasn->sae); +#endif /* CONFIG_SAE */ + +#ifdef CONFIG_FILS + /* In practice this pointer should be NULL */ + wpabuf_free(sta->pasn->fils.erp_resp); + sta->pasn->fils.erp_resp = NULL; +#endif /* CONFIG_FILS */ + + pasn_data_deinit(sta->pasn); + sta->pasn = NULL; + } +} + +#endif /* CONFIG_PASN */ + + +static void __ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) +{ +#ifdef CONFIG_IEEE80211BE + if (hostapd_sta_is_link_sta(hapd, sta) && + !hostapd_drv_link_sta_remove(hapd, sta->addr)) + return; +#endif /* CONFIG_IEEE80211BE */ + + hostapd_drv_sta_remove(hapd, sta->addr); +} + + +#ifdef CONFIG_IEEE80211BE +static void clear_wpa_sm_for_each_partner_link(struct hostapd_data *hapd, + struct sta_info *psta) +{ + struct sta_info *lsta; + struct hostapd_data *lhapd; + + if (!ap_sta_is_mld(hapd, psta)) + return; + + for_each_mld_link(lhapd, hapd) { + if (lhapd == hapd) + continue; + + lsta = ap_get_sta(lhapd, psta->addr); + if (lsta) + lsta->wpa_sm = NULL; + } +} +#endif /* CONFIG_IEEE80211BE */ + + +void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) +{ + int set_beacon = 0; + + accounting_sta_stop(hapd, sta); + + /* just in case */ + ap_sta_set_authorized(hapd, sta, 0); + hostapd_set_sta_flags(hapd, sta); + + if ((sta->flags & WLAN_STA_WDS) || + (sta->flags & WLAN_STA_MULTI_AP && + (hapd->conf->multi_ap & BACKHAUL_BSS) && + hapd->conf->wds_sta && + !(sta->flags & WLAN_STA_WPS))) + hostapd_set_wds_sta(hapd, NULL, sta->addr, sta->aid, 0); + + if (sta->ipaddr) + hostapd_drv_br_delete_ip_neigh(hapd, 4, (u8 *) &sta->ipaddr); + ap_sta_ip6addr_del(hapd, sta); + + if (!hapd->iface->driver_ap_teardown && + !(sta->flags & WLAN_STA_PREAUTH)) { + __ap_free_sta(hapd, sta); + sta->added_unassoc = 0; + } + + ap_sta_hash_del(hapd, sta); + ap_sta_list_del(hapd, sta); + + if (sta->aid > 0) + hapd->sta_aid[(sta->aid - 1) / 32] &= + ~BIT((sta->aid - 1) % 32); + + hapd->num_sta--; + if (sta->nonerp_set) { + sta->nonerp_set = 0; + hapd->iface->num_sta_non_erp--; + if (hapd->iface->num_sta_non_erp == 0) + set_beacon++; + } + + if (sta->no_short_slot_time_set) { + sta->no_short_slot_time_set = 0; + hapd->iface->num_sta_no_short_slot_time--; + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G + && hapd->iface->num_sta_no_short_slot_time == 0) + set_beacon++; + } + + if (sta->no_short_preamble_set) { + sta->no_short_preamble_set = 0; + hapd->iface->num_sta_no_short_preamble--; + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G + && hapd->iface->num_sta_no_short_preamble == 0) + set_beacon++; + } + + if (sta->no_ht_gf_set) { + sta->no_ht_gf_set = 0; + hapd->iface->num_sta_ht_no_gf--; + } + + if (sta->no_ht_set) { + sta->no_ht_set = 0; + hapd->iface->num_sta_no_ht--; + } + + if (sta->ht_20mhz_set) { + sta->ht_20mhz_set = 0; + hapd->iface->num_sta_ht_20mhz--; + } + +#ifdef CONFIG_TAXONOMY + wpabuf_free(sta->probe_ie_taxonomy); + sta->probe_ie_taxonomy = NULL; + wpabuf_free(sta->assoc_ie_taxonomy); + sta->assoc_ie_taxonomy = NULL; +#endif /* CONFIG_TAXONOMY */ + + ht40_intolerant_remove(hapd->iface, sta); + +#ifdef CONFIG_P2P + if (sta->no_p2p_set) { + sta->no_p2p_set = 0; + hapd->num_sta_no_p2p--; + if (hapd->num_sta_no_p2p == 0) + hostapd_p2p_non_p2p_sta_disconnected(hapd); + } +#endif /* CONFIG_P2P */ + +#ifdef NEED_AP_MLME + if (hostapd_ht_operation_update(hapd->iface) > 0) + set_beacon++; +#endif /* NEED_AP_MLME */ + +#ifdef CONFIG_MESH + if (hapd->mesh_sta_free_cb) + hapd->mesh_sta_free_cb(hapd, sta); +#endif /* CONFIG_MESH */ + + if (set_beacon) + ieee802_11_update_beacons(hapd->iface); + + wpa_printf(MSG_DEBUG, "%s: cancel ap_handle_timer for " MACSTR, + __func__, MAC2STR(sta->addr)); + eloop_cancel_timeout(ap_handle_timer, hapd, sta); + eloop_cancel_timeout(ap_handle_session_timer, hapd, sta); + eloop_cancel_timeout(ap_handle_session_warning_timer, hapd, sta); + ap_sta_clear_disconnect_timeouts(hapd, sta); + sae_clear_retransmit_timer(hapd, sta); + + ieee802_1x_free_station(hapd, sta); + +#ifdef CONFIG_IEEE80211BE + if (!ap_sta_is_mld(hapd, sta) || + hapd->mld_link_id == sta->mld_assoc_link_id) { + wpa_auth_sta_deinit(sta->wpa_sm); + /* Remove references from partner links. */ + clear_wpa_sm_for_each_partner_link(hapd, sta); + } + + /* Release group references in case non-association link STA is removed + * before association link STA */ + if (hostapd_sta_is_link_sta(hapd, sta)) + wpa_release_link_auth_ref(sta->wpa_sm, hapd->mld_link_id); +#else /* CONFIG_IEEE80211BE */ + wpa_auth_sta_deinit(sta->wpa_sm); +#endif /* CONFIG_IEEE80211BE */ + + rsn_preauth_free_station(hapd, sta); +#ifndef CONFIG_NO_RADIUS + if (hapd->radius) + radius_client_flush_auth(hapd->radius, sta->addr); +#endif /* CONFIG_NO_RADIUS */ + +#ifndef CONFIG_NO_VLAN + /* + * sta->wpa_sm->group needs to be released before so that + * vlan_remove_dynamic() can check that no stations are left on the + * AP_VLAN netdev. + */ + if (sta->vlan_id) + vlan_remove_dynamic(hapd, sta->vlan_id); + if (sta->vlan_id_bound) { + /* + * Need to remove the STA entry before potentially removing the + * VLAN. + */ + if (hapd->iface->driver_ap_teardown && + !(sta->flags & WLAN_STA_PREAUTH)) { + hostapd_drv_sta_remove(hapd, sta->addr); + sta->added_unassoc = 0; + } + vlan_remove_dynamic(hapd, sta->vlan_id_bound); + } +#endif /* CONFIG_NO_VLAN */ + + os_free(sta->challenge); + + os_free(sta->sa_query_trans_id); + eloop_cancel_timeout(ap_sa_query_timer, hapd, sta); + +#ifdef CONFIG_P2P + p2p_group_notif_disassoc(hapd->p2p_group, sta->addr); +#endif /* CONFIG_P2P */ + +#ifdef CONFIG_INTERWORKING + if (sta->gas_dialog) { + int i; + + for (i = 0; i < GAS_DIALOG_MAX; i++) + gas_serv_dialog_clear(&sta->gas_dialog[i]); + os_free(sta->gas_dialog); + } +#endif /* CONFIG_INTERWORKING */ + + wpabuf_free(sta->wps_ie); + wpabuf_free(sta->p2p_ie); + wpabuf_free(sta->hs20_ie); + wpabuf_free(sta->roaming_consortium); +#ifdef CONFIG_FST + wpabuf_free(sta->mb_ies); +#endif /* CONFIG_FST */ + + os_free(sta->ht_capabilities); + os_free(sta->vht_capabilities); + os_free(sta->vht_operation); + os_free(sta->he_capab); + os_free(sta->he_6ghz_capab); + os_free(sta->eht_capab); + hostapd_free_psk_list(sta->psk); + os_free(sta->identity); + os_free(sta->radius_cui); + os_free(sta->remediation_url); + os_free(sta->t_c_url); + wpabuf_free(sta->hs20_deauth_req); + os_free(sta->hs20_session_info_url); + +#ifdef CONFIG_SAE + sae_clear_data(sta->sae); + os_free(sta->sae); +#endif /* CONFIG_SAE */ + + mbo_ap_sta_free(sta); + os_free(sta->supp_op_classes); + +#ifdef CONFIG_FILS + os_free(sta->fils_pending_assoc_req); + wpabuf_free(sta->fils_hlp_resp); + wpabuf_free(sta->hlp_dhcp_discover); + eloop_cancel_timeout(fils_hlp_timeout, hapd, sta); +#ifdef CONFIG_FILS_SK_PFS + crypto_ecdh_deinit(sta->fils_ecdh); + wpabuf_clear_free(sta->fils_dh_ss); + wpabuf_free(sta->fils_g_sta); +#endif /* CONFIG_FILS_SK_PFS */ +#endif /* CONFIG_FILS */ + +#ifdef CONFIG_OWE + bin_clear_free(sta->owe_pmk, sta->owe_pmk_len); + crypto_ecdh_deinit(sta->owe_ecdh); +#endif /* CONFIG_OWE */ + +#ifdef CONFIG_DPP2 + dpp_pfs_free(sta->dpp_pfs); + sta->dpp_pfs = NULL; +#endif /* CONFIG_DPP2 */ + + os_free(sta->ext_capability); + +#ifdef CONFIG_WNM_AP + eloop_cancel_timeout(ap_sta_reset_steer_flag_timer, hapd, sta); +#endif /* CONFIG_WNM_AP */ + +#ifdef CONFIG_PASN + ap_free_sta_pasn(hapd, sta); +#endif /* CONFIG_PASN */ + + os_free(sta->ifname_wds); + +#ifdef CONFIG_IEEE80211BE + ap_sta_free_sta_profile(&sta->mld_info); +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_TESTING_OPTIONS + os_free(sta->sae_postponed_commit); + forced_memzero(sta->last_tk, WPA_TK_MAX_LEN); +#endif /* CONFIG_TESTING_OPTIONS */ + + os_free(sta); +} + + +void hostapd_free_stas(struct hostapd_data *hapd) +{ + struct sta_info *sta, *prev; + + sta = hapd->sta_list; + + while (sta) { + prev = sta; + if (sta->flags & WLAN_STA_AUTH) { + mlme_deauthenticate_indication( + hapd, sta, WLAN_REASON_UNSPECIFIED); + } + sta = sta->next; + wpa_printf(MSG_DEBUG, "Removing station " MACSTR, + MAC2STR(prev->addr)); + ap_free_sta(hapd, prev); + } +} + + +#ifdef CONFIG_IEEE80211BE +void hostapd_free_link_stas(struct hostapd_data *hapd) +{ + struct sta_info *sta, *prev; + + sta = hapd->sta_list; + while (sta) { + prev = sta; + sta = sta->next; + + if (!hostapd_sta_is_link_sta(hapd, prev)) + continue; + + wpa_printf(MSG_DEBUG, "Removing link station from MLD " MACSTR, + MAC2STR(prev->addr)); + ap_free_sta(hapd, prev); + } +} +#endif /* CONFIG_IEEE80211BE */ + + +/** + * ap_handle_timer - Per STA timer handler + * @eloop_ctx: struct hostapd_data * + * @timeout_ctx: struct sta_info * + * + * This function is called to check station activity and to remove inactive + * stations. + */ +void ap_handle_timer(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + unsigned long next_time = 0; + int reason; + int max_inactivity = hapd->conf->ap_max_inactivity; + + wpa_printf(MSG_DEBUG, "%s: %s: " MACSTR " flags=0x%x timeout_next=%d", + hapd->conf->iface, __func__, MAC2STR(sta->addr), sta->flags, + sta->timeout_next); + if (sta->timeout_next == STA_REMOVE) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "deauthenticated due to " + "local deauth request"); + ap_free_sta(hapd, sta); + return; + } + + if (sta->max_idle_period) + max_inactivity = (sta->max_idle_period * 1024 + 999) / 1000; + + if ((sta->flags & WLAN_STA_ASSOC) && + (sta->timeout_next == STA_NULLFUNC || + sta->timeout_next == STA_DISASSOC)) { + int inactive_sec; + /* + * Add random value to timeout so that we don't end up bouncing + * all stations at the same time if we have lots of associated + * stations that are idle (but keep re-associating). + */ + int fuzz = os_random() % 20; + inactive_sec = hostapd_drv_get_inact_sec(hapd, sta->addr); + if (inactive_sec == -1) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "Check inactivity: Could not " + "get station info from kernel driver for " + MACSTR, MAC2STR(sta->addr)); + /* + * The driver may not support this functionality. + * Anyway, try again after the next inactivity timeout, + * but do not disconnect the station now. + */ + next_time = max_inactivity + fuzz; + } else if (inactive_sec == -ENOENT) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "Station " MACSTR " has lost its driver entry", + MAC2STR(sta->addr)); + + /* Avoid sending client probe on removed client */ + sta->timeout_next = STA_DISASSOC; + goto skip_poll; + } else if (inactive_sec < max_inactivity) { + /* station activity detected; reset timeout state */ + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "Station " MACSTR " has been active %is ago", + MAC2STR(sta->addr), inactive_sec); + sta->timeout_next = STA_NULLFUNC; + next_time = max_inactivity + fuzz - inactive_sec; + } else { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, + "Station " MACSTR " has been " + "inactive too long: %d sec, max allowed: %d", + MAC2STR(sta->addr), inactive_sec, + max_inactivity); + + if (hapd->conf->skip_inactivity_poll) + sta->timeout_next = STA_DISASSOC; + } + } + + if ((sta->flags & WLAN_STA_ASSOC) && + sta->timeout_next == STA_DISASSOC && + !(sta->flags & WLAN_STA_PENDING_POLL) && + !hapd->conf->skip_inactivity_poll) { + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Station " MACSTR + " has ACKed data poll", MAC2STR(sta->addr)); + /* data nullfunc frame poll did not produce TX errors; assume + * station ACKed it */ + sta->timeout_next = STA_NULLFUNC; + next_time = max_inactivity; + } + +skip_poll: + if (next_time) { + wpa_printf(MSG_DEBUG, "%s: register ap_handle_timer timeout " + "for " MACSTR " (%lu seconds)", + __func__, MAC2STR(sta->addr), next_time); + eloop_register_timeout(next_time, 0, ap_handle_timer, hapd, + sta); + return; + } + + if (sta->timeout_next == STA_NULLFUNC && + (sta->flags & WLAN_STA_ASSOC)) { + wpa_printf(MSG_DEBUG, " Polling STA"); + sta->flags |= WLAN_STA_PENDING_POLL; + hostapd_drv_poll_client(hapd, hapd->own_addr, sta->addr, + sta->flags & WLAN_STA_WMM); + } else if (sta->timeout_next != STA_REMOVE) { + int deauth = sta->timeout_next == STA_DEAUTH; + + if (!deauth && !(sta->flags & WLAN_STA_ASSOC)) { + /* Cannot disassociate not-associated STA, so move + * directly to deauthentication. */ + sta->timeout_next = STA_DEAUTH; + deauth = 1; + } + + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, + "Timeout, sending %s info to STA " MACSTR, + deauth ? "deauthentication" : "disassociation", + MAC2STR(sta->addr)); + + if (deauth) { + hostapd_drv_sta_deauth( + hapd, sta->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + } else { + reason = (sta->timeout_next == STA_DISASSOC) ? + WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY : + WLAN_REASON_PREV_AUTH_NOT_VALID; + + hostapd_drv_sta_disassoc(hapd, sta->addr, reason); + } + } + + switch (sta->timeout_next) { + case STA_NULLFUNC: + sta->timeout_next = STA_DISASSOC; + wpa_printf(MSG_DEBUG, "%s: register ap_handle_timer timeout " + "for " MACSTR " (%d seconds - AP_DISASSOC_DELAY)", + __func__, MAC2STR(sta->addr), AP_DISASSOC_DELAY); + eloop_register_timeout(AP_DISASSOC_DELAY, 0, ap_handle_timer, + hapd, sta); + break; + case STA_DISASSOC: + case STA_DISASSOC_FROM_CLI: + ap_sta_set_authorized(hapd, sta, 0); + sta->flags &= ~WLAN_STA_ASSOC; + hostapd_set_sta_flags(hapd, sta); + ieee802_1x_notify_port_enabled(sta->eapol_sm, 0); + if (!sta->acct_terminate_cause) + sta->acct_terminate_cause = + RADIUS_ACCT_TERMINATE_CAUSE_IDLE_TIMEOUT; + accounting_sta_stop(hapd, sta); + ieee802_1x_free_station(hapd, sta); + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "disassociated due to " + "inactivity"); + reason = (sta->timeout_next == STA_DISASSOC) ? + WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY : + WLAN_REASON_PREV_AUTH_NOT_VALID; + sta->timeout_next = STA_DEAUTH; + wpa_printf(MSG_DEBUG, "%s: register ap_handle_timer timeout " + "for " MACSTR " (%d seconds - AP_DEAUTH_DELAY)", + __func__, MAC2STR(sta->addr), AP_DEAUTH_DELAY); + eloop_register_timeout(AP_DEAUTH_DELAY, 0, ap_handle_timer, + hapd, sta); + mlme_disassociate_indication(hapd, sta, reason); + break; + case STA_DEAUTH: + case STA_REMOVE: + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "deauthenticated due to " + "inactivity (timer DEAUTH/REMOVE)"); + if (!sta->acct_terminate_cause) + sta->acct_terminate_cause = + RADIUS_ACCT_TERMINATE_CAUSE_IDLE_TIMEOUT; + mlme_deauthenticate_indication( + hapd, sta, + WLAN_REASON_PREV_AUTH_NOT_VALID); + ap_free_sta(hapd, sta); + break; + } +} + + +static void ap_handle_session_timer(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + + wpa_printf(MSG_DEBUG, "%s: Session timer for STA " MACSTR, + hapd->conf->iface, MAC2STR(sta->addr)); + if (!(sta->flags & (WLAN_STA_AUTH | WLAN_STA_ASSOC | + WLAN_STA_AUTHORIZED))) { + if (sta->flags & WLAN_STA_GAS) { + wpa_printf(MSG_DEBUG, "GAS: Remove temporary STA " + "entry " MACSTR, MAC2STR(sta->addr)); + ap_free_sta(hapd, sta); + } + return; + } + + hostapd_drv_sta_deauth(hapd, sta->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + mlme_deauthenticate_indication(hapd, sta, + WLAN_REASON_PREV_AUTH_NOT_VALID); + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "deauthenticated due to " + "session timeout"); + sta->acct_terminate_cause = + RADIUS_ACCT_TERMINATE_CAUSE_SESSION_TIMEOUT; + ap_free_sta(hapd, sta); +} + + +void ap_sta_replenish_timeout(struct hostapd_data *hapd, struct sta_info *sta, + u32 session_timeout) +{ + if (eloop_replenish_timeout(session_timeout, 0, + ap_handle_session_timer, hapd, sta) == 1) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "setting session timeout " + "to %d seconds", session_timeout); + } +} + + +void ap_sta_session_timeout(struct hostapd_data *hapd, struct sta_info *sta, + u32 session_timeout) +{ + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "setting session timeout to %d " + "seconds", session_timeout); + eloop_cancel_timeout(ap_handle_session_timer, hapd, sta); + eloop_register_timeout(session_timeout, 0, ap_handle_session_timer, + hapd, sta); +} + + +void ap_sta_no_session_timeout(struct hostapd_data *hapd, struct sta_info *sta) +{ + eloop_cancel_timeout(ap_handle_session_timer, hapd, sta); +} + + +static void ap_handle_session_warning_timer(void *eloop_ctx, void *timeout_ctx) +{ +#ifdef CONFIG_WNM_AP + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + + wpa_printf(MSG_DEBUG, "%s: WNM: Session warning time reached for " + MACSTR, hapd->conf->iface, MAC2STR(sta->addr)); + if (sta->hs20_session_info_url == NULL) + return; + + wnm_send_ess_disassoc_imminent(hapd, sta, sta->hs20_session_info_url, + sta->hs20_disassoc_timer); +#endif /* CONFIG_WNM_AP */ +} + + +void ap_sta_session_warning_timeout(struct hostapd_data *hapd, + struct sta_info *sta, int warning_time) +{ + eloop_cancel_timeout(ap_handle_session_warning_timer, hapd, sta); + eloop_register_timeout(warning_time, 0, ap_handle_session_warning_timer, + hapd, sta); +} + + +struct sta_info * ap_sta_add(struct hostapd_data *hapd, const u8 *addr) +{ + struct sta_info *sta; + int i; + int max_inactivity = hapd->conf->ap_max_inactivity; + + sta = ap_get_sta(hapd, addr); + if (sta) + return sta; + + wpa_printf(MSG_DEBUG, " New STA"); + if (hapd->num_sta >= hapd->conf->max_num_sta) { + /* FIX: might try to remove some old STAs first? */ + wpa_printf(MSG_DEBUG, "no more room for new STAs (%d/%d)", + hapd->num_sta, hapd->conf->max_num_sta); + return NULL; + } + + sta = os_zalloc(sizeof(struct sta_info)); + if (sta == NULL) { + wpa_printf(MSG_ERROR, "malloc failed"); + return NULL; + } + sta->acct_interim_interval = hapd->conf->acct_interim_interval; + if (accounting_sta_get_id(hapd, sta) < 0) { + os_free(sta); + return NULL; + } + + for (i = 0; i < WLAN_SUPP_RATES_MAX; i++) { + if (!hapd->iface->basic_rates) + break; + if (hapd->iface->basic_rates[i] < 0) + break; + sta->supported_rates[i] = hapd->iface->basic_rates[i] / 5; + } + sta->supported_rates_len = i; + + if (sta->max_idle_period) + max_inactivity = (sta->max_idle_period * 1024 + 999) / 1000; + + if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_INACTIVITY_TIMER)) { + wpa_printf(MSG_DEBUG, "%s: register ap_handle_timer timeout " + "for " MACSTR " (%d seconds - ap_max_inactivity)", + __func__, MAC2STR(addr), + max_inactivity); + eloop_register_timeout(max_inactivity, 0, + ap_handle_timer, hapd, sta); + } + + /* initialize STA info data */ + os_memcpy(sta->addr, addr, ETH_ALEN); + sta->next = hapd->sta_list; + hapd->sta_list = sta; + hapd->num_sta++; + ap_sta_hash_add(hapd, sta); + ap_sta_remove_in_other_bss(hapd, sta); + sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ; + dl_list_init(&sta->ip6addr); + +#ifdef CONFIG_TAXONOMY + sta_track_claim_taxonomy_info(hapd->iface, addr, + &sta->probe_ie_taxonomy); +#endif /* CONFIG_TAXONOMY */ + + return sta; +} + + +static int ap_sta_remove(struct hostapd_data *hapd, struct sta_info *sta) +{ + ieee802_1x_notify_port_enabled(sta->eapol_sm, 0); + + if (sta->ipaddr) + hostapd_drv_br_delete_ip_neigh(hapd, 4, (u8 *) &sta->ipaddr); + ap_sta_ip6addr_del(hapd, sta); + + wpa_printf(MSG_DEBUG, "%s: Removing STA " MACSTR " from kernel driver", + hapd->conf->iface, MAC2STR(sta->addr)); + if (hostapd_drv_sta_remove(hapd, sta->addr) && + sta->flags & WLAN_STA_ASSOC) { + wpa_printf(MSG_DEBUG, "%s: Could not remove station " MACSTR + " from kernel driver", + hapd->conf->iface, MAC2STR(sta->addr)); + return -1; + } + sta->added_unassoc = 0; + return 0; +} + + +static void ap_sta_remove_in_other_bss(struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct hostapd_iface *iface = hapd->iface; + size_t i; + + for (i = 0; i < iface->num_bss; i++) { + struct hostapd_data *bss = iface->bss[i]; + struct sta_info *sta2; + /* bss should always be set during operation, but it may be + * NULL during reconfiguration. Assume the STA is not + * associated to another BSS in that case to avoid NULL pointer + * dereferences. */ + if (bss == hapd || bss == NULL) + continue; + sta2 = ap_get_sta(bss, sta->addr); + if (!sta2) + continue; + + wpa_printf(MSG_DEBUG, "%s: disconnect old STA " MACSTR + " association from another BSS %s", + hapd->conf->iface, MAC2STR(sta2->addr), + bss->conf->iface); + ap_sta_disconnect(bss, sta2, sta2->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + } +} + + +static void ap_sta_disassoc_cb_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + + wpa_printf(MSG_DEBUG, "%s: Disassociation callback for STA " MACSTR, + hapd->conf->iface, MAC2STR(sta->addr)); + ap_sta_remove(hapd, sta); + mlme_disassociate_indication(hapd, sta, sta->disassoc_reason); +} + + +static void ap_sta_disconnect_common(struct hostapd_data *hapd, + struct sta_info *sta, unsigned int timeout) +{ + sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ; + + ap_sta_set_authorized(hapd, sta, 0); + hostapd_set_sta_flags(hapd, sta); + + wpa_printf(MSG_DEBUG, + "reschedule ap_handle_timer timeout (%u sec) for " MACSTR, + MAC2STR(sta->addr), timeout); + + eloop_cancel_timeout(ap_handle_timer, hapd, sta); + eloop_register_timeout(timeout, 0, ap_handle_timer, hapd, sta); + accounting_sta_stop(hapd, sta); + ieee802_1x_free_station(hapd, sta); +#ifdef CONFIG_IEEE80211BE + if (!hapd->conf->mld_ap || + hapd->mld_link_id == sta->mld_assoc_link_id) { + wpa_auth_sta_deinit(sta->wpa_sm); + clear_wpa_sm_for_each_partner_link(hapd, sta); + } +#else /* CONFIG_IEEE80211BE */ + wpa_auth_sta_deinit(sta->wpa_sm); +#endif /* CONFIG_IEEE80211BE */ + + sta->wpa_sm = NULL; +} + + +static void ap_sta_handle_disassociate(struct hostapd_data *hapd, + struct sta_info *sta, u16 reason) +{ + wpa_printf(MSG_DEBUG, "%s: disassociate STA " MACSTR, + hapd->conf->iface, MAC2STR(sta->addr)); + + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) { + /* Skip deauthentication in DMG/IEEE 802.11ad */ + sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC | + WLAN_STA_ASSOC_REQ_OK); + sta->timeout_next = STA_REMOVE; + } else { + sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK); + sta->timeout_next = STA_DEAUTH; + } + + ap_sta_disconnect_common(hapd, sta, AP_MAX_INACTIVITY_AFTER_DISASSOC); + + sta->disassoc_reason = reason; + sta->flags |= WLAN_STA_PENDING_DISASSOC_CB; + eloop_cancel_timeout(ap_sta_disassoc_cb_timeout, hapd, sta); + eloop_register_timeout(hapd->iface->drv_flags & + WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS ? 2 : 0, 0, + ap_sta_disassoc_cb_timeout, hapd, sta); +} + + +static void ap_sta_deauth_cb_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + + wpa_printf(MSG_DEBUG, "%s: Deauthentication callback for STA " MACSTR, + hapd->conf->iface, MAC2STR(sta->addr)); + ap_sta_remove(hapd, sta); + mlme_deauthenticate_indication(hapd, sta, sta->deauth_reason); +} + + +static void ap_sta_handle_deauthenticate(struct hostapd_data *hapd, + struct sta_info *sta, u16 reason) +{ + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) { + /* Deauthentication is not used in DMG/IEEE 802.11ad; + * disassociate the STA instead. */ + ap_sta_disassociate(hapd, sta, reason); + return; + } + + wpa_printf(MSG_DEBUG, "%s: deauthenticate STA " MACSTR, + hapd->conf->iface, MAC2STR(sta->addr)); + + sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK); + + sta->timeout_next = STA_REMOVE; + ap_sta_disconnect_common(hapd, sta, AP_MAX_INACTIVITY_AFTER_DEAUTH); + + sta->deauth_reason = reason; + sta->flags |= WLAN_STA_PENDING_DEAUTH_CB; + eloop_cancel_timeout(ap_sta_deauth_cb_timeout, hapd, sta); + eloop_register_timeout(hapd->iface->drv_flags & + WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS ? 2 : 0, 0, + ap_sta_deauth_cb_timeout, hapd, sta); +} + + +static bool ap_sta_ml_disconnect(struct hostapd_data *hapd, + struct sta_info *sta, u16 reason, + bool disassoc) +{ +#ifdef CONFIG_IEEE80211BE + struct hostapd_data *assoc_hapd, *tmp_hapd; + struct sta_info *assoc_sta; + unsigned int i, link_id; + struct hapd_interfaces *interfaces; + + if (!hostapd_is_mld_ap(hapd)) + return false; + + /* + * Get the station on which the association was performed, as it holds + * the information about all the other links. + */ + assoc_sta = hostapd_ml_get_assoc_sta(hapd, sta, &assoc_hapd); + if (!assoc_sta) + return false; + interfaces = assoc_hapd->iface->interfaces; + + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + if (!assoc_sta->mld_info.links[link_id].valid) + continue; + + for (i = 0; i < interfaces->count; i++) { + struct sta_info *tmp_sta; + + tmp_hapd = interfaces->iface[i]->bss[0]; + + if (!hostapd_is_ml_partner(tmp_hapd, assoc_hapd)) + continue; + + for (tmp_sta = tmp_hapd->sta_list; tmp_sta; + tmp_sta = tmp_sta->next) { + /* + * Handle the station on which the association + * was done only after all other link station + * are removed. Since there is a only a single + * station per hapd with the same association + * link simply break; + */ + if (tmp_sta == assoc_sta) + break; + + if (tmp_sta->mld_assoc_link_id != + assoc_sta->mld_assoc_link_id || + tmp_sta->aid != assoc_sta->aid) + continue; + + if (disassoc) + ap_sta_handle_disassociate(tmp_hapd, + tmp_sta, + reason); + else + ap_sta_handle_deauthenticate(tmp_hapd, + tmp_sta, + reason); + + break; + } + } + } + + /* Disconnect the station on which the association was performed. */ + if (disassoc) + ap_sta_handle_disassociate(assoc_hapd, assoc_sta, reason); + else + ap_sta_handle_deauthenticate(assoc_hapd, assoc_sta, reason); + + return true; +#else /* CONFIG_IEEE80211BE */ + return false; +#endif /* CONFIG_IEEE80211BE */ +} + + +void ap_sta_disassociate(struct hostapd_data *hapd, struct sta_info *sta, + u16 reason) +{ + if (ap_sta_ml_disconnect(hapd, sta, reason, true)) + return; + + ap_sta_handle_disassociate(hapd, sta, reason); +} + + +void ap_sta_deauthenticate(struct hostapd_data *hapd, struct sta_info *sta, + u16 reason) +{ + if (ap_sta_ml_disconnect(hapd, sta, reason, false)) + return; + + ap_sta_handle_deauthenticate(hapd, sta, reason); +} + + +#ifdef CONFIG_WPS +int ap_sta_wps_cancel(struct hostapd_data *hapd, + struct sta_info *sta, void *ctx) +{ + if (sta && (sta->flags & WLAN_STA_WPS)) { + ap_sta_deauthenticate(hapd, sta, + WLAN_REASON_PREV_AUTH_NOT_VALID); + wpa_printf(MSG_DEBUG, "WPS: %s: Deauth sta=" MACSTR, + __func__, MAC2STR(sta->addr)); + return 1; + } + + return 0; +} +#endif /* CONFIG_WPS */ + + +static int ap_sta_get_free_vlan_id(struct hostapd_data *hapd) +{ + struct hostapd_vlan *vlan; + int vlan_id = MAX_VLAN_ID + 2; + +retry: + for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) { + if (vlan->vlan_id == vlan_id) { + vlan_id++; + goto retry; + } + } + return vlan_id; +} + + +int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta, + struct vlan_description *vlan_desc) +{ + struct hostapd_vlan *vlan = NULL, *wildcard_vlan = NULL; + int old_vlan_id, vlan_id = 0, ret = 0; + + /* Check if there is something to do */ + if (hapd->conf->ssid.per_sta_vif && !sta->vlan_id) { + /* This sta is lacking its own vif */ + } else if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED && + !hapd->conf->ssid.per_sta_vif && sta->vlan_id) { + /* sta->vlan_id needs to be reset */ + } else if (!vlan_compare(vlan_desc, sta->vlan_desc)) { + return 0; /* nothing to change */ + } + + /* Now the real VLAN changed or the STA just needs its own vif */ + if (hapd->conf->ssid.per_sta_vif) { + /* Assign a new vif, always */ + /* find a free vlan_id sufficiently big */ + vlan_id = ap_sta_get_free_vlan_id(hapd); + /* Get wildcard VLAN */ + for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) { + if (vlan->vlan_id == VLAN_ID_WILDCARD) + break; + } + if (!vlan) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "per_sta_vif missing wildcard"); + vlan_id = 0; + ret = -1; + goto done; + } + } else if (vlan_desc && vlan_desc->notempty) { + for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) { + if (!vlan_compare(&vlan->vlan_desc, vlan_desc)) + break; + if (vlan->vlan_id == VLAN_ID_WILDCARD) + wildcard_vlan = vlan; + } + if (vlan) { + vlan_id = vlan->vlan_id; + } else if (wildcard_vlan) { + vlan = wildcard_vlan; + vlan_id = vlan_desc->untagged; + if (vlan_desc->tagged[0]) { + /* Tagged VLAN configuration */ + vlan_id = ap_sta_get_free_vlan_id(hapd); + } + } else { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "missing vlan and wildcard for vlan=%d%s", + vlan_desc->untagged, + vlan_desc->tagged[0] ? "+" : ""); + vlan_id = 0; + ret = -1; + goto done; + } + } + + if (vlan && vlan->vlan_id == VLAN_ID_WILDCARD) { + vlan = vlan_add_dynamic(hapd, vlan, vlan_id, vlan_desc); + if (vlan == NULL) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "could not add dynamic VLAN interface for vlan=%d%s", + vlan_desc ? vlan_desc->untagged : -1, + (vlan_desc && vlan_desc->tagged[0]) ? + "+" : ""); + vlan_id = 0; + ret = -1; + goto done; + } + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "added new dynamic VLAN interface '%s'", + vlan->ifname); + } else if (vlan && vlan->dynamic_vlan > 0) { + vlan->dynamic_vlan++; + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "updated existing dynamic VLAN interface '%s'", + vlan->ifname); + } +done: + old_vlan_id = sta->vlan_id; + sta->vlan_id = vlan_id; + sta->vlan_desc = vlan ? &vlan->vlan_desc : NULL; + + if (vlan_id != old_vlan_id && old_vlan_id) + vlan_remove_dynamic(hapd, old_vlan_id); + + return ret; +} + + +int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta) +{ +#ifndef CONFIG_NO_VLAN + const char *iface; + struct hostapd_vlan *vlan = NULL; + int ret; + int old_vlanid = sta->vlan_id_bound; + int mld_link_id = -1; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap) + mld_link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + + if ((sta->flags & WLAN_STA_WDS) && sta->vlan_id == 0) { + wpa_printf(MSG_DEBUG, + "Do not override WDS VLAN assignment for STA " + MACSTR, MAC2STR(sta->addr)); + return 0; + } + + iface = hapd->conf->iface; + if (hapd->conf->ssid.vlan[0]) + iface = hapd->conf->ssid.vlan; + + if (sta->vlan_id > 0) { + for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) { + if (vlan->vlan_id == sta->vlan_id) + break; + } + if (vlan) + iface = vlan->ifname; + } + + /* + * Do not increment ref counters if the VLAN ID remains same, but do + * not skip hostapd_drv_set_sta_vlan() as hostapd_drv_sta_remove() might + * have been called before. + */ + if (sta->vlan_id == old_vlanid) + goto skip_counting; + + if (sta->vlan_id > 0 && !vlan && + !(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD)) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "could not find VLAN for " + "binding station to (vlan_id=%d)", + sta->vlan_id); + ret = -1; + goto done; + } else if (vlan && vlan->dynamic_vlan > 0) { + vlan->dynamic_vlan++; + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "updated existing dynamic VLAN interface '%s'", + iface); + } + + /* ref counters have been increased, so mark the station */ + sta->vlan_id_bound = sta->vlan_id; + +skip_counting: + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "binding station to interface " + "'%s'", iface); + + if (wpa_auth_sta_set_vlan(sta->wpa_sm, sta->vlan_id) < 0) + wpa_printf(MSG_INFO, "Failed to update VLAN-ID for WPA"); + + ret = hostapd_drv_set_sta_vlan(iface, hapd, sta->addr, sta->vlan_id, + mld_link_id); + if (ret < 0) { + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, "could not bind the STA " + "entry to vlan_id=%d", sta->vlan_id); + } + + /* During 1x reauth, if the vlan id changes, then remove the old id. */ + if (old_vlanid > 0 && old_vlanid != sta->vlan_id) + vlan_remove_dynamic(hapd, old_vlanid); +done: + + return ret; +#else /* CONFIG_NO_VLAN */ + return 0; +#endif /* CONFIG_NO_VLAN */ +} + + +int ap_check_sa_query_timeout(struct hostapd_data *hapd, struct sta_info *sta) +{ + u32 tu; + struct os_reltime now, passed; + os_get_reltime(&now); + os_reltime_sub(&now, &sta->sa_query_start, &passed); + tu = (passed.sec * 1000000 + passed.usec) / 1024; + if (hapd->conf->assoc_sa_query_max_timeout < tu) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "association SA Query timed out"); + sta->sa_query_timed_out = 1; + os_free(sta->sa_query_trans_id); + sta->sa_query_trans_id = NULL; + sta->sa_query_count = 0; + eloop_cancel_timeout(ap_sa_query_timer, hapd, sta); + return 1; + } + + return 0; +} + + +static void ap_sa_query_timer(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + unsigned int timeout, sec, usec; + u8 *trans_id, *nbuf; + + wpa_printf(MSG_DEBUG, "%s: SA Query timer for STA " MACSTR + " (count=%d)", + hapd->conf->iface, MAC2STR(sta->addr), sta->sa_query_count); + + if (sta->sa_query_count > 0 && + ap_check_sa_query_timeout(hapd, sta)) + return; + if (sta->sa_query_count >= 1000) + return; + + nbuf = os_realloc_array(sta->sa_query_trans_id, + sta->sa_query_count + 1, + WLAN_SA_QUERY_TR_ID_LEN); + if (nbuf == NULL) + return; + if (sta->sa_query_count == 0) { + /* Starting a new SA Query procedure */ + os_get_reltime(&sta->sa_query_start); + } + trans_id = nbuf + sta->sa_query_count * WLAN_SA_QUERY_TR_ID_LEN; + sta->sa_query_trans_id = nbuf; + sta->sa_query_count++; + + if (os_get_random(trans_id, WLAN_SA_QUERY_TR_ID_LEN) < 0) { + /* + * We don't really care which ID is used here, so simply + * hardcode this if the mostly theoretical os_get_random() + * failure happens. + */ + trans_id[0] = 0x12; + trans_id[1] = 0x34; + } + + timeout = hapd->conf->assoc_sa_query_retry_timeout; + sec = ((timeout / 1000) * 1024) / 1000; + usec = (timeout % 1000) * 1024; + eloop_register_timeout(sec, usec, ap_sa_query_timer, hapd, sta); + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "association SA Query attempt %d", sta->sa_query_count); + + ieee802_11_send_sa_query_req(hapd, sta->addr, trans_id); +} + + +void ap_sta_start_sa_query(struct hostapd_data *hapd, struct sta_info *sta) +{ + ap_sa_query_timer(hapd, sta); +} + + +void ap_sta_stop_sa_query(struct hostapd_data *hapd, struct sta_info *sta) +{ + eloop_cancel_timeout(ap_sa_query_timer, hapd, sta); + os_free(sta->sa_query_trans_id); + sta->sa_query_trans_id = NULL; + sta->sa_query_count = 0; +} + + +const char * ap_sta_wpa_get_keyid(struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct hostapd_wpa_psk *psk; + struct hostapd_ssid *ssid; + const u8 *pmk; + int pmk_len; + + ssid = &hapd->conf->ssid; + + pmk = wpa_auth_get_pmk(sta->wpa_sm, &pmk_len); + if (!pmk || pmk_len != PMK_LEN) + return NULL; + + for (psk = ssid->wpa_psk; psk; psk = psk->next) + if (os_memcmp(pmk, psk->psk, PMK_LEN) == 0) + break; + if (!psk || !psk->keyid[0]) + return NULL; + + return psk->keyid; +} + + +const u8 * ap_sta_wpa_get_dpp_pkhash(struct hostapd_data *hapd, + struct sta_info *sta) +{ + return wpa_auth_get_dpp_pkhash(sta->wpa_sm); +} + + +bool ap_sta_set_authorized_flag(struct hostapd_data *hapd, struct sta_info *sta, + int authorized) +{ + if (!!authorized == !!(sta->flags & WLAN_STA_AUTHORIZED)) + return false; + + if (authorized) { + int mld_assoc_link_id = -1; + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) { + if (sta->mld_assoc_link_id == hapd->mld_link_id) + mld_assoc_link_id = sta->mld_assoc_link_id; + else + mld_assoc_link_id = -2; + } +#endif /* CONFIG_IEEE80211BE */ + if (mld_assoc_link_id != -2) + hostapd_prune_associations(hapd, sta->addr, + mld_assoc_link_id); + sta->flags |= WLAN_STA_AUTHORIZED; + } else { + sta->flags &= ~WLAN_STA_AUTHORIZED; + } + + return true; +} + + +void ap_sta_set_authorized_event(struct hostapd_data *hapd, + struct sta_info *sta, int authorized) +{ + const u8 *dev_addr = NULL; + char buf[100]; +#ifdef CONFIG_P2P + u8 addr[ETH_ALEN]; + u8 ip_addr_buf[4]; +#endif /* CONFIG_P2P */ + const u8 *ip_ptr = NULL; + +#ifdef CONFIG_P2P + if (hapd->p2p_group == NULL) { + if (sta->p2p_ie != NULL && + p2p_parse_dev_addr_in_p2p_ie(sta->p2p_ie, addr) == 0) + dev_addr = addr; + } else + dev_addr = p2p_group_get_dev_addr(hapd->p2p_group, sta->addr); + + if (dev_addr) + os_snprintf(buf, sizeof(buf), MACSTR " p2p_dev_addr=" MACSTR, + MAC2STR(sta->addr), MAC2STR(dev_addr)); + else +#endif /* CONFIG_P2P */ + os_snprintf(buf, sizeof(buf), MACSTR, MAC2STR(sta->addr)); + + if (authorized) { + const u8 *dpp_pkhash; + const char *keyid; + char dpp_pkhash_buf[100]; + char keyid_buf[100]; + char ip_addr[100]; + + dpp_pkhash_buf[0] = '\0'; + keyid_buf[0] = '\0'; + ip_addr[0] = '\0'; +#ifdef CONFIG_P2P + if (wpa_auth_get_ip_addr(sta->wpa_sm, ip_addr_buf) == 0) { + os_snprintf(ip_addr, sizeof(ip_addr), + " ip_addr=%u.%u.%u.%u", + ip_addr_buf[0], ip_addr_buf[1], + ip_addr_buf[2], ip_addr_buf[3]); + ip_ptr = ip_addr_buf; + } +#endif /* CONFIG_P2P */ + + keyid = ap_sta_wpa_get_keyid(hapd, sta); + if (keyid) { + os_snprintf(keyid_buf, sizeof(keyid_buf), + " keyid=%s", keyid); + } + + dpp_pkhash = ap_sta_wpa_get_dpp_pkhash(hapd, sta); + if (dpp_pkhash) { + const char *prefix = " dpp_pkhash="; + size_t plen = os_strlen(prefix); + + os_strlcpy(dpp_pkhash_buf, prefix, + sizeof(dpp_pkhash_buf)); + wpa_snprintf_hex(&dpp_pkhash_buf[plen], + sizeof(dpp_pkhash_buf) - plen, + dpp_pkhash, SHA256_MAC_LEN); + } + + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_CONNECTED "%s%s%s%s", + buf, ip_addr, keyid_buf, dpp_pkhash_buf); + + if (hapd->msg_ctx_parent && + hapd->msg_ctx_parent != hapd->msg_ctx) + wpa_msg_no_global(hapd->msg_ctx_parent, MSG_INFO, + AP_STA_CONNECTED "%s%s%s%s", + buf, ip_addr, keyid_buf, + dpp_pkhash_buf); + } else { + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_DISCONNECTED "%s", buf); + + if (hapd->msg_ctx_parent && + hapd->msg_ctx_parent != hapd->msg_ctx) + wpa_msg_no_global(hapd->msg_ctx_parent, MSG_INFO, + AP_STA_DISCONNECTED "%s", buf); + } + + if (hapd->sta_authorized_cb) + hapd->sta_authorized_cb(hapd->sta_authorized_cb_ctx, + sta->addr, authorized, dev_addr, + ip_ptr); + +#ifdef CONFIG_FST + if (hapd->iface->fst) { + if (authorized) + fst_notify_peer_connected(hapd->iface->fst, sta->addr); + else + fst_notify_peer_disconnected(hapd->iface->fst, + sta->addr); + } +#endif /* CONFIG_FST */ +} + + +void ap_sta_set_authorized(struct hostapd_data *hapd, struct sta_info *sta, + int authorized) +{ + if (!ap_sta_set_authorized_flag(hapd, sta, authorized)) + return; + ap_sta_set_authorized_event(hapd, sta, authorized); +} + + +void ap_sta_disconnect(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *addr, u16 reason) +{ + if (sta) + wpa_printf(MSG_DEBUG, "%s: %s STA " MACSTR " reason=%u", + hapd->conf->iface, __func__, MAC2STR(sta->addr), + reason); + else if (addr) + wpa_printf(MSG_DEBUG, "%s: %s addr " MACSTR " reason=%u", + hapd->conf->iface, __func__, MAC2STR(addr), + reason); + + if (sta == NULL && addr) + sta = ap_get_sta(hapd, addr); + + if (addr) + hostapd_drv_sta_deauth(hapd, addr, reason); + + if (sta == NULL) + return; + ap_sta_set_authorized(hapd, sta, 0); + sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC); + hostapd_set_sta_flags(hapd, sta); + wpa_auth_sm_event(sta->wpa_sm, WPA_DEAUTH); + ieee802_1x_notify_port_enabled(sta->eapol_sm, 0); + wpa_printf(MSG_DEBUG, "%s: %s: reschedule ap_handle_timer timeout " + "for " MACSTR " (%d seconds - " + "AP_MAX_INACTIVITY_AFTER_DEAUTH)", + hapd->conf->iface, __func__, MAC2STR(sta->addr), + AP_MAX_INACTIVITY_AFTER_DEAUTH); + eloop_cancel_timeout(ap_handle_timer, hapd, sta); + eloop_register_timeout(AP_MAX_INACTIVITY_AFTER_DEAUTH, 0, + ap_handle_timer, hapd, sta); + sta->timeout_next = STA_REMOVE; + + if (hapd->iface->current_mode && + hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) { + /* Deauthentication is not used in DMG/IEEE 802.11ad; + * disassociate the STA instead. */ + sta->disassoc_reason = reason; + sta->flags |= WLAN_STA_PENDING_DISASSOC_CB; + eloop_cancel_timeout(ap_sta_disassoc_cb_timeout, hapd, sta); + eloop_register_timeout(hapd->iface->drv_flags & + WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS ? + 2 : 0, 0, ap_sta_disassoc_cb_timeout, + hapd, sta); + return; + } + + sta->deauth_reason = reason; + sta->flags |= WLAN_STA_PENDING_DEAUTH_CB; + eloop_cancel_timeout(ap_sta_deauth_cb_timeout, hapd, sta); + eloop_register_timeout(hapd->iface->drv_flags & + WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS ? 2 : 0, 0, + ap_sta_deauth_cb_timeout, hapd, sta); +} + + +void ap_sta_deauth_cb(struct hostapd_data *hapd, struct sta_info *sta) +{ + if (!(sta->flags & WLAN_STA_PENDING_DEAUTH_CB)) { + wpa_printf(MSG_DEBUG, "Ignore deauth cb for test frame"); + return; + } + sta->flags &= ~WLAN_STA_PENDING_DEAUTH_CB; + eloop_cancel_timeout(ap_sta_deauth_cb_timeout, hapd, sta); + ap_sta_deauth_cb_timeout(hapd, sta); +} + + +void ap_sta_disassoc_cb(struct hostapd_data *hapd, struct sta_info *sta) +{ + if (!(sta->flags & WLAN_STA_PENDING_DISASSOC_CB)) { + wpa_printf(MSG_DEBUG, "Ignore disassoc cb for test frame"); + return; + } + sta->flags &= ~WLAN_STA_PENDING_DISASSOC_CB; + eloop_cancel_timeout(ap_sta_disassoc_cb_timeout, hapd, sta); + ap_sta_disassoc_cb_timeout(hapd, sta); +} + + +void ap_sta_clear_disconnect_timeouts(struct hostapd_data *hapd, + struct sta_info *sta) +{ + if (eloop_cancel_timeout(ap_sta_deauth_cb_timeout, hapd, sta) > 0) + wpa_printf(MSG_DEBUG, + "%s: Removed ap_sta_deauth_cb_timeout timeout for " + MACSTR, + hapd->conf->iface, MAC2STR(sta->addr)); + if (eloop_cancel_timeout(ap_sta_disassoc_cb_timeout, hapd, sta) > 0) + wpa_printf(MSG_DEBUG, + "%s: Removed ap_sta_disassoc_cb_timeout timeout for " + MACSTR, + hapd->conf->iface, MAC2STR(sta->addr)); + if (eloop_cancel_timeout(ap_sta_delayed_1x_auth_fail_cb, hapd, sta) > 0) + { + wpa_printf(MSG_DEBUG, + "%s: Removed ap_sta_delayed_1x_auth_fail_cb timeout for " + MACSTR, + hapd->conf->iface, MAC2STR(sta->addr)); + if (sta->flags & WLAN_STA_WPS) + hostapd_wps_eap_completed(hapd); + } +} + + +int ap_sta_flags_txt(u32 flags, char *buf, size_t buflen) +{ + int res; + + buf[0] = '\0'; + res = os_snprintf(buf, buflen, + "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + (flags & WLAN_STA_AUTH ? "[AUTH]" : ""), + (flags & WLAN_STA_ASSOC ? "[ASSOC]" : ""), + (flags & WLAN_STA_AUTHORIZED ? "[AUTHORIZED]" : ""), + (flags & WLAN_STA_PENDING_POLL ? "[PENDING_POLL" : + ""), + (flags & WLAN_STA_SHORT_PREAMBLE ? + "[SHORT_PREAMBLE]" : ""), + (flags & WLAN_STA_PREAUTH ? "[PREAUTH]" : ""), + (flags & WLAN_STA_WMM ? "[WMM]" : ""), + (flags & WLAN_STA_MFP ? "[MFP]" : ""), + (flags & WLAN_STA_WPS ? "[WPS]" : ""), + (flags & WLAN_STA_MAYBE_WPS ? "[MAYBE_WPS]" : ""), + (flags & WLAN_STA_WDS ? "[WDS]" : ""), + (flags & WLAN_STA_NONERP ? "[NonERP]" : ""), + (flags & WLAN_STA_WPS2 ? "[WPS2]" : ""), + (flags & WLAN_STA_GAS ? "[GAS]" : ""), + (flags & WLAN_STA_HT ? "[HT]" : ""), + (flags & WLAN_STA_VHT ? "[VHT]" : ""), + (flags & WLAN_STA_HE ? "[HE]" : ""), + (flags & WLAN_STA_EHT ? "[EHT]" : ""), + (flags & WLAN_STA_6GHZ ? "[6GHZ]" : ""), + (flags & WLAN_STA_VENDOR_VHT ? "[VENDOR_VHT]" : ""), + (flags & WLAN_STA_WNM_SLEEP_MODE ? + "[WNM_SLEEP_MODE]" : "")); + if (os_snprintf_error(buflen, res)) + res = -1; + + return res; +} + + +static void ap_sta_delayed_1x_auth_fail_cb(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + u16 reason; + + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, + "IEEE 802.1X: Scheduled disconnection of " MACSTR + " after EAP-Failure", MAC2STR(sta->addr)); + + reason = sta->disconnect_reason_code; + if (!reason) + reason = WLAN_REASON_IEEE_802_1X_AUTH_FAILED; + ap_sta_disconnect(hapd, sta, sta->addr, reason); + if (sta->flags & WLAN_STA_WPS) + hostapd_wps_eap_completed(hapd); +} + + +void ap_sta_delayed_1x_auth_fail_disconnect(struct hostapd_data *hapd, + struct sta_info *sta, + unsigned timeout) +{ + wpa_dbg(hapd->msg_ctx, MSG_DEBUG, + "IEEE 802.1X: Force disconnection of " MACSTR + " after EAP-Failure in %u ms", MAC2STR(sta->addr), timeout); + + /* + * Add a small sleep to increase likelihood of previously requested + * EAP-Failure TX getting out before this should the driver reorder + * operations. + */ + eloop_cancel_timeout(ap_sta_delayed_1x_auth_fail_cb, hapd, sta); + eloop_register_timeout(0, timeout * 1000, + ap_sta_delayed_1x_auth_fail_cb, hapd, sta); +} + + +int ap_sta_pending_delayed_1x_auth_fail_disconnect(struct hostapd_data *hapd, + struct sta_info *sta) +{ + return eloop_is_timeout_registered(ap_sta_delayed_1x_auth_fail_cb, + hapd, sta); +} + + +#ifdef CONFIG_IEEE80211BE +static void ap_sta_remove_link_sta(struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct hostapd_data *tmp_hapd; + + for_each_mld_link(tmp_hapd, hapd) { + struct sta_info *tmp_sta; + + if (hapd == tmp_hapd) + continue; + + for (tmp_sta = tmp_hapd->sta_list; tmp_sta; + tmp_sta = tmp_sta->next) { + if (tmp_sta == sta || + !ether_addr_equal(tmp_sta->addr, sta->addr)) + continue; + + ap_free_sta(tmp_hapd, tmp_sta); + break; + } + } +} +#endif /* CONFIG_IEEE80211BE */ + + +int ap_sta_re_add(struct hostapd_data *hapd, struct sta_info *sta) +{ + const u8 *mld_link_addr = NULL; + bool mld_link_sta = false; + + /* + * If a station that is already associated to the AP, is trying to + * authenticate again, remove the STA entry, in order to make sure the + * STA PS state gets cleared and configuration gets updated. To handle + * this, station's added_unassoc flag is cleared once the station has + * completed association. + */ + +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) { + u8 mld_link_id = hapd->mld_link_id; + + mld_link_sta = sta->mld_assoc_link_id != mld_link_id; + mld_link_addr = sta->mld_info.links[mld_link_id].peer_addr; + + /* + * In case the AP is affiliated with an AP MLD, we need to + * remove the station from all relevant links/APs. + */ + ap_sta_remove_link_sta(hapd, sta); + } +#endif /* CONFIG_IEEE80211BE */ + + ap_sta_set_authorized(hapd, sta, 0); + hostapd_drv_sta_remove(hapd, sta->addr); + sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_AUTH | WLAN_STA_AUTHORIZED); + + if (hostapd_sta_add(hapd, sta->addr, 0, 0, + sta->supported_rates, + sta->supported_rates_len, + 0, NULL, NULL, NULL, 0, NULL, 0, NULL, + sta->flags, 0, 0, 0, 0, + mld_link_addr, mld_link_sta)) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_NOTICE, + "Could not add STA to kernel driver"); + return -1; + } + + sta->added_unassoc = 1; + return 0; +} + + +#ifdef CONFIG_IEEE80211BE +void ap_sta_free_sta_profile(struct mld_info *info) +{ + int i; + + if (!info) + return; + + for (i = 0; i < MAX_NUM_MLD_LINKS; i++) { + os_free(info->links[i].resp_sta_profile); + info->links[i].resp_sta_profile = NULL; + } +} +#endif /* CONFIG_IEEE80211BE */ diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h new file mode 100644 index 0000000..8462935 --- /dev/null +++ b/src/ap/sta_info.h @@ -0,0 +1,446 @@ +/* + * hostapd / Station table + * Copyright (c) 2002-2017, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef STA_INFO_H +#define STA_INFO_H + +#include "common/defs.h" +#include "list.h" +#include "vlan.h" +#include "common/wpa_common.h" +#include "common/ieee802_11_defs.h" +#include "common/sae.h" +#include "crypto/sha384.h" +#include "pasn/pasn_common.h" +#include "hostapd.h" + +/* STA flags */ +#define WLAN_STA_AUTH BIT(0) +#define WLAN_STA_ASSOC BIT(1) +#define WLAN_STA_AUTHORIZED BIT(5) +#define WLAN_STA_PENDING_POLL BIT(6) /* pending activity poll not ACKed */ +#define WLAN_STA_SHORT_PREAMBLE BIT(7) +#define WLAN_STA_PREAUTH BIT(8) +#define WLAN_STA_WMM BIT(9) +#define WLAN_STA_MFP BIT(10) +#define WLAN_STA_HT BIT(11) +#define WLAN_STA_WPS BIT(12) +#define WLAN_STA_MAYBE_WPS BIT(13) +#define WLAN_STA_WDS BIT(14) +#define WLAN_STA_ASSOC_REQ_OK BIT(15) +#define WLAN_STA_WPS2 BIT(16) +#define WLAN_STA_GAS BIT(17) +#define WLAN_STA_VHT BIT(18) +#define WLAN_STA_WNM_SLEEP_MODE BIT(19) +#define WLAN_STA_VHT_OPMODE_ENABLED BIT(20) +#define WLAN_STA_VENDOR_VHT BIT(21) +#define WLAN_STA_PENDING_FILS_ERP BIT(22) +#define WLAN_STA_MULTI_AP BIT(23) +#define WLAN_STA_HE BIT(24) +#define WLAN_STA_6GHZ BIT(25) +#define WLAN_STA_PENDING_PASN_FILS_ERP BIT(26) +#define WLAN_STA_EHT BIT(27) +#define WLAN_STA_PENDING_DISASSOC_CB BIT(29) +#define WLAN_STA_PENDING_DEAUTH_CB BIT(30) +#define WLAN_STA_NONERP BIT(31) + +/* Maximum number of supported rates (from both Supported Rates and Extended + * Supported Rates IEs). */ +#define WLAN_SUPP_RATES_MAX 32 + +struct hostapd_data; + +struct mbo_non_pref_chan_info { + struct mbo_non_pref_chan_info *next; + u8 op_class; + u8 pref; + u8 reason_code; + u8 num_channels; + u8 channels[]; +}; + +struct pending_eapol_rx { + struct wpabuf *buf; + struct os_reltime rx_time; + enum frame_encryption encrypted; +}; + +#define EHT_ML_MAX_STA_PROF_LEN 1024 +struct mld_info { + bool mld_sta; + + struct ml_common_info { + u8 mld_addr[ETH_ALEN]; + u16 medium_sync_delay; + u16 eml_capa; + u16 mld_capa; + } common_info; + + struct mld_link_info { + u8 valid:1; + u8 nstr_bitmap_len:2; + u8 local_addr[ETH_ALEN]; + u8 peer_addr[ETH_ALEN]; + + u8 nstr_bitmap[2]; + + u16 capability; + + u16 status; + u16 resp_sta_profile_len; + u8 *resp_sta_profile; + } links[MAX_NUM_MLD_LINKS]; +}; + +struct sta_info { + struct sta_info *next; /* next entry in sta list */ + struct sta_info *hnext; /* next entry in hash table list */ + u8 addr[6]; + be32 ipaddr; + struct dl_list ip6addr; /* list head for struct ip6addr */ + u16 aid; /* STA's unique AID (1 .. 2007) or 0 if not yet assigned */ + u16 disconnect_reason_code; /* RADIUS server override */ + u32 flags; /* Bitfield of WLAN_STA_* */ + u16 capability; + u16 listen_interval; /* or beacon_int for APs */ + u8 supported_rates[WLAN_SUPP_RATES_MAX]; + int supported_rates_len; + u8 qosinfo; /* Valid when WLAN_STA_WMM is set */ + +#ifdef CONFIG_MESH + enum mesh_plink_state plink_state; + u16 peer_lid; + u16 my_lid; + u16 peer_aid; + u16 mpm_close_reason; + int mpm_retries; + u8 my_nonce[WPA_NONCE_LEN]; + u8 peer_nonce[WPA_NONCE_LEN]; + u8 aek[32]; /* SHA256 digest length */ + u8 mtk[WPA_TK_MAX_LEN]; + size_t mtk_len; + u8 mgtk_rsc[6]; + u8 mgtk_key_id; + u8 mgtk[WPA_TK_MAX_LEN]; + size_t mgtk_len; + u8 igtk_rsc[6]; + u8 igtk[WPA_TK_MAX_LEN]; + size_t igtk_len; + u16 igtk_key_id; + u8 sae_auth_retry; +#endif /* CONFIG_MESH */ + + unsigned int nonerp_set:1; + unsigned int no_short_slot_time_set:1; + unsigned int no_short_preamble_set:1; + unsigned int no_ht_gf_set:1; + unsigned int no_ht_set:1; + unsigned int ht40_intolerant_set:1; + unsigned int ht_20mhz_set:1; + unsigned int no_p2p_set:1; + unsigned int qos_map_enabled:1; + unsigned int remediation:1; + unsigned int hs20_deauth_requested:1; + unsigned int hs20_deauth_on_ack:1; + unsigned int session_timeout_set:1; + unsigned int radius_das_match:1; + unsigned int ecsa_supported:1; + unsigned int added_unassoc:1; + unsigned int pending_wds_enable:1; + unsigned int power_capab:1; + unsigned int agreed_to_steer:1; + unsigned int hs20_t_c_filtering:1; + unsigned int ft_over_ds:1; + unsigned int external_dh_updated:1; + unsigned int post_csa_sa_query:1; + + u16 auth_alg; + + enum { + STA_NULLFUNC = 0, STA_DISASSOC, STA_DEAUTH, STA_REMOVE, + STA_DISASSOC_FROM_CLI + } timeout_next; + + u16 deauth_reason; + u16 disassoc_reason; + + /* IEEE 802.1X related data */ + struct eapol_state_machine *eapol_sm; + + struct pending_eapol_rx *pending_eapol_rx; + + u64 acct_session_id; + struct os_reltime acct_session_start; + int acct_session_started; + int acct_terminate_cause; /* Acct-Terminate-Cause */ + int acct_interim_interval; /* Acct-Interim-Interval */ + unsigned int acct_interim_errors; + + /* For extending 32-bit driver counters to 64-bit counters */ + u32 last_rx_bytes_hi; + u32 last_rx_bytes_lo; + u32 last_tx_bytes_hi; + u32 last_tx_bytes_lo; + + u8 *challenge; /* IEEE 802.11 Shared Key Authentication Challenge */ + + struct wpa_state_machine *wpa_sm; + struct rsn_preauth_interface *preauth_iface; + + int vlan_id; /* 0: none, >0: VID */ + struct vlan_description *vlan_desc; + int vlan_id_bound; /* updated by ap_sta_bind_vlan() */ + /* PSKs from RADIUS authentication server */ + struct hostapd_sta_wpa_psk_short *psk; + + char *identity; /* User-Name from RADIUS */ + char *radius_cui; /* Chargeable-User-Identity from RADIUS */ + + struct ieee80211_ht_capabilities *ht_capabilities; + struct ieee80211_vht_capabilities *vht_capabilities; + struct ieee80211_vht_operation *vht_operation; + u8 vht_opmode; + struct ieee80211_he_capabilities *he_capab; + size_t he_capab_len; + struct ieee80211_he_6ghz_band_cap *he_6ghz_capab; + struct ieee80211_eht_capabilities *eht_capab; + size_t eht_capab_len; + + int sa_query_count; /* number of pending SA Query requests; + * 0 = no SA Query in progress */ + int sa_query_timed_out; + u8 *sa_query_trans_id; /* buffer of WLAN_SA_QUERY_TR_ID_LEN * + * sa_query_count octets of pending SA Query + * transaction identifiers */ + struct os_reltime sa_query_start; + +#if defined(CONFIG_INTERWORKING) || defined(CONFIG_DPP) +#define GAS_DIALOG_MAX 8 /* Max concurrent dialog number */ + struct gas_dialog_info *gas_dialog; + u8 gas_dialog_next; +#endif /* CONFIG_INTERWORKING || CONFIG_DPP */ + + struct wpabuf *wps_ie; /* WPS IE from (Re)Association Request */ + struct wpabuf *p2p_ie; /* P2P IE from (Re)Association Request */ + struct wpabuf *hs20_ie; /* HS 2.0 IE from (Re)Association Request */ + /* Hotspot 2.0 Roaming Consortium from (Re)Association Request */ + struct wpabuf *roaming_consortium; + u8 remediation_method; + char *remediation_url; /* HS 2.0 Subscription Remediation Server URL */ + char *t_c_url; /* HS 2.0 Terms and Conditions Server URL */ + struct wpabuf *hs20_deauth_req; + char *hs20_session_info_url; + int hs20_disassoc_timer; +#ifdef CONFIG_FST + struct wpabuf *mb_ies; /* MB IEs from (Re)Association Request */ +#endif /* CONFIG_FST */ + + struct os_reltime connected_time; + +#ifdef CONFIG_SAE + struct sae_data *sae; + unsigned int mesh_sae_pmksa_caching:1; +#endif /* CONFIG_SAE */ + + /* valid only if session_timeout_set == 1 */ + struct os_reltime session_timeout; + + /* Last Authentication/(Re)Association Request/Action frame sequence + * control */ + u16 last_seq_ctrl; + /* Last Authentication/(Re)Association Request/Action frame subtype */ + u8 last_subtype; + +#ifdef CONFIG_MBO + u8 cell_capa; /* 0 = unknown (not an MBO STA); otherwise, + * enum mbo_cellular_capa values */ + struct mbo_non_pref_chan_info *non_pref_chan; + int auth_rssi; /* Last Authentication frame RSSI */ +#endif /* CONFIG_MBO */ + + u8 *supp_op_classes; /* Supported Operating Classes element, if + * received, starting from the Length field */ + + u8 rrm_enabled_capa[5]; + + s8 min_tx_power; + s8 max_tx_power; + +#ifdef CONFIG_TAXONOMY + struct wpabuf *probe_ie_taxonomy; + struct wpabuf *assoc_ie_taxonomy; +#endif /* CONFIG_TAXONOMY */ + +#ifdef CONFIG_FILS + u8 fils_snonce[FILS_NONCE_LEN]; + u8 fils_session[FILS_SESSION_LEN]; + u8 fils_erp_pmkid[PMKID_LEN]; + u8 *fils_pending_assoc_req; + size_t fils_pending_assoc_req_len; + unsigned int fils_pending_assoc_is_reassoc:1; + unsigned int fils_dhcp_rapid_commit_proxy:1; + unsigned int fils_erp_pmkid_set:1; + unsigned int fils_drv_assoc_finish:1; + struct wpabuf *fils_hlp_resp; + struct wpabuf *hlp_dhcp_discover; + void (*fils_pending_cb)(struct hostapd_data *hapd, struct sta_info *sta, + u16 resp, struct wpabuf *data, int pub); +#ifdef CONFIG_FILS_SK_PFS + struct crypto_ecdh *fils_ecdh; +#endif /* CONFIG_FILS_SK_PFS */ + struct wpabuf *fils_dh_ss; + struct wpabuf *fils_g_sta; +#endif /* CONFIG_FILS */ + +#ifdef CONFIG_OWE + u8 *owe_pmk; + size_t owe_pmk_len; + struct crypto_ecdh *owe_ecdh; + u16 owe_group; +#endif /* CONFIG_OWE */ + + u8 *ext_capability; + char *ifname_wds; /* WDS ifname, if in use */ + +#ifdef CONFIG_DPP2 + struct dpp_pfs *dpp_pfs; +#endif /* CONFIG_DPP2 */ + +#ifdef CONFIG_TESTING_OPTIONS + enum wpa_alg last_tk_alg; + int last_tk_key_idx; + u8 last_tk[WPA_TK_MAX_LEN]; + size_t last_tk_len; + u8 *sae_postponed_commit; + size_t sae_postponed_commit_len; +#endif /* CONFIG_TESTING_OPTIONS */ +#ifdef CONFIG_AIRTIME_POLICY + unsigned int airtime_weight; + struct os_reltime backlogged_until; +#endif /* CONFIG_AIRTIME_POLICY */ + +#ifdef CONFIG_PASN + struct pasn_data *pasn; +#endif /* CONFIG_PASN */ + +#ifdef CONFIG_IEEE80211BE + struct mld_info mld_info; + u8 mld_assoc_link_id; +#endif /* CONFIG_IEEE80211BE */ + + u16 max_idle_period; /* if nonzero, the granted BSS max idle period in + * units of 1000 TUs */ +}; + + +/* Default value for maximum station inactivity. After AP_MAX_INACTIVITY has + * passed since last received frame from the station, a nullfunc data frame is + * sent to the station. If this frame is not acknowledged and no other frames + * have been received, the station will be disassociated after + * AP_DISASSOC_DELAY seconds. Similarly, the station will be deauthenticated + * after AP_DEAUTH_DELAY seconds has passed after disassociation. */ +#define AP_MAX_INACTIVITY (5 * 60) +#define AP_DISASSOC_DELAY (3) +#define AP_DEAUTH_DELAY (1) +/* Number of seconds to keep STA entry with Authenticated flag after it has + * been disassociated. */ +#define AP_MAX_INACTIVITY_AFTER_DISASSOC (1 * 30) +/* Number of seconds to keep STA entry after it has been deauthenticated. */ +#define AP_MAX_INACTIVITY_AFTER_DEAUTH (1 * 5) + + +int ap_for_each_sta(struct hostapd_data *hapd, + int (*cb)(struct hostapd_data *hapd, struct sta_info *sta, + void *ctx), + void *ctx); +struct sta_info * ap_get_sta(struct hostapd_data *hapd, const u8 *sta); +struct sta_info * ap_get_sta_p2p(struct hostapd_data *hapd, const u8 *addr); +void ap_sta_hash_add(struct hostapd_data *hapd, struct sta_info *sta); +void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta); +void ap_sta_ip6addr_del(struct hostapd_data *hapd, struct sta_info *sta); +void hostapd_free_stas(struct hostapd_data *hapd); +void ap_handle_timer(void *eloop_ctx, void *timeout_ctx); +void ap_sta_replenish_timeout(struct hostapd_data *hapd, struct sta_info *sta, + u32 session_timeout); +void ap_sta_session_timeout(struct hostapd_data *hapd, struct sta_info *sta, + u32 session_timeout); +void ap_sta_no_session_timeout(struct hostapd_data *hapd, + struct sta_info *sta); +void ap_sta_session_warning_timeout(struct hostapd_data *hapd, + struct sta_info *sta, int warning_time); +struct sta_info * ap_sta_add(struct hostapd_data *hapd, const u8 *addr); +void ap_sta_disassociate(struct hostapd_data *hapd, struct sta_info *sta, + u16 reason); +void ap_sta_deauthenticate(struct hostapd_data *hapd, struct sta_info *sta, + u16 reason); +#ifdef CONFIG_WPS +int ap_sta_wps_cancel(struct hostapd_data *hapd, + struct sta_info *sta, void *ctx); +#endif /* CONFIG_WPS */ +int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta); +int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta, + struct vlan_description *vlan_desc); +void ap_sta_start_sa_query(struct hostapd_data *hapd, struct sta_info *sta); +void ap_sta_stop_sa_query(struct hostapd_data *hapd, struct sta_info *sta); +int ap_check_sa_query_timeout(struct hostapd_data *hapd, struct sta_info *sta); +const char * ap_sta_wpa_get_keyid(struct hostapd_data *hapd, + struct sta_info *sta); +const u8 * ap_sta_wpa_get_dpp_pkhash(struct hostapd_data *hapd, + struct sta_info *sta); +void ap_sta_disconnect(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *addr, u16 reason); + +bool ap_sta_set_authorized_flag(struct hostapd_data *hapd, struct sta_info *sta, + int authorized); +void ap_sta_set_authorized_event(struct hostapd_data *hapd, + struct sta_info *sta, int authorized); +void ap_sta_set_authorized(struct hostapd_data *hapd, + struct sta_info *sta, int authorized); +static inline int ap_sta_is_authorized(struct sta_info *sta) +{ + return sta->flags & WLAN_STA_AUTHORIZED; +} + +void ap_sta_deauth_cb(struct hostapd_data *hapd, struct sta_info *sta); +void ap_sta_disassoc_cb(struct hostapd_data *hapd, struct sta_info *sta); +void ap_sta_clear_disconnect_timeouts(struct hostapd_data *hapd, + struct sta_info *sta); + +int ap_sta_flags_txt(u32 flags, char *buf, size_t buflen); +void ap_sta_delayed_1x_auth_fail_disconnect(struct hostapd_data *hapd, + struct sta_info *sta, + unsigned timeout); +int ap_sta_pending_delayed_1x_auth_fail_disconnect(struct hostapd_data *hapd, + struct sta_info *sta); +int ap_sta_re_add(struct hostapd_data *hapd, struct sta_info *sta); + +void ap_free_sta_pasn(struct hostapd_data *hapd, struct sta_info *sta); + +static inline bool ap_sta_is_mld(struct hostapd_data *hapd, + struct sta_info *sta) +{ +#ifdef CONFIG_IEEE80211BE + return hapd->conf->mld_ap && sta && sta->mld_info.mld_sta; +#else /* CONFIG_IEEE80211BE */ + return false; +#endif /* CONFIG_IEEE80211BE */ +} + +static inline void ap_sta_set_mld(struct sta_info *sta, bool mld) +{ +#ifdef CONFIG_IEEE80211BE + if (sta) + sta->mld_info.mld_sta = mld; +#endif /* CONFIG_IEEE80211BE */ +} + +void ap_sta_free_sta_profile(struct mld_info *info); + +void hostapd_free_link_stas(struct hostapd_data *hapd); + +#endif /* STA_INFO_H */ diff --git a/src/ap/taxonomy.c b/src/ap/taxonomy.c new file mode 100644 index 0000000..ae157a7 --- /dev/null +++ b/src/ap/taxonomy.c @@ -0,0 +1,292 @@ +/* + * hostapd / Client taxonomy + * Copyright (c) 2015 Google, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * Parse a series of IEs, as in Probe Request or (Re)Association Request frames, + * and render them to a descriptive string. The tag number of standard options + * is written to the string, while the vendor ID and subtag are written for + * vendor options. + * + * Example strings: + * 0,1,50,45,221(00904c,51) + * 0,1,33,36,48,45,221(00904c,51),221(0050f2,2) + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/wpa_ctrl.h" +#include "hostapd.h" +#include "sta_info.h" +#include "taxonomy.h" + + +/* Copy a string with no funny schtuff allowed; only alphanumerics. */ +static void no_mischief_strncpy(char *dst, const char *src, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) { + unsigned char s = src[i]; + int is_lower = s >= 'a' && s <= 'z'; + int is_upper = s >= 'A' && s <= 'Z'; + int is_digit = s >= '0' && s <= '9'; + + if (is_lower || is_upper || is_digit) { + /* TODO: if any manufacturer uses Unicode within the + * WPS header, it will get mangled here. */ + dst[i] = s; + } else { + /* Note that even spaces will be transformed to + * underscores, so 'Nexus 7' will turn into 'Nexus_7'. + * This is deliberate, to make the string easier to + * parse. */ + dst[i] = '_'; + } + } +} + + +static int get_wps_name(char *name, size_t name_len, + const u8 *data, size_t data_len) +{ + /* Inside the WPS IE are a series of attributes, using two byte IDs + * and two byte lengths. We're looking for the model name, if + * present. */ + while (data_len >= 4) { + u16 id, elen; + + id = WPA_GET_BE16(data); + elen = WPA_GET_BE16(data + 2); + data += 4; + data_len -= 4; + + if (elen > data_len) + return 0; + + if (id == 0x1023) { + /* Model name, like 'Nexus 7' */ + size_t n = (elen < name_len) ? elen : name_len; + no_mischief_strncpy(name, (const char *) data, n); + return n; + } + + data += elen; + data_len -= elen; + } + + return 0; +} + + +static void ie_to_string(char *fstr, size_t fstr_len, const struct wpabuf *ies) +{ + char *fpos = fstr; + char *fend = fstr + fstr_len; + char htcap[7 + 4 + 1]; /* ",htcap:" + %04hx + trailing NUL */ + char htagg[7 + 2 + 1]; /* ",htagg:" + %02hx + trailing NUL */ + char htmcs[7 + 8 + 1]; /* ",htmcs:" + %08x + trailing NUL */ + char vhtcap[8 + 8 + 1]; /* ",vhtcap:" + %08x + trailing NUL */ + char vhtrxmcs[10 + 8 + 1]; /* ",vhtrxmcs:" + %08x + trailing NUL */ + char vhttxmcs[10 + 8 + 1]; /* ",vhttxmcs:" + %08x + trailing NUL */ +#define MAX_EXTCAP 254 + char extcap[8 + 2 * MAX_EXTCAP + 1]; /* ",extcap:" + hex + trailing NUL + */ + char txpow[7 + 4 + 1]; /* ",txpow:" + %04hx + trailing NUL */ +#define WPS_NAME_LEN 32 + char wps[WPS_NAME_LEN + 5 + 1]; /* room to prepend ",wps:" + trailing + * NUL */ + int num = 0; + const u8 *ie; + size_t ie_len; + int ret; + + os_memset(htcap, 0, sizeof(htcap)); + os_memset(htagg, 0, sizeof(htagg)); + os_memset(htmcs, 0, sizeof(htmcs)); + os_memset(vhtcap, 0, sizeof(vhtcap)); + os_memset(vhtrxmcs, 0, sizeof(vhtrxmcs)); + os_memset(vhttxmcs, 0, sizeof(vhttxmcs)); + os_memset(extcap, 0, sizeof(extcap)); + os_memset(txpow, 0, sizeof(txpow)); + os_memset(wps, 0, sizeof(wps)); + *fpos = '\0'; + + if (!ies) + return; + ie = wpabuf_head(ies); + ie_len = wpabuf_len(ies); + + while (ie_len >= 2) { + u8 id, elen; + char *sep = (num++ == 0) ? "" : ","; + + id = *ie++; + elen = *ie++; + ie_len -= 2; + + if (elen > ie_len) + break; + + if (id == WLAN_EID_VENDOR_SPECIFIC && elen >= 4) { + /* Vendor specific */ + if (WPA_GET_BE32(ie) == WPS_IE_VENDOR_TYPE) { + /* WPS */ + char model_name[WPS_NAME_LEN + 1]; + const u8 *data = &ie[4]; + size_t data_len = elen - 4; + + os_memset(model_name, 0, sizeof(model_name)); + if (get_wps_name(model_name, WPS_NAME_LEN, data, + data_len)) { + os_snprintf(wps, sizeof(wps), + ",wps:%s", model_name); + } + } + + ret = os_snprintf(fpos, fend - fpos, + "%s%d(%02x%02x%02x,%d)", + sep, id, ie[0], ie[1], ie[2], ie[3]); + } else { + if (id == WLAN_EID_HT_CAP && elen >= 2) { + /* HT Capabilities (802.11n) */ + os_snprintf(htcap, sizeof(htcap), + ",htcap:%04hx", + WPA_GET_LE16(ie)); + } + if (id == WLAN_EID_HT_CAP && elen >= 3) { + /* HT Capabilities (802.11n), A-MPDU information + */ + os_snprintf(htagg, sizeof(htagg), + ",htagg:%02hx", (u16) ie[2]); + } + if (id == WLAN_EID_HT_CAP && elen >= 7) { + /* HT Capabilities (802.11n), MCS information */ + os_snprintf(htmcs, sizeof(htmcs), + ",htmcs:%08hx", + (u16) WPA_GET_LE32(ie + 3)); + } + if (id == WLAN_EID_VHT_CAP && elen >= 4) { + /* VHT Capabilities (802.11ac) */ + os_snprintf(vhtcap, sizeof(vhtcap), + ",vhtcap:%08x", + WPA_GET_LE32(ie)); + } + if (id == WLAN_EID_VHT_CAP && elen >= 8) { + /* VHT Capabilities (802.11ac), RX MCS + * information */ + os_snprintf(vhtrxmcs, sizeof(vhtrxmcs), + ",vhtrxmcs:%08x", + WPA_GET_LE32(ie + 4)); + } + if (id == WLAN_EID_VHT_CAP && elen >= 12) { + /* VHT Capabilities (802.11ac), TX MCS + * information */ + os_snprintf(vhttxmcs, sizeof(vhttxmcs), + ",vhttxmcs:%08x", + WPA_GET_LE32(ie + 8)); + } + if (id == WLAN_EID_EXT_CAPAB) { + /* Extended Capabilities */ + int i; + int len = (elen < MAX_EXTCAP) ? elen : + MAX_EXTCAP; + char *p = extcap; + + p += os_snprintf(extcap, sizeof(extcap), + ",extcap:"); + for (i = 0; i < len; i++) { + int lim; + + lim = sizeof(extcap) - + os_strlen(extcap); + if (lim <= 0) + break; + p += os_snprintf(p, lim, "%02x", + *(ie + i)); + } + } + if (id == WLAN_EID_PWR_CAPABILITY && elen == 2) { + /* TX Power */ + os_snprintf(txpow, sizeof(txpow), + ",txpow:%04hx", + WPA_GET_LE16(ie)); + } + + ret = os_snprintf(fpos, fend - fpos, "%s%d", sep, id); + } + if (os_snprintf_error(fend - fpos, ret)) + goto fail; + fpos += ret; + + ie += elen; + ie_len -= elen; + } + + ret = os_snprintf(fpos, fend - fpos, "%s%s%s%s%s%s%s%s%s", + htcap, htagg, htmcs, vhtcap, vhtrxmcs, vhttxmcs, + txpow, extcap, wps); + if (os_snprintf_error(fend - fpos, ret)) { + fail: + fstr[0] = '\0'; + } +} + + +int retrieve_sta_taxonomy(const struct hostapd_data *hapd, + struct sta_info *sta, char *buf, size_t buflen) +{ + int ret; + char *pos, *end; + + if (!sta->probe_ie_taxonomy || !sta->assoc_ie_taxonomy) + return 0; + + ret = os_snprintf(buf, buflen, "wifi4|probe:"); + if (os_snprintf_error(buflen, ret)) + return 0; + pos = buf + ret; + end = buf + buflen; + + ie_to_string(pos, end - pos, sta->probe_ie_taxonomy); + pos = os_strchr(pos, '\0'); + if (pos >= end) + return 0; + ret = os_snprintf(pos, end - pos, "|assoc:"); + if (os_snprintf_error(end - pos, ret)) + return 0; + pos += ret; + ie_to_string(pos, end - pos, sta->assoc_ie_taxonomy); + pos = os_strchr(pos, '\0'); + return pos - buf; +} + + +void taxonomy_sta_info_probe_req(const struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *ie, size_t ie_len) +{ + wpabuf_free(sta->probe_ie_taxonomy); + sta->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len); +} + + +void taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data *hapd, + struct hostapd_sta_info *info, + const u8 *ie, size_t ie_len) +{ + wpabuf_free(info->probe_ie_taxonomy); + info->probe_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len); +} + + +void taxonomy_sta_info_assoc_req(const struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *ie, size_t ie_len) +{ + wpabuf_free(sta->assoc_ie_taxonomy); + sta->assoc_ie_taxonomy = wpabuf_alloc_copy(ie, ie_len); +} diff --git a/src/ap/taxonomy.h b/src/ap/taxonomy.h new file mode 100644 index 0000000..80f245c --- /dev/null +++ b/src/ap/taxonomy.h @@ -0,0 +1,24 @@ +/* + * hostapd / Station client taxonomy + * Copyright (c) 2015 Google, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef TAXONOMY_H +#define TAXONOMY_H + +void taxonomy_sta_info_probe_req(const struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *ie, size_t ie_len); +void taxonomy_hostapd_sta_info_probe_req(const struct hostapd_data *hapd, + struct hostapd_sta_info *sta, + const u8 *ie, size_t ie_len); +void taxonomy_sta_info_assoc_req(const struct hostapd_data *hapd, + struct sta_info *sta, + const u8 *ie, size_t ie_len); +int retrieve_sta_taxonomy(const struct hostapd_data *hapd, + struct sta_info *sta, char *buf, size_t buflen); + +#endif /* TAXONOMY_H */ diff --git a/src/ap/tkip_countermeasures.c b/src/ap/tkip_countermeasures.c new file mode 100644 index 0000000..557570c --- /dev/null +++ b/src/ap/tkip_countermeasures.c @@ -0,0 +1,110 @@ +/* + * hostapd / TKIP countermeasures + * Copyright (c) 2002-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/ieee802_11_defs.h" +#include "radius/radius.h" +#include "hostapd.h" +#include "sta_info.h" +#include "ap_mlme.h" +#include "wpa_auth.h" +#include "ap_drv_ops.h" +#include "tkip_countermeasures.h" + + +static void ieee80211_tkip_countermeasures_stop(void *eloop_ctx, + void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + hapd->tkip_countermeasures = 0; + hostapd_drv_set_countermeasures(hapd, 0); + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "TKIP countermeasures ended"); +} + + +static void ieee80211_tkip_countermeasures_start(struct hostapd_data *hapd) +{ + struct sta_info *sta; + + hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "TKIP countermeasures initiated"); + + wpa_auth_countermeasures_start(hapd->wpa_auth); + hapd->tkip_countermeasures = 1; + hostapd_drv_set_countermeasures(hapd, 1); + wpa_gtk_rekey(hapd->wpa_auth); + eloop_cancel_timeout(ieee80211_tkip_countermeasures_stop, hapd, NULL); + eloop_register_timeout(60, 0, ieee80211_tkip_countermeasures_stop, + hapd, NULL); + while ((sta = hapd->sta_list)) { + sta->acct_terminate_cause = + RADIUS_ACCT_TERMINATE_CAUSE_ADMIN_RESET; + if (sta->flags & WLAN_STA_AUTH) { + mlme_deauthenticate_indication( + hapd, sta, + WLAN_REASON_MICHAEL_MIC_FAILURE); + } + hostapd_drv_sta_deauth(hapd, sta->addr, + WLAN_REASON_MICHAEL_MIC_FAILURE); + ap_free_sta(hapd, sta); + } +} + + +void ieee80211_tkip_countermeasures_deinit(struct hostapd_data *hapd) +{ + eloop_cancel_timeout(ieee80211_tkip_countermeasures_stop, hapd, NULL); +} + + +int michael_mic_failure(struct hostapd_data *hapd, const u8 *addr, int local) +{ + struct os_reltime now; + int ret = 0; + + hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Michael MIC failure detected in received frame%s", + local ? " (local)" : ""); + + if (addr && local) { + struct sta_info *sta = ap_get_sta(hapd, addr); + if (sta != NULL) { + wpa_auth_sta_local_mic_failure_report(sta->wpa_sm); + hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Michael MIC failure detected in " + "received frame"); + mlme_michaelmicfailure_indication(hapd, addr); + } else { + wpa_printf(MSG_DEBUG, + "MLME-MICHAELMICFAILURE.indication " + "for not associated STA (" MACSTR + ") ignored", MAC2STR(addr)); + return ret; + } + } + + os_get_reltime(&now); + if (os_reltime_expired(&now, &hapd->michael_mic_failure, 60)) { + hapd->michael_mic_failures = 1; + } else { + hapd->michael_mic_failures++; + if (hapd->michael_mic_failures > 1) { + ieee80211_tkip_countermeasures_start(hapd); + ret = 1; + } + } + hapd->michael_mic_failure = now; + + return ret; +} diff --git a/src/ap/tkip_countermeasures.h b/src/ap/tkip_countermeasures.h new file mode 100644 index 0000000..d3eaed3 --- /dev/null +++ b/src/ap/tkip_countermeasures.h @@ -0,0 +1,15 @@ +/* + * hostapd / TKIP countermeasures + * Copyright (c) 2002-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef TKIP_COUNTERMEASURES_H +#define TKIP_COUNTERMEASURES_H + +int michael_mic_failure(struct hostapd_data *hapd, const u8 *addr, int local); +void ieee80211_tkip_countermeasures_deinit(struct hostapd_data *hapd); + +#endif /* TKIP_COUNTERMEASURES_H */ diff --git a/src/ap/utils.c b/src/ap/utils.c new file mode 100644 index 0000000..e93e531 --- /dev/null +++ b/src/ap/utils.c @@ -0,0 +1,112 @@ +/* + * AP mode helper functions + * Copyright (c) 2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "common.h" +#include "common/ieee802_11_defs.h" +#include "fst/fst.h" +#include "sta_info.h" +#include "hostapd.h" + + +int hostapd_register_probereq_cb(struct hostapd_data *hapd, + int (*cb)(void *ctx, const u8 *sa, + const u8 *da, const u8 *bssid, + const u8 *ie, size_t ie_len, + int ssi_signal), + void *ctx) +{ + struct hostapd_probereq_cb *n; + + n = os_realloc_array(hapd->probereq_cb, hapd->num_probereq_cb + 1, + sizeof(struct hostapd_probereq_cb)); + if (n == NULL) + return -1; + + hapd->probereq_cb = n; + n = &hapd->probereq_cb[hapd->num_probereq_cb]; + hapd->num_probereq_cb++; + + n->cb = cb; + n->ctx = ctx; + + return 0; +} + + +struct prune_data { + struct hostapd_data *hapd; + const u8 *addr; + int mld_assoc_link_id; +}; + +static int prune_associations(struct hostapd_iface *iface, void *ctx) +{ + struct prune_data *data = ctx; + struct sta_info *osta; + struct hostapd_data *ohapd; + size_t j; + + for (j = 0; j < iface->num_bss; j++) { + ohapd = iface->bss[j]; + if (ohapd == data->hapd) + continue; +#ifdef CONFIG_TESTING_OPTIONS + if (ohapd->conf->skip_prune_assoc) + continue; +#endif /* CONFIG_TESTING_OPTIONS */ +#ifdef CONFIG_FST + /* Don't prune STAs belong to same FST */ + if (ohapd->iface->fst && + data->hapd->iface->fst && + fst_are_ifaces_aggregated(ohapd->iface->fst, + data->hapd->iface->fst)) + continue; +#endif /* CONFIG_FST */ + osta = ap_get_sta(ohapd, data->addr); + if (!osta) + continue; + +#ifdef CONFIG_IEEE80211BE + if (data->mld_assoc_link_id >= 0 && + osta->mld_assoc_link_id == data->mld_assoc_link_id) + continue; +#endif /* CONFIG_IEEE80211BE */ + + wpa_printf(MSG_INFO, "%s: Prune association for " MACSTR, + ohapd->conf->iface, MAC2STR(osta->addr)); + ap_sta_disassociate(ohapd, osta, WLAN_REASON_UNSPECIFIED); + } + + return 0; +} + +/** + * hostapd_prune_associations - Remove extraneous associations + * @hapd: Pointer to BSS data for the most recent association + * @addr: Associated STA address + * @mld_assoc_link_id: MLD link id used for association or -1 for non MLO + * + * This function looks through all radios and BSS's for previous + * (stale) associations of STA. If any are found they are removed. + */ +void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr, + int mld_assoc_link_id) +{ + struct prune_data data; + + data.hapd = hapd; + data.addr = addr; + data.mld_assoc_link_id = mld_assoc_link_id; + + if (hapd->iface->interfaces && + hapd->iface->interfaces->for_each_interface) + hapd->iface->interfaces->for_each_interface( + hapd->iface->interfaces, prune_associations, &data); +} diff --git a/src/ap/vlan.c b/src/ap/vlan.c new file mode 100644 index 0000000..b6f6bb1 --- /dev/null +++ b/src/ap/vlan.c @@ -0,0 +1,34 @@ +/* + * hostapd / VLAN definition + * Copyright (c) 2016, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "ap/vlan.h" + +/* compare the two arguments, NULL is treated as empty + * return zero iff they are equal + */ +int vlan_compare(struct vlan_description *a, struct vlan_description *b) +{ + int i; + const int a_empty = !a || !a->notempty; + const int b_empty = !b || !b->notempty; + + if (a_empty && b_empty) + return 0; + if (a_empty || b_empty) + return 1; + if (a->untagged != b->untagged) + return 1; + for (i = 0; i < MAX_NUM_TAGGED_VLAN; i++) { + if (a->tagged[i] != b->tagged[i]) + return 1; + } + return 0; +} diff --git a/src/ap/vlan.h b/src/ap/vlan.h new file mode 100644 index 0000000..af84929 --- /dev/null +++ b/src/ap/vlan.h @@ -0,0 +1,30 @@ +/* + * hostapd / VLAN definition + * Copyright (c) 2015, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef VLAN_H +#define VLAN_H + +#define MAX_NUM_TAGGED_VLAN 32 + +struct vlan_description { + int notempty; /* 0 : no vlan information present, 1: else */ + int untagged; /* >0 802.1q vid */ + int tagged[MAX_NUM_TAGGED_VLAN]; /* first k items, ascending order */ +}; + +#ifndef CONFIG_NO_VLAN +int vlan_compare(struct vlan_description *a, struct vlan_description *b); +#else /* CONFIG_NO_VLAN */ +static inline int +vlan_compare(struct vlan_description *a, struct vlan_description *b) +{ + return 0; +} +#endif /* CONFIG_NO_VLAN */ + +#endif /* VLAN_H */ diff --git a/src/ap/vlan_full.c b/src/ap/vlan_full.c new file mode 100644 index 0000000..19aa3c6 --- /dev/null +++ b/src/ap/vlan_full.c @@ -0,0 +1,801 @@ +/* + * hostapd / VLAN initialization - full dynamic VLAN + * Copyright 2003, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * Copyright (c) 2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include +/* Avoid conflicts due to NetBSD net/if.h if_type define with driver.h */ +#undef if_type +#include + +#include "utils/common.h" +#include "drivers/priv_netlink.h" +#include "drivers/linux_ioctl.h" +#include "common/linux_bridge.h" +#include "common/linux_vlan.h" +#include "utils/eloop.h" +#include "hostapd.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "wpa_auth.h" +#include "vlan_init.h" +#include "vlan_util.h" + + +struct full_dynamic_vlan { + int s; /* socket on which to listen for new/removed interfaces. */ +}; + +#define DVLAN_CLEAN_BR 0x1 +#define DVLAN_CLEAN_VLAN 0x2 +#define DVLAN_CLEAN_VLAN_PORT 0x4 + +struct dynamic_iface { + char ifname[IFNAMSIZ + 1]; + int usage; + int clean; + struct dynamic_iface *next; +}; + + +/* Increment ref counter for ifname and add clean flag. + * If not in list, add it only if some flags are given. + */ +static void dyn_iface_get(struct hostapd_data *hapd, const char *ifname, + int clean) +{ + struct dynamic_iface *next, **dynamic_ifaces; + struct hapd_interfaces *interfaces; + + interfaces = hapd->iface->interfaces; + dynamic_ifaces = &interfaces->vlan_priv; + + for (next = *dynamic_ifaces; next; next = next->next) { + if (os_strcmp(ifname, next->ifname) == 0) + break; + } + + if (next) { + next->usage++; + next->clean |= clean; + return; + } + + if (!clean) + return; + + next = os_zalloc(sizeof(*next)); + if (!next) + return; + os_strlcpy(next->ifname, ifname, sizeof(next->ifname)); + next->usage = 1; + next->clean = clean; + next->next = *dynamic_ifaces; + *dynamic_ifaces = next; +} + + +/* Decrement reference counter for given ifname. + * Return clean flag iff reference counter was decreased to zero, else zero + */ +static int dyn_iface_put(struct hostapd_data *hapd, const char *ifname) +{ + struct dynamic_iface *next, *prev = NULL, **dynamic_ifaces; + struct hapd_interfaces *interfaces; + int clean; + + interfaces = hapd->iface->interfaces; + dynamic_ifaces = &interfaces->vlan_priv; + + for (next = *dynamic_ifaces; next; next = next->next) { + if (os_strcmp(ifname, next->ifname) == 0) + break; + prev = next; + } + + if (!next) + return 0; + + next->usage--; + if (next->usage) + return 0; + + if (prev) + prev->next = next->next; + else + *dynamic_ifaces = next->next; + clean = next->clean; + os_free(next); + + return clean; +} + + +static int ifconfig_down(const char *if_name) +{ + wpa_printf(MSG_DEBUG, "VLAN: Set interface %s down", if_name); + return ifconfig_helper(if_name, 0); +} + + +/* This value should be 256 ONLY. If it is something else, then hostapd + * might crash!, as this value has been hard-coded in 2.4.x kernel + * bridging code. + */ +#define MAX_BR_PORTS 256 + +static int br_delif(const char *br_name, const char *if_name) +{ + int fd; + struct ifreq ifr; + unsigned long args[2]; + int if_index; + + wpa_printf(MSG_DEBUG, "VLAN: br_delif(%s, %s)", br_name, if_name); + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: socket(AF_INET,SOCK_STREAM) " + "failed: %s", __func__, strerror(errno)); + return -1; + } + + if (linux_br_del_if(fd, br_name, if_name) == 0) + goto done; + + if_index = if_nametoindex(if_name); + + if (if_index == 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: Failure determining " + "interface index for '%s'", + __func__, if_name); + close(fd); + return -1; + } + + args[0] = BRCTL_DEL_IF; + args[1] = if_index; + + os_strlcpy(ifr.ifr_name, br_name, sizeof(ifr.ifr_name)); + ifr.ifr_data = (void *) args; + + if (ioctl(fd, SIOCDEVPRIVATE, &ifr) < 0 && errno != EINVAL) { + /* No error if interface already removed. */ + wpa_printf(MSG_ERROR, "VLAN: %s: ioctl[SIOCDEVPRIVATE," + "BRCTL_DEL_IF] failed for br_name=%s if_name=%s: " + "%s", __func__, br_name, if_name, strerror(errno)); + close(fd); + return -1; + } + +done: + close(fd); + return 0; +} + + +/* + Add interface 'if_name' to the bridge 'br_name' + + returns -1 on error + returns 1 if the interface is already part of the bridge + returns 0 otherwise +*/ +static int br_addif(const char *br_name, const char *if_name) +{ + int fd; + struct ifreq ifr; + unsigned long args[2]; + int if_index; + + wpa_printf(MSG_DEBUG, "VLAN: br_addif(%s, %s)", br_name, if_name); + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: socket(AF_INET,SOCK_STREAM) " + "failed: %s", __func__, strerror(errno)); + return -1; + } + + if (linux_br_add_if(fd, br_name, if_name) == 0) + goto done; + if (errno == EBUSY) { + /* The interface is already added. */ + close(fd); + return 1; + } + + if_index = if_nametoindex(if_name); + + if (if_index == 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: Failure determining " + "interface index for '%s'", + __func__, if_name); + close(fd); + return -1; + } + + args[0] = BRCTL_ADD_IF; + args[1] = if_index; + + os_strlcpy(ifr.ifr_name, br_name, sizeof(ifr.ifr_name)); + ifr.ifr_data = (void *) args; + + if (ioctl(fd, SIOCDEVPRIVATE, &ifr) < 0) { + if (errno == EBUSY) { + /* The interface is already added. */ + close(fd); + return 1; + } + + wpa_printf(MSG_ERROR, "VLAN: %s: ioctl[SIOCDEVPRIVATE," + "BRCTL_ADD_IF] failed for br_name=%s if_name=%s: " + "%s", __func__, br_name, if_name, strerror(errno)); + close(fd); + return -1; + } + +done: + close(fd); + return 0; +} + + +static int br_delbr(const char *br_name) +{ + int fd; + unsigned long arg[2]; + + wpa_printf(MSG_DEBUG, "VLAN: br_delbr(%s)", br_name); + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: socket(AF_INET,SOCK_STREAM) " + "failed: %s", __func__, strerror(errno)); + return -1; + } + + if (linux_br_del(fd, br_name) == 0) + goto done; + + arg[0] = BRCTL_DEL_BRIDGE; + arg[1] = (unsigned long) br_name; + + if (ioctl(fd, SIOCGIFBR, arg) < 0 && errno != ENXIO) { + /* No error if bridge already removed. */ + wpa_printf(MSG_ERROR, "VLAN: %s: BRCTL_DEL_BRIDGE failed for " + "%s: %s", __func__, br_name, strerror(errno)); + close(fd); + return -1; + } + +done: + close(fd); + return 0; +} + + +/* + Add a bridge with the name 'br_name'. + + returns -1 on error + returns 1 if the bridge already exists + returns 0 otherwise +*/ +static int br_addbr(const char *br_name) +{ + int fd; + unsigned long arg[4]; + struct ifreq ifr; + + wpa_printf(MSG_DEBUG, "VLAN: br_addbr(%s)", br_name); + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: socket(AF_INET,SOCK_STREAM) " + "failed: %s", __func__, strerror(errno)); + return -1; + } + + if (linux_br_add(fd, br_name) == 0) + goto done; + if (errno == EEXIST) { + /* The bridge is already added. */ + close(fd); + return 1; + } + + arg[0] = BRCTL_ADD_BRIDGE; + arg[1] = (unsigned long) br_name; + + if (ioctl(fd, SIOCGIFBR, arg) < 0) { + if (errno == EEXIST) { + /* The bridge is already added. */ + close(fd); + return 1; + } else { + wpa_printf(MSG_ERROR, "VLAN: %s: BRCTL_ADD_BRIDGE " + "failed for %s: %s", + __func__, br_name, strerror(errno)); + close(fd); + return -1; + } + } + +done: + /* Decrease forwarding delay to avoid EAPOL timeouts. */ + os_memset(&ifr, 0, sizeof(ifr)); + os_strlcpy(ifr.ifr_name, br_name, IFNAMSIZ); + arg[0] = BRCTL_SET_BRIDGE_FORWARD_DELAY; + arg[1] = 1; + arg[2] = 0; + arg[3] = 0; + ifr.ifr_data = (char *) &arg; + if (ioctl(fd, SIOCDEVPRIVATE, &ifr) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: " + "BRCTL_SET_BRIDGE_FORWARD_DELAY (1 sec) failed for " + "%s: %s", __func__, br_name, strerror(errno)); + /* Continue anyway */ + } + + close(fd); + return 0; +} + + +static int br_getnumports(const char *br_name) +{ + int fd; + int i; + int port_cnt = 0; + unsigned long arg[4]; + int ifindices[MAX_BR_PORTS]; + struct ifreq ifr; + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: socket(AF_INET,SOCK_STREAM) " + "failed: %s", __func__, strerror(errno)); + return -1; + } + + arg[0] = BRCTL_GET_PORT_LIST; + arg[1] = (unsigned long) ifindices; + arg[2] = MAX_BR_PORTS; + arg[3] = 0; + + os_memset(ifindices, 0, sizeof(ifindices)); + os_strlcpy(ifr.ifr_name, br_name, sizeof(ifr.ifr_name)); + ifr.ifr_data = (void *) arg; + + if (ioctl(fd, SIOCDEVPRIVATE, &ifr) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: BRCTL_GET_PORT_LIST " + "failed for %s: %s", + __func__, br_name, strerror(errno)); + close(fd); + return -1; + } + + for (i = 1; i < MAX_BR_PORTS; i++) { + if (ifindices[i] > 0) { + port_cnt++; + } + } + + close(fd); + return port_cnt; +} + + +static void vlan_newlink_tagged(int vlan_naming, const char *tagged_interface, + const char *br_name, int vid, + struct hostapd_data *hapd) +{ + char vlan_ifname[IFNAMSIZ]; + int clean; + int ret; + + if (vlan_naming == DYNAMIC_VLAN_NAMING_WITH_DEVICE) + ret = os_snprintf(vlan_ifname, sizeof(vlan_ifname), "%s.%d", + tagged_interface, vid); + else + ret = os_snprintf(vlan_ifname, sizeof(vlan_ifname), "vlan%d", + vid); + if (ret >= (int) sizeof(vlan_ifname)) + wpa_printf(MSG_WARNING, + "VLAN: Interface name was truncated to %s", + vlan_ifname); + + clean = 0; + ifconfig_up(tagged_interface); + if (!vlan_add(tagged_interface, vid, vlan_ifname)) + clean |= DVLAN_CLEAN_VLAN; + + if (!br_addif(br_name, vlan_ifname)) + clean |= DVLAN_CLEAN_VLAN_PORT; + + dyn_iface_get(hapd, vlan_ifname, clean); + + ifconfig_up(vlan_ifname); +} + + +static void vlan_bridge_name(char *br_name, struct hostapd_data *hapd, + struct hostapd_vlan *vlan, int vid) +{ + char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface; + int ret; + + if (vlan->bridge[0]) { + os_strlcpy(br_name, vlan->bridge, IFNAMSIZ); + ret = 0; + } else if (hapd->conf->vlan_bridge[0]) { + ret = os_snprintf(br_name, IFNAMSIZ, "%s%d", + hapd->conf->vlan_bridge, vid); + } else if (tagged_interface) { + ret = os_snprintf(br_name, IFNAMSIZ, "br%s.%d", + tagged_interface, vid); + } else { + ret = os_snprintf(br_name, IFNAMSIZ, "brvlan%d", vid); + } + if (ret >= IFNAMSIZ) + wpa_printf(MSG_WARNING, + "VLAN: Interface name was truncated to %s", + br_name); +} + + +static void vlan_get_bridge(const char *br_name, struct hostapd_data *hapd, + int vid) +{ + char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface; + int vlan_naming = hapd->conf->ssid.vlan_naming; + + dyn_iface_get(hapd, br_name, br_addbr(br_name) ? 0 : DVLAN_CLEAN_BR); + + ifconfig_up(br_name); + + if (tagged_interface) + vlan_newlink_tagged(vlan_naming, tagged_interface, br_name, + vid, hapd); +} + + +void vlan_newlink(const char *ifname, struct hostapd_data *hapd) +{ + char br_name[IFNAMSIZ]; + struct hostapd_vlan *vlan; + int untagged, *tagged, i, notempty; + + wpa_printf(MSG_DEBUG, "VLAN: vlan_newlink(%s)", ifname); + + for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) { + if (vlan->configured || + os_strcmp(ifname, vlan->ifname) != 0) + continue; + break; + } + if (!vlan) + return; + + vlan->configured = 1; + + notempty = vlan->vlan_desc.notempty; + untagged = vlan->vlan_desc.untagged; + tagged = vlan->vlan_desc.tagged; + + if (!notempty) { + /* Non-VLAN STA */ + if (hapd->conf->bridge[0] && + !br_addif(hapd->conf->bridge, ifname)) + vlan->clean |= DVLAN_CLEAN_WLAN_PORT; + } else if (untagged > 0 && untagged <= MAX_VLAN_ID) { + vlan_bridge_name(br_name, hapd, vlan, untagged); + + vlan_get_bridge(br_name, hapd, untagged); + + if (!br_addif(br_name, ifname)) + vlan->clean |= DVLAN_CLEAN_WLAN_PORT; + } + + for (i = 0; i < MAX_NUM_TAGGED_VLAN && tagged[i]; i++) { + if (tagged[i] == untagged || + tagged[i] <= 0 || tagged[i] > MAX_VLAN_ID || + (i > 0 && tagged[i] == tagged[i - 1])) + continue; + vlan_bridge_name(br_name, hapd, vlan, tagged[i]); + vlan_get_bridge(br_name, hapd, tagged[i]); + vlan_newlink_tagged(DYNAMIC_VLAN_NAMING_WITH_DEVICE, + ifname, br_name, tagged[i], hapd); + } + + ifconfig_up(ifname); +} + + +static void vlan_dellink_tagged(int vlan_naming, const char *tagged_interface, + const char *br_name, int vid, + struct hostapd_data *hapd) +{ + char vlan_ifname[IFNAMSIZ]; + int clean; + int ret; + + if (vlan_naming == DYNAMIC_VLAN_NAMING_WITH_DEVICE) + ret = os_snprintf(vlan_ifname, sizeof(vlan_ifname), "%s.%d", + tagged_interface, vid); + else + ret = os_snprintf(vlan_ifname, sizeof(vlan_ifname), "vlan%d", + vid); + if (ret >= (int) sizeof(vlan_ifname)) + wpa_printf(MSG_WARNING, + "VLAN: Interface name was truncated to %s", + vlan_ifname); + + + clean = dyn_iface_put(hapd, vlan_ifname); + + if (clean & DVLAN_CLEAN_VLAN_PORT) + br_delif(br_name, vlan_ifname); + + if (clean & DVLAN_CLEAN_VLAN) { + ifconfig_down(vlan_ifname); + vlan_rem(vlan_ifname); + } +} + + +static void vlan_put_bridge(const char *br_name, struct hostapd_data *hapd, + int vid) +{ + int clean; + char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface; + int vlan_naming = hapd->conf->ssid.vlan_naming; + + if (tagged_interface) + vlan_dellink_tagged(vlan_naming, tagged_interface, br_name, + vid, hapd); + + clean = dyn_iface_put(hapd, br_name); + if ((clean & DVLAN_CLEAN_BR) && br_getnumports(br_name) == 0) { + ifconfig_down(br_name); + br_delbr(br_name); + } +} + + +void vlan_dellink(const char *ifname, struct hostapd_data *hapd) +{ + struct hostapd_vlan *first, *prev, *vlan = hapd->conf->vlan; + + wpa_printf(MSG_DEBUG, "VLAN: vlan_dellink(%s)", ifname); + + first = prev = vlan; + + while (vlan) { + if (os_strcmp(ifname, vlan->ifname) != 0) { + prev = vlan; + vlan = vlan->next; + continue; + } + break; + } + if (!vlan) + return; + + if (vlan->configured) { + int notempty = vlan->vlan_desc.notempty; + int untagged = vlan->vlan_desc.untagged; + int *tagged = vlan->vlan_desc.tagged; + char br_name[IFNAMSIZ]; + int i; + + for (i = 0; i < MAX_NUM_TAGGED_VLAN && tagged[i]; i++) { + if (tagged[i] == untagged || + tagged[i] <= 0 || tagged[i] > MAX_VLAN_ID || + (i > 0 && tagged[i] == tagged[i - 1])) + continue; + vlan_bridge_name(br_name, hapd, vlan, tagged[i]); + vlan_dellink_tagged(DYNAMIC_VLAN_NAMING_WITH_DEVICE, + ifname, br_name, tagged[i], hapd); + vlan_put_bridge(br_name, hapd, tagged[i]); + } + + if (!notempty) { + /* Non-VLAN STA */ + if (hapd->conf->bridge[0] && + (vlan->clean & DVLAN_CLEAN_WLAN_PORT)) + br_delif(hapd->conf->bridge, ifname); + } else if (untagged > 0 && untagged <= MAX_VLAN_ID) { + vlan_bridge_name(br_name, hapd, vlan, untagged); + + if (vlan->clean & DVLAN_CLEAN_WLAN_PORT) + br_delif(br_name, vlan->ifname); + + vlan_put_bridge(br_name, hapd, untagged); + } + } + + /* + * Ensure this VLAN interface is actually removed even if + * NEWLINK message is only received later. + */ + if (if_nametoindex(vlan->ifname) && vlan_if_remove(hapd, vlan)) + wpa_printf(MSG_ERROR, + "VLAN: Could not remove VLAN iface: %s: %s", + vlan->ifname, strerror(errno)); + + if (vlan == first) + hapd->conf->vlan = vlan->next; + else + prev->next = vlan->next; + + os_free(vlan); +} + + +static void +vlan_read_ifnames(struct nlmsghdr *h, size_t len, int del, + struct hostapd_data *hapd) +{ + struct ifinfomsg *ifi; + int attrlen, nlmsg_len, rta_len; + struct rtattr *attr; + char ifname[IFNAMSIZ + 1]; + + if (len < sizeof(*ifi)) + return; + + ifi = NLMSG_DATA(h); + + nlmsg_len = NLMSG_ALIGN(sizeof(struct ifinfomsg)); + + attrlen = h->nlmsg_len - nlmsg_len; + if (attrlen < 0) + return; + + attr = (struct rtattr *) (((char *) ifi) + nlmsg_len); + + os_memset(ifname, 0, sizeof(ifname)); + rta_len = RTA_ALIGN(sizeof(struct rtattr)); + while (RTA_OK(attr, attrlen)) { + if (attr->rta_type == IFLA_IFNAME) { + int n = attr->rta_len - rta_len; + if (n < 0) + break; + + if ((size_t) n >= sizeof(ifname)) + n = sizeof(ifname) - 1; + os_memcpy(ifname, ((char *) attr) + rta_len, n); + + } + + attr = RTA_NEXT(attr, attrlen); + } + + if (!ifname[0]) + return; + if (del && if_nametoindex(ifname)) { + /* interface still exists, race condition -> + * iface has just been recreated */ + return; + } + + wpa_printf(MSG_DEBUG, + "VLAN: RTM_%sLINK: ifi_index=%d ifname=%s ifi_family=%d ifi_flags=0x%x (%s%s%s%s)", + del ? "DEL" : "NEW", + ifi->ifi_index, ifname, ifi->ifi_family, ifi->ifi_flags, + (ifi->ifi_flags & IFF_UP) ? "[UP]" : "", + (ifi->ifi_flags & IFF_RUNNING) ? "[RUNNING]" : "", + (ifi->ifi_flags & IFF_LOWER_UP) ? "[LOWER_UP]" : "", + (ifi->ifi_flags & IFF_DORMANT) ? "[DORMANT]" : ""); + + if (del) + vlan_dellink(ifname, hapd); + else + vlan_newlink(ifname, hapd); +} + + +static void vlan_event_receive(int sock, void *eloop_ctx, void *sock_ctx) +{ + char buf[8192]; + int left; + struct sockaddr_nl from; + socklen_t fromlen; + struct nlmsghdr *h; + struct hostapd_data *hapd = eloop_ctx; + + fromlen = sizeof(from); + left = recvfrom(sock, buf, sizeof(buf), MSG_DONTWAIT, + (struct sockaddr *) &from, &fromlen); + if (left < 0) { + if (errno != EINTR && errno != EAGAIN) + wpa_printf(MSG_ERROR, "VLAN: %s: recvfrom failed: %s", + __func__, strerror(errno)); + return; + } + + h = (struct nlmsghdr *) buf; + while (NLMSG_OK(h, left)) { + int len, plen; + + len = h->nlmsg_len; + plen = len - sizeof(*h); + if (len > left || plen < 0) { + wpa_printf(MSG_DEBUG, "VLAN: Malformed netlink " + "message: len=%d left=%d plen=%d", + len, left, plen); + break; + } + + switch (h->nlmsg_type) { + case RTM_NEWLINK: + vlan_read_ifnames(h, plen, 0, hapd); + break; + case RTM_DELLINK: + vlan_read_ifnames(h, plen, 1, hapd); + break; + } + + h = NLMSG_NEXT(h, left); + } + + if (left > 0) { + wpa_printf(MSG_DEBUG, "VLAN: %s: %d extra bytes in the end of " + "netlink message", __func__, left); + } +} + + +struct full_dynamic_vlan * +full_dynamic_vlan_init(struct hostapd_data *hapd) +{ + struct sockaddr_nl local; + struct full_dynamic_vlan *priv; + + priv = os_zalloc(sizeof(*priv)); + if (priv == NULL) + return NULL; + + vlan_set_name_type(hapd->conf->ssid.vlan_naming == + DYNAMIC_VLAN_NAMING_WITH_DEVICE ? + VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD : + VLAN_NAME_TYPE_PLUS_VID_NO_PAD); + + priv->s = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (priv->s < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: socket(PF_NETLINK,SOCK_RAW," + "NETLINK_ROUTE) failed: %s", + __func__, strerror(errno)); + os_free(priv); + return NULL; + } + + os_memset(&local, 0, sizeof(local)); + local.nl_family = AF_NETLINK; + local.nl_groups = RTMGRP_LINK; + if (bind(priv->s, (struct sockaddr *) &local, sizeof(local)) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: bind(netlink) failed: %s", + __func__, strerror(errno)); + close(priv->s); + os_free(priv); + return NULL; + } + + if (eloop_register_read_sock(priv->s, vlan_event_receive, hapd, NULL)) + { + close(priv->s); + os_free(priv); + return NULL; + } + + return priv; +} + + +void full_dynamic_vlan_deinit(struct full_dynamic_vlan *priv) +{ + if (priv == NULL) + return; + eloop_unregister_read_sock(priv->s); + close(priv->s); + os_free(priv); +} diff --git a/src/ap/vlan_ifconfig.c b/src/ap/vlan_ifconfig.c new file mode 100644 index 0000000..ef953a5 --- /dev/null +++ b/src/ap/vlan_ifconfig.c @@ -0,0 +1,69 @@ +/* + * hostapd / VLAN ifconfig helpers + * Copyright 2003, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * Copyright (c) 2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include +#include + +#include "utils/common.h" +#include "vlan_util.h" + + +int ifconfig_helper(const char *if_name, int up) +{ + int fd; + struct ifreq ifr; + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: socket(AF_INET,SOCK_STREAM) " + "failed: %s", __func__, strerror(errno)); + return -1; + } + + os_memset(&ifr, 0, sizeof(ifr)); + os_strlcpy(ifr.ifr_name, if_name, IFNAMSIZ); + + if (ioctl(fd, SIOCGIFFLAGS, &ifr) != 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: ioctl(SIOCGIFFLAGS) failed " + "for interface %s: %s", + __func__, if_name, strerror(errno)); + close(fd); + return -1; + } + + if (up) + ifr.ifr_flags |= IFF_UP; + else + ifr.ifr_flags &= ~IFF_UP; + + if (ioctl(fd, SIOCSIFFLAGS, &ifr) != 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: ioctl(SIOCSIFFLAGS) failed " + "for interface %s (up=%d): %s", + __func__, if_name, up, strerror(errno)); + close(fd); + return -1; + } + + close(fd); + return 0; +} + + +int ifconfig_up(const char *if_name) +{ + wpa_printf(MSG_DEBUG, "VLAN: Set interface %s up", if_name); + return ifconfig_helper(if_name, 1); +} + + +int iface_exists(const char *ifname) +{ + return if_nametoindex(ifname); +} diff --git a/src/ap/vlan_init.c b/src/ap/vlan_init.c new file mode 100644 index 0000000..53eacfb --- /dev/null +++ b/src/ap/vlan_init.c @@ -0,0 +1,267 @@ +/* + * hostapd / VLAN initialization + * Copyright 2003, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * Copyright (c) 2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "hostapd.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "wpa_auth.h" +#include "vlan_init.h" +#include "vlan_util.h" + + +static int vlan_if_add(struct hostapd_data *hapd, struct hostapd_vlan *vlan, + int existsok) +{ + int ret; +#ifdef CONFIG_WEP + int i; + + for (i = 0; i < NUM_WEP_KEYS; i++) { + if (!hapd->conf->ssid.wep.key[i]) + continue; + wpa_printf(MSG_ERROR, + "VLAN: Refusing to set up VLAN iface %s with WEP", + vlan->ifname); + return -1; + } +#endif /* CONFIG_WEP */ + + if (!iface_exists(vlan->ifname)) + ret = hostapd_vlan_if_add(hapd, vlan->ifname); + else if (!existsok) + return -1; + else + ret = 0; + + if (ret) + return ret; + + ifconfig_up(vlan->ifname); /* else wpa group will fail fatal */ + + if (hapd->wpa_auth) + ret = wpa_auth_ensure_group(hapd->wpa_auth, vlan->vlan_id); + + if (ret == 0) + return ret; + + wpa_printf(MSG_ERROR, "WPA initialization for VLAN %d failed (%d)", + vlan->vlan_id, ret); + if (wpa_auth_release_group(hapd->wpa_auth, vlan->vlan_id)) + wpa_printf(MSG_ERROR, "WPA deinit of %s failed", vlan->ifname); + + /* group state machine setup failed */ + if (hostapd_vlan_if_remove(hapd, vlan->ifname)) + wpa_printf(MSG_ERROR, "Removal of %s failed", vlan->ifname); + + return ret; +} + + +int vlan_if_remove(struct hostapd_data *hapd, struct hostapd_vlan *vlan) +{ + int ret; + + ret = wpa_auth_release_group(hapd->wpa_auth, vlan->vlan_id); + if (ret) + wpa_printf(MSG_ERROR, + "WPA deinitialization for VLAN %d failed (%d)", + vlan->vlan_id, ret); + + return hostapd_vlan_if_remove(hapd, vlan->ifname); +} + + +static int vlan_dynamic_add(struct hostapd_data *hapd, + struct hostapd_vlan *vlan) +{ + while (vlan) { + if (vlan->vlan_id != VLAN_ID_WILDCARD) { + if (vlan_if_add(hapd, vlan, 1)) { + wpa_printf(MSG_ERROR, + "VLAN: Could not add VLAN %s: %s", + vlan->ifname, strerror(errno)); + return -1; + } +#ifdef CONFIG_FULL_DYNAMIC_VLAN + vlan_newlink(vlan->ifname, hapd); +#endif /* CONFIG_FULL_DYNAMIC_VLAN */ + } + + vlan = vlan->next; + } + + return 0; +} + + +static void vlan_dynamic_remove(struct hostapd_data *hapd, + struct hostapd_vlan *vlan) +{ + struct hostapd_vlan *next; + + while (vlan) { + next = vlan->next; + +#ifdef CONFIG_FULL_DYNAMIC_VLAN + /* vlan_dellink() takes care of cleanup and interface removal */ + if (vlan->vlan_id != VLAN_ID_WILDCARD) + vlan_dellink(vlan->ifname, hapd); +#else /* CONFIG_FULL_DYNAMIC_VLAN */ + if (vlan->vlan_id != VLAN_ID_WILDCARD && + vlan_if_remove(hapd, vlan)) { + wpa_printf(MSG_ERROR, "VLAN: Could not remove VLAN " + "iface: %s: %s", + vlan->ifname, strerror(errno)); + } +#endif /* CONFIG_FULL_DYNAMIC_VLAN */ + + vlan = next; + } +} + + +int vlan_init(struct hostapd_data *hapd) +{ +#ifdef CONFIG_FULL_DYNAMIC_VLAN + hapd->full_dynamic_vlan = full_dynamic_vlan_init(hapd); +#endif /* CONFIG_FULL_DYNAMIC_VLAN */ + + if ((hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED || + hapd->conf->ssid.per_sta_vif) && + !hapd->conf->vlan) { + /* dynamic vlans enabled but no (or empty) vlan_file given */ + struct hostapd_vlan *vlan; + int ret; + + vlan = os_zalloc(sizeof(*vlan)); + if (vlan == NULL) { + wpa_printf(MSG_ERROR, "Out of memory while assigning " + "VLAN interfaces"); + return -1; + } + + vlan->vlan_id = VLAN_ID_WILDCARD; + ret = os_snprintf(vlan->ifname, sizeof(vlan->ifname), "%s.#", + hapd->conf->iface); + if (ret >= (int) sizeof(vlan->ifname)) { + wpa_printf(MSG_WARNING, + "VLAN: Interface name was truncated to %s", + vlan->ifname); + } else if (ret < 0) { + os_free(vlan); + return ret; + } + vlan->next = hapd->conf->vlan; + hapd->conf->vlan = vlan; + } + + if (vlan_dynamic_add(hapd, hapd->conf->vlan)) + return -1; + + return 0; +} + + +void vlan_deinit(struct hostapd_data *hapd) +{ + vlan_dynamic_remove(hapd, hapd->conf->vlan); + +#ifdef CONFIG_FULL_DYNAMIC_VLAN + full_dynamic_vlan_deinit(hapd->full_dynamic_vlan); + hapd->full_dynamic_vlan = NULL; +#endif /* CONFIG_FULL_DYNAMIC_VLAN */ +} + + +struct hostapd_vlan * vlan_add_dynamic(struct hostapd_data *hapd, + struct hostapd_vlan *vlan, + int vlan_id, + struct vlan_description *vlan_desc) +{ + struct hostapd_vlan *n; + char ifname[IFNAMSIZ + 1], *pos; + int ret; + + if (vlan == NULL || vlan->vlan_id != VLAN_ID_WILDCARD) + return NULL; + + wpa_printf(MSG_DEBUG, "VLAN: %s(vlan_id=%d ifname=%s)", + __func__, vlan_id, vlan->ifname); + os_strlcpy(ifname, vlan->ifname, sizeof(ifname)); + pos = os_strchr(ifname, '#'); + if (pos == NULL) + return NULL; + *pos++ = '\0'; + + n = os_zalloc(sizeof(*n)); + if (n == NULL) + return NULL; + + n->vlan_id = vlan_id; + if (vlan_desc) + n->vlan_desc = *vlan_desc; + n->dynamic_vlan = 1; + + ret = os_snprintf(n->ifname, sizeof(n->ifname), "%s%d%s", + ifname, vlan_id, pos); + if (os_snprintf_error(sizeof(n->ifname), ret)) { + os_free(n); + return NULL; + } + os_strlcpy(n->bridge, vlan->bridge, sizeof(n->bridge)); + + n->next = hapd->conf->vlan; + hapd->conf->vlan = n; + + /* hapd->conf->vlan needs this new VLAN here for WPA setup */ + if (vlan_if_add(hapd, n, 0)) { + hapd->conf->vlan = n->next; + os_free(n); + n = NULL; + } + + return n; +} + + +int vlan_remove_dynamic(struct hostapd_data *hapd, int vlan_id) +{ + struct hostapd_vlan *vlan; + + if (vlan_id <= 0) + return 1; + + wpa_printf(MSG_DEBUG, "VLAN: %s(ifname=%s vlan_id=%d)", + __func__, hapd->conf->iface, vlan_id); + + vlan = hapd->conf->vlan; + while (vlan) { + if (vlan->vlan_id == vlan_id && vlan->dynamic_vlan > 0) { + vlan->dynamic_vlan--; + break; + } + vlan = vlan->next; + } + + if (vlan == NULL) + return 1; + + if (vlan->dynamic_vlan == 0) { + vlan_if_remove(hapd, vlan); +#ifdef CONFIG_FULL_DYNAMIC_VLAN + vlan_dellink(vlan->ifname, hapd); +#endif /* CONFIG_FULL_DYNAMIC_VLAN */ + } + + return 0; +} diff --git a/src/ap/vlan_init.h b/src/ap/vlan_init.h new file mode 100644 index 0000000..d17c82c --- /dev/null +++ b/src/ap/vlan_init.h @@ -0,0 +1,44 @@ +/* + * hostapd / VLAN initialization + * Copyright 2003, Instant802 Networks, Inc. + * Copyright 2005, Devicescape Software, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef VLAN_INIT_H +#define VLAN_INIT_H + +#ifndef CONFIG_NO_VLAN +int vlan_init(struct hostapd_data *hapd); +void vlan_deinit(struct hostapd_data *hapd); +struct hostapd_vlan * vlan_add_dynamic(struct hostapd_data *hapd, + struct hostapd_vlan *vlan, + int vlan_id, + struct vlan_description *vlan_desc); +int vlan_remove_dynamic(struct hostapd_data *hapd, int vlan_id); +#else /* CONFIG_NO_VLAN */ +static inline int vlan_init(struct hostapd_data *hapd) +{ + return 0; +} + +static inline void vlan_deinit(struct hostapd_data *hapd) +{ +} + +static inline struct hostapd_vlan * +vlan_add_dynamic(struct hostapd_data *hapd, struct hostapd_vlan *vlan, + int vlan_id, struct vlan_description *vlan_desc) +{ + return NULL; +} + +static inline int vlan_remove_dynamic(struct hostapd_data *hapd, int vlan_id) +{ + return -1; +} +#endif /* CONFIG_NO_VLAN */ + +#endif /* VLAN_INIT_H */ diff --git a/src/ap/vlan_ioctl.c b/src/ap/vlan_ioctl.c new file mode 100644 index 0000000..987b612 --- /dev/null +++ b/src/ap/vlan_ioctl.c @@ -0,0 +1,155 @@ +/* + * hostapd / VLAN ioctl API + * Copyright 2003, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * Copyright (c) 2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include + +#include "utils/common.h" +#include "common/linux_vlan.h" +#include "vlan_util.h" + + +int vlan_rem(const char *if_name) +{ + int fd; + struct vlan_ioctl_args if_request; + + wpa_printf(MSG_DEBUG, "VLAN: vlan_rem(%s)", if_name); + if ((os_strlen(if_name) + 1) > sizeof(if_request.device1)) { + wpa_printf(MSG_ERROR, "VLAN: Interface name too long: '%s'", + if_name); + return -1; + } + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: socket(AF_INET,SOCK_STREAM) " + "failed: %s", __func__, strerror(errno)); + return -1; + } + + os_memset(&if_request, 0, sizeof(if_request)); + + os_strlcpy(if_request.device1, if_name, sizeof(if_request.device1)); + if_request.cmd = DEL_VLAN_CMD; + + if (ioctl(fd, SIOCSIFVLAN, &if_request) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: DEL_VLAN_CMD failed for %s: " + "%s", __func__, if_name, strerror(errno)); + close(fd); + return -1; + } + + close(fd); + return 0; +} + + +/* + Add a vlan interface with VLAN ID 'vid' and tagged interface + 'if_name'. + + returns -1 on error + returns 1 if the interface already exists + returns 0 otherwise +*/ +int vlan_add(const char *if_name, int vid, const char *vlan_if_name) +{ + int fd; + struct vlan_ioctl_args if_request; + + wpa_printf(MSG_DEBUG, "VLAN: vlan_add(if_name=%s, vid=%d)", + if_name, vid); + ifconfig_up(if_name); + + if ((os_strlen(if_name) + 1) > sizeof(if_request.device1)) { + wpa_printf(MSG_ERROR, "VLAN: Interface name too long: '%s'", + if_name); + return -1; + } + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + wpa_printf(MSG_ERROR, "VLAN: %s: socket(AF_INET,SOCK_STREAM) " + "failed: %s", __func__, strerror(errno)); + return -1; + } + + os_memset(&if_request, 0, sizeof(if_request)); + + /* Determine if a suitable vlan device already exists. */ + + os_snprintf(if_request.device1, sizeof(if_request.device1), "vlan%d", + vid); + + if_request.cmd = GET_VLAN_VID_CMD; + + if (ioctl(fd, SIOCSIFVLAN, &if_request) == 0 && + if_request.u.VID == vid) { + if_request.cmd = GET_VLAN_REALDEV_NAME_CMD; + + if (ioctl(fd, SIOCSIFVLAN, &if_request) == 0 && + os_strncmp(if_request.u.device2, if_name, + sizeof(if_request.u.device2)) == 0) { + close(fd); + wpa_printf(MSG_DEBUG, + "VLAN: vlan_add: if_name %s exists already", + if_request.device1); + return 1; + } + } + + /* A suitable vlan device does not already exist, add one. */ + + os_memset(&if_request, 0, sizeof(if_request)); + os_strlcpy(if_request.device1, if_name, sizeof(if_request.device1)); + if_request.u.VID = vid; + if_request.cmd = ADD_VLAN_CMD; + + if (ioctl(fd, SIOCSIFVLAN, &if_request) < 0) { + wpa_printf(MSG_ERROR, + "VLAN: %s: ADD_VLAN_CMD failed for %s: %s", + __func__, if_request.device1, strerror(errno)); + close(fd); + return -1; + } + + close(fd); + return 0; +} + + +int vlan_set_name_type(unsigned int name_type) +{ + int fd; + struct vlan_ioctl_args if_request; + + wpa_printf(MSG_DEBUG, "VLAN: vlan_set_name_type(name_type=%u)", + name_type); + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + wpa_printf(MSG_ERROR, + "VLAN: %s: socket(AF_INET,SOCK_STREAM) failed: %s", + __func__, strerror(errno)); + return -1; + } + + os_memset(&if_request, 0, sizeof(if_request)); + + if_request.u.name_type = name_type; + if_request.cmd = SET_VLAN_NAME_TYPE_CMD; + if (ioctl(fd, SIOCSIFVLAN, &if_request) < 0) { + wpa_printf(MSG_ERROR, + "VLAN: %s: SET_VLAN_NAME_TYPE_CMD name_type=%u failed: %s", + __func__, name_type, strerror(errno)); + close(fd); + return -1; + } + + close(fd); + return 0; +} diff --git a/src/ap/vlan_util.c b/src/ap/vlan_util.c new file mode 100644 index 0000000..56d1d3d --- /dev/null +++ b/src/ap/vlan_util.c @@ -0,0 +1,174 @@ +/* + * hostapd / VLAN netlink api + * Copyright (c) 2012, Michael Braun + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include +#include + +#include "utils/common.h" +#include "vlan_util.h" + +/* + * Add a vlan interface with name 'vlan_if_name', VLAN ID 'vid' and + * tagged interface 'if_name'. + * + * returns -1 on error + * returns 1 if the interface already exists + * returns 0 otherwise +*/ +int vlan_add(const char *if_name, int vid, const char *vlan_if_name) +{ + int err, ret = -1; + struct nl_sock *handle = NULL; + struct rtnl_link *rlink = NULL; + int if_idx = 0; + + wpa_printf(MSG_DEBUG, "VLAN: vlan_add(if_name=%s, vid=%d, " + "vlan_if_name=%s)", if_name, vid, vlan_if_name); + + if ((os_strlen(if_name) + 1) > IFNAMSIZ) { + wpa_printf(MSG_ERROR, "VLAN: Interface name too long: '%s'", + if_name); + return -1; + } + + if ((os_strlen(vlan_if_name) + 1) > IFNAMSIZ) { + wpa_printf(MSG_ERROR, "VLAN: Interface name too long: '%s'", + vlan_if_name); + return -1; + } + + handle = nl_socket_alloc(); + if (!handle) { + wpa_printf(MSG_ERROR, "VLAN: failed to open netlink socket"); + goto vlan_add_error; + } + + err = nl_connect(handle, NETLINK_ROUTE); + if (err < 0) { + wpa_printf(MSG_ERROR, "VLAN: failed to connect to netlink: %s", + nl_geterror(err)); + goto vlan_add_error; + } + + err = rtnl_link_get_kernel(handle, 0, if_name, &rlink); + if (err < 0) { + /* link does not exist */ + wpa_printf(MSG_ERROR, "VLAN: interface %s does not exist", + if_name); + goto vlan_add_error; + } + if_idx = rtnl_link_get_ifindex(rlink); + rtnl_link_put(rlink); + rlink = NULL; + + err = rtnl_link_get_kernel(handle, 0, vlan_if_name, &rlink); + if (err >= 0) { + /* link does exist */ + rtnl_link_put(rlink); + rlink = NULL; + wpa_printf(MSG_ERROR, "VLAN: interface %s already exists", + vlan_if_name); + ret = 1; + goto vlan_add_error; + } + + rlink = rtnl_link_alloc(); + if (!rlink) { + wpa_printf(MSG_ERROR, "VLAN: failed to allocate new link"); + goto vlan_add_error; + } + + err = rtnl_link_set_type(rlink, "vlan"); + if (err < 0) { + wpa_printf(MSG_ERROR, "VLAN: failed to set link type: %s", + nl_geterror(err)); + goto vlan_add_error; + } + + rtnl_link_set_link(rlink, if_idx); + rtnl_link_set_name(rlink, vlan_if_name); + + err = rtnl_link_vlan_set_id(rlink, vid); + if (err < 0) { + wpa_printf(MSG_ERROR, "VLAN: failed to set link vlan id: %s", + nl_geterror(err)); + goto vlan_add_error; + } + + err = rtnl_link_add(handle, rlink, NLM_F_CREATE); + if (err < 0) { + wpa_printf(MSG_ERROR, "VLAN: failed to create link %s for " + "vlan %d on %s (%d): %s", + vlan_if_name, vid, if_name, if_idx, + nl_geterror(err)); + goto vlan_add_error; + } + + ret = 0; + +vlan_add_error: + if (rlink) + rtnl_link_put(rlink); + if (handle) + nl_socket_free(handle); + return ret; +} + + +int vlan_rem(const char *if_name) +{ + int err, ret = -1; + struct nl_sock *handle = NULL; + struct rtnl_link *rlink = NULL; + + wpa_printf(MSG_DEBUG, "VLAN: vlan_rem(if_name=%s)", if_name); + + handle = nl_socket_alloc(); + if (!handle) { + wpa_printf(MSG_ERROR, "VLAN: failed to open netlink socket"); + goto vlan_rem_error; + } + + err = nl_connect(handle, NETLINK_ROUTE); + if (err < 0) { + wpa_printf(MSG_ERROR, "VLAN: failed to connect to netlink: %s", + nl_geterror(err)); + goto vlan_rem_error; + } + + err = rtnl_link_get_kernel(handle, 0, if_name, &rlink); + if (err < 0) { + /* link does not exist */ + wpa_printf(MSG_ERROR, "VLAN: interface %s does not exists", + if_name); + goto vlan_rem_error; + } + + err = rtnl_link_delete(handle, rlink); + if (err < 0) { + wpa_printf(MSG_ERROR, "VLAN: failed to remove link %s: %s", + if_name, nl_geterror(err)); + goto vlan_rem_error; + } + + ret = 0; + +vlan_rem_error: + if (rlink) + rtnl_link_put(rlink); + if (handle) + nl_socket_free(handle); + return ret; +} + + +int vlan_set_name_type(unsigned int name_type) +{ + return 0; +} diff --git a/src/ap/vlan_util.h b/src/ap/vlan_util.h new file mode 100644 index 0000000..2446859 --- /dev/null +++ b/src/ap/vlan_util.h @@ -0,0 +1,31 @@ +/* + * hostapd / VLAN netlink/ioctl api + * Copyright (c) 2012, Michael Braun + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef VLAN_UTIL_H +#define VLAN_UTIL_H + +struct hostapd_data; +struct hostapd_vlan; +struct full_dynamic_vlan; + +int vlan_add(const char *if_name, int vid, const char *vlan_if_name); +int vlan_rem(const char *if_name); +int vlan_set_name_type(unsigned int name_type); + +int ifconfig_helper(const char *if_name, int up); +int ifconfig_up(const char *if_name); +int iface_exists(const char *ifname); +int vlan_if_remove(struct hostapd_data *hapd, struct hostapd_vlan *vlan); + +struct full_dynamic_vlan * +full_dynamic_vlan_init(struct hostapd_data *hapd); +void full_dynamic_vlan_deinit(struct full_dynamic_vlan *priv); +void vlan_newlink(const char *ifname, struct hostapd_data *hapd); +void vlan_dellink(const char *ifname, struct hostapd_data *hapd); + +#endif /* VLAN_UTIL_H */ diff --git a/src/ap/wmm.c b/src/ap/wmm.c new file mode 100644 index 0000000..dad768e --- /dev/null +++ b/src/ap/wmm.c @@ -0,0 +1,385 @@ +/* + * hostapd / WMM (Wi-Fi Multimedia) + * Copyright 2002-2003, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * Copyright (c) 2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "hostapd.h" +#include "ieee802_11.h" +#include "sta_info.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "wmm.h" + + +static inline u8 wmm_aci_aifsn(int aifsn, int acm, int aci) +{ + u8 ret; + ret = (aifsn << WMM_AC_AIFNS_SHIFT) & WMM_AC_AIFSN_MASK; + if (acm) + ret |= WMM_AC_ACM; + ret |= (aci << WMM_AC_ACI_SHIFT) & WMM_AC_ACI_MASK; + return ret; +} + + +static inline u8 wmm_ecw(int ecwmin, int ecwmax) +{ + return ((ecwmin << WMM_AC_ECWMIN_SHIFT) & WMM_AC_ECWMIN_MASK) | + ((ecwmax << WMM_AC_ECWMAX_SHIFT) & WMM_AC_ECWMAX_MASK); +} + + +static void +wmm_set_regulatory_limit(const struct hostapd_wmm_ac_params *wmm_conf, + struct hostapd_wmm_ac_params *wmm, + const struct hostapd_wmm_rule *wmm_reg) +{ + int ac; + + for (ac = 0; ac < WMM_AC_NUM; ac++) { + wmm[ac].cwmin = MAX(wmm_conf[ac].cwmin, wmm_reg[ac].min_cwmin); + wmm[ac].cwmax = MAX(wmm_conf[ac].cwmax, wmm_reg[ac].min_cwmax); + wmm[ac].aifs = MAX(wmm_conf[ac].aifs, wmm_reg[ac].min_aifs); + wmm[ac].txop_limit = + MIN(wmm_conf[ac].txop_limit, wmm_reg[ac].max_txop); + wmm[ac].admission_control_mandatory = + wmm_conf[ac].admission_control_mandatory; + } +} + + +/* + * Calculate WMM regulatory limit if any. + */ +static void wmm_calc_regulatory_limit(struct hostapd_data *hapd, + struct hostapd_wmm_ac_params *acp) +{ + struct hostapd_hw_modes *mode = hapd->iface->current_mode; + int c; + + os_memcpy(acp, hapd->iconf->wmm_ac_params, + sizeof(hapd->iconf->wmm_ac_params)); + + for (c = 0; mode && c < mode->num_channels; c++) { + struct hostapd_channel_data *chan = &mode->channels[c]; + + if (chan->freq != hapd->iface->freq) + continue; + + if (chan->wmm_rules_valid) + wmm_set_regulatory_limit(hapd->iconf->wmm_ac_params, + acp, chan->wmm_rules); + break; + } + + /* + * Check if we need to update set count. Since both were initialized to + * zero we can compare the whole array in one shot. + */ + if (os_memcmp(acp, hapd->iface->prev_wmm, + sizeof(hapd->iconf->wmm_ac_params)) != 0) { + os_memcpy(hapd->iface->prev_wmm, acp, + sizeof(hapd->iconf->wmm_ac_params)); + hapd->parameter_set_count++; + } +} + + +/* + * Add WMM Parameter Element to Beacon, Probe Response, and (Re)Association + * Response frames. + */ +u8 * hostapd_eid_wmm(struct hostapd_data *hapd, u8 *eid) +{ + u8 *pos = eid; + struct wmm_parameter_element *wmm = + (struct wmm_parameter_element *) (pos + 2); + struct hostapd_wmm_ac_params wmmp[WMM_AC_NUM]; + int e; + + os_memset(wmmp, 0, sizeof(wmmp)); + + if (!hapd->conf->wmm_enabled) + return eid; + wmm_calc_regulatory_limit(hapd, wmmp); + eid[0] = WLAN_EID_VENDOR_SPECIFIC; + wmm->oui[0] = 0x00; + wmm->oui[1] = 0x50; + wmm->oui[2] = 0xf2; + wmm->oui_type = WMM_OUI_TYPE; + wmm->oui_subtype = WMM_OUI_SUBTYPE_PARAMETER_ELEMENT; + wmm->version = WMM_VERSION; + wmm->qos_info = hapd->parameter_set_count & 0xf; + + if (hapd->conf->wmm_uapsd && + (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_UAPSD)) + wmm->qos_info |= 0x80; + + wmm->reserved = 0; + + /* fill in a parameter set record for each AC */ + for (e = 0; e < 4; e++) { + struct wmm_ac_parameter *ac = &wmm->ac[e]; + struct hostapd_wmm_ac_params *acp = &wmmp[e]; + + ac->aci_aifsn = wmm_aci_aifsn(acp->aifs, + acp->admission_control_mandatory, + e); + ac->cw = wmm_ecw(acp->cwmin, acp->cwmax); + ac->txop_limit = host_to_le16(acp->txop_limit); + } + + pos = (u8 *) (wmm + 1); + eid[1] = pos - eid - 2; /* element length */ + + return pos; +} + + +/* + * This function is called when a station sends an association request with + * WMM info element. The function returns 1 on success or 0 on any error in WMM + * element. eid does not include Element ID and Length octets. + */ +int hostapd_eid_wmm_valid(struct hostapd_data *hapd, const u8 *eid, size_t len) +{ + struct wmm_information_element *wmm; + + wpa_hexdump(MSG_MSGDUMP, "WMM IE", eid, len); + + if (len < sizeof(struct wmm_information_element)) { + wpa_printf(MSG_DEBUG, "Too short WMM IE (len=%lu)", + (unsigned long) len); + return 0; + } + + wmm = (struct wmm_information_element *) eid; + wpa_printf(MSG_DEBUG, "Validating WMM IE: OUI %02x:%02x:%02x " + "OUI type %d OUI sub-type %d version %d QoS info 0x%x", + wmm->oui[0], wmm->oui[1], wmm->oui[2], wmm->oui_type, + wmm->oui_subtype, wmm->version, wmm->qos_info); + if (wmm->oui_subtype != WMM_OUI_SUBTYPE_INFORMATION_ELEMENT || + wmm->version != WMM_VERSION) { + wpa_printf(MSG_DEBUG, "Unsupported WMM IE Subtype/Version"); + return 0; + } + + return 1; +} + + +static void wmm_send_action(struct hostapd_data *hapd, const u8 *addr, + const struct wmm_tspec_element *tspec, + u8 action_code, u8 dialogue_token, u8 status_code) +{ + u8 buf[256]; + struct ieee80211_mgmt *m = (struct ieee80211_mgmt *) buf; + struct wmm_tspec_element *t = (struct wmm_tspec_element *) + m->u.action.u.wmm_action.variable; + int len; + + hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "action response - reason %d", status_code); + os_memset(buf, 0, sizeof(buf)); + m->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memcpy(m->da, addr, ETH_ALEN); + os_memcpy(m->sa, hapd->own_addr, ETH_ALEN); + os_memcpy(m->bssid, hapd->own_addr, ETH_ALEN); + m->u.action.category = WLAN_ACTION_WMM; + m->u.action.u.wmm_action.action_code = action_code; + m->u.action.u.wmm_action.dialog_token = dialogue_token; + m->u.action.u.wmm_action.status_code = status_code; + os_memcpy(t, tspec, sizeof(struct wmm_tspec_element)); + len = ((u8 *) (t + 1)) - buf; + + if (hostapd_drv_send_mlme(hapd, m, len, 0, NULL, 0, 0) < 0) + wpa_printf(MSG_INFO, "wmm_send_action: send failed"); +} + + +int wmm_process_tspec(struct wmm_tspec_element *tspec) +{ + u64 medium_time; + unsigned int pps, duration; + unsigned int up, psb, dir, tid; + u16 val, surplus; + + up = (tspec->ts_info[1] >> 3) & 0x07; + psb = (tspec->ts_info[1] >> 2) & 0x01; + dir = (tspec->ts_info[0] >> 5) & 0x03; + tid = (tspec->ts_info[0] >> 1) & 0x0f; + wpa_printf(MSG_DEBUG, "WMM: TS Info: UP=%d PSB=%d Direction=%d TID=%d", + up, psb, dir, tid); + val = le_to_host16(tspec->nominal_msdu_size); + wpa_printf(MSG_DEBUG, "WMM: Nominal MSDU Size: %d%s", + val & 0x7fff, val & 0x8000 ? " (fixed)" : ""); + wpa_printf(MSG_DEBUG, "WMM: Mean Data Rate: %u bps", + le_to_host32(tspec->mean_data_rate)); + wpa_printf(MSG_DEBUG, "WMM: Minimum PHY Rate: %u bps", + le_to_host32(tspec->minimum_phy_rate)); + val = le_to_host16(tspec->surplus_bandwidth_allowance); + wpa_printf(MSG_DEBUG, "WMM: Surplus Bandwidth Allowance: %u.%04u", + val >> 13, 10000 * (val & 0x1fff) / 0x2000); + + val = le_to_host16(tspec->nominal_msdu_size); + if (val == 0) { + wpa_printf(MSG_DEBUG, "WMM: Invalid Nominal MSDU Size (0)"); + return WMM_ADDTS_STATUS_INVALID_PARAMETERS; + } + /* pps = Ceiling((Mean Data Rate / 8) / Nominal MSDU Size) */ + pps = ((le_to_host32(tspec->mean_data_rate) / 8) + val - 1) / val; + wpa_printf(MSG_DEBUG, "WMM: Packets-per-second estimate for TSPEC: %d", + pps); + + if (le_to_host32(tspec->minimum_phy_rate) < 1000000) { + wpa_printf(MSG_DEBUG, "WMM: Too small Minimum PHY Rate"); + return WMM_ADDTS_STATUS_INVALID_PARAMETERS; + } + + duration = (le_to_host16(tspec->nominal_msdu_size) & 0x7fff) * 8 / + (le_to_host32(tspec->minimum_phy_rate) / 1000000) + + 50 /* FIX: proper SIFS + ACK duration */; + + /* unsigned binary number with an implicit binary point after the + * leftmost 3 bits, i.e., 0x2000 = 1.0 */ + surplus = le_to_host16(tspec->surplus_bandwidth_allowance); + if (surplus <= 0x2000) { + wpa_printf(MSG_DEBUG, "WMM: Surplus Bandwidth Allowance not " + "greater than unity"); + return WMM_ADDTS_STATUS_INVALID_PARAMETERS; + } + + medium_time = (u64) surplus * pps * duration / 0x2000; + wpa_printf(MSG_DEBUG, "WMM: Estimated medium time: %lu", + (unsigned long) medium_time); + + /* + * TODO: store list of granted (and still active) TSPECs and check + * whether there is available medium time for this request. For now, + * just refuse requests that would by themselves take very large + * portion of the available bandwidth. + */ + if (medium_time > 750000) { + wpa_printf(MSG_DEBUG, "WMM: Refuse TSPEC request for over " + "75%% of available bandwidth"); + return WMM_ADDTS_STATUS_REFUSED; + } + + /* Convert to 32 microseconds per second unit */ + tspec->medium_time = host_to_le16(medium_time / 32); + + return WMM_ADDTS_STATUS_ADMISSION_ACCEPTED; +} + + +static void wmm_addts_req(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, + const struct wmm_tspec_element *tspec, size_t len) +{ + const u8 *end = ((const u8 *) mgmt) + len; + int res; + struct wmm_tspec_element tspec_resp; + + if ((const u8 *) (tspec + 1) > end) { + wpa_printf(MSG_DEBUG, "WMM: TSPEC overflow in ADDTS Request"); + return; + } + + wpa_printf(MSG_DEBUG, "WMM: ADDTS Request (Dialog Token %d) for TSPEC " + "from " MACSTR, + mgmt->u.action.u.wmm_action.dialog_token, + MAC2STR(mgmt->sa)); + + os_memcpy(&tspec_resp, tspec, sizeof(struct wmm_tspec_element)); + res = wmm_process_tspec(&tspec_resp); + wpa_printf(MSG_DEBUG, "WMM: ADDTS processing result: %d", res); + + wmm_send_action(hapd, mgmt->sa, &tspec_resp, WMM_ACTION_CODE_ADDTS_RESP, + mgmt->u.action.u.wmm_action.dialog_token, res); +} + + +void hostapd_wmm_action(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len) +{ + int action_code; + int left = len - IEEE80211_HDRLEN - 4; + const u8 *pos = ((const u8 *) mgmt) + IEEE80211_HDRLEN + 4; + struct ieee802_11_elems elems; + struct sta_info *sta = ap_get_sta(hapd, mgmt->sa); + + /* check that the request comes from a valid station */ + if (!sta || + (sta->flags & (WLAN_STA_ASSOC | WLAN_STA_WMM)) != + (WLAN_STA_ASSOC | WLAN_STA_WMM)) { + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "wmm action received is not from associated wmm" + " station"); + /* TODO: respond with action frame refused status code */ + return; + } + + if (left < 0) + return; /* not a valid WMM Action frame */ + + /* extract the tspec info element */ + if (ieee802_11_parse_elems(pos, left, &elems, 1) == ParseFailed) { + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "hostapd_wmm_action - could not parse wmm " + "action"); + /* TODO: respond with action frame invalid parameters status + * code */ + return; + } + + if (!elems.wmm_tspec || + elems.wmm_tspec_len != (sizeof(struct wmm_tspec_element) - 2)) { + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "hostapd_wmm_action - missing or wrong length " + "tspec"); + /* TODO: respond with action frame invalid parameters status + * code */ + return; + } + + /* TODO: check the request is for an AC with ACM set, if not, refuse + * request */ + + action_code = mgmt->u.action.u.wmm_action.action_code; + switch (action_code) { + case WMM_ACTION_CODE_ADDTS_REQ: + wmm_addts_req(hapd, mgmt, (struct wmm_tspec_element *) + (elems.wmm_tspec - 2), len); + return; +#if 0 + /* TODO: needed for client implementation */ + case WMM_ACTION_CODE_ADDTS_RESP: + wmm_setup_request(hapd, mgmt, len); + return; + /* TODO: handle station teardown requests */ + case WMM_ACTION_CODE_DELTS: + wmm_teardown(hapd, mgmt, len); + return; +#endif + } + + hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "hostapd_wmm_action - unknown action code %d", + action_code); +} diff --git a/src/ap/wmm.h b/src/ap/wmm.h new file mode 100644 index 0000000..b70b863 --- /dev/null +++ b/src/ap/wmm.h @@ -0,0 +1,23 @@ +/* + * hostapd / WMM (Wi-Fi Multimedia) + * Copyright 2002-2003, Instant802 Networks, Inc. + * Copyright 2005-2006, Devicescape Software, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WME_H +#define WME_H + +struct ieee80211_mgmt; +struct wmm_tspec_element; + +u8 * hostapd_eid_wmm(struct hostapd_data *hapd, u8 *eid); +int hostapd_eid_wmm_valid(struct hostapd_data *hapd, const u8 *eid, + size_t len); +void hostapd_wmm_action(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len); +int wmm_process_tspec(struct wmm_tspec_element *tspec); + +#endif /* WME_H */ diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c new file mode 100644 index 0000000..af8ccca --- /dev/null +++ b/src/ap/wnm_ap.c @@ -0,0 +1,1095 @@ +/* + * hostapd - WNM + * Copyright (c) 2011-2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/ieee802_11_defs.h" +#include "common/wpa_ctrl.h" +#include "common/ocv.h" +#include "ap/hostapd.h" +#include "ap/sta_info.h" +#include "ap/ap_config.h" +#include "ap/ap_drv_ops.h" +#include "ap/wpa_auth.h" +#include "mbo_ap.h" +#include "wnm_ap.h" + +#define MAX_TFS_IE_LEN 1024 + + +/* get the TFS IE from driver */ +static int ieee80211_11_get_tfs_ie(struct hostapd_data *hapd, const u8 *addr, + u8 *buf, u16 *buf_len, enum wnm_oper oper) +{ + wpa_printf(MSG_DEBUG, "%s: TFS get operation %d", __func__, oper); + + return hostapd_drv_wnm_oper(hapd, oper, addr, buf, buf_len); +} + + +/* set the TFS IE to driver */ +static int ieee80211_11_set_tfs_ie(struct hostapd_data *hapd, const u8 *addr, + u8 *buf, u16 *buf_len, enum wnm_oper oper) +{ + wpa_printf(MSG_DEBUG, "%s: TFS set operation %d", __func__, oper); + + return hostapd_drv_wnm_oper(hapd, oper, addr, buf, buf_len); +} + + +static const u8 * wnm_ap_get_own_addr(struct hostapd_data *hapd, + struct sta_info *sta) +{ + const u8 *own_addr = hapd->own_addr; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap && (!sta || ap_sta_is_mld(hapd, sta))) + own_addr = hapd->mld->mld_addr; +#endif /* CONFIG_IEEE80211BE */ + + return own_addr; +} + + +/* MLME-SLEEPMODE.response */ +static int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd, + const u8 *addr, u8 dialog_token, + u8 action_type, u16 intval) +{ + struct ieee80211_mgmt *mgmt; + int res; + size_t len; + size_t gtk_elem_len = 0; + size_t igtk_elem_len = 0; + size_t bigtk_elem_len = 0; + struct wnm_sleep_element wnmsleep_ie; + u8 *wnmtfs_ie, *oci_ie; + u8 wnmsleep_ie_len, oci_ie_len; + u16 wnmtfs_ie_len; + u8 *pos; + struct sta_info *sta; + enum wnm_oper tfs_oper = action_type == WNM_SLEEP_MODE_ENTER ? + WNM_SLEEP_TFS_RESP_IE_ADD : WNM_SLEEP_TFS_RESP_IE_NONE; + const u8 *own_addr; + + sta = ap_get_sta(hapd, addr); + if (sta == NULL) { + wpa_printf(MSG_DEBUG, "%s: station not found", __func__); + return -EINVAL; + } + + /* WNM-Sleep Mode IE */ + os_memset(&wnmsleep_ie, 0, sizeof(struct wnm_sleep_element)); + wnmsleep_ie_len = sizeof(struct wnm_sleep_element); + wnmsleep_ie.eid = WLAN_EID_WNMSLEEP; + wnmsleep_ie.len = wnmsleep_ie_len - 2; + wnmsleep_ie.action_type = action_type; + wnmsleep_ie.status = WNM_STATUS_SLEEP_ACCEPT; + wnmsleep_ie.intval = host_to_le16(intval); + + /* TFS IE(s) */ + wnmtfs_ie = os_zalloc(MAX_TFS_IE_LEN); + if (wnmtfs_ie == NULL) + return -1; + if (ieee80211_11_get_tfs_ie(hapd, addr, wnmtfs_ie, &wnmtfs_ie_len, + tfs_oper)) { + wnmtfs_ie_len = 0; + os_free(wnmtfs_ie); + wnmtfs_ie = NULL; + } + + oci_ie = NULL; + oci_ie_len = 0; +#ifdef CONFIG_OCV + if (action_type == WNM_SLEEP_MODE_EXIT && + wpa_auth_uses_ocv(sta->wpa_sm)) { + struct wpa_channel_info ci; + + if (hostapd_drv_channel_info(hapd, &ci) != 0) { + wpa_printf(MSG_WARNING, + "Failed to get channel info for OCI element in WNM-Sleep Mode frame"); + os_free(wnmtfs_ie); + return -1; + } +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->conf->oci_freq_override_wnm_sleep) { + wpa_printf(MSG_INFO, + "TEST: Override OCI frequency %d -> %u MHz", + ci.frequency, + hapd->conf->oci_freq_override_wnm_sleep); + ci.frequency = hapd->conf->oci_freq_override_wnm_sleep; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + oci_ie_len = OCV_OCI_EXTENDED_LEN; + oci_ie = os_zalloc(oci_ie_len); + if (!oci_ie) { + wpa_printf(MSG_WARNING, + "Failed to allocate buffer for OCI element in WNM-Sleep Mode frame"); + os_free(wnmtfs_ie); + return -1; + } + + if (ocv_insert_extended_oci(&ci, oci_ie) < 0) { + os_free(wnmtfs_ie); + os_free(oci_ie); + return -1; + } + } +#endif /* CONFIG_OCV */ + +#define MAX_GTK_SUBELEM_LEN 45 +#define MAX_IGTK_SUBELEM_LEN 26 +#define MAX_BIGTK_SUBELEM_LEN 26 + mgmt = os_zalloc(sizeof(*mgmt) + wnmsleep_ie_len + + MAX_GTK_SUBELEM_LEN + MAX_IGTK_SUBELEM_LEN + + MAX_BIGTK_SUBELEM_LEN + + oci_ie_len); + if (mgmt == NULL) { + wpa_printf(MSG_DEBUG, "MLME: Failed to allocate buffer for " + "WNM-Sleep Response action frame"); + res = -1; + goto fail; + } + + own_addr = wnm_ap_get_own_addr(hapd, sta); + + os_memcpy(mgmt->da, addr, ETH_ALEN); + os_memcpy(mgmt->sa, own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, own_addr, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.wnm_sleep_resp.action = WNM_SLEEP_MODE_RESP; + mgmt->u.action.u.wnm_sleep_resp.dialogtoken = dialog_token; + pos = (u8 *)mgmt->u.action.u.wnm_sleep_resp.variable; + /* add key data if MFP is enabled */ + if (!wpa_auth_uses_mfp(sta->wpa_sm) || + hapd->conf->wnm_sleep_mode_no_keys || + action_type != WNM_SLEEP_MODE_EXIT) { + mgmt->u.action.u.wnm_sleep_resp.keydata_len = 0; + } else { + gtk_elem_len = wpa_wnmsleep_gtk_subelem(sta->wpa_sm, pos); + pos += gtk_elem_len; + wpa_printf(MSG_DEBUG, "Pass 4, gtk_len = %d", + (int) gtk_elem_len); + res = wpa_wnmsleep_igtk_subelem(sta->wpa_sm, pos); + if (res < 0) + goto fail; + igtk_elem_len = res; + pos += igtk_elem_len; + wpa_printf(MSG_DEBUG, "Pass 4 igtk_len = %d", + (int) igtk_elem_len); + if (hapd->conf->beacon_prot && + (hapd->iface->drv_flags & + WPA_DRIVER_FLAGS_BEACON_PROTECTION)) { + res = wpa_wnmsleep_bigtk_subelem(sta->wpa_sm, pos); + if (res < 0) + goto fail; + bigtk_elem_len = res; + pos += bigtk_elem_len; + wpa_printf(MSG_DEBUG, "Pass 4 bigtk_len = %d", + (int) bigtk_elem_len); + } + + WPA_PUT_LE16((u8 *) + &mgmt->u.action.u.wnm_sleep_resp.keydata_len, + gtk_elem_len + igtk_elem_len + bigtk_elem_len); + } + os_memcpy(pos, &wnmsleep_ie, wnmsleep_ie_len); + /* copy TFS IE here */ + pos += wnmsleep_ie_len; + if (wnmtfs_ie) { + os_memcpy(pos, wnmtfs_ie, wnmtfs_ie_len); + pos += wnmtfs_ie_len; + } +#ifdef CONFIG_OCV + /* copy OCV OCI here */ + if (oci_ie_len > 0) + os_memcpy(pos, oci_ie, oci_ie_len); +#endif /* CONFIG_OCV */ + + len = 1 + sizeof(mgmt->u.action.u.wnm_sleep_resp) + gtk_elem_len + + igtk_elem_len + bigtk_elem_len + + wnmsleep_ie_len + wnmtfs_ie_len + oci_ie_len; + + /* In driver, response frame should be forced to sent when STA is in + * PS mode */ + res = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, + mgmt->da, &mgmt->u.action.category, len); + + if (!res) { + wpa_printf(MSG_DEBUG, "Successfully send WNM-Sleep Response " + "frame"); + + /* when entering wnmsleep + * 1. pause the node in driver + * 2. mark the node so that AP won't update GTK/IGTK/BIGTK + * during WNM Sleep + */ + if (wnmsleep_ie.status == WNM_STATUS_SLEEP_ACCEPT && + wnmsleep_ie.action_type == WNM_SLEEP_MODE_ENTER) { + sta->flags |= WLAN_STA_WNM_SLEEP_MODE; + hostapd_drv_wnm_oper(hapd, WNM_SLEEP_ENTER_CONFIRM, + addr, NULL, NULL); + wpa_set_wnmsleep(sta->wpa_sm, 1); + } + /* when exiting wnmsleep + * 1. unmark the node + * 2. start GTK/IGTK/BIGTK update if MFP is not used + * 3. unpause the node in driver + */ + if ((wnmsleep_ie.status == WNM_STATUS_SLEEP_ACCEPT || + wnmsleep_ie.status == + WNM_STATUS_SLEEP_EXIT_ACCEPT_GTK_UPDATE) && + wnmsleep_ie.action_type == WNM_SLEEP_MODE_EXIT) { + sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE; + wpa_set_wnmsleep(sta->wpa_sm, 0); + hostapd_drv_wnm_oper(hapd, WNM_SLEEP_EXIT_CONFIRM, + addr, NULL, NULL); + if (!wpa_auth_uses_mfp(sta->wpa_sm) || + hapd->conf->wnm_sleep_mode_no_keys) + wpa_wnmsleep_rekey_gtk(sta->wpa_sm); + } + } else + wpa_printf(MSG_DEBUG, "Fail to send WNM-Sleep Response frame"); + +#undef MAX_GTK_SUBELEM_LEN +#undef MAX_IGTK_SUBELEM_LEN +#undef MAX_BIGTK_SUBELEM_LEN +fail: + os_free(wnmtfs_ie); + os_free(oci_ie); + os_free(mgmt); + return res; +} + + +static void ieee802_11_rx_wnmsleep_req(struct hostapd_data *hapd, + const u8 *addr, const u8 *frm, int len) +{ + /* Dialog Token [1] | WNM-Sleep Mode IE | TFS Response IE */ + const u8 *pos = frm; + u8 dialog_token; + struct wnm_sleep_element *wnmsleep_ie = NULL; + /* multiple TFS Req IE (assuming consecutive) */ + u8 *tfsreq_ie_start = NULL; + u8 *tfsreq_ie_end = NULL; + u16 tfsreq_ie_len = 0; +#ifdef CONFIG_OCV + struct sta_info *sta; + const u8 *oci_ie = NULL; + u8 oci_ie_len = 0; +#endif /* CONFIG_OCV */ + + if (!hapd->conf->wnm_sleep_mode) { + wpa_printf(MSG_DEBUG, "Ignore WNM-Sleep Mode Request from " + MACSTR " since WNM-Sleep Mode is disabled", + MAC2STR(addr)); + return; + } + + if (len < 1) { + wpa_printf(MSG_DEBUG, + "WNM: Ignore too short WNM-Sleep Mode Request from " + MACSTR, MAC2STR(addr)); + return; + } + + dialog_token = *pos++; + while (pos + 1 < frm + len) { + u8 ie_len = pos[1]; + if (pos + 2 + ie_len > frm + len) + break; + if (*pos == WLAN_EID_WNMSLEEP && + ie_len >= (int) sizeof(*wnmsleep_ie) - 2) + wnmsleep_ie = (struct wnm_sleep_element *) pos; + else if (*pos == WLAN_EID_TFS_REQ) { + if (!tfsreq_ie_start) + tfsreq_ie_start = (u8 *) pos; + tfsreq_ie_end = (u8 *) pos; +#ifdef CONFIG_OCV + } else if (*pos == WLAN_EID_EXTENSION && ie_len >= 1 && + pos[2] == WLAN_EID_EXT_OCV_OCI) { + oci_ie = pos + 3; + oci_ie_len = ie_len - 1; +#endif /* CONFIG_OCV */ + } else + wpa_printf(MSG_DEBUG, "WNM: EID %d not recognized", + *pos); + pos += ie_len + 2; + } + + if (!wnmsleep_ie) { + wpa_printf(MSG_DEBUG, "No WNM-Sleep IE found"); + return; + } + +#ifdef CONFIG_OCV + sta = ap_get_sta(hapd, addr); + if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT && + sta && wpa_auth_uses_ocv(sta->wpa_sm)) { + struct wpa_channel_info ci; + + if (hostapd_drv_channel_info(hapd, &ci) != 0) { + wpa_printf(MSG_WARNING, + "Failed to get channel info to validate received OCI in WNM-Sleep Mode frame"); + return; + } + + if (ocv_verify_tx_params(oci_ie, oci_ie_len, &ci, + channel_width_to_int(ci.chanwidth), + ci.seg1_idx) != OCI_SUCCESS) { + wpa_msg(hapd, MSG_WARNING, "WNM: OCV failed: %s", + ocv_errorstr); + return; + } + } +#endif /* CONFIG_OCV */ + + if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_ENTER && + tfsreq_ie_start && tfsreq_ie_end && + tfsreq_ie_end - tfsreq_ie_start >= 0) { + tfsreq_ie_len = (tfsreq_ie_end + tfsreq_ie_end[1] + 2) - + tfsreq_ie_start; + wpa_printf(MSG_DEBUG, "TFS Req IE(s) found"); + /* pass the TFS Req IE(s) to driver for processing */ + if (ieee80211_11_set_tfs_ie(hapd, addr, tfsreq_ie_start, + &tfsreq_ie_len, + WNM_SLEEP_TFS_REQ_IE_SET)) + wpa_printf(MSG_DEBUG, "Fail to set TFS Req IE"); + } + + ieee802_11_send_wnmsleep_resp(hapd, addr, dialog_token, + wnmsleep_ie->action_type, + le_to_host16(wnmsleep_ie->intval)); + + if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT) { + /* clear the tfs after sending the resp frame */ + ieee80211_11_set_tfs_ie(hapd, addr, tfsreq_ie_start, + &tfsreq_ie_len, WNM_SLEEP_TFS_IE_DEL); + } +} + + +static int ieee802_11_send_bss_trans_mgmt_request(struct hostapd_data *hapd, + const u8 *addr, + u8 dialog_token) +{ + struct ieee80211_mgmt *mgmt; + const u8 *own_addr; + struct sta_info *sta; + size_t len; + u8 *pos; + int res; + + mgmt = os_zalloc(sizeof(*mgmt)); + if (mgmt == NULL) + return -1; + + sta = ap_get_sta(hapd, addr); + own_addr = wnm_ap_get_own_addr(hapd, sta); + + os_memcpy(mgmt->da, addr, ETH_ALEN); + os_memcpy(mgmt->sa, own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, own_addr, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ; + mgmt->u.action.u.bss_tm_req.dialog_token = dialog_token; + mgmt->u.action.u.bss_tm_req.req_mode = 0; + mgmt->u.action.u.bss_tm_req.disassoc_timer = host_to_le16(0); + mgmt->u.action.u.bss_tm_req.validity_interval = 1; + pos = mgmt->u.action.u.bss_tm_req.variable; + + wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request to " + MACSTR " dialog_token=%u req_mode=0x%x disassoc_timer=%u " + "validity_interval=%u", + MAC2STR(addr), dialog_token, + mgmt->u.action.u.bss_tm_req.req_mode, + le_to_host16(mgmt->u.action.u.bss_tm_req.disassoc_timer), + mgmt->u.action.u.bss_tm_req.validity_interval); + + len = pos - &mgmt->u.action.category; + res = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, + mgmt->da, &mgmt->u.action.category, len); + os_free(mgmt); + return res; +} + + +static void ieee802_11_rx_bss_trans_mgmt_query(struct hostapd_data *hapd, + const u8 *addr, const u8 *frm, + size_t len) +{ + u8 dialog_token, reason; + const u8 *pos, *end; + int enabled = hapd->conf->bss_transition; + char *hex = NULL; + size_t hex_len; + +#ifdef CONFIG_MBO + if (hapd->conf->mbo_enabled) + enabled = 1; +#endif /* CONFIG_MBO */ + if (!enabled) { + wpa_printf(MSG_DEBUG, + "Ignore BSS Transition Management Query from " + MACSTR + " since BSS Transition Management is disabled", + MAC2STR(addr)); + return; + } + + if (len < 2) { + wpa_printf(MSG_DEBUG, "WNM: Ignore too short BSS Transition Management Query from " + MACSTR, MAC2STR(addr)); + return; + } + + pos = frm; + end = pos + len; + dialog_token = *pos++; + reason = *pos++; + + wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Query from " + MACSTR " dialog_token=%u reason=%u", + MAC2STR(addr), dialog_token, reason); + + wpa_hexdump(MSG_DEBUG, "WNM: BSS Transition Candidate List Entries", + pos, end - pos); + + hex_len = 2 * (end - pos) + 1; + if (hex_len > 1) { + hex = os_malloc(hex_len); + if (hex) + wpa_snprintf_hex(hex, hex_len, pos, end - pos); + } + wpa_msg(hapd->msg_ctx, MSG_INFO, + BSS_TM_QUERY MACSTR " reason=%u%s%s", + MAC2STR(addr), reason, hex ? " neighbor=" : "", hex); + os_free(hex); + + ieee802_11_send_bss_trans_mgmt_request(hapd, addr, dialog_token); +} + + +void ap_sta_reset_steer_flag_timer(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + + if (sta->agreed_to_steer) { + wpa_printf(MSG_DEBUG, "%s: Reset steering flag for STA " MACSTR, + hapd->conf->iface, MAC2STR(sta->addr)); + sta->agreed_to_steer = 0; + } +} + + +static void ieee802_11_rx_bss_trans_mgmt_resp(struct hostapd_data *hapd, + const u8 *addr, const u8 *frm, + size_t len) +{ + u8 dialog_token, status_code, bss_termination_delay; + const u8 *pos, *end; + int enabled = hapd->conf->bss_transition; + struct sta_info *sta; + +#ifdef CONFIG_MBO + if (hapd->conf->mbo_enabled) + enabled = 1; +#endif /* CONFIG_MBO */ + if (!enabled) { + wpa_printf(MSG_DEBUG, + "Ignore BSS Transition Management Response from " + MACSTR + " since BSS Transition Management is disabled", + MAC2STR(addr)); + return; + } + + if (len < 3) { + wpa_printf(MSG_DEBUG, "WNM: Ignore too short BSS Transition Management Response from " + MACSTR, MAC2STR(addr)); + return; + } + + pos = frm; + end = pos + len; + dialog_token = *pos++; + status_code = *pos++; + bss_termination_delay = *pos++; + + wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Response from " + MACSTR " dialog_token=%u status_code=%u " + "bss_termination_delay=%u", MAC2STR(addr), dialog_token, + status_code, bss_termination_delay); + + sta = ap_get_sta(hapd, addr); + if (!sta) { + wpa_printf(MSG_DEBUG, "Station " MACSTR + " not found for received BSS TM Response", + MAC2STR(addr)); + return; + } + + if (status_code == WNM_BSS_TM_ACCEPT) { + if (end - pos < ETH_ALEN) { + wpa_printf(MSG_DEBUG, "WNM: not enough room for Target BSSID field"); + return; + } + sta->agreed_to_steer = 1; + eloop_cancel_timeout(ap_sta_reset_steer_flag_timer, hapd, sta); + eloop_register_timeout(2, 0, ap_sta_reset_steer_flag_timer, + hapd, sta); + wpa_printf(MSG_DEBUG, "WNM: Target BSSID: " MACSTR, + MAC2STR(pos)); + wpa_msg(hapd->msg_ctx, MSG_INFO, BSS_TM_RESP MACSTR + " status_code=%u bss_termination_delay=%u target_bssid=" + MACSTR, + MAC2STR(addr), status_code, bss_termination_delay, + MAC2STR(pos)); + pos += ETH_ALEN; + } else { + sta->agreed_to_steer = 0; + wpa_msg(hapd->msg_ctx, MSG_INFO, BSS_TM_RESP MACSTR + " status_code=%u bss_termination_delay=%u", + MAC2STR(addr), status_code, bss_termination_delay); + } + + wpa_hexdump(MSG_DEBUG, "WNM: BSS Transition Candidate List Entries", + pos, end - pos); +} + + +static void wnm_beacon_protection_failure(struct hostapd_data *hapd, + const u8 *addr) +{ + struct sta_info *sta; + + if (!hapd->conf->beacon_prot || + !(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_BEACON_PROTECTION)) + return; + + sta = ap_get_sta(hapd, addr); + if (!sta || !(sta->flags & WLAN_STA_AUTHORIZED)) { + wpa_printf(MSG_DEBUG, "Station " MACSTR + " not found for received WNM-Notification Request", + MAC2STR(addr)); + return; + } + + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Beacon protection failure reported"); + wpa_msg(hapd->msg_ctx, MSG_INFO, WPA_EVENT_UNPROT_BEACON "reporter=" + MACSTR, MAC2STR(addr)); +} + + +static void ieee802_11_rx_wnm_notification_req(struct hostapd_data *hapd, + const u8 *addr, const u8 *buf, + size_t len) +{ + u8 dialog_token, type; + + if (len < 2) + return; + dialog_token = *buf++; + type = *buf++; + len -= 2; + + wpa_printf(MSG_DEBUG, + "WNM: Received WNM Notification Request frame from " + MACSTR " (dialog_token=%u type=%u)", + MAC2STR(addr), dialog_token, type); + wpa_hexdump(MSG_MSGDUMP, "WNM: Notification Request subelements", + buf, len); + switch (type) { + case WNM_NOTIF_TYPE_BEACON_PROTECTION_FAILURE: + wnm_beacon_protection_failure(hapd, addr); + break; + case WNM_NOTIF_TYPE_VENDOR_SPECIFIC: + mbo_ap_wnm_notification_req(hapd, addr, buf, len); + break; + } +} + + +static void ieee802_11_rx_wnm_coloc_intf_report(struct hostapd_data *hapd, + const u8 *addr, const u8 *buf, + size_t len) +{ + u8 dialog_token; + char *hex; + size_t hex_len; + + if (!hapd->conf->coloc_intf_reporting) { + wpa_printf(MSG_DEBUG, + "WNM: Ignore unexpected Collocated Interference Report from " + MACSTR, MAC2STR(addr)); + return; + } + + if (len < 1) { + wpa_printf(MSG_DEBUG, + "WNM: Ignore too short Collocated Interference Report from " + MACSTR, MAC2STR(addr)); + return; + } + dialog_token = *buf++; + len--; + + wpa_printf(MSG_DEBUG, + "WNM: Received Collocated Interference Report frame from " + MACSTR " (dialog_token=%u)", + MAC2STR(addr), dialog_token); + wpa_hexdump(MSG_MSGDUMP, "WNM: Collocated Interference Report Elements", + buf, len); + + hex_len = 2 * len + 1; + hex = os_malloc(hex_len); + if (!hex) + return; + wpa_snprintf_hex(hex, hex_len, buf, len); + wpa_msg_ctrl(hapd->msg_ctx, MSG_INFO, COLOC_INTF_REPORT MACSTR " %d %s", + MAC2STR(addr), dialog_token, hex); + os_free(hex); +} + + + +static const char * wnm_event_type2str(enum wnm_event_report_type wtype) +{ +#define W2S(wtype) case WNM_EVENT_TYPE_ ## wtype: return #wtype; + switch (wtype) { + W2S(TRANSITION) + W2S(RSNA) + W2S(P2P_LINK) + W2S(WNM_LOG) + W2S(BSS_COLOR_COLLISION) + W2S(BSS_COLOR_IN_USE) + } + return "UNKNOWN"; +#undef W2S +} + + +static void ieee802_11_rx_wnm_event_report(struct hostapd_data *hapd, + const u8 *addr, const u8 *buf, + size_t len) +{ + struct sta_info *sta; + u8 dialog_token; + struct wnm_event_report_element *report_ie; + const u8 *pos = buf, *end = buf + len; + const size_t fixed_field_len = 3; /* Event Token/Type/Report Status */ +#ifdef CONFIG_IEEE80211AX + const size_t tsf_len = 8; + u8 color; + u64 bitmap; +#endif /* CONFIG_IEEE80211AX */ + + if (end - pos < 1 + 2) { + wpa_printf(MSG_DEBUG, + "WNM: Ignore too short WNM Event Report frame from " + MACSTR, MAC2STR(addr)); + return; + } + + dialog_token = *pos++; + report_ie = (struct wnm_event_report_element *) pos; + + if (end - pos < 2 + report_ie->len || + report_ie->len < fixed_field_len) { + wpa_printf(MSG_DEBUG, + "WNM: Ignore truncated WNM Event Report frame from " + MACSTR, MAC2STR(addr)); + return; + } + + if (report_ie->eid != WLAN_EID_EVENT_REPORT || + report_ie->status != WNM_STATUS_SUCCESSFUL) + return; + + wpa_printf(MSG_DEBUG, "WNM: Received WNM Event Report frame from " + MACSTR " dialog_token=%u event_token=%u type=%d (%s)", + MAC2STR(addr), dialog_token, report_ie->token, + report_ie->type, wnm_event_type2str(report_ie->type)); + + pos += 2 + fixed_field_len; + wpa_hexdump(MSG_MSGDUMP, "WNM: Event Report", pos, end - pos); + + sta = ap_get_sta(hapd, addr); + if (!sta || !(sta->flags & WLAN_STA_ASSOC)) { + wpa_printf(MSG_DEBUG, "Station " MACSTR + " not found for received WNM Event Report", + MAC2STR(addr)); + return; + } + + switch (report_ie->type) { +#ifdef CONFIG_IEEE80211AX + case WNM_EVENT_TYPE_BSS_COLOR_COLLISION: + if (!hapd->iconf->ieee80211ax || hapd->conf->disable_11ax) + return; + if (report_ie->len < + fixed_field_len + tsf_len + 8) { + wpa_printf(MSG_DEBUG, + "WNM: Too short BSS color collision event report from " + MACSTR, MAC2STR(addr)); + return; + } + bitmap = WPA_GET_LE64(report_ie->u.bss_color_collision.color_bitmap); + wpa_printf(MSG_DEBUG, + "WNM: BSS color collision bitmap 0x%llx reported by " + MACSTR, (unsigned long long) bitmap, MAC2STR(addr)); + hostapd_switch_color(hapd->iface->bss[0], bitmap); + break; + case WNM_EVENT_TYPE_BSS_COLOR_IN_USE: + if (!hapd->iconf->ieee80211ax || hapd->conf->disable_11ax) + return; + if (report_ie->len < fixed_field_len + tsf_len + 1) { + wpa_printf(MSG_DEBUG, + "WNM: Too short BSS color in use event report from " + MACSTR, MAC2STR(addr)); + return; + } + color = report_ie->u.bss_color_in_use.color; + if (color > 63) { + wpa_printf(MSG_DEBUG, + "WNM: Invalid BSS color %u report from " + MACSTR, color, MAC2STR(addr)); + return; + } + if (color == 0) { + wpa_printf(MSG_DEBUG, + "WNM: BSS color use report canceled by " + MACSTR, MAC2STR(addr)); + /* TODO: Clear stored color from the collision bitmap + * if there are no other users for it. */ + return; + } + wpa_printf(MSG_DEBUG, "WNM: BSS color %u use report by " + MACSTR, color, MAC2STR(addr)); + hapd->color_collision_bitmap |= 1ULL << color; + break; +#endif /* CONFIG_IEEE80211AX */ + default: + wpa_printf(MSG_DEBUG, + "WNM Event Report type=%d (%s) not supported", + report_ie->type, + wnm_event_type2str(report_ie->type)); + break; + } +} + + +int ieee802_11_rx_wnm_action_ap(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len) +{ + u8 action; + const u8 *payload; + size_t plen; + + if (len < IEEE80211_HDRLEN + 2) + return -1; + + payload = ((const u8 *) mgmt) + IEEE80211_HDRLEN + 1; + action = *payload++; + plen = len - IEEE80211_HDRLEN - 2; + + switch (action) { + case WNM_EVENT_REPORT: + ieee802_11_rx_wnm_event_report(hapd, mgmt->sa, payload, + plen); + return 0; + case WNM_BSS_TRANS_MGMT_QUERY: + ieee802_11_rx_bss_trans_mgmt_query(hapd, mgmt->sa, payload, + plen); + return 0; + case WNM_BSS_TRANS_MGMT_RESP: + ieee802_11_rx_bss_trans_mgmt_resp(hapd, mgmt->sa, payload, + plen); + return 0; + case WNM_SLEEP_MODE_REQ: + ieee802_11_rx_wnmsleep_req(hapd, mgmt->sa, payload, plen); + return 0; + case WNM_NOTIFICATION_REQ: + ieee802_11_rx_wnm_notification_req(hapd, mgmt->sa, payload, + plen); + return 0; + case WNM_COLLOCATED_INTERFERENCE_REPORT: + ieee802_11_rx_wnm_coloc_intf_report(hapd, mgmt->sa, payload, + plen); + return 0; + } + + wpa_printf(MSG_DEBUG, "WNM: Unsupported WNM Action %u from " MACSTR, + action, MAC2STR(mgmt->sa)); + return -1; +} + + +int wnm_send_disassoc_imminent(struct hostapd_data *hapd, + struct sta_info *sta, int disassoc_timer) +{ + u8 buf[1000], *pos; + struct ieee80211_mgmt *mgmt; + const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta); + + os_memset(buf, 0, sizeof(buf)); + mgmt = (struct ieee80211_mgmt *) buf; + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memcpy(mgmt->da, sta->addr, ETH_ALEN); + os_memcpy(mgmt->sa, own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, own_addr, ETH_ALEN); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ; + mgmt->u.action.u.bss_tm_req.dialog_token = 1; + mgmt->u.action.u.bss_tm_req.req_mode = + WNM_BSS_TM_REQ_DISASSOC_IMMINENT; + mgmt->u.action.u.bss_tm_req.disassoc_timer = + host_to_le16(disassoc_timer); + mgmt->u.action.u.bss_tm_req.validity_interval = 0; + + pos = mgmt->u.action.u.bss_tm_req.variable; + + wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request frame to indicate imminent disassociation (disassoc_timer=%d) to " + MACSTR, disassoc_timer, MAC2STR(sta->addr)); + if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0, NULL, 0, 0) < 0) { + wpa_printf(MSG_DEBUG, "Failed to send BSS Transition " + "Management Request frame"); + return -1; + } + + return 0; +} + + +static void set_disassoc_timer(struct hostapd_data *hapd, struct sta_info *sta, + int disassoc_timer) +{ + int timeout, beacon_int; + + /* + * Prevent STA from reconnecting using cached PMKSA to force + * full authentication with the authentication server (which may + * decide to reject the connection), + */ + wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr); + + beacon_int = hapd->iconf->beacon_int; + if (beacon_int < 1) + beacon_int = 100; /* best guess */ + /* Calculate timeout in ms based on beacon_int in TU */ + timeout = disassoc_timer * beacon_int * 128 / 125; + wpa_printf(MSG_DEBUG, "Disassociation timer for " MACSTR + " set to %d ms", MAC2STR(sta->addr), timeout); + + sta->timeout_next = STA_DISASSOC_FROM_CLI; + eloop_cancel_timeout(ap_handle_timer, hapd, sta); + eloop_register_timeout(timeout / 1000, + timeout % 1000 * 1000, + ap_handle_timer, hapd, sta); +} + + +int wnm_send_ess_disassoc_imminent(struct hostapd_data *hapd, + struct sta_info *sta, const char *url, + int disassoc_timer) +{ + u8 buf[1000], *pos; + struct ieee80211_mgmt *mgmt; + size_t url_len; + const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta); + + os_memset(buf, 0, sizeof(buf)); + mgmt = (struct ieee80211_mgmt *) buf; + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memcpy(mgmt->da, sta->addr, ETH_ALEN); + os_memcpy(mgmt->sa, own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, own_addr, ETH_ALEN); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ; + mgmt->u.action.u.bss_tm_req.dialog_token = 1; + mgmt->u.action.u.bss_tm_req.req_mode = + WNM_BSS_TM_REQ_DISASSOC_IMMINENT | + WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT; + mgmt->u.action.u.bss_tm_req.disassoc_timer = + host_to_le16(disassoc_timer); + mgmt->u.action.u.bss_tm_req.validity_interval = 0x01; + + pos = mgmt->u.action.u.bss_tm_req.variable; + + /* Session Information URL */ + url_len = os_strlen(url); + if (url_len > 255) + return -1; + *pos++ = url_len; + os_memcpy(pos, url, url_len); + pos += url_len; + + if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0, NULL, 0, 0) < 0) { + wpa_printf(MSG_DEBUG, "Failed to send BSS Transition " + "Management Request frame"); + return -1; + } + + if (disassoc_timer) { + /* send disassociation frame after time-out */ + set_disassoc_timer(hapd, sta, disassoc_timer); + } + + return 0; +} + + +int wnm_send_bss_tm_req(struct hostapd_data *hapd, struct sta_info *sta, + u8 req_mode, int disassoc_timer, u8 valid_int, + const u8 *bss_term_dur, u8 dialog_token, + const char *url, const u8 *nei_rep, size_t nei_rep_len, + const u8 *mbo_attrs, size_t mbo_len) +{ + u8 *buf, *pos; + struct ieee80211_mgmt *mgmt; + size_t url_len; + const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta); + + wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request to " + MACSTR + " req_mode=0x%x disassoc_timer=%d valid_int=0x%x dialog_token=%u", + MAC2STR(sta->addr), req_mode, disassoc_timer, valid_int, + dialog_token); + buf = os_zalloc(1000 + nei_rep_len + mbo_len); + if (buf == NULL) + return -1; + mgmt = (struct ieee80211_mgmt *) buf; + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memcpy(mgmt->da, sta->addr, ETH_ALEN); + os_memcpy(mgmt->sa, own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, own_addr, ETH_ALEN); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ; + mgmt->u.action.u.bss_tm_req.dialog_token = dialog_token; + mgmt->u.action.u.bss_tm_req.req_mode = req_mode; + mgmt->u.action.u.bss_tm_req.disassoc_timer = + host_to_le16(disassoc_timer); + mgmt->u.action.u.bss_tm_req.validity_interval = valid_int; + + pos = mgmt->u.action.u.bss_tm_req.variable; + + if ((req_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) && + bss_term_dur) { + os_memcpy(pos, bss_term_dur, 12); + pos += 12; + } + + if (url) { + /* Session Information URL */ + url_len = os_strlen(url); + if (url_len > 255) { + os_free(buf); + return -1; + } + + *pos++ = url_len; + os_memcpy(pos, url, url_len); + pos += url_len; + } + + if (nei_rep) { + os_memcpy(pos, nei_rep, nei_rep_len); + pos += nei_rep_len; + } + + if (mbo_len > 0) { + pos += mbo_add_ie(pos, buf + sizeof(buf) - pos, mbo_attrs, + mbo_len); + } + + if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0, NULL, 0, 0) < 0) { + wpa_printf(MSG_DEBUG, + "Failed to send BSS Transition Management Request frame"); + os_free(buf); + return -1; + } + os_free(buf); + + if (disassoc_timer) { +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta)) { + int i; + unsigned int links = 0; + + for (i = 0; i < MAX_NUM_MLD_LINKS; i++) { + if (sta->mld_info.links[i].valid) + links++; + } + + if (links > 1) { + wpa_printf(MSG_DEBUG, + "WNM: Only terminating one link - other links remains associated for " + MACSTR, + MAC2STR(sta->mld_info.common_info.mld_addr)); + return 0; + } + } +#endif /* CONFIG_IEEE80211BE */ + + /* send disassociation frame after time-out */ + set_disassoc_timer(hapd, sta, disassoc_timer); + } + + return 0; +} + + +int wnm_send_coloc_intf_req(struct hostapd_data *hapd, struct sta_info *sta, + unsigned int auto_report, unsigned int timeout) +{ + u8 buf[100], *pos; + struct ieee80211_mgmt *mgmt; + u8 dialog_token = 1; + const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta); + + if (auto_report > 3 || timeout > 63) + return -1; + os_memset(buf, 0, sizeof(buf)); + mgmt = (struct ieee80211_mgmt *) buf; + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memcpy(mgmt->da, sta->addr, ETH_ALEN); + os_memcpy(mgmt->sa, own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, own_addr, ETH_ALEN); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.coloc_intf_req.action = + WNM_COLLOCATED_INTERFERENCE_REQ; + mgmt->u.action.u.coloc_intf_req.dialog_token = dialog_token; + mgmt->u.action.u.coloc_intf_req.req_info = auto_report | (timeout << 2); + pos = &mgmt->u.action.u.coloc_intf_req.req_info; + pos++; + + wpa_printf(MSG_DEBUG, "WNM: Sending Collocated Interference Request to " + MACSTR " (dialog_token=%u auto_report=%u timeout=%u)", + MAC2STR(sta->addr), dialog_token, auto_report, timeout); + if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0, NULL, 0, 0) < 0) { + wpa_printf(MSG_DEBUG, + "WNM: Failed to send Collocated Interference Request frame"); + return -1; + } + + return 0; +} diff --git a/src/ap/wnm_ap.h b/src/ap/wnm_ap.h new file mode 100644 index 0000000..f86c6b2 --- /dev/null +++ b/src/ap/wnm_ap.h @@ -0,0 +1,30 @@ +/* + * IEEE 802.11v WNM related functions and structures + * Copyright (c) 2011-2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WNM_AP_H +#define WNM_AP_H + +struct sta_info; + +int ieee802_11_rx_wnm_action_ap(struct hostapd_data *hapd, + const struct ieee80211_mgmt *mgmt, size_t len); +int wnm_send_disassoc_imminent(struct hostapd_data *hapd, + struct sta_info *sta, int disassoc_timer); +int wnm_send_ess_disassoc_imminent(struct hostapd_data *hapd, + struct sta_info *sta, const char *url, + int disassoc_timer); +int wnm_send_bss_tm_req(struct hostapd_data *hapd, struct sta_info *sta, + u8 req_mode, int disassoc_timer, u8 valid_int, + const u8 *bss_term_dur, u8 dialog_token, + const char *url, const u8 *nei_rep, size_t nei_rep_len, + const u8 *mbo_attrs, size_t mbo_len); +void ap_sta_reset_steer_flag_timer(void *eloop_ctx, void *timeout_ctx); +int wnm_send_coloc_intf_req(struct hostapd_data *hapd, struct sta_info *sta, + unsigned int auto_report, unsigned int timeout); + +#endif /* WNM_AP_H */ diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c new file mode 100644 index 0000000..504ecf1 --- /dev/null +++ b/src/ap/wpa_auth.c @@ -0,0 +1,7254 @@ +/* + * IEEE 802.11 RSN / WPA Authenticator + * Copyright (c) 2004-2022, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "utils/state_machine.h" +#include "utils/bitfield.h" +#include "common/ieee802_11_defs.h" +#include "common/ocv.h" +#include "common/dpp.h" +#include "common/wpa_ctrl.h" +#include "crypto/aes.h" +#include "crypto/aes_wrap.h" +#include "crypto/aes_siv.h" +#include "crypto/crypto.h" +#include "crypto/sha1.h" +#include "crypto/sha256.h" +#include "crypto/sha384.h" +#include "crypto/sha512.h" +#include "crypto/random.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "drivers/driver.h" +#include "ap_config.h" +#include "ieee802_11.h" +#include "sta_info.h" +#include "wpa_auth.h" +#include "pmksa_cache_auth.h" +#include "wpa_auth_i.h" +#include "wpa_auth_ie.h" + +#define STATE_MACHINE_DATA struct wpa_state_machine +#define STATE_MACHINE_DEBUG_PREFIX "WPA" +#define STATE_MACHINE_ADDR wpa_auth_get_spa(sm) + + +static void wpa_send_eapol_timeout(void *eloop_ctx, void *timeout_ctx); +static int wpa_sm_step(struct wpa_state_machine *sm); +static int wpa_verify_key_mic(int akmp, size_t pmk_len, struct wpa_ptk *PTK, + u8 *data, size_t data_len); +#ifdef CONFIG_FILS +static int wpa_aead_decrypt(struct wpa_state_machine *sm, struct wpa_ptk *ptk, + u8 *buf, size_t buf_len, u16 *_key_data_len); +static struct wpabuf * fils_prepare_plainbuf(struct wpa_state_machine *sm, + const struct wpabuf *hlp); +#endif /* CONFIG_FILS */ +static void wpa_sm_call_step(void *eloop_ctx, void *timeout_ctx); +static void wpa_group_sm_step(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static void wpa_request_new_ptk(struct wpa_state_machine *sm); +static int wpa_gtk_update(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static int wpa_group_config_group_keys(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *snonce, + const u8 *pmk, unsigned int pmk_len, + struct wpa_ptk *ptk, int force_sha256, + u8 *pmk_r0, u8 *pmk_r1, u8 *pmk_r0_name, + size_t *key_len, bool no_kdk); +static void wpa_group_free(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static void wpa_group_get(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static void wpa_group_put(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); +static int ieee80211w_kde_len(struct wpa_state_machine *sm); +static u8 * ieee80211w_kde_add(struct wpa_state_machine *sm, u8 *pos); +static void wpa_group_update_gtk(struct wpa_authenticator *wpa_auth, + struct wpa_group *group); + + +static const u32 eapol_key_timeout_first = 100; /* ms */ +static const u32 eapol_key_timeout_subseq = 1000; /* ms */ +static const u32 eapol_key_timeout_first_group = 500; /* ms */ +static const u32 eapol_key_timeout_no_retrans = 4000; /* ms */ + +/* TODO: make these configurable */ +static const int dot11RSNAConfigPMKLifetime = 43200; +static const int dot11RSNAConfigPMKReauthThreshold = 70; +static const int dot11RSNAConfigSATimeout = 60; + + +static const u8 * wpa_auth_get_aa(const struct wpa_state_machine *sm) +{ +#ifdef CONFIG_IEEE80211BE + if (sm->mld_assoc_link_id >= 0) + return sm->wpa_auth->mld_addr; +#endif /* CONFIG_IEEE80211BE */ + return sm->wpa_auth->addr; +} + + +static const u8 * wpa_auth_get_spa(const struct wpa_state_machine *sm) +{ +#ifdef CONFIG_IEEE80211BE + if (sm->mld_assoc_link_id >= 0) + return sm->peer_mld_addr; +#endif /* CONFIG_IEEE80211BE */ + return sm->addr; +} + + +static void wpa_gkeydone_sta(struct wpa_state_machine *sm) +{ +#ifdef CONFIG_IEEE80211BE + int link_id; +#endif /* CONFIG_IEEE80211BE */ + + if (!sm->wpa_auth) + return; + + sm->wpa_auth->group->GKeyDoneStations--; + sm->GUpdateStationKeys = false; + +#ifdef CONFIG_IEEE80211BE + for_each_sm_auth(sm, link_id) + sm->mld_links[link_id].wpa_auth->group->GKeyDoneStations--; +#endif /* CONFIG_IEEE80211BE */ +} + + +#ifdef CONFIG_IEEE80211BE + +void wpa_release_link_auth_ref(struct wpa_state_machine *sm, + int release_link_id) +{ + int link_id; + + if (!sm || release_link_id >= MAX_NUM_MLD_LINKS) + return; + + for_each_sm_auth(sm, link_id) { + if (link_id == release_link_id) { + wpa_group_put(sm->mld_links[link_id].wpa_auth, + sm->mld_links[link_id].wpa_auth->group); + sm->mld_links[link_id].wpa_auth = NULL; + } + } +} + + +struct wpa_get_link_auth_ctx { + const u8 *addr; + const u8 *mld_addr; + int link_id; + struct wpa_authenticator *wpa_auth; +}; + +static int wpa_get_link_sta_auth(struct wpa_authenticator *wpa_auth, void *data) +{ + struct wpa_get_link_auth_ctx *ctx = data; + + if (!wpa_auth->is_ml) + return 0; + + if (ctx->mld_addr && + !ether_addr_equal(wpa_auth->mld_addr, ctx->mld_addr)) + return 0; + + if ((ctx->addr && ether_addr_equal(wpa_auth->addr, ctx->addr)) || + (ctx->link_id > -1 && wpa_auth->is_ml && + wpa_auth->link_id == ctx->link_id)) { + ctx->wpa_auth = wpa_auth; + return 1; + + } + return 0; +} + + +static struct wpa_authenticator * +wpa_get_link_auth(struct wpa_authenticator *wpa_auth, int link_id) +{ + struct wpa_get_link_auth_ctx ctx; + + ctx.addr = NULL; + ctx.mld_addr = wpa_auth->mld_addr; + ctx.link_id = link_id; + ctx.wpa_auth = NULL; + wpa_auth_for_each_auth(wpa_auth, wpa_get_link_sta_auth, &ctx); + return ctx.wpa_auth; +} + + +static int wpa_get_primary_auth_cb(struct wpa_authenticator *wpa_auth, + void *data) +{ + struct wpa_get_link_auth_ctx *ctx = data; + + if (!wpa_auth->is_ml || + !ether_addr_equal(wpa_auth->mld_addr, ctx->addr) || + !wpa_auth->primary_auth) + return 0; + + ctx->wpa_auth = wpa_auth; + return 1; +} + +#endif /* CONFIG_IEEE80211BE */ + + +static struct wpa_authenticator * +wpa_get_primary_auth(struct wpa_authenticator *wpa_auth) +{ +#ifdef CONFIG_IEEE80211BE + struct wpa_get_link_auth_ctx ctx; + + if (!wpa_auth || !wpa_auth->is_ml || wpa_auth->primary_auth) + return wpa_auth; + + ctx.addr = wpa_auth->mld_addr; + ctx.wpa_auth = NULL; + wpa_auth_for_each_auth(wpa_auth, wpa_get_primary_auth_cb, &ctx); + + return ctx.wpa_auth; +#else /* CONFIG_IEEE80211BE */ + return wpa_auth; +#endif /* CONFIG_IEEE80211BE */ +} + + +static inline int wpa_auth_mic_failure_report( + struct wpa_authenticator *wpa_auth, const u8 *addr) +{ + if (wpa_auth->cb->mic_failure_report) + return wpa_auth->cb->mic_failure_report(wpa_auth->cb_ctx, addr); + return 0; +} + + +static inline void wpa_auth_psk_failure_report( + struct wpa_authenticator *wpa_auth, const u8 *addr) +{ + if (wpa_auth->cb->psk_failure_report) + wpa_auth->cb->psk_failure_report(wpa_auth->cb_ctx, addr); +} + + +static inline void wpa_auth_set_eapol(struct wpa_authenticator *wpa_auth, + const u8 *addr, wpa_eapol_variable var, + int value) +{ + if (wpa_auth->cb->set_eapol) + wpa_auth->cb->set_eapol(wpa_auth->cb_ctx, addr, var, value); +} + + +static inline int wpa_auth_get_eapol(struct wpa_authenticator *wpa_auth, + const u8 *addr, wpa_eapol_variable var) +{ + if (!wpa_auth->cb->get_eapol) + return -1; + return wpa_auth->cb->get_eapol(wpa_auth->cb_ctx, addr, var); +} + + +static inline const u8 * wpa_auth_get_psk(struct wpa_authenticator *wpa_auth, + const u8 *addr, + const u8 *p2p_dev_addr, + const u8 *prev_psk, size_t *psk_len, + int *vlan_id) +{ + if (!wpa_auth->cb->get_psk) + return NULL; + return wpa_auth->cb->get_psk(wpa_auth->cb_ctx, addr, p2p_dev_addr, + prev_psk, psk_len, vlan_id); +} + + +static inline int wpa_auth_get_msk(struct wpa_authenticator *wpa_auth, + const u8 *addr, u8 *msk, size_t *len) +{ + if (!wpa_auth->cb->get_msk) + return -1; + return wpa_auth->cb->get_msk(wpa_auth->cb_ctx, addr, msk, len); +} + + +static inline int wpa_auth_set_key(struct wpa_authenticator *wpa_auth, + int vlan_id, + enum wpa_alg alg, const u8 *addr, int idx, + u8 *key, size_t key_len, + enum key_flag key_flag) +{ + if (!wpa_auth->cb->set_key) + return -1; + return wpa_auth->cb->set_key(wpa_auth->cb_ctx, vlan_id, alg, addr, idx, + key, key_len, key_flag); +} + + +#ifdef CONFIG_PASN +static inline int wpa_auth_set_ltf_keyseed(struct wpa_authenticator *wpa_auth, + const u8 *peer_addr, + const u8 *ltf_keyseed, + size_t ltf_keyseed_len) +{ + if (!wpa_auth->cb->set_ltf_keyseed) + return -1; + return wpa_auth->cb->set_ltf_keyseed(wpa_auth->cb_ctx, peer_addr, + ltf_keyseed, ltf_keyseed_len); +} +#endif /* CONFIG_PASN */ + + +static inline int wpa_auth_get_seqnum(struct wpa_authenticator *wpa_auth, + const u8 *addr, int idx, u8 *seq) +{ + int res; + + if (!wpa_auth->cb->get_seqnum) + return -1; +#ifdef CONFIG_TESTING_OPTIONS + os_memset(seq, 0, WPA_KEY_RSC_LEN); +#endif /* CONFIG_TESTING_OPTIONS */ + res = wpa_auth->cb->get_seqnum(wpa_auth->cb_ctx, addr, idx, seq); +#ifdef CONFIG_TESTING_OPTIONS + if (!addr && idx < 4 && wpa_auth->conf.gtk_rsc_override_set) { + wpa_printf(MSG_DEBUG, + "TESTING: Override GTK RSC %016llx --> %016llx", + (long long unsigned) WPA_GET_LE64(seq), + (long long unsigned) + WPA_GET_LE64(wpa_auth->conf.gtk_rsc_override)); + os_memcpy(seq, wpa_auth->conf.gtk_rsc_override, + WPA_KEY_RSC_LEN); + } + if (!addr && idx >= 4 && idx <= 5 && + wpa_auth->conf.igtk_rsc_override_set) { + wpa_printf(MSG_DEBUG, + "TESTING: Override IGTK RSC %016llx --> %016llx", + (long long unsigned) WPA_GET_LE64(seq), + (long long unsigned) + WPA_GET_LE64(wpa_auth->conf.igtk_rsc_override)); + os_memcpy(seq, wpa_auth->conf.igtk_rsc_override, + WPA_KEY_RSC_LEN); + } +#endif /* CONFIG_TESTING_OPTIONS */ + return res; +} + + +static inline int +wpa_auth_send_eapol(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *data, size_t data_len, int encrypt) +{ + if (!wpa_auth->cb->send_eapol) + return -1; + return wpa_auth->cb->send_eapol(wpa_auth->cb_ctx, addr, data, data_len, + encrypt); +} + + +#ifdef CONFIG_MESH +static inline int wpa_auth_start_ampe(struct wpa_authenticator *wpa_auth, + const u8 *addr) +{ + if (!wpa_auth->cb->start_ampe) + return -1; + return wpa_auth->cb->start_ampe(wpa_auth->cb_ctx, addr); +} +#endif /* CONFIG_MESH */ + + +int wpa_auth_for_each_sta(struct wpa_authenticator *wpa_auth, + int (*cb)(struct wpa_state_machine *sm, void *ctx), + void *cb_ctx) +{ + if (!wpa_auth->cb->for_each_sta) + return 0; + return wpa_auth->cb->for_each_sta(wpa_auth->cb_ctx, cb, cb_ctx); +} + + +int wpa_auth_for_each_auth(struct wpa_authenticator *wpa_auth, + int (*cb)(struct wpa_authenticator *a, void *ctx), + void *cb_ctx) +{ + if (!wpa_auth->cb->for_each_auth) + return 0; + return wpa_auth->cb->for_each_auth(wpa_auth->cb_ctx, cb, cb_ctx); +} + + +void wpa_auth_store_ptksa(struct wpa_authenticator *wpa_auth, + const u8 *addr, int cipher, + u32 life_time, const struct wpa_ptk *ptk) +{ + if (wpa_auth->cb->store_ptksa) + wpa_auth->cb->store_ptksa(wpa_auth->cb_ctx, addr, cipher, + life_time, ptk); +} + + +static void wpa_auth_remove_ptksa(struct wpa_authenticator *wpa_auth, + const u8 *addr, int cipher) +{ + if (wpa_auth->cb->clear_ptksa) + wpa_auth->cb->clear_ptksa(wpa_auth->cb_ctx, addr, cipher); +} + + +void wpa_auth_logger(struct wpa_authenticator *wpa_auth, const u8 *addr, + logger_level level, const char *txt) +{ + if (!wpa_auth->cb->logger) + return; + wpa_auth->cb->logger(wpa_auth->cb_ctx, addr, level, txt); +} + + +void wpa_auth_vlogger(struct wpa_authenticator *wpa_auth, const u8 *addr, + logger_level level, const char *fmt, ...) +{ + char *format; + int maxlen; + va_list ap; + + if (!wpa_auth->cb->logger) + return; + + maxlen = os_strlen(fmt) + 100; + format = os_malloc(maxlen); + if (!format) + return; + + va_start(ap, fmt); + vsnprintf(format, maxlen, fmt, ap); + va_end(ap); + + wpa_auth_logger(wpa_auth, addr, level, format); + + os_free(format); +} + + +static void wpa_sta_disconnect(struct wpa_authenticator *wpa_auth, + const u8 *addr, u16 reason) +{ + if (!wpa_auth->cb->disconnect) + return; + wpa_printf(MSG_DEBUG, "wpa_sta_disconnect STA " MACSTR " (reason %u)", + MAC2STR(addr), reason); + wpa_auth->cb->disconnect(wpa_auth->cb_ctx, addr, reason); +} + + +#ifdef CONFIG_OCV +static int wpa_channel_info(struct wpa_authenticator *wpa_auth, + struct wpa_channel_info *ci) +{ + if (!wpa_auth->cb->channel_info) + return -1; + return wpa_auth->cb->channel_info(wpa_auth->cb_ctx, ci); +} +#endif /* CONFIG_OCV */ + + +static int wpa_auth_update_vlan(struct wpa_authenticator *wpa_auth, + const u8 *addr, int vlan_id) +{ + if (!wpa_auth->cb->update_vlan) + return -1; + return wpa_auth->cb->update_vlan(wpa_auth->cb_ctx, addr, vlan_id); +} + + +static void wpa_rekey_gmk(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_authenticator *wpa_auth = eloop_ctx; + + if (random_get_bytes(wpa_auth->group->GMK, WPA_GMK_LEN)) { + wpa_printf(MSG_ERROR, + "Failed to get random data for WPA initialization."); + } else { + wpa_auth_logger(wpa_auth, NULL, LOGGER_DEBUG, "GMK rekeyd"); + wpa_hexdump_key(MSG_DEBUG, "GMK", + wpa_auth->group->GMK, WPA_GMK_LEN); + } + + if (wpa_auth->conf.wpa_gmk_rekey) { + eloop_register_timeout(wpa_auth->conf.wpa_gmk_rekey, 0, + wpa_rekey_gmk, wpa_auth, NULL); + } +} + + +static void wpa_rekey_all_groups(struct wpa_authenticator *wpa_auth) +{ + struct wpa_group *group, *next; + + wpa_auth_logger(wpa_auth, NULL, LOGGER_DEBUG, "rekeying GTK"); + group = wpa_auth->group; + while (group) { + wpa_printf(MSG_DEBUG, "GTK rekey start for authenticator (" + MACSTR "), group vlan %d", + MAC2STR(wpa_auth->addr), group->vlan_id); + wpa_group_get(wpa_auth, group); + + group->GTKReKey = true; + do { + group->changed = false; + wpa_group_sm_step(wpa_auth, group); + } while (group->changed); + + next = group->next; + wpa_group_put(wpa_auth, group); + group = next; + } +} + + +#ifdef CONFIG_IEEE80211BE + +static void wpa_update_all_gtks(struct wpa_authenticator *wpa_auth) +{ + struct wpa_group *group, *next; + + group = wpa_auth->group; + while (group) { + wpa_group_get(wpa_auth, group); + + wpa_group_update_gtk(wpa_auth, group); + next = group->next; + wpa_group_put(wpa_auth, group); + group = next; + } +} + + +static int wpa_update_all_gtks_cb(struct wpa_authenticator *wpa_auth, void *ctx) +{ + const u8 *mld_addr = ctx; + + if (!ether_addr_equal(wpa_auth->mld_addr, mld_addr)) + return 0; + + wpa_update_all_gtks(wpa_auth); + return 0; +} + + +static int wpa_rekey_all_groups_cb(struct wpa_authenticator *wpa_auth, + void *ctx) +{ + const u8 *mld_addr = ctx; + + if (!ether_addr_equal(wpa_auth->mld_addr, mld_addr)) + return 0; + + wpa_rekey_all_groups(wpa_auth); + return 0; +} + +#endif /* CONFIG_IEEE80211BE */ + + +static void wpa_rekey_gtk(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_authenticator *wpa_auth = eloop_ctx; + +#ifdef CONFIG_IEEE80211BE + if (wpa_auth->is_ml) { + /* Non-primary ML authenticator eloop timer for group rekey is + * never started and shouldn't fire. Check and warn just in + * case. */ + if (!wpa_auth->primary_auth) { + wpa_printf(MSG_DEBUG, + "RSN: Cannot start GTK rekey on non-primary ML authenticator"); + return; + } + + /* Generate all the new group keys */ + wpa_auth_for_each_auth(wpa_auth, wpa_update_all_gtks_cb, + wpa_auth->mld_addr); + + /* Send all the generated group keys to the respective stations + * with group key handshake. */ + wpa_auth_for_each_auth(wpa_auth, wpa_rekey_all_groups_cb, + wpa_auth->mld_addr); + } else { + wpa_rekey_all_groups(wpa_auth); + } +#else /* CONFIG_IEEE80211BE */ + wpa_rekey_all_groups(wpa_auth); +#endif /* CONFIG_IEEE80211BE */ + + if (wpa_auth->conf.wpa_group_rekey) { + eloop_register_timeout(wpa_auth->conf.wpa_group_rekey, + 0, wpa_rekey_gtk, wpa_auth, NULL); + } +} + + +static void wpa_rekey_ptk(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_authenticator *wpa_auth = eloop_ctx; + struct wpa_state_machine *sm = timeout_ctx; + + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "rekeying PTK"); + wpa_request_new_ptk(sm); + wpa_sm_step(sm); +} + + +void wpa_auth_set_ptk_rekey_timer(struct wpa_state_machine *sm) +{ + if (sm && sm->wpa_auth->conf.wpa_ptk_rekey) { + wpa_printf(MSG_DEBUG, "WPA: Start PTK rekeying timer for " + MACSTR " (%d seconds)", + MAC2STR(wpa_auth_get_spa(sm)), + sm->wpa_auth->conf.wpa_ptk_rekey); + eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm); + eloop_register_timeout(sm->wpa_auth->conf.wpa_ptk_rekey, 0, + wpa_rekey_ptk, sm->wpa_auth, sm); + } +} + + +static int wpa_auth_pmksa_clear_cb(struct wpa_state_machine *sm, void *ctx) +{ + if (sm->pmksa == ctx) + sm->pmksa = NULL; + return 0; +} + + +static void wpa_auth_pmksa_free_cb(struct rsn_pmksa_cache_entry *entry, + void *ctx) +{ + struct wpa_authenticator *wpa_auth = ctx; + wpa_auth_for_each_sta(wpa_auth, wpa_auth_pmksa_clear_cb, entry); +} + + +static int wpa_group_init_gmk_and_counter(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + u8 buf[ETH_ALEN + 8 + sizeof(unsigned long)]; + u8 rkey[32]; + unsigned long ptr; + + if (random_get_bytes(group->GMK, WPA_GMK_LEN) < 0) + return -1; + wpa_hexdump_key(MSG_DEBUG, "GMK", group->GMK, WPA_GMK_LEN); + + /* + * Counter = PRF-256(Random number, "Init Counter", + * Local MAC Address || Time) + */ + os_memcpy(buf, wpa_auth->addr, ETH_ALEN); + wpa_get_ntp_timestamp(buf + ETH_ALEN); + ptr = (unsigned long) group; + os_memcpy(buf + ETH_ALEN + 8, &ptr, sizeof(ptr)); +#ifdef TEST_FUZZ + os_memset(buf + ETH_ALEN, 0xab, 8); + os_memset(buf + ETH_ALEN + 8, 0xcd, sizeof(ptr)); +#endif /* TEST_FUZZ */ + if (random_get_bytes(rkey, sizeof(rkey)) < 0) + return -1; + + if (sha1_prf(rkey, sizeof(rkey), "Init Counter", buf, sizeof(buf), + group->Counter, WPA_NONCE_LEN) < 0) + return -1; + wpa_hexdump_key(MSG_DEBUG, "Key Counter", + group->Counter, WPA_NONCE_LEN); + + return 0; +} + + +static struct wpa_group * wpa_group_init(struct wpa_authenticator *wpa_auth, + int vlan_id, int delay_init) +{ + struct wpa_group *group; + + group = os_zalloc(sizeof(struct wpa_group)); + if (!group) + return NULL; + + group->GTKAuthenticator = true; + group->vlan_id = vlan_id; + group->GTK_len = wpa_cipher_key_len(wpa_auth->conf.wpa_group); + + if (random_pool_ready() != 1) { + wpa_printf(MSG_INFO, + "WPA: Not enough entropy in random pool for secure operations - update keys later when the first station connects"); + } + + /* + * Set initial GMK/Counter value here. The actual values that will be + * used in negotiations will be set once the first station tries to + * connect. This allows more time for collecting additional randomness + * on embedded devices. + */ + if (wpa_group_init_gmk_and_counter(wpa_auth, group) < 0) { + wpa_printf(MSG_ERROR, + "Failed to get random data for WPA initialization."); + os_free(group); + return NULL; + } + + group->GInit = true; + if (delay_init) { + wpa_printf(MSG_DEBUG, + "WPA: Delay group state machine start until Beacon frames have been configured"); + /* Initialization is completed in wpa_init_keys(). */ + } else { + wpa_group_sm_step(wpa_auth, group); + group->GInit = false; + wpa_group_sm_step(wpa_auth, group); + } + + return group; +} + + +/** + * wpa_init - Initialize WPA authenticator + * @addr: Authenticator address + * @conf: Configuration for WPA authenticator + * @cb: Callback functions for WPA authenticator + * Returns: Pointer to WPA authenticator data or %NULL on failure + */ +struct wpa_authenticator * wpa_init(const u8 *addr, + struct wpa_auth_config *conf, + const struct wpa_auth_callbacks *cb, + void *cb_ctx) +{ + struct wpa_authenticator *wpa_auth; + + wpa_auth = os_zalloc(sizeof(struct wpa_authenticator)); + if (!wpa_auth) + return NULL; + + os_memcpy(wpa_auth->addr, addr, ETH_ALEN); + os_memcpy(&wpa_auth->conf, conf, sizeof(*conf)); + +#ifdef CONFIG_IEEE80211BE + if (conf->mld_addr) { + wpa_auth->is_ml = true; + wpa_auth->link_id = conf->link_id; + wpa_auth->primary_auth = !conf->first_link_auth; + os_memcpy(wpa_auth->mld_addr, conf->mld_addr, ETH_ALEN); + } +#endif /* CONFIG_IEEE80211BE */ + + wpa_auth->cb = cb; + wpa_auth->cb_ctx = cb_ctx; + + if (wpa_auth_gen_wpa_ie(wpa_auth)) { + wpa_printf(MSG_ERROR, "Could not generate WPA IE."); + os_free(wpa_auth); + return NULL; + } + + wpa_auth->group = wpa_group_init(wpa_auth, 0, 1); + if (!wpa_auth->group) { + os_free(wpa_auth->wpa_ie); + os_free(wpa_auth); + return NULL; + } + + wpa_auth->pmksa = pmksa_cache_auth_init(wpa_auth_pmksa_free_cb, + wpa_auth); + if (!wpa_auth->pmksa) { + wpa_printf(MSG_ERROR, "PMKSA cache initialization failed."); + os_free(wpa_auth->group); + os_free(wpa_auth->wpa_ie); + os_free(wpa_auth); + return NULL; + } + +#ifdef CONFIG_IEEE80211R_AP + wpa_auth->ft_pmk_cache = wpa_ft_pmk_cache_init(); + if (!wpa_auth->ft_pmk_cache) { + wpa_printf(MSG_ERROR, "FT PMK cache initialization failed."); + os_free(wpa_auth->group); + os_free(wpa_auth->wpa_ie); + pmksa_cache_auth_deinit(wpa_auth->pmksa); + os_free(wpa_auth); + return NULL; + } +#endif /* CONFIG_IEEE80211R_AP */ + + if (wpa_auth->conf.wpa_gmk_rekey) { + eloop_register_timeout(wpa_auth->conf.wpa_gmk_rekey, 0, + wpa_rekey_gmk, wpa_auth, NULL); + } + +#ifdef CONFIG_IEEE80211BE + /* For AP MLD, run group rekey timer only on one link (first) and + * whenever it fires do rekey on all associated ML links in one shot. + */ + if ((!wpa_auth->is_ml || !conf->first_link_auth) && + wpa_auth->conf.wpa_group_rekey) { +#else /* CONFIG_IEEE80211BE */ + if (wpa_auth->conf.wpa_group_rekey) { +#endif /* CONFIG_IEEE80211BE */ + eloop_register_timeout(wpa_auth->conf.wpa_group_rekey, 0, + wpa_rekey_gtk, wpa_auth, NULL); + } + +#ifdef CONFIG_P2P + if (WPA_GET_BE32(conf->ip_addr_start)) { + int count = WPA_GET_BE32(conf->ip_addr_end) - + WPA_GET_BE32(conf->ip_addr_start) + 1; + if (count > 1000) + count = 1000; + if (count > 0) + wpa_auth->ip_pool = bitfield_alloc(count); + } +#endif /* CONFIG_P2P */ + + if (conf->tx_bss_auth && conf->beacon_prot) { + conf->tx_bss_auth->non_tx_beacon_prot = true; + if (!conf->tx_bss_auth->conf.beacon_prot) + conf->tx_bss_auth->conf.beacon_prot = true; + if (!conf->tx_bss_auth->conf.group_mgmt_cipher) + conf->tx_bss_auth->conf.group_mgmt_cipher = + conf->group_mgmt_cipher; + } + + return wpa_auth; +} + + +int wpa_init_keys(struct wpa_authenticator *wpa_auth) +{ + struct wpa_group *group = wpa_auth->group; + + wpa_printf(MSG_DEBUG, + "WPA: Start group state machine to set initial keys"); + wpa_group_sm_step(wpa_auth, group); + group->GInit = false; + wpa_group_sm_step(wpa_auth, group); + if (group->wpa_group_state == WPA_GROUP_FATAL_FAILURE) + return -1; + return 0; +} + + +static void wpa_auth_free_conf(struct wpa_auth_config *conf) +{ +#ifdef CONFIG_TESTING_OPTIONS + wpabuf_free(conf->eapol_m1_elements); + conf->eapol_m1_elements = NULL; + wpabuf_free(conf->eapol_m3_elements); + conf->eapol_m3_elements = NULL; +#endif /* CONFIG_TESTING_OPTIONS */ +} + + +/** + * wpa_deinit - Deinitialize WPA authenticator + * @wpa_auth: Pointer to WPA authenticator data from wpa_init() + */ +void wpa_deinit(struct wpa_authenticator *wpa_auth) +{ + struct wpa_group *group, *prev; + + eloop_cancel_timeout(wpa_rekey_gmk, wpa_auth, NULL); + + /* TODO: Assign ML primary authenticator to next link authenticator and + * start rekey timer. */ + eloop_cancel_timeout(wpa_rekey_gtk, wpa_auth, NULL); + + pmksa_cache_auth_deinit(wpa_auth->pmksa); + +#ifdef CONFIG_IEEE80211R_AP + wpa_ft_pmk_cache_deinit(wpa_auth->ft_pmk_cache); + wpa_auth->ft_pmk_cache = NULL; + wpa_ft_deinit(wpa_auth); +#endif /* CONFIG_IEEE80211R_AP */ + +#ifdef CONFIG_P2P + bitfield_free(wpa_auth->ip_pool); +#endif /* CONFIG_P2P */ + + + os_free(wpa_auth->wpa_ie); + + group = wpa_auth->group; + while (group) { + prev = group; + group = group->next; + bin_clear_free(prev, sizeof(*prev)); + } + + wpa_auth_free_conf(&wpa_auth->conf); + os_free(wpa_auth); +} + + +/** + * wpa_reconfig - Update WPA authenticator configuration + * @wpa_auth: Pointer to WPA authenticator data from wpa_init() + * @conf: Configuration for WPA authenticator + */ +int wpa_reconfig(struct wpa_authenticator *wpa_auth, + struct wpa_auth_config *conf) +{ + struct wpa_group *group; + + if (!wpa_auth) + return 0; + + wpa_auth_free_conf(&wpa_auth->conf); + os_memcpy(&wpa_auth->conf, conf, sizeof(*conf)); + if (wpa_auth_gen_wpa_ie(wpa_auth)) { + wpa_printf(MSG_ERROR, "Could not generate WPA IE."); + return -1; + } + + /* + * Reinitialize GTK to make sure it is suitable for the new + * configuration. + */ + group = wpa_auth->group; + group->GTK_len = wpa_cipher_key_len(wpa_auth->conf.wpa_group); + group->GInit = true; + wpa_group_sm_step(wpa_auth, group); + group->GInit = false; + wpa_group_sm_step(wpa_auth, group); + + return 0; +} + + +struct wpa_state_machine * +wpa_auth_sta_init(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *p2p_dev_addr) +{ + struct wpa_state_machine *sm; + + if (wpa_auth->group->wpa_group_state == WPA_GROUP_FATAL_FAILURE) + return NULL; + + sm = os_zalloc(sizeof(struct wpa_state_machine)); + if (!sm) + return NULL; + os_memcpy(sm->addr, addr, ETH_ALEN); + if (p2p_dev_addr) + os_memcpy(sm->p2p_dev_addr, p2p_dev_addr, ETH_ALEN); + + sm->wpa_auth = wpa_auth; + sm->group = wpa_auth->group; + wpa_group_get(sm->wpa_auth, sm->group); +#ifdef CONFIG_IEEE80211BE + sm->mld_assoc_link_id = -1; +#endif /* CONFIG_IEEE80211BE */ + + return sm; +} + + +int wpa_auth_sta_associated(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm) +{ + if (!wpa_auth || !wpa_auth->conf.wpa || !sm) + return -1; + +#ifdef CONFIG_IEEE80211R_AP + if (sm->ft_completed) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "FT authentication already completed - do not start 4-way handshake"); + /* Go to PTKINITDONE state to allow GTK rekeying */ + sm->wpa_ptk_state = WPA_PTK_PTKINITDONE; + sm->Pair = true; + return 0; + } +#endif /* CONFIG_IEEE80211R_AP */ + +#ifdef CONFIG_FILS + if (sm->fils_completed) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "FILS authentication already completed - do not start 4-way handshake"); + /* Go to PTKINITDONE state to allow GTK rekeying */ + sm->wpa_ptk_state = WPA_PTK_PTKINITDONE; + sm->Pair = true; + return 0; + } +#endif /* CONFIG_FILS */ + + if (sm->started) { + os_memset(&sm->key_replay, 0, sizeof(sm->key_replay)); + sm->ReAuthenticationRequest = true; + return wpa_sm_step(sm); + } + + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "start authentication"); + sm->started = 1; + + sm->Init = true; + if (wpa_sm_step(sm) == 1) + return 1; /* should not really happen */ + sm->Init = false; + sm->AuthenticationRequest = true; + return wpa_sm_step(sm); +} + + +void wpa_auth_sta_no_wpa(struct wpa_state_machine *sm) +{ + /* WPA/RSN was not used - clear WPA state. This is needed if the STA + * reassociates back to the same AP while the previous entry for the + * STA has not yet been removed. */ + if (!sm) + return; + + sm->wpa_key_mgmt = 0; +} + + +static void wpa_free_sta_sm(struct wpa_state_machine *sm) +{ +#ifdef CONFIG_IEEE80211BE + int link_id; +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_P2P + if (WPA_GET_BE32(sm->ip_addr)) { + wpa_printf(MSG_DEBUG, + "P2P: Free assigned IP address %u.%u.%u.%u from " + MACSTR " (bit %u)", + sm->ip_addr[0], sm->ip_addr[1], + sm->ip_addr[2], sm->ip_addr[3], + MAC2STR(wpa_auth_get_spa(sm)), + sm->ip_addr_bit); + bitfield_clear(sm->wpa_auth->ip_pool, sm->ip_addr_bit); + } +#endif /* CONFIG_P2P */ + if (sm->GUpdateStationKeys) + wpa_gkeydone_sta(sm); +#ifdef CONFIG_IEEE80211R_AP + os_free(sm->assoc_resp_ftie); + wpabuf_free(sm->ft_pending_req_ies); +#endif /* CONFIG_IEEE80211R_AP */ + os_free(sm->last_rx_eapol_key); + os_free(sm->wpa_ie); + os_free(sm->rsnxe); +#ifdef CONFIG_IEEE80211BE + for_each_sm_auth(sm, link_id) { + wpa_group_put(sm->mld_links[link_id].wpa_auth, + sm->mld_links[link_id].wpa_auth->group); + sm->mld_links[link_id].wpa_auth = NULL; + } +#endif /* CONFIG_IEEE80211BE */ + wpa_group_put(sm->wpa_auth, sm->group); +#ifdef CONFIG_DPP2 + wpabuf_clear_free(sm->dpp_z); +#endif /* CONFIG_DPP2 */ + bin_clear_free(sm, sizeof(*sm)); +} + + +void wpa_auth_sta_deinit(struct wpa_state_machine *sm) +{ + struct wpa_authenticator *wpa_auth; + + if (!sm) + return; + + wpa_auth = sm->wpa_auth; + if (wpa_auth->conf.wpa_strict_rekey && sm->has_GTK) { + struct wpa_authenticator *primary_auth = wpa_auth; + + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "strict rekeying - force GTK rekey since STA is leaving"); + +#ifdef CONFIG_IEEE80211BE + if (wpa_auth->is_ml && !wpa_auth->primary_auth) + primary_auth = wpa_get_primary_auth(wpa_auth); +#endif /* CONFIG_IEEE80211BE */ + + if (eloop_deplete_timeout(0, 500000, wpa_rekey_gtk, + primary_auth, NULL) == -1) + eloop_register_timeout(0, 500000, wpa_rekey_gtk, + primary_auth, NULL); + } + + eloop_cancel_timeout(wpa_send_eapol_timeout, wpa_auth, sm); + sm->pending_1_of_4_timeout = 0; + eloop_cancel_timeout(wpa_sm_call_step, sm, NULL); + eloop_cancel_timeout(wpa_rekey_ptk, wpa_auth, sm); +#ifdef CONFIG_IEEE80211R_AP + wpa_ft_sta_deinit(sm); +#endif /* CONFIG_IEEE80211R_AP */ + if (sm->in_step_loop) { + /* Must not free state machine while wpa_sm_step() is running. + * Freeing will be completed in the end of wpa_sm_step(). */ + wpa_printf(MSG_DEBUG, + "WPA: Registering pending STA state machine deinit for " + MACSTR, MAC2STR(wpa_auth_get_spa(sm))); + sm->pending_deinit = 1; + } else + wpa_free_sta_sm(sm); +} + + +static void wpa_request_new_ptk(struct wpa_state_machine *sm) +{ + if (!sm) + return; + + if (!sm->use_ext_key_id && sm->wpa_auth->conf.wpa_deny_ptk0_rekey) { + wpa_printf(MSG_INFO, + "WPA: PTK0 rekey not allowed, disconnect " MACSTR, + MAC2STR(wpa_auth_get_spa(sm))); + sm->Disconnect = true; + /* Try to encourage the STA to reconnect */ + sm->disconnect_reason = + WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA; + } else { + if (sm->use_ext_key_id) + sm->keyidx_active ^= 1; /* flip Key ID */ + sm->PTKRequest = true; + sm->PTK_valid = 0; + } +} + + +static int wpa_replay_counter_valid(struct wpa_key_replay_counter *ctr, + const u8 *replay_counter) +{ + int i; + for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) { + if (!ctr[i].valid) + break; + if (os_memcmp(replay_counter, ctr[i].counter, + WPA_REPLAY_COUNTER_LEN) == 0) + return 1; + } + return 0; +} + + +static void wpa_replay_counter_mark_invalid(struct wpa_key_replay_counter *ctr, + const u8 *replay_counter) +{ + int i; + for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) { + if (ctr[i].valid && + (!replay_counter || + os_memcmp(replay_counter, ctr[i].counter, + WPA_REPLAY_COUNTER_LEN) == 0)) + ctr[i].valid = false; + } +} + + +#ifdef CONFIG_IEEE80211R_AP +static int ft_check_msg_2_of_4(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + struct wpa_eapol_ie_parse *kde) +{ + struct wpa_ie_data ie, assoc_ie; + struct rsn_mdie *mdie; + unsigned int i, j; + bool found = false; + + /* Verify that PMKR1Name from EAPOL-Key message 2/4 matches the value + * we derived. */ + + if (wpa_parse_wpa_ie_rsn(kde->rsn_ie, kde->rsn_ie_len, &ie) < 0 || + ie.num_pmkid < 1 || !ie.pmkid) { + wpa_printf(MSG_DEBUG, + "FT: No PMKR1Name in FT 4-way handshake message 2/4"); + return -1; + } + + if (wpa_parse_wpa_ie_rsn(sm->wpa_ie, sm->wpa_ie_len, &assoc_ie) < 0) { + wpa_printf(MSG_DEBUG, + "FT: Could not parse (Re)Association Request frame RSNE"); + os_memset(&assoc_ie, 0, sizeof(assoc_ie)); + /* Continue to allow PMKR1Name matching to be done to cover the + * case where it is the only listed PMKID. */ + } + + for (i = 0; i < ie.num_pmkid; i++) { + const u8 *pmkid = ie.pmkid + i * PMKID_LEN; + + if (os_memcmp_const(pmkid, sm->pmk_r1_name, + WPA_PMK_NAME_LEN) == 0) { + wpa_printf(MSG_DEBUG, + "FT: RSNE[PMKID[%u]] from supplicant matches PMKR1Name", + i); + found = true; + } else { + for (j = 0; j < assoc_ie.num_pmkid; j++) { + if (os_memcmp(pmkid, + assoc_ie.pmkid + j * PMKID_LEN, + PMKID_LEN) == 0) + break; + } + + if (j == assoc_ie.num_pmkid) { + wpa_printf(MSG_DEBUG, + "FT: RSNE[PMKID[%u]] from supplicant is neither PMKR1Name nor included in AssocReq", + i); + found = false; + break; + } + wpa_printf(MSG_DEBUG, + "FT: RSNE[PMKID[%u]] from supplicant is not PMKR1Name, but matches a PMKID in AssocReq", + i); + } + } + + if (!found) { + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "PMKR1Name mismatch in FT 4-way handshake"); + wpa_hexdump(MSG_DEBUG, + "FT: PMKIDs/PMKR1Name from Supplicant", + ie.pmkid, ie.num_pmkid * PMKID_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Derived PMKR1Name", + sm->pmk_r1_name, WPA_PMK_NAME_LEN); + return -1; + } + + if (!kde->mdie || !kde->ftie) { + wpa_printf(MSG_DEBUG, + "FT: No %s in FT 4-way handshake message 2/4", + kde->mdie ? "FTIE" : "MDIE"); + return -1; + } + + mdie = (struct rsn_mdie *) (kde->mdie + 2); + if (kde->mdie[1] < sizeof(struct rsn_mdie) || + os_memcmp(wpa_auth->conf.mobility_domain, mdie->mobility_domain, + MOBILITY_DOMAIN_ID_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: MDIE mismatch"); + return -1; + } + + if (sm->assoc_resp_ftie && + (kde->ftie[1] != sm->assoc_resp_ftie[1] || + os_memcmp(kde->ftie, sm->assoc_resp_ftie, + 2 + sm->assoc_resp_ftie[1]) != 0)) { + wpa_printf(MSG_DEBUG, "FT: FTIE mismatch"); + wpa_hexdump(MSG_DEBUG, "FT: FTIE in EAPOL-Key msg 2/4", + kde->ftie, kde->ftie_len); + wpa_hexdump(MSG_DEBUG, "FT: FTIE in (Re)AssocResp", + sm->assoc_resp_ftie, 2 + sm->assoc_resp_ftie[1]); + return -1; + } + + return 0; +} +#endif /* CONFIG_IEEE80211R_AP */ + + +static int wpa_receive_error_report(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int group) +{ + /* Supplicant reported a Michael MIC error */ + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "received EAPOL-Key Error Request (STA detected Michael MIC failure (group=%d))", + group); + + if (group && wpa_auth->conf.wpa_group != WPA_CIPHER_TKIP) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "ignore Michael MIC failure report since group cipher is not TKIP"); + } else if (!group && sm->pairwise != WPA_CIPHER_TKIP) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "ignore Michael MIC failure report since pairwise cipher is not TKIP"); + } else { + if (wpa_auth_mic_failure_report(wpa_auth, + wpa_auth_get_spa(sm)) > 0) + return 1; /* STA entry was removed */ + sm->dot11RSNAStatsTKIPRemoteMICFailures++; + wpa_auth->dot11RSNAStatsTKIPRemoteMICFailures++; + } + + /* + * Error report is not a request for a new key handshake, but since + * Authenticator may do it, let's change the keys now anyway. + */ + wpa_request_new_ptk(sm); + return 0; +} + + +static int wpa_try_alt_snonce(struct wpa_state_machine *sm, u8 *data, + size_t data_len) +{ + struct wpa_ptk PTK; + int ok = 0; + const u8 *pmk = NULL; + size_t pmk_len; + int vlan_id = 0; + u8 pmk_r0[PMK_LEN_MAX], pmk_r0_name[WPA_PMK_NAME_LEN]; + u8 pmk_r1[PMK_LEN_MAX]; + size_t key_len; + int ret = -1; + + os_memset(&PTK, 0, sizeof(PTK)); + for (;;) { + if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) && + !wpa_key_mgmt_sae(sm->wpa_key_mgmt)) { + pmk = wpa_auth_get_psk(sm->wpa_auth, sm->addr, + sm->p2p_dev_addr, pmk, &pmk_len, + &vlan_id); + if (!pmk) + break; +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt)) { + os_memcpy(sm->xxkey, pmk, pmk_len); + sm->xxkey_len = pmk_len; + } +#endif /* CONFIG_IEEE80211R_AP */ + } else { + pmk = sm->PMK; + pmk_len = sm->pmk_len; + } + + if (wpa_derive_ptk(sm, sm->alt_SNonce, pmk, pmk_len, &PTK, 0, + pmk_r0, pmk_r1, pmk_r0_name, &key_len, + false) < 0) + break; + + if (wpa_verify_key_mic(sm->wpa_key_mgmt, pmk_len, &PTK, + data, data_len) == 0) { + if (sm->PMK != pmk) { + os_memcpy(sm->PMK, pmk, pmk_len); + sm->pmk_len = pmk_len; + } + ok = 1; + break; + } + + if (!wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) || + wpa_key_mgmt_sae(sm->wpa_key_mgmt)) + break; + } + + if (!ok) { + wpa_printf(MSG_DEBUG, + "WPA: Earlier SNonce did not result in matching MIC"); + goto fail; + } + + wpa_printf(MSG_DEBUG, + "WPA: Earlier SNonce resulted in matching MIC"); + sm->alt_snonce_valid = 0; + + if (vlan_id && wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) && + wpa_auth_update_vlan(sm->wpa_auth, sm->addr, vlan_id) < 0) + goto fail; + +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt) && !sm->ft_completed) { + wpa_printf(MSG_DEBUG, "FT: Store PMK-R0/PMK-R1"); + wpa_auth_ft_store_keys(sm, pmk_r0, pmk_r1, pmk_r0_name, + key_len); + } +#endif /* CONFIG_IEEE80211R_AP */ + + os_memcpy(sm->SNonce, sm->alt_SNonce, WPA_NONCE_LEN); + os_memcpy(&sm->PTK, &PTK, sizeof(PTK)); + forced_memzero(&PTK, sizeof(PTK)); + sm->PTK_valid = true; + + ret = 0; +fail: + forced_memzero(pmk_r0, sizeof(pmk_r0)); + forced_memzero(pmk_r1, sizeof(pmk_r1)); + return ret; +} + + +static bool wpa_auth_gtk_rekey_in_process(struct wpa_authenticator *wpa_auth) +{ + struct wpa_group *group; + + for (group = wpa_auth->group; group; group = group->next) { + if (group->GKeyDoneStations) + return true; + } + return false; +} + + +enum eapol_key_msg { PAIRWISE_2, PAIRWISE_4, GROUP_2, REQUEST }; + +static bool wpa_auth_valid_key_desc_ver(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, u16 ver) +{ + if (ver > WPA_KEY_INFO_TYPE_AES_128_CMAC) { + wpa_printf(MSG_INFO, "RSN: " MACSTR + " used undefined Key Descriptor Version %d", + MAC2STR(wpa_auth_get_spa(sm)), ver); + return false; + } + + if (!wpa_use_akm_defined(sm->wpa_key_mgmt) && + wpa_use_cmac(sm->wpa_key_mgmt) && + ver != WPA_KEY_INFO_TYPE_AES_128_CMAC) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_WARNING, + "advertised support for AES-128-CMAC, but did not use it"); + return false; + } + + if (sm->pairwise != WPA_CIPHER_TKIP && + !wpa_use_akm_defined(sm->wpa_key_mgmt) && + !wpa_use_cmac(sm->wpa_key_mgmt) && + ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_WARNING, + "did not use HMAC-SHA1-AES with CCMP/GCMP"); + return false; + } + + if (wpa_use_akm_defined(sm->wpa_key_mgmt) && + ver != WPA_KEY_INFO_TYPE_AKM_DEFINED) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_WARNING, + "did not use EAPOL-Key descriptor version 0 as required for AKM-defined cases"); + return false; + } + + return true; +} + + +static bool wpa_auth_valid_request_counter(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + const u8 *replay_counter) +{ + + if (sm->req_replay_counter_used && + os_memcmp(replay_counter, sm->req_replay_counter, + WPA_REPLAY_COUNTER_LEN) <= 0) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_WARNING, + "received EAPOL-Key request with replayed counter"); + return false; + } + + return true; +} + + +static bool wpa_auth_valid_counter(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + const struct wpa_eapol_key *key, + enum eapol_key_msg msg, + const char *msgtxt) +{ + int i; + + if (msg == REQUEST) + return wpa_auth_valid_request_counter(wpa_auth, sm, + key->replay_counter); + + if (wpa_replay_counter_valid(sm->key_replay, key->replay_counter)) + return true; + + if (msg == PAIRWISE_2 && + wpa_replay_counter_valid(sm->prev_key_replay, + key->replay_counter) && + sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING && + os_memcmp(sm->SNonce, key->key_nonce, WPA_NONCE_LEN) != 0) { + /* + * Some supplicant implementations (e.g., Windows XP + * WZC) update SNonce for each EAPOL-Key 2/4. This + * breaks the workaround on accepting any of the + * pending requests, so allow the SNonce to be updated + * even if we have already sent out EAPOL-Key 3/4. + */ + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "Process SNonce update from STA based on retransmitted EAPOL-Key 1/4"); + sm->update_snonce = 1; + os_memcpy(sm->alt_SNonce, sm->SNonce, WPA_NONCE_LEN); + sm->alt_snonce_valid = true; + os_memcpy(sm->alt_replay_counter, + sm->key_replay[0].counter, + WPA_REPLAY_COUNTER_LEN); + return true; + } + + if (msg == PAIRWISE_4 && sm->alt_snonce_valid && + sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING && + os_memcmp(key->replay_counter, sm->alt_replay_counter, + WPA_REPLAY_COUNTER_LEN) == 0) { + /* + * Supplicant may still be using the old SNonce since + * there was two EAPOL-Key 2/4 messages and they had + * different SNonce values. + */ + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "Try to process received EAPOL-Key 4/4 based on old Replay Counter and SNonce from an earlier EAPOL-Key 1/4"); + return true; + } + + if (msg == PAIRWISE_2 && + wpa_replay_counter_valid(sm->prev_key_replay, + key->replay_counter) && + sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING) { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "ignore retransmitted EAPOL-Key %s - SNonce did not change", + msgtxt); + } else { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "received EAPOL-Key %s with unexpected replay counter", + msgtxt); + } + for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) { + if (!sm->key_replay[i].valid) + break; + wpa_hexdump(MSG_DEBUG, "pending replay counter", + sm->key_replay[i].counter, + WPA_REPLAY_COUNTER_LEN); + } + wpa_hexdump(MSG_DEBUG, "received replay counter", + key->replay_counter, WPA_REPLAY_COUNTER_LEN); + return false; +} + + +void wpa_receive(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + u8 *data, size_t data_len) +{ + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + u16 key_info, ver, key_data_length; + enum eapol_key_msg msg; + const char *msgtxt; + const u8 *key_data; + size_t keyhdrlen, mic_len; + u8 *mic; + u8 *key_data_buf = NULL; + size_t key_data_buf_len = 0; + + if (!wpa_auth || !wpa_auth->conf.wpa || !sm) + return; + + wpa_hexdump(MSG_MSGDUMP, "WPA: RX EAPOL data", data, data_len); + + mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len); + keyhdrlen = sizeof(*key) + mic_len + 2; + + if (data_len < sizeof(*hdr) + keyhdrlen) { + wpa_printf(MSG_DEBUG, "WPA: Ignore too short EAPOL-Key frame"); + return; + } + + hdr = (struct ieee802_1x_hdr *) data; + key = (struct wpa_eapol_key *) (hdr + 1); + mic = (u8 *) (key + 1); + key_info = WPA_GET_BE16(key->key_info); + key_data = mic + mic_len + 2; + key_data_length = WPA_GET_BE16(mic + mic_len); + wpa_printf(MSG_DEBUG, "WPA: Received EAPOL-Key from " MACSTR + " key_info=0x%x type=%u mic_len=%zu key_data_length=%u", + MAC2STR(wpa_auth_get_spa(sm)), key_info, key->type, + mic_len, key_data_length); + wpa_hexdump(MSG_MSGDUMP, + "WPA: EAPOL-Key header (ending before Key MIC)", + key, sizeof(*key)); + wpa_hexdump(MSG_MSGDUMP, "WPA: EAPOL-Key Key MIC", + mic, mic_len); + if (key_data_length > data_len - sizeof(*hdr) - keyhdrlen) { + wpa_printf(MSG_INFO, + "WPA: Invalid EAPOL-Key frame - key_data overflow (%d > %zu)", + key_data_length, + data_len - sizeof(*hdr) - keyhdrlen); + return; + } + + if (sm->wpa == WPA_VERSION_WPA2) { + if (key->type == EAPOL_KEY_TYPE_WPA) { + /* + * Some deployed station implementations seem to send + * msg 4/4 with incorrect type value in WPA2 mode. + */ + wpa_printf(MSG_DEBUG, + "Workaround: Allow EAPOL-Key with unexpected WPA type in RSN mode"); + } else if (key->type != EAPOL_KEY_TYPE_RSN) { + wpa_printf(MSG_DEBUG, + "Ignore EAPOL-Key with unexpected type %d in RSN mode", + key->type); + return; + } + } else { + if (key->type != EAPOL_KEY_TYPE_WPA) { + wpa_printf(MSG_DEBUG, + "Ignore EAPOL-Key with unexpected type %d in WPA mode", + key->type); + return; + } + } + + wpa_hexdump(MSG_DEBUG, "WPA: Received Key Nonce", key->key_nonce, + WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "WPA: Received Replay Counter", + key->replay_counter, WPA_REPLAY_COUNTER_LEN); + + /* FIX: verify that the EAPOL-Key frame was encrypted if pairwise keys + * are set */ + + if (key_info & WPA_KEY_INFO_SMK_MESSAGE) { + wpa_printf(MSG_DEBUG, "WPA: Ignore SMK message"); + return; + } + + ver = key_info & WPA_KEY_INFO_TYPE_MASK; + if (!wpa_auth_valid_key_desc_ver(wpa_auth, sm, ver)) + goto out; + if (mic_len > 0 && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) && + sm->PTK_valid && + (ver == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || + ver == WPA_KEY_INFO_TYPE_AES_128_CMAC || + wpa_use_aes_key_wrap(sm->wpa_key_mgmt)) && + key_data_length >= 8 && key_data_length % 8 == 0) { + key_data_length -= 8; /* AES-WRAP adds 8 bytes */ + key_data_buf = os_malloc(key_data_length); + if (!key_data_buf) + goto out; + key_data_buf_len = key_data_length; + if (aes_unwrap(sm->PTK.kek, sm->PTK.kek_len, + key_data_length / 8, key_data, key_data_buf)) { + wpa_printf(MSG_INFO, + "RSN: AES unwrap failed - could not decrypt EAPOL-Key key data"); + goto out; + } + key_data = key_data_buf; + wpa_hexdump_key(MSG_DEBUG, "RSN: Decrypted EAPOL-Key Key Data", + key_data, key_data_length); + } + + if (key_info & WPA_KEY_INFO_REQUEST) { + msg = REQUEST; + msgtxt = "Request"; + } else if (!(key_info & WPA_KEY_INFO_KEY_TYPE)) { + msg = GROUP_2; + msgtxt = "2/2 Group"; + } else if (key_data_length == 0 || + (sm->wpa == WPA_VERSION_WPA2 && + (!(key_info & WPA_KEY_INFO_ENCR_KEY_DATA) || + key_data_buf) && + (key_info & WPA_KEY_INFO_SECURE) && + !get_ie(key_data, key_data_length, WLAN_EID_RSN)) || + (mic_len == 0 && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) && + key_data_length == AES_BLOCK_SIZE)) { + msg = PAIRWISE_4; + msgtxt = "4/4 Pairwise"; + } else { + msg = PAIRWISE_2; + msgtxt = "2/4 Pairwise"; + } + + if (!wpa_auth_valid_counter(wpa_auth, sm, key, msg, msgtxt)) + goto out; + +#ifdef CONFIG_FILS + if (sm->wpa == WPA_VERSION_WPA2 && mic_len == 0 && + !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "WPA: Encr Key Data bit not set even though AEAD cipher is supposed to be used - drop frame"); + goto out; + } +#endif /* CONFIG_FILS */ + + switch (msg) { + case PAIRWISE_2: + if (sm->wpa_ptk_state != WPA_PTK_PTKSTART && + sm->wpa_ptk_state != WPA_PTK_PTKCALCNEGOTIATING && + (!sm->update_snonce || + sm->wpa_ptk_state != WPA_PTK_PTKINITNEGOTIATING)) { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key msg 2/4 in invalid state (%d) - dropped", + sm->wpa_ptk_state); + goto out; + } + random_add_randomness(key->key_nonce, WPA_NONCE_LEN); + if (sm->group->reject_4way_hs_for_entropy) { + /* + * The system did not have enough entropy to generate + * strong random numbers. Reject the first 4-way + * handshake(s) and collect some entropy based on the + * information from it. Once enough entropy is + * available, the next atempt will trigger GMK/Key + * Counter update and the station will be allowed to + * continue. + */ + wpa_printf(MSG_DEBUG, + "WPA: Reject 4-way handshake to collect more entropy for random number generation"); + random_mark_pool_ready(); + wpa_sta_disconnect(wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + goto out; + } + break; + case PAIRWISE_4: + if (sm->wpa_ptk_state != WPA_PTK_PTKINITNEGOTIATING || + !sm->PTK_valid) { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key msg 4/4 in invalid state (%d) - dropped", + sm->wpa_ptk_state); + goto out; + } + break; + case GROUP_2: + if (sm->wpa_ptk_group_state != WPA_PTK_GROUP_REKEYNEGOTIATING + || !sm->PTK_valid) { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key msg 2/2 in invalid state (%d) - dropped", + sm->wpa_ptk_group_state); + goto out; + } + break; + case REQUEST: + if (sm->wpa_ptk_state == WPA_PTK_PTKSTART || + sm->wpa_ptk_state == WPA_PTK_PTKCALCNEGOTIATING || + sm->wpa_ptk_state == WPA_PTK_PTKCALCNEGOTIATING2 || + sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING) { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key Request in invalid state (%d) - dropped", + sm->wpa_ptk_state); + goto out; + } + break; + } + + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "received EAPOL-Key frame (%s)", msgtxt); + + if (key_info & WPA_KEY_INFO_ACK) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "received invalid EAPOL-Key: Key Ack set"); + goto out; + } + + if (!wpa_key_mgmt_fils(sm->wpa_key_mgmt) && + !(key_info & WPA_KEY_INFO_MIC)) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "received invalid EAPOL-Key: Key MIC not set"); + goto out; + } + +#ifdef CONFIG_FILS + if (wpa_key_mgmt_fils(sm->wpa_key_mgmt) && + (key_info & WPA_KEY_INFO_MIC)) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "received invalid EAPOL-Key: Key MIC set"); + goto out; + } +#endif /* CONFIG_FILS */ + + sm->MICVerified = false; + if (sm->PTK_valid && !sm->update_snonce) { + if (mic_len && + wpa_verify_key_mic(sm->wpa_key_mgmt, sm->pmk_len, &sm->PTK, + data, data_len) && + (msg != PAIRWISE_4 || !sm->alt_snonce_valid || + wpa_try_alt_snonce(sm, data, data_len))) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key with invalid MIC"); +#ifdef TEST_FUZZ + wpa_printf(MSG_INFO, + "TEST: Ignore Key MIC failure for fuzz testing"); + goto continue_fuzz; +#endif /* TEST_FUZZ */ + goto out; + } +#ifdef CONFIG_FILS + if (!mic_len && + wpa_aead_decrypt(sm, &sm->PTK, data, data_len, + &key_data_length) < 0) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key with invalid MIC"); +#ifdef TEST_FUZZ + wpa_printf(MSG_INFO, + "TEST: Ignore Key MIC failure for fuzz testing"); + goto continue_fuzz; +#endif /* TEST_FUZZ */ + goto out; + } +#endif /* CONFIG_FILS */ +#ifdef TEST_FUZZ + continue_fuzz: +#endif /* TEST_FUZZ */ + sm->MICVerified = true; + eloop_cancel_timeout(wpa_send_eapol_timeout, wpa_auth, sm); + sm->pending_1_of_4_timeout = 0; + } + + if (key_info & WPA_KEY_INFO_REQUEST) { + if (!(key_info & WPA_KEY_INFO_SECURE)) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key request without Secure=1"); + goto out; + } + if (sm->MICVerified) { + sm->req_replay_counter_used = 1; + os_memcpy(sm->req_replay_counter, key->replay_counter, + WPA_REPLAY_COUNTER_LEN); + } else { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key request with invalid MIC"); + goto out; + } + + if (key_info & WPA_KEY_INFO_ERROR) { + if (wpa_receive_error_report( + wpa_auth, sm, + !(key_info & WPA_KEY_INFO_KEY_TYPE)) > 0) + goto out; /* STA entry was removed */ + } else if (key_info & WPA_KEY_INFO_KEY_TYPE) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key Request for new 4-Way Handshake"); + wpa_request_new_ptk(sm); + } else { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key Request for GTK rekeying"); + + eloop_cancel_timeout(wpa_rekey_gtk, + wpa_get_primary_auth(wpa_auth), + NULL); + if (wpa_auth_gtk_rekey_in_process(wpa_auth)) + wpa_auth_logger(wpa_auth, NULL, LOGGER_DEBUG, + "skip new GTK rekey - already in process"); + else + wpa_rekey_gtk(wpa_get_primary_auth(wpa_auth), + NULL); + } + } else { + /* Do not allow the same key replay counter to be reused. */ + wpa_replay_counter_mark_invalid(sm->key_replay, + key->replay_counter); + + if (msg == PAIRWISE_2) { + /* + * Maintain a copy of the pending EAPOL-Key frames in + * case the EAPOL-Key frame was retransmitted. This is + * needed to allow EAPOL-Key msg 2/4 reply to another + * pending msg 1/4 to update the SNonce to work around + * unexpected supplicant behavior. + */ + os_memcpy(sm->prev_key_replay, sm->key_replay, + sizeof(sm->key_replay)); + } else { + os_memset(sm->prev_key_replay, 0, + sizeof(sm->prev_key_replay)); + } + + /* + * Make sure old valid counters are not accepted anymore and + * do not get copied again. + */ + wpa_replay_counter_mark_invalid(sm->key_replay, NULL); + } + + os_free(sm->last_rx_eapol_key); + sm->last_rx_eapol_key = os_memdup(data, data_len); + if (!sm->last_rx_eapol_key) + goto out; + sm->last_rx_eapol_key_len = data_len; + + sm->rx_eapol_key_secure = !!(key_info & WPA_KEY_INFO_SECURE); + sm->EAPOLKeyReceived = true; + sm->EAPOLKeyPairwise = !!(key_info & WPA_KEY_INFO_KEY_TYPE); + sm->EAPOLKeyRequest = !!(key_info & WPA_KEY_INFO_REQUEST); + os_memcpy(sm->SNonce, key->key_nonce, WPA_NONCE_LEN); + wpa_sm_step(sm); + +out: + bin_clear_free(key_data_buf, key_data_buf_len); +} + + +static int wpa_gmk_to_gtk(const u8 *gmk, const char *label, const u8 *addr, + const u8 *gnonce, u8 *gtk, size_t gtk_len) +{ + u8 data[ETH_ALEN + WPA_NONCE_LEN + 8 + WPA_GTK_MAX_LEN]; + u8 *pos; + int ret = 0; + + /* GTK = PRF-X(GMK, "Group key expansion", + * AA || GNonce || Time || random data) + * The example described in the IEEE 802.11 standard uses only AA and + * GNonce as inputs here. Add some more entropy since this derivation + * is done only at the Authenticator and as such, does not need to be + * exactly same. + */ + os_memset(data, 0, sizeof(data)); + os_memcpy(data, addr, ETH_ALEN); + os_memcpy(data + ETH_ALEN, gnonce, WPA_NONCE_LEN); + pos = data + ETH_ALEN + WPA_NONCE_LEN; + wpa_get_ntp_timestamp(pos); +#ifdef TEST_FUZZ + os_memset(pos, 0xef, 8); +#endif /* TEST_FUZZ */ + pos += 8; + if (random_get_bytes(pos, gtk_len) < 0) + ret = -1; + +#ifdef CONFIG_SHA384 + if (sha384_prf(gmk, WPA_GMK_LEN, label, data, sizeof(data), + gtk, gtk_len) < 0) + ret = -1; +#else /* CONFIG_SHA384 */ +#ifdef CONFIG_SHA256 + if (sha256_prf(gmk, WPA_GMK_LEN, label, data, sizeof(data), + gtk, gtk_len) < 0) + ret = -1; +#else /* CONFIG_SHA256 */ + if (sha1_prf(gmk, WPA_GMK_LEN, label, data, sizeof(data), + gtk, gtk_len) < 0) + ret = -1; +#endif /* CONFIG_SHA256 */ +#endif /* CONFIG_SHA384 */ + + forced_memzero(data, sizeof(data)); + + return ret; +} + + +static void wpa_send_eapol_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_authenticator *wpa_auth = eloop_ctx; + struct wpa_state_machine *sm = timeout_ctx; + + if (sm->waiting_radius_psk) { + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG, + "Ignore EAPOL-Key timeout while waiting for RADIUS PSK"); + return; + } + + sm->pending_1_of_4_timeout = 0; + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "EAPOL-Key timeout"); + sm->TimeoutEvt = true; + wpa_sm_step(sm); +} + + +void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int key_info, + const u8 *key_rsc, const u8 *nonce, + const u8 *kde, size_t kde_len, + int keyidx, int encr, int force_version) +{ + struct wpa_auth_config *conf = &wpa_auth->conf; + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + size_t len, mic_len, keyhdrlen; + int alg; + int key_data_len, pad_len = 0; + u8 *buf, *pos; + int version, pairwise; + int i; + u8 *key_mic, *key_data; + + mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len); + keyhdrlen = sizeof(*key) + mic_len + 2; + + len = sizeof(struct ieee802_1x_hdr) + keyhdrlen; + + if (force_version) + version = force_version; + else if (wpa_use_akm_defined(sm->wpa_key_mgmt)) + version = WPA_KEY_INFO_TYPE_AKM_DEFINED; + else if (wpa_use_cmac(sm->wpa_key_mgmt)) + version = WPA_KEY_INFO_TYPE_AES_128_CMAC; + else if (sm->pairwise != WPA_CIPHER_TKIP) + version = WPA_KEY_INFO_TYPE_HMAC_SHA1_AES; + else + version = WPA_KEY_INFO_TYPE_HMAC_MD5_RC4; + + pairwise = !!(key_info & WPA_KEY_INFO_KEY_TYPE); + + wpa_printf(MSG_DEBUG, + "WPA: Send EAPOL(version=%d secure=%d mic=%d ack=%d install=%d pairwise=%d kde_len=%zu keyidx=%d encr=%d)", + version, + (key_info & WPA_KEY_INFO_SECURE) ? 1 : 0, + (key_info & WPA_KEY_INFO_MIC) ? 1 : 0, + (key_info & WPA_KEY_INFO_ACK) ? 1 : 0, + (key_info & WPA_KEY_INFO_INSTALL) ? 1 : 0, + pairwise, kde_len, keyidx, encr); + + key_data_len = kde_len; + + if ((version == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || + wpa_use_aes_key_wrap(sm->wpa_key_mgmt) || + version == WPA_KEY_INFO_TYPE_AES_128_CMAC) && encr) { + pad_len = key_data_len % 8; + if (pad_len) + pad_len = 8 - pad_len; + key_data_len += pad_len + 8; + } + + len += key_data_len; + if (!mic_len && encr) + len += AES_BLOCK_SIZE; + + hdr = os_zalloc(len); + if (!hdr) + return; + hdr->version = conf->eapol_version; + hdr->type = IEEE802_1X_TYPE_EAPOL_KEY; + hdr->length = host_to_be16(len - sizeof(*hdr)); + key = (struct wpa_eapol_key *) (hdr + 1); + key_mic = (u8 *) (key + 1); + key_data = ((u8 *) (hdr + 1)) + keyhdrlen; + + key->type = sm->wpa == WPA_VERSION_WPA2 ? + EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA; + key_info |= version; + if (encr && sm->wpa == WPA_VERSION_WPA2) + key_info |= WPA_KEY_INFO_ENCR_KEY_DATA; + if (sm->wpa != WPA_VERSION_WPA2) + key_info |= keyidx << WPA_KEY_INFO_KEY_INDEX_SHIFT; + WPA_PUT_BE16(key->key_info, key_info); + + alg = pairwise ? sm->pairwise : conf->wpa_group; + if (sm->wpa == WPA_VERSION_WPA2 && !pairwise) + WPA_PUT_BE16(key->key_length, 0); + else + WPA_PUT_BE16(key->key_length, wpa_cipher_key_len(alg)); + + for (i = RSNA_MAX_EAPOL_RETRIES - 1; i > 0; i--) { + sm->key_replay[i].valid = sm->key_replay[i - 1].valid; + os_memcpy(sm->key_replay[i].counter, + sm->key_replay[i - 1].counter, + WPA_REPLAY_COUNTER_LEN); + } + inc_byte_array(sm->key_replay[0].counter, WPA_REPLAY_COUNTER_LEN); + os_memcpy(key->replay_counter, sm->key_replay[0].counter, + WPA_REPLAY_COUNTER_LEN); + wpa_hexdump(MSG_DEBUG, "WPA: Replay Counter", + key->replay_counter, WPA_REPLAY_COUNTER_LEN); + sm->key_replay[0].valid = true; + + if (nonce) + os_memcpy(key->key_nonce, nonce, WPA_NONCE_LEN); + + if (key_rsc) + os_memcpy(key->key_rsc, key_rsc, WPA_KEY_RSC_LEN); + + if (kde && !encr) { + os_memcpy(key_data, kde, kde_len); + WPA_PUT_BE16(key_mic + mic_len, kde_len); +#ifdef CONFIG_FILS + } else if (!mic_len && kde) { + const u8 *aad[1]; + size_t aad_len[1]; + + WPA_PUT_BE16(key_mic, AES_BLOCK_SIZE + kde_len); + wpa_hexdump_key(MSG_DEBUG, "Plaintext EAPOL-Key Key Data", + kde, kde_len); + + wpa_hexdump_key(MSG_DEBUG, "WPA: KEK", + sm->PTK.kek, sm->PTK.kek_len); + /* AES-SIV AAD from EAPOL protocol version field (inclusive) to + * to Key Data (exclusive). */ + aad[0] = (u8 *) hdr; + aad_len[0] = key_mic + 2 - (u8 *) hdr; + if (aes_siv_encrypt(sm->PTK.kek, sm->PTK.kek_len, kde, kde_len, + 1, aad, aad_len, key_mic + 2) < 0) { + wpa_printf(MSG_DEBUG, "WPA: AES-SIV encryption failed"); + return; + } + + wpa_hexdump(MSG_DEBUG, "WPA: Encrypted Key Data from SIV", + key_mic + 2, AES_BLOCK_SIZE + kde_len); +#endif /* CONFIG_FILS */ + } else if (encr && kde) { + buf = os_zalloc(key_data_len); + if (!buf) { + os_free(hdr); + return; + } + pos = buf; + os_memcpy(pos, kde, kde_len); + pos += kde_len; + + if (pad_len) + *pos++ = 0xdd; + + wpa_hexdump_key(MSG_DEBUG, + "Plaintext EAPOL-Key Key Data (+ padding)", + buf, key_data_len); + if (version == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || + wpa_use_aes_key_wrap(sm->wpa_key_mgmt) || + version == WPA_KEY_INFO_TYPE_AES_128_CMAC) { + wpa_hexdump_key(MSG_DEBUG, "RSN: AES-WRAP using KEK", + sm->PTK.kek, sm->PTK.kek_len); + if (aes_wrap(sm->PTK.kek, sm->PTK.kek_len, + (key_data_len - 8) / 8, buf, key_data)) { + os_free(hdr); + bin_clear_free(buf, key_data_len); + return; + } + wpa_hexdump(MSG_DEBUG, + "RSN: Encrypted Key Data from AES-WRAP", + key_data, key_data_len); + WPA_PUT_BE16(key_mic + mic_len, key_data_len); +#if !defined(CONFIG_NO_RC4) && !defined(CONFIG_FIPS) + } else if (sm->PTK.kek_len == 16) { + u8 ek[32]; + + wpa_printf(MSG_DEBUG, + "WPA: Encrypt Key Data using RC4"); + os_memcpy(key->key_iv, + sm->group->Counter + WPA_NONCE_LEN - 16, 16); + inc_byte_array(sm->group->Counter, WPA_NONCE_LEN); + os_memcpy(ek, key->key_iv, 16); + os_memcpy(ek + 16, sm->PTK.kek, sm->PTK.kek_len); + os_memcpy(key_data, buf, key_data_len); + rc4_skip(ek, 32, 256, key_data, key_data_len); + WPA_PUT_BE16(key_mic + mic_len, key_data_len); +#endif /* !(CONFIG_NO_RC4 || CONFIG_FIPS) */ + } else { + os_free(hdr); + bin_clear_free(buf, key_data_len); + return; + } + bin_clear_free(buf, key_data_len); + } + + if (key_info & WPA_KEY_INFO_MIC) { + if (!sm->PTK_valid || !mic_len) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "PTK not valid when sending EAPOL-Key frame"); + os_free(hdr); + return; + } + + if (wpa_eapol_key_mic(sm->PTK.kck, sm->PTK.kck_len, + sm->wpa_key_mgmt, version, + (u8 *) hdr, len, key_mic) < 0) { + os_free(hdr); + return; + } +#ifdef CONFIG_TESTING_OPTIONS + if (!pairwise && + conf->corrupt_gtk_rekey_mic_probability > 0.0 && + drand48() < conf->corrupt_gtk_rekey_mic_probability) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "Corrupting group EAPOL-Key Key MIC"); + key_mic[0]++; + } +#endif /* CONFIG_TESTING_OPTIONS */ + } + + wpa_auth_set_eapol(wpa_auth, sm->addr, WPA_EAPOL_inc_EapolFramesTx, 1); + wpa_hexdump(MSG_DEBUG, "Send EAPOL-Key msg", hdr, len); + wpa_auth_send_eapol(wpa_auth, sm->addr, (u8 *) hdr, len, + sm->pairwise_set); + os_free(hdr); +} + + +static int wpa_auth_get_sta_count(struct wpa_authenticator *wpa_auth) +{ + if (!wpa_auth->cb->get_sta_count) + return -1; + + return wpa_auth->cb->get_sta_count(wpa_auth->cb_ctx); +} + + +static void wpa_send_eapol(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int key_info, + const u8 *key_rsc, const u8 *nonce, + const u8 *kde, size_t kde_len, + int keyidx, int encr) +{ + int timeout_ms; + int pairwise = key_info & WPA_KEY_INFO_KEY_TYPE; + u32 ctr; + + if (!sm) + return; + + ctr = pairwise ? sm->TimeoutCtr : sm->GTimeoutCtr; + +#ifdef CONFIG_TESTING_OPTIONS + /* When delay_eapol_tx is true, delay the EAPOL-Key transmission by + * sending it only on the last attempt after all timeouts for the prior + * skipped attemps. */ + if (wpa_auth->conf.delay_eapol_tx && + ctr != wpa_auth->conf.wpa_pairwise_update_count) { + wpa_msg(sm->wpa_auth->conf.msg_ctx, MSG_INFO, + "DELAY-EAPOL-TX-%d", ctr); + goto skip_tx; + } +#endif /* CONFIG_TESTING_OPTIONS */ + __wpa_send_eapol(wpa_auth, sm, key_info, key_rsc, nonce, kde, kde_len, + keyidx, encr, 0); +#ifdef CONFIG_TESTING_OPTIONS +skip_tx: +#endif /* CONFIG_TESTING_OPTIONS */ + + if (ctr == 1 && wpa_auth->conf.tx_status) { + if (pairwise) + timeout_ms = eapol_key_timeout_first; + else if (wpa_auth_get_sta_count(wpa_auth) > 100) + timeout_ms = eapol_key_timeout_first_group * 2; + else + timeout_ms = eapol_key_timeout_first_group; + } else { + timeout_ms = eapol_key_timeout_subseq; + } + if (wpa_auth->conf.wpa_disable_eapol_key_retries && + (!pairwise || (key_info & WPA_KEY_INFO_MIC))) + timeout_ms = eapol_key_timeout_no_retrans; + if (pairwise && ctr == 1 && !(key_info & WPA_KEY_INFO_MIC)) + sm->pending_1_of_4_timeout = 1; +#ifdef TEST_FUZZ + timeout_ms = 1; +#endif /* TEST_FUZZ */ + wpa_printf(MSG_DEBUG, + "WPA: Use EAPOL-Key timeout of %u ms (retry counter %u)", + timeout_ms, ctr); + eloop_register_timeout(timeout_ms / 1000, (timeout_ms % 1000) * 1000, + wpa_send_eapol_timeout, wpa_auth, sm); +} + + +static int wpa_verify_key_mic(int akmp, size_t pmk_len, struct wpa_ptk *PTK, + u8 *data, size_t data_len) +{ + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + u16 key_info; + int ret = 0; + u8 mic[WPA_EAPOL_KEY_MIC_MAX_LEN], *mic_pos; + size_t mic_len = wpa_mic_len(akmp, pmk_len); + + if (data_len < sizeof(*hdr) + sizeof(*key)) + return -1; + + hdr = (struct ieee802_1x_hdr *) data; + key = (struct wpa_eapol_key *) (hdr + 1); + mic_pos = (u8 *) (key + 1); + key_info = WPA_GET_BE16(key->key_info); + os_memcpy(mic, mic_pos, mic_len); + os_memset(mic_pos, 0, mic_len); + if (wpa_eapol_key_mic(PTK->kck, PTK->kck_len, akmp, + key_info & WPA_KEY_INFO_TYPE_MASK, + data, data_len, mic_pos) || + os_memcmp_const(mic, mic_pos, mic_len) != 0) + ret = -1; + os_memcpy(mic_pos, mic, mic_len); + return ret; +} + + +void wpa_remove_ptk(struct wpa_state_machine *sm) +{ + sm->PTK_valid = false; + os_memset(&sm->PTK, 0, sizeof(sm->PTK)); + + wpa_auth_remove_ptksa(sm->wpa_auth, sm->addr, sm->pairwise); + + if (wpa_auth_set_key(sm->wpa_auth, 0, WPA_ALG_NONE, sm->addr, 0, NULL, + 0, KEY_FLAG_PAIRWISE)) + wpa_printf(MSG_DEBUG, + "RSN: PTK removal from the driver failed"); + if (sm->use_ext_key_id && + wpa_auth_set_key(sm->wpa_auth, 0, WPA_ALG_NONE, sm->addr, 1, NULL, + 0, KEY_FLAG_PAIRWISE)) + wpa_printf(MSG_DEBUG, + "RSN: PTK Key ID 1 removal from the driver failed"); + sm->pairwise_set = false; + eloop_cancel_timeout(wpa_rekey_ptk, sm->wpa_auth, sm); +} + + +int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event) +{ + int remove_ptk = 1; + + if (!sm) + return -1; + + wpa_auth_vlogger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "event %d notification", event); + + switch (event) { + case WPA_AUTH: +#ifdef CONFIG_MESH + /* PTKs are derived through AMPE */ + if (wpa_auth_start_ampe(sm->wpa_auth, sm->addr)) { + /* not mesh */ + break; + } + return 0; +#endif /* CONFIG_MESH */ + case WPA_ASSOC: + break; + case WPA_DEAUTH: + case WPA_DISASSOC: + sm->DeauthenticationRequest = true; + os_memset(sm->PMK, 0, sizeof(sm->PMK)); + sm->pmk_len = 0; +#ifdef CONFIG_IEEE80211R_AP + os_memset(sm->xxkey, 0, sizeof(sm->xxkey)); + sm->xxkey_len = 0; + os_memset(sm->pmk_r1, 0, sizeof(sm->pmk_r1)); + sm->pmk_r1_len = 0; +#endif /* CONFIG_IEEE80211R_AP */ + break; + case WPA_REAUTH: + case WPA_REAUTH_EAPOL: + if (!sm->started) { + /* + * When using WPS, we may end up here if the STA + * manages to re-associate without the previous STA + * entry getting removed. Consequently, we need to make + * sure that the WPA state machines gets initialized + * properly at this point. + */ + wpa_printf(MSG_DEBUG, + "WPA state machine had not been started - initialize now"); + sm->started = 1; + sm->Init = true; + if (wpa_sm_step(sm) == 1) + return 1; /* should not really happen */ + sm->Init = false; + sm->AuthenticationRequest = true; + break; + } + + if (sm->ptkstart_without_success > 3) { + wpa_printf(MSG_INFO, + "WPA: Multiple EAP reauth attempts without 4-way handshake completion, disconnect " + MACSTR, MAC2STR(sm->addr)); + sm->Disconnect = true; + break; + } + + if (!sm->use_ext_key_id && + sm->wpa_auth->conf.wpa_deny_ptk0_rekey) { + wpa_printf(MSG_INFO, + "WPA: PTK0 rekey not allowed, disconnect " + MACSTR, MAC2STR(wpa_auth_get_spa(sm))); + sm->Disconnect = true; + /* Try to encourage the STA to reconnect */ + sm->disconnect_reason = + WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA; + break; + } + + if (sm->use_ext_key_id) + sm->keyidx_active ^= 1; /* flip Key ID */ + + if (sm->GUpdateStationKeys) { + /* + * Reauthentication cancels the pending group key + * update for this STA. + */ + wpa_gkeydone_sta(sm); + sm->PtkGroupInit = true; + } + sm->ReAuthenticationRequest = true; + break; + case WPA_ASSOC_FT: +#ifdef CONFIG_IEEE80211R_AP + wpa_printf(MSG_DEBUG, + "FT: Retry PTK configuration after association"); + wpa_ft_install_ptk(sm, 1); + + /* Using FT protocol, not WPA auth state machine */ + sm->ft_completed = 1; + wpa_auth_set_ptk_rekey_timer(sm); + return 0; +#else /* CONFIG_IEEE80211R_AP */ + break; +#endif /* CONFIG_IEEE80211R_AP */ + case WPA_ASSOC_FILS: +#ifdef CONFIG_FILS + wpa_printf(MSG_DEBUG, + "FILS: TK configuration after association"); + fils_set_tk(sm); + sm->fils_completed = 1; + return 0; +#else /* CONFIG_FILS */ + break; +#endif /* CONFIG_FILS */ + case WPA_DRV_STA_REMOVED: + sm->tk_already_set = false; + return 0; + } + +#ifdef CONFIG_IEEE80211R_AP + sm->ft_completed = 0; +#endif /* CONFIG_IEEE80211R_AP */ + + if (sm->mgmt_frame_prot && event == WPA_AUTH) + remove_ptk = 0; +#ifdef CONFIG_FILS + if (wpa_key_mgmt_fils(sm->wpa_key_mgmt) && + (event == WPA_AUTH || event == WPA_ASSOC)) + remove_ptk = 0; +#endif /* CONFIG_FILS */ + + if (remove_ptk) { + sm->PTK_valid = false; + os_memset(&sm->PTK, 0, sizeof(sm->PTK)); + + if (event != WPA_REAUTH_EAPOL) + wpa_remove_ptk(sm); + } + + if (sm->in_step_loop) { + /* + * wpa_sm_step() is already running - avoid recursive call to + * it by making the existing loop process the new update. + */ + sm->changed = true; + return 0; + } + return wpa_sm_step(sm); +} + + +SM_STATE(WPA_PTK, INITIALIZE) +{ + SM_ENTRY_MA(WPA_PTK, INITIALIZE, wpa_ptk); + if (sm->Init) { + /* Init flag is not cleared here, so avoid busy + * loop by claiming nothing changed. */ + sm->changed = false; + } + + sm->keycount = 0; + if (sm->GUpdateStationKeys) + wpa_gkeydone_sta(sm); + if (sm->wpa == WPA_VERSION_WPA) + sm->PInitAKeys = false; + if (1 /* Unicast cipher supported AND (ESS OR ((IBSS or WDS) and + * Local AA > Remote AA)) */) { + sm->Pair = true; + } + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_portEnabled, 0); + wpa_remove_ptk(sm); + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_portValid, 0); + sm->TimeoutCtr = 0; + if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) || + sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OWE) { + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, + WPA_EAPOL_authorized, 0); + } +} + + +SM_STATE(WPA_PTK, DISCONNECT) +{ + u16 reason = sm->disconnect_reason; + + SM_ENTRY_MA(WPA_PTK, DISCONNECT, wpa_ptk); + sm->Disconnect = false; + sm->disconnect_reason = 0; + if (!reason) + reason = WLAN_REASON_PREV_AUTH_NOT_VALID; + wpa_sta_disconnect(sm->wpa_auth, sm->addr, reason); +} + + +SM_STATE(WPA_PTK, DISCONNECTED) +{ + SM_ENTRY_MA(WPA_PTK, DISCONNECTED, wpa_ptk); + sm->DeauthenticationRequest = false; +} + + +SM_STATE(WPA_PTK, AUTHENTICATION) +{ + SM_ENTRY_MA(WPA_PTK, AUTHENTICATION, wpa_ptk); + os_memset(&sm->PTK, 0, sizeof(sm->PTK)); + sm->PTK_valid = false; + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_portControl_Auto, + 1); + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_portEnabled, 1); + sm->AuthenticationRequest = false; +} + + +static void wpa_group_ensure_init(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + if (group->first_sta_seen) + return; + /* + * System has run bit further than at the time hostapd was started + * potentially very early during boot up. This provides better chances + * of collecting more randomness on embedded systems. Re-initialize the + * GMK and Counter here to improve their strength if there was not + * enough entropy available immediately after system startup. + */ + wpa_printf(MSG_DEBUG, + "WPA: Re-initialize GMK/Counter on first station"); + if (random_pool_ready() != 1) { + wpa_printf(MSG_INFO, + "WPA: Not enough entropy in random pool to proceed - reject first 4-way handshake"); + group->reject_4way_hs_for_entropy = true; + } else { + group->first_sta_seen = true; + group->reject_4way_hs_for_entropy = false; + } + + if (wpa_group_init_gmk_and_counter(wpa_auth, group) < 0 || + wpa_gtk_update(wpa_auth, group) < 0 || + wpa_group_config_group_keys(wpa_auth, group) < 0) { + wpa_printf(MSG_INFO, "WPA: GMK/GTK setup failed"); + group->first_sta_seen = false; + group->reject_4way_hs_for_entropy = true; + } +} + + +SM_STATE(WPA_PTK, AUTHENTICATION2) +{ + SM_ENTRY_MA(WPA_PTK, AUTHENTICATION2, wpa_ptk); + + wpa_group_ensure_init(sm->wpa_auth, sm->group); + sm->ReAuthenticationRequest = false; + + /* + * Definition of ANonce selection in IEEE Std 802.11i-2004 is somewhat + * ambiguous. The Authenticator state machine uses a counter that is + * incremented by one for each 4-way handshake. However, the security + * analysis of 4-way handshake points out that unpredictable nonces + * help in preventing precomputation attacks. Instead of the state + * machine definition, use an unpredictable nonce value here to provide + * stronger protection against potential precomputation attacks. + */ + if (random_get_bytes(sm->ANonce, WPA_NONCE_LEN)) { + wpa_printf(MSG_ERROR, + "WPA: Failed to get random data for ANonce."); + sm->Disconnect = true; + return; + } + wpa_hexdump(MSG_DEBUG, "WPA: Assign ANonce", sm->ANonce, + WPA_NONCE_LEN); + /* IEEE 802.11i does not clear TimeoutCtr here, but this is more + * logical place than INITIALIZE since AUTHENTICATION2 can be + * re-entered on ReAuthenticationRequest without going through + * INITIALIZE. */ + sm->TimeoutCtr = 0; +} + + +static int wpa_auth_sm_ptk_update(struct wpa_state_machine *sm) +{ + if (random_get_bytes(sm->ANonce, WPA_NONCE_LEN)) { + wpa_printf(MSG_ERROR, + "WPA: Failed to get random data for ANonce"); + sm->Disconnect = true; + return -1; + } + wpa_hexdump(MSG_DEBUG, "WPA: Assign new ANonce", sm->ANonce, + WPA_NONCE_LEN); + sm->TimeoutCtr = 0; + return 0; +} + + +SM_STATE(WPA_PTK, INITPMK) +{ + u8 msk[2 * PMK_LEN]; + size_t len = 2 * PMK_LEN; + + SM_ENTRY_MA(WPA_PTK, INITPMK, wpa_ptk); +#ifdef CONFIG_IEEE80211R_AP + sm->xxkey_len = 0; +#endif /* CONFIG_IEEE80211R_AP */ + if (sm->pmksa) { + wpa_printf(MSG_DEBUG, "WPA: PMK from PMKSA cache"); + os_memcpy(sm->PMK, sm->pmksa->pmk, sm->pmksa->pmk_len); + sm->pmk_len = sm->pmksa->pmk_len; +#ifdef CONFIG_DPP + } else if (sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP) { + wpa_printf(MSG_DEBUG, + "DPP: No PMKSA cache entry for STA - reject connection"); + sm->Disconnect = true; + sm->disconnect_reason = WLAN_REASON_INVALID_PMKID; + return; +#endif /* CONFIG_DPP */ + } else if (wpa_auth_get_msk(sm->wpa_auth, wpa_auth_get_spa(sm), + msk, &len) == 0) { + unsigned int pmk_len; + + if (wpa_key_mgmt_sha384(sm->wpa_key_mgmt)) + pmk_len = PMK_LEN_SUITE_B_192; + else + pmk_len = PMK_LEN; + wpa_printf(MSG_DEBUG, + "WPA: PMK from EAPOL state machine (MSK len=%zu PMK len=%u)", + len, pmk_len); + if (len < pmk_len) { + wpa_printf(MSG_DEBUG, + "WPA: MSK not long enough (%zu) to create PMK (%u)", + len, pmk_len); + sm->Disconnect = true; + return; + } + os_memcpy(sm->PMK, msk, pmk_len); + sm->pmk_len = pmk_len; +#ifdef CONFIG_IEEE80211R_AP + if (len >= 2 * PMK_LEN) { + if (wpa_key_mgmt_sha384(sm->wpa_key_mgmt)) { + os_memcpy(sm->xxkey, msk, SHA384_MAC_LEN); + sm->xxkey_len = SHA384_MAC_LEN; + } else { + os_memcpy(sm->xxkey, msk + PMK_LEN, PMK_LEN); + sm->xxkey_len = PMK_LEN; + } + } +#endif /* CONFIG_IEEE80211R_AP */ + } else { + wpa_printf(MSG_DEBUG, "WPA: Could not get PMK, get_msk: %p", + sm->wpa_auth->cb->get_msk); + sm->Disconnect = true; + return; + } + forced_memzero(msk, sizeof(msk)); + + sm->req_replay_counter_used = 0; + /* IEEE 802.11i does not set keyRun to false, but not doing this + * will break reauthentication since EAPOL state machines may not be + * get into AUTHENTICATING state that clears keyRun before WPA state + * machine enters AUTHENTICATION2 state and goes immediately to INITPMK + * state and takes PMK from the previously used AAA Key. This will + * eventually fail in 4-Way Handshake because Supplicant uses PMK + * derived from the new AAA Key. Setting keyRun = false here seems to + * be good workaround for this issue. */ + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_keyRun, false); +} + + +SM_STATE(WPA_PTK, INITPSK) +{ + const u8 *psk; + size_t psk_len; + + SM_ENTRY_MA(WPA_PTK, INITPSK, wpa_ptk); + psk = wpa_auth_get_psk(sm->wpa_auth, sm->addr, sm->p2p_dev_addr, NULL, + &psk_len, NULL); + if (psk) { + os_memcpy(sm->PMK, psk, psk_len); + sm->pmk_len = psk_len; +#ifdef CONFIG_IEEE80211R_AP + sm->xxkey_len = PMK_LEN; +#ifdef CONFIG_SAE + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + (psk_len == SHA512_MAC_LEN || psk_len == SHA384_MAC_LEN || + psk_len == SHA256_MAC_LEN)) + sm->xxkey_len = psk_len; +#endif /* CONFIG_SAE */ + os_memcpy(sm->xxkey, psk, sm->xxkey_len); +#endif /* CONFIG_IEEE80211R_AP */ + } +#ifdef CONFIG_SAE + if (wpa_auth_uses_sae(sm) && sm->pmksa) { + wpa_printf(MSG_DEBUG, "SAE: PMK from PMKSA cache (len=%zu)", + sm->pmksa->pmk_len); + os_memcpy(sm->PMK, sm->pmksa->pmk, sm->pmksa->pmk_len); + sm->pmk_len = sm->pmksa->pmk_len; +#ifdef CONFIG_IEEE80211R_AP + os_memcpy(sm->xxkey, sm->pmksa->pmk, sm->pmksa->pmk_len); + sm->xxkey_len = sm->pmksa->pmk_len; +#endif /* CONFIG_IEEE80211R_AP */ + } +#endif /* CONFIG_SAE */ + sm->req_replay_counter_used = 0; +} + + +SM_STATE(WPA_PTK, PTKSTART) +{ + u8 *buf; + size_t buf_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN; + u8 *pmkid = NULL; + size_t kde_len = 0; + u16 key_info; +#ifdef CONFIG_TESTING_OPTIONS + struct wpa_auth_config *conf = &sm->wpa_auth->conf; +#endif /* CONFIG_TESTING_OPTIONS */ + + SM_ENTRY_MA(WPA_PTK, PTKSTART, wpa_ptk); + sm->PTKRequest = false; + sm->TimeoutEvt = false; + sm->alt_snonce_valid = false; + sm->ptkstart_without_success++; + + sm->TimeoutCtr++; + if (sm->TimeoutCtr > sm->wpa_auth->conf.wpa_pairwise_update_count) { + /* No point in sending the EAPOL-Key - we will disconnect + * immediately following this. */ + return; + } + +#ifdef CONFIG_IEEE80211BE + if (sm->mld_assoc_link_id >= 0) + buf_len += 2 + RSN_SELECTOR_LEN + ETH_ALEN; +#endif /* CONFIG_IEEE80211BE */ +#ifdef CONFIG_TESTING_OPTIONS + if (conf->eapol_m1_elements) + buf_len += wpabuf_len(conf->eapol_m1_elements); +#endif /* CONFIG_TESTING_OPTIONS */ + + buf = os_zalloc(buf_len); + if (!buf) + return; + + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "sending 1/4 msg of 4-Way Handshake"); + /* + * For infrastructure BSS cases, it is better for the AP not to include + * the PMKID KDE in EAPOL-Key msg 1/4 since it could be used to initiate + * offline search for the passphrase/PSK without having to be able to + * capture a 4-way handshake from a STA that has access to the network. + * + * For IBSS cases, addition of PMKID KDE could be considered even with + * WPA2-PSK cases that use multiple PSKs, but only if there is a single + * possible PSK for this STA. However, this should not be done unless + * there is support for using that information on the supplicant side. + * The concern about exposing PMKID unnecessarily in infrastructure BSS + * cases would also apply here, but at least in the IBSS case, this + * would cover a potential real use case. + */ + if (sm->wpa == WPA_VERSION_WPA2 && + (wpa_key_mgmt_wpa_ieee8021x(sm->wpa_key_mgmt) || + (sm->wpa_key_mgmt == WPA_KEY_MGMT_OWE && sm->pmksa) || + wpa_key_mgmt_sae(sm->wpa_key_mgmt)) && + sm->wpa_key_mgmt != WPA_KEY_MGMT_OSEN) { + pmkid = buf; + kde_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN; + pmkid[0] = WLAN_EID_VENDOR_SPECIFIC; + pmkid[1] = RSN_SELECTOR_LEN + PMKID_LEN; + RSN_SELECTOR_PUT(&pmkid[2], RSN_KEY_DATA_PMKID); + if (sm->pmksa) { + wpa_hexdump(MSG_DEBUG, + "RSN: Message 1/4 PMKID from PMKSA entry", + sm->pmksa->pmkid, PMKID_LEN); + os_memcpy(&pmkid[2 + RSN_SELECTOR_LEN], + sm->pmksa->pmkid, PMKID_LEN); + } else if (wpa_key_mgmt_suite_b(sm->wpa_key_mgmt)) { + /* No KCK available to derive PMKID */ + wpa_printf(MSG_DEBUG, + "RSN: No KCK available to derive PMKID for message 1/4"); + pmkid = NULL; +#ifdef CONFIG_FILS + } else if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) { + if (sm->pmkid_set) { + wpa_hexdump(MSG_DEBUG, + "RSN: Message 1/4 PMKID from FILS/ERP", + sm->pmkid, PMKID_LEN); + os_memcpy(&pmkid[2 + RSN_SELECTOR_LEN], + sm->pmkid, PMKID_LEN); + } else { + /* No PMKID available */ + wpa_printf(MSG_DEBUG, + "RSN: No FILS/ERP PMKID available for message 1/4"); + pmkid = NULL; + } +#endif /* CONFIG_FILS */ +#ifdef CONFIG_IEEE80211R_AP + } else if (wpa_key_mgmt_ft(sm->wpa_key_mgmt) && + sm->ft_completed) { + wpa_printf(MSG_DEBUG, + "FT: No PMKID in message 1/4 when using FT protocol"); + pmkid = NULL; +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_SAE + } else if (wpa_key_mgmt_sae(sm->wpa_key_mgmt)) { + if (sm->pmkid_set) { + wpa_hexdump(MSG_DEBUG, + "RSN: Message 1/4 PMKID from SAE", + sm->pmkid, PMKID_LEN); + os_memcpy(&pmkid[2 + RSN_SELECTOR_LEN], + sm->pmkid, PMKID_LEN); + } else { + /* No PMKID available */ + wpa_printf(MSG_DEBUG, + "RSN: No SAE PMKID available for message 1/4"); + pmkid = NULL; + } +#endif /* CONFIG_SAE */ + } else { + /* + * Calculate PMKID since no PMKSA cache entry was + * available with pre-calculated PMKID. + */ + rsn_pmkid(sm->PMK, sm->pmk_len, + wpa_auth_get_aa(sm), + wpa_auth_get_spa(sm), + &pmkid[2 + RSN_SELECTOR_LEN], + sm->wpa_key_mgmt); + wpa_hexdump(MSG_DEBUG, + "RSN: Message 1/4 PMKID derived from PMK", + &pmkid[2 + RSN_SELECTOR_LEN], PMKID_LEN); + } + } + if (!pmkid) + kde_len = 0; + +#ifdef CONFIG_IEEE80211BE + if (sm->mld_assoc_link_id >= 0) { + wpa_printf(MSG_DEBUG, + "RSN: MLD: Add MAC Address KDE: kde_len=%zu", + kde_len); + wpa_add_kde(buf + kde_len, RSN_KEY_DATA_MAC_ADDR, + sm->wpa_auth->mld_addr, ETH_ALEN, NULL, 0); + kde_len += 2 + RSN_SELECTOR_LEN + ETH_ALEN; + } +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_TESTING_OPTIONS + if (conf->eapol_m1_elements) { + os_memcpy(buf + kde_len, wpabuf_head(conf->eapol_m1_elements), + wpabuf_len(conf->eapol_m1_elements)); + kde_len += wpabuf_len(conf->eapol_m1_elements); + } +#endif /* CONFIG_TESTING_OPTIONS */ + + key_info = WPA_KEY_INFO_ACK | WPA_KEY_INFO_KEY_TYPE; + if (sm->pairwise_set && sm->wpa != WPA_VERSION_WPA) + key_info |= WPA_KEY_INFO_SECURE; + wpa_send_eapol(sm->wpa_auth, sm, key_info, NULL, + sm->ANonce, kde_len ? buf : NULL, kde_len, 0, 0); + os_free(buf); +} + + +static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *snonce, + const u8 *pmk, unsigned int pmk_len, + struct wpa_ptk *ptk, int force_sha256, + u8 *pmk_r0, u8 *pmk_r1, u8 *pmk_r0_name, + size_t *key_len, bool no_kdk) +{ + const u8 *z = NULL; + size_t z_len = 0, kdk_len; + int akmp; + int ret; + + if (sm->wpa_auth->conf.force_kdk_derivation || + (!no_kdk && sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF))) + kdk_len = WPA_KDK_MAX_LEN; + else + kdk_len = 0; + +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + if (sm->ft_completed) { + u8 ptk_name[WPA_PMK_NAME_LEN]; + + ret = wpa_pmk_r1_to_ptk(sm->pmk_r1, sm->pmk_r1_len, + sm->SNonce, sm->ANonce, + wpa_auth_get_spa(sm), + wpa_auth_get_aa(sm), + sm->pmk_r1_name, ptk, + ptk_name, sm->wpa_key_mgmt, + sm->pairwise, kdk_len); + } else { + ret = wpa_auth_derive_ptk_ft(sm, ptk, pmk_r0, pmk_r1, + pmk_r0_name, key_len, + kdk_len); + } + if (ret) { + wpa_printf(MSG_ERROR, "FT: PTK derivation failed"); + return ret; + } + +#ifdef CONFIG_PASN + if (!no_kdk && sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, + WLAN_RSNX_CAPAB_SECURE_LTF)) { + ret = wpa_ltf_keyseed(ptk, sm->wpa_key_mgmt, + sm->pairwise); + if (ret) { + wpa_printf(MSG_ERROR, + "FT: LTF keyseed derivation failed"); + } + } +#endif /* CONFIG_PASN */ + return ret; + } +#endif /* CONFIG_IEEE80211R_AP */ + +#ifdef CONFIG_DPP2 + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP && sm->dpp_z) { + z = wpabuf_head(sm->dpp_z); + z_len = wpabuf_len(sm->dpp_z); + } +#endif /* CONFIG_DPP2 */ + + akmp = sm->wpa_key_mgmt; + if (force_sha256) + akmp |= WPA_KEY_MGMT_PSK_SHA256; + ret = wpa_pmk_to_ptk(pmk, pmk_len, "Pairwise key expansion", + wpa_auth_get_aa(sm), wpa_auth_get_spa(sm), + sm->ANonce, snonce, ptk, akmp, + sm->pairwise, z, z_len, kdk_len); + if (ret) { + wpa_printf(MSG_DEBUG, + "WPA: PTK derivation failed"); + return ret; + } + +#ifdef CONFIG_PASN + if (!no_kdk && sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF)) { + ret = wpa_ltf_keyseed(ptk, sm->wpa_key_mgmt, sm->pairwise); + if (ret) { + wpa_printf(MSG_DEBUG, + "WPA: LTF keyseed derivation failed"); + } + } +#endif /* CONFIG_PASN */ + return ret; +} + + +#ifdef CONFIG_FILS + +int fils_auth_pmk_to_ptk(struct wpa_state_machine *sm, const u8 *pmk, + size_t pmk_len, const u8 *snonce, const u8 *anonce, + const u8 *dhss, size_t dhss_len, + struct wpabuf *g_sta, struct wpabuf *g_ap) +{ + u8 ick[FILS_ICK_MAX_LEN]; + size_t ick_len; + int res; + u8 fils_ft[FILS_FT_MAX_LEN]; + size_t fils_ft_len = 0, kdk_len; + + if (sm->wpa_auth->conf.force_kdk_derivation || + (sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF))) + kdk_len = WPA_KDK_MAX_LEN; + else + kdk_len = 0; + + res = fils_pmk_to_ptk(pmk, pmk_len, wpa_auth_get_spa(sm), + wpa_auth_get_aa(sm), + snonce, anonce, dhss, dhss_len, + &sm->PTK, ick, &ick_len, + sm->wpa_key_mgmt, sm->pairwise, + fils_ft, &fils_ft_len, kdk_len); + if (res < 0) + return res; + +#ifdef CONFIG_PASN + if (sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF)) { + res = wpa_ltf_keyseed(&sm->PTK, sm->wpa_key_mgmt, sm->pairwise); + if (res) { + wpa_printf(MSG_ERROR, + "FILS: LTF keyseed derivation failed"); + return res; + } + } +#endif /* CONFIG_PASN */ + + sm->PTK_valid = true; + sm->tk_already_set = false; + +#ifdef CONFIG_IEEE80211R_AP + if (fils_ft_len) { + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + struct wpa_auth_config *conf = &wpa_auth->conf; + u8 pmk_r0[PMK_LEN_MAX], pmk_r0_name[WPA_PMK_NAME_LEN]; + + if (wpa_derive_pmk_r0(fils_ft, fils_ft_len, + conf->ssid, conf->ssid_len, + conf->mobility_domain, + conf->r0_key_holder, + conf->r0_key_holder_len, + wpa_auth_get_spa(sm), pmk_r0, pmk_r0_name, + sm->wpa_key_mgmt) < 0) + return -1; + + wpa_ft_store_pmk_fils(sm, pmk_r0, pmk_r0_name); + forced_memzero(fils_ft, sizeof(fils_ft)); + + res = wpa_derive_pmk_r1_name(pmk_r0_name, conf->r1_key_holder, + wpa_auth_get_spa(sm), + sm->pmk_r1_name, + fils_ft_len); + forced_memzero(pmk_r0, PMK_LEN_MAX); + if (res < 0) + return -1; + wpa_hexdump(MSG_DEBUG, "FILS+FT: PMKR1Name", sm->pmk_r1_name, + WPA_PMK_NAME_LEN); + sm->pmk_r1_name_valid = 1; + } +#endif /* CONFIG_IEEE80211R_AP */ + + res = fils_key_auth_sk(ick, ick_len, snonce, anonce, + wpa_auth_get_spa(sm), + wpa_auth_get_aa(sm), + g_sta ? wpabuf_head(g_sta) : NULL, + g_sta ? wpabuf_len(g_sta) : 0, + g_ap ? wpabuf_head(g_ap) : NULL, + g_ap ? wpabuf_len(g_ap) : 0, + sm->wpa_key_mgmt, sm->fils_key_auth_sta, + sm->fils_key_auth_ap, + &sm->fils_key_auth_len); + forced_memzero(ick, sizeof(ick)); + + /* Store nonces for (Re)Association Request/Response frame processing */ + os_memcpy(sm->SNonce, snonce, FILS_NONCE_LEN); + os_memcpy(sm->ANonce, anonce, FILS_NONCE_LEN); + + return res; +} + + +static int wpa_aead_decrypt(struct wpa_state_machine *sm, struct wpa_ptk *ptk, + u8 *buf, size_t buf_len, u16 *_key_data_len) +{ + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + u8 *pos; + u16 key_data_len; + u8 *tmp; + const u8 *aad[1]; + size_t aad_len[1]; + + hdr = (struct ieee802_1x_hdr *) buf; + key = (struct wpa_eapol_key *) (hdr + 1); + pos = (u8 *) (key + 1); + key_data_len = WPA_GET_BE16(pos); + if (key_data_len < AES_BLOCK_SIZE || + key_data_len > buf_len - sizeof(*hdr) - sizeof(*key) - 2) { + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "No room for AES-SIV data in the frame"); + return -1; + } + pos += 2; /* Pointing at the Encrypted Key Data field */ + + tmp = os_malloc(key_data_len); + if (!tmp) + return -1; + + /* AES-SIV AAD from EAPOL protocol version field (inclusive) to + * to Key Data (exclusive). */ + aad[0] = buf; + aad_len[0] = pos - buf; + if (aes_siv_decrypt(ptk->kek, ptk->kek_len, pos, key_data_len, + 1, aad, aad_len, tmp) < 0) { + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "Invalid AES-SIV data in the frame"); + bin_clear_free(tmp, key_data_len); + return -1; + } + + /* AEAD decryption and validation completed successfully */ + key_data_len -= AES_BLOCK_SIZE; + wpa_hexdump_key(MSG_DEBUG, "WPA: Decrypted Key Data", + tmp, key_data_len); + + /* Replace Key Data field with the decrypted version */ + os_memcpy(pos, tmp, key_data_len); + pos -= 2; /* Key Data Length field */ + WPA_PUT_BE16(pos, key_data_len); + bin_clear_free(tmp, key_data_len); + if (_key_data_len) + *_key_data_len = key_data_len; + return 0; +} + + +const u8 * wpa_fils_validate_fils_session(struct wpa_state_machine *sm, + const u8 *ies, size_t ies_len, + const u8 *fils_session) +{ + const u8 *ie, *end; + const u8 *session = NULL; + + if (!wpa_key_mgmt_fils(sm->wpa_key_mgmt)) { + wpa_printf(MSG_DEBUG, + "FILS: Not a FILS AKM - reject association"); + return NULL; + } + + /* Verify Session element */ + ie = ies; + end = ((const u8 *) ie) + ies_len; + while (ie + 1 < end) { + if (ie + 2 + ie[1] > end) + break; + if (ie[0] == WLAN_EID_EXTENSION && + ie[1] >= 1 + FILS_SESSION_LEN && + ie[2] == WLAN_EID_EXT_FILS_SESSION) { + session = ie; + break; + } + ie += 2 + ie[1]; + } + + if (!session) { + wpa_printf(MSG_DEBUG, + "FILS: %s: Could not find FILS Session element in Assoc Req - reject", + __func__); + return NULL; + } + + if (!fils_session) { + wpa_printf(MSG_DEBUG, + "FILS: %s: Could not find FILS Session element in STA entry - reject", + __func__); + return NULL; + } + + if (os_memcmp(fils_session, session + 3, FILS_SESSION_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FILS: Session mismatch"); + wpa_hexdump(MSG_DEBUG, "FILS: Expected FILS Session", + fils_session, FILS_SESSION_LEN); + wpa_hexdump(MSG_DEBUG, "FILS: Received FILS Session", + session + 3, FILS_SESSION_LEN); + return NULL; + } + return session; +} + + +int wpa_fils_validate_key_confirm(struct wpa_state_machine *sm, const u8 *ies, + size_t ies_len) +{ + struct ieee802_11_elems elems; + + if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) { + wpa_printf(MSG_DEBUG, + "FILS: Failed to parse decrypted elements"); + return -1; + } + + if (!elems.fils_session) { + wpa_printf(MSG_DEBUG, "FILS: No FILS Session element"); + return -1; + } + + if (!elems.fils_key_confirm) { + wpa_printf(MSG_DEBUG, "FILS: No FILS Key Confirm element"); + return -1; + } + + if (elems.fils_key_confirm_len != sm->fils_key_auth_len) { + wpa_printf(MSG_DEBUG, + "FILS: Unexpected Key-Auth length %d (expected %zu)", + elems.fils_key_confirm_len, + sm->fils_key_auth_len); + return -1; + } + + if (os_memcmp(elems.fils_key_confirm, sm->fils_key_auth_sta, + sm->fils_key_auth_len) != 0) { + wpa_printf(MSG_DEBUG, "FILS: Key-Auth mismatch"); + wpa_hexdump(MSG_DEBUG, "FILS: Received Key-Auth", + elems.fils_key_confirm, elems.fils_key_confirm_len); + wpa_hexdump(MSG_DEBUG, "FILS: Expected Key-Auth", + sm->fils_key_auth_sta, sm->fils_key_auth_len); + return -1; + } + + return 0; +} + + +int fils_decrypt_assoc(struct wpa_state_machine *sm, const u8 *fils_session, + const struct ieee80211_mgmt *mgmt, size_t frame_len, + u8 *pos, size_t left) +{ + u16 fc, stype; + const u8 *end, *ie_start, *ie, *session, *crypt; + const u8 *aad[5]; + size_t aad_len[5]; + + if (!sm || !sm->PTK_valid) { + wpa_printf(MSG_DEBUG, + "FILS: No KEK to decrypt Assocication Request frame"); + return -1; + } + + if (!wpa_key_mgmt_fils(sm->wpa_key_mgmt)) { + wpa_printf(MSG_DEBUG, + "FILS: Not a FILS AKM - reject association"); + return -1; + } + + end = ((const u8 *) mgmt) + frame_len; + fc = le_to_host16(mgmt->frame_control); + stype = WLAN_FC_GET_STYPE(fc); + if (stype == WLAN_FC_STYPE_REASSOC_REQ) + ie_start = mgmt->u.reassoc_req.variable; + else + ie_start = mgmt->u.assoc_req.variable; + ie = ie_start; + + /* + * Find FILS Session element which is the last unencrypted element in + * the frame. + */ + session = wpa_fils_validate_fils_session(sm, ie, end - ie, + fils_session); + if (!session) { + wpa_printf(MSG_DEBUG, "FILS: Session validation failed"); + return -1; + } + + crypt = session + 2 + session[1]; + + if (end - crypt < AES_BLOCK_SIZE) { + wpa_printf(MSG_DEBUG, + "FILS: Too short frame to include AES-SIV data"); + return -1; + } + + /* AES-SIV AAD vectors */ + + /* The STA's MAC address */ + aad[0] = mgmt->sa; + aad_len[0] = ETH_ALEN; + /* The AP's BSSID */ + aad[1] = mgmt->da; + aad_len[1] = ETH_ALEN; + /* The STA's nonce */ + aad[2] = sm->SNonce; + aad_len[2] = FILS_NONCE_LEN; + /* The AP's nonce */ + aad[3] = sm->ANonce; + aad_len[3] = FILS_NONCE_LEN; + /* + * The (Re)Association Request frame from the Capability Information + * field to the FILS Session element (both inclusive). + */ + aad[4] = (const u8 *) &mgmt->u.assoc_req.capab_info; + aad_len[4] = crypt - aad[4]; + + if (aes_siv_decrypt(sm->PTK.kek, sm->PTK.kek_len, crypt, end - crypt, + 5, aad, aad_len, pos + (crypt - ie_start)) < 0) { + wpa_printf(MSG_DEBUG, + "FILS: Invalid AES-SIV data in the frame"); + return -1; + } + wpa_hexdump(MSG_DEBUG, "FILS: Decrypted Association Request elements", + pos, left - AES_BLOCK_SIZE); + + if (wpa_fils_validate_key_confirm(sm, pos, left - AES_BLOCK_SIZE) < 0) { + wpa_printf(MSG_DEBUG, "FILS: Key Confirm validation failed"); + return -1; + } + + return left - AES_BLOCK_SIZE; +} + + +int fils_encrypt_assoc(struct wpa_state_machine *sm, u8 *buf, + size_t current_len, size_t max_len, + const struct wpabuf *hlp) +{ + u8 *end = buf + max_len; + u8 *pos = buf + current_len; + struct ieee80211_mgmt *mgmt; + struct wpabuf *plain; + const u8 *aad[5]; + size_t aad_len[5]; + + if (!sm || !sm->PTK_valid) + return -1; + + wpa_hexdump(MSG_DEBUG, + "FILS: Association Response frame before FILS processing", + buf, current_len); + + mgmt = (struct ieee80211_mgmt *) buf; + + /* AES-SIV AAD vectors */ + + /* The AP's BSSID */ + aad[0] = mgmt->sa; + aad_len[0] = ETH_ALEN; + /* The STA's MAC address */ + aad[1] = mgmt->da; + aad_len[1] = ETH_ALEN; + /* The AP's nonce */ + aad[2] = sm->ANonce; + aad_len[2] = FILS_NONCE_LEN; + /* The STA's nonce */ + aad[3] = sm->SNonce; + aad_len[3] = FILS_NONCE_LEN; + /* + * The (Re)Association Response frame from the Capability Information + * field (the same offset in both Association and Reassociation + * Response frames) to the FILS Session element (both inclusive). + */ + aad[4] = (const u8 *) &mgmt->u.assoc_resp.capab_info; + aad_len[4] = pos - aad[4]; + + /* The following elements will be encrypted with AES-SIV */ + plain = fils_prepare_plainbuf(sm, hlp); + if (!plain) { + wpa_printf(MSG_DEBUG, "FILS: Plain buffer prep failed"); + return -1; + } + + if (pos + wpabuf_len(plain) + AES_BLOCK_SIZE > end) { + wpa_printf(MSG_DEBUG, + "FILS: Not enough room for FILS elements"); + wpabuf_clear_free(plain); + return -1; + } + + wpa_hexdump_buf_key(MSG_DEBUG, "FILS: Association Response plaintext", + plain); + + if (aes_siv_encrypt(sm->PTK.kek, sm->PTK.kek_len, + wpabuf_head(plain), wpabuf_len(plain), + 5, aad, aad_len, pos) < 0) { + wpabuf_clear_free(plain); + return -1; + } + + wpa_hexdump(MSG_DEBUG, + "FILS: Encrypted Association Response elements", + pos, AES_BLOCK_SIZE + wpabuf_len(plain)); + current_len += wpabuf_len(plain) + AES_BLOCK_SIZE; + wpabuf_clear_free(plain); + + sm->fils_completed = 1; + + return current_len; +} + + +static struct wpabuf * fils_prepare_plainbuf(struct wpa_state_machine *sm, + const struct wpabuf *hlp) +{ + struct wpabuf *plain; + u8 *len, *tmp, *tmp2; + u8 hdr[2]; + u8 *gtk, stub_gtk[32]; + size_t gtk_len; + struct wpa_group *gsm; + size_t plain_len; + struct wpa_auth_config *conf = &sm->wpa_auth->conf; + + plain_len = 1000 + ieee80211w_kde_len(sm); + if (conf->transition_disable) + plain_len += 2 + RSN_SELECTOR_LEN + 1; + plain = wpabuf_alloc(plain_len); + if (!plain) + return NULL; + + /* TODO: FILS Public Key */ + + /* FILS Key Confirmation */ + wpabuf_put_u8(plain, WLAN_EID_EXTENSION); /* Element ID */ + wpabuf_put_u8(plain, 1 + sm->fils_key_auth_len); /* Length */ + /* Element ID Extension */ + wpabuf_put_u8(plain, WLAN_EID_EXT_FILS_KEY_CONFIRM); + wpabuf_put_data(plain, sm->fils_key_auth_ap, sm->fils_key_auth_len); + + /* FILS HLP Container */ + if (hlp) + wpabuf_put_buf(plain, hlp); + + /* TODO: FILS IP Address Assignment */ + + /* Key Delivery */ + gsm = sm->group; + wpabuf_put_u8(plain, WLAN_EID_EXTENSION); /* Element ID */ + len = wpabuf_put(plain, 1); + wpabuf_put_u8(plain, WLAN_EID_EXT_KEY_DELIVERY); + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, + wpabuf_put(plain, WPA_KEY_RSC_LEN)); + /* GTK KDE */ + gtk = gsm->GTK[gsm->GN - 1]; + gtk_len = gsm->GTK_len; + if (conf->disable_gtk || sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random GTK to each STA to prevent use + * of GTK in the BSS. + */ + if (random_get_bytes(stub_gtk, gtk_len) < 0) { + wpabuf_clear_free(plain); + return NULL; + } + gtk = stub_gtk; + } + hdr[0] = gsm->GN & 0x03; + hdr[1] = 0; + tmp = wpabuf_put(plain, 0); + tmp2 = wpa_add_kde(tmp, RSN_KEY_DATA_GROUPKEY, hdr, 2, + gtk, gtk_len); + wpabuf_put(plain, tmp2 - tmp); + + /* IGTK KDE and BIGTK KDE */ + tmp = wpabuf_put(plain, 0); + tmp2 = ieee80211w_kde_add(sm, tmp); + wpabuf_put(plain, tmp2 - tmp); + + if (conf->transition_disable) { + tmp = wpabuf_put(plain, 0); + tmp2 = wpa_add_kde(tmp, WFA_KEY_DATA_TRANSITION_DISABLE, + &conf->transition_disable, 1, NULL, 0); + wpabuf_put(plain, tmp2 - tmp); + } + + *len = (u8 *) wpabuf_put(plain, 0) - len - 1; + +#ifdef CONFIG_OCV + if (wpa_auth_uses_ocv(sm)) { + struct wpa_channel_info ci; + u8 *pos; + + if (wpa_channel_info(sm->wpa_auth, &ci) != 0) { + wpa_printf(MSG_WARNING, + "FILS: Failed to get channel info for OCI element"); + wpabuf_clear_free(plain); + return NULL; + } +#ifdef CONFIG_TESTING_OPTIONS + if (conf->oci_freq_override_fils_assoc) { + wpa_printf(MSG_INFO, + "TEST: Override OCI frequency %d -> %u MHz", + ci.frequency, + conf->oci_freq_override_fils_assoc); + ci.frequency = conf->oci_freq_override_fils_assoc; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + pos = wpabuf_put(plain, OCV_OCI_EXTENDED_LEN); + if (ocv_insert_extended_oci(&ci, pos) < 0) { + wpabuf_clear_free(plain); + return NULL; + } + } +#endif /* CONFIG_OCV */ + + return plain; +} + + +int fils_set_tk(struct wpa_state_machine *sm) +{ + enum wpa_alg alg; + int klen; + + if (!sm || !sm->PTK_valid) { + wpa_printf(MSG_DEBUG, "FILS: No valid PTK available to set TK"); + return -1; + } + if (sm->tk_already_set) { + wpa_printf(MSG_DEBUG, "FILS: TK already set to the driver"); + return -1; + } + + alg = wpa_cipher_to_alg(sm->pairwise); + klen = wpa_cipher_key_len(sm->pairwise); + + wpa_printf(MSG_DEBUG, "FILS: Configure TK to the driver"); + if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, 0, + sm->PTK.tk, klen, KEY_FLAG_PAIRWISE_RX_TX)) { + wpa_printf(MSG_DEBUG, "FILS: Failed to set TK to the driver"); + return -1; + } + +#ifdef CONFIG_PASN + if (sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF) && + wpa_auth_set_ltf_keyseed(sm->wpa_auth, sm->addr, + sm->PTK.ltf_keyseed, + sm->PTK.ltf_keyseed_len)) { + wpa_printf(MSG_ERROR, + "FILS: Failed to set LTF keyseed to driver"); + return -1; + } +#endif /* CONFIG_PASN */ + + sm->pairwise_set = true; + sm->tk_already_set = true; + + wpa_auth_store_ptksa(sm->wpa_auth, sm->addr, sm->pairwise, + dot11RSNAConfigPMKLifetime, &sm->PTK); + + return 0; +} + + +u8 * hostapd_eid_assoc_fils_session(struct wpa_state_machine *sm, u8 *buf, + const u8 *fils_session, struct wpabuf *hlp) +{ + struct wpabuf *plain; + u8 *pos = buf; + + /* FILS Session */ + *pos++ = WLAN_EID_EXTENSION; /* Element ID */ + *pos++ = 1 + FILS_SESSION_LEN; /* Length */ + *pos++ = WLAN_EID_EXT_FILS_SESSION; /* Element ID Extension */ + os_memcpy(pos, fils_session, FILS_SESSION_LEN); + pos += FILS_SESSION_LEN; + + plain = fils_prepare_plainbuf(sm, hlp); + if (!plain) { + wpa_printf(MSG_DEBUG, "FILS: Plain buffer prep failed"); + return NULL; + } + + os_memcpy(pos, wpabuf_head(plain), wpabuf_len(plain)); + pos += wpabuf_len(plain); + + wpa_printf(MSG_DEBUG, "%s: plain buf_len: %zu", __func__, + wpabuf_len(plain)); + wpabuf_clear_free(plain); + sm->fils_completed = 1; + return pos; +} + +#endif /* CONFIG_FILS */ + + +#ifdef CONFIG_OCV +int get_sta_tx_parameters(struct wpa_state_machine *sm, int ap_max_chanwidth, + int ap_seg1_idx, int *bandwidth, int *seg1_idx) +{ + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + + if (!wpa_auth->cb->get_sta_tx_params) + return -1; + return wpa_auth->cb->get_sta_tx_params(wpa_auth->cb_ctx, sm->addr, + ap_max_chanwidth, ap_seg1_idx, + bandwidth, seg1_idx); +} +#endif /* CONFIG_OCV */ + + +static int wpa_auth_validate_ml_kdes_m2(struct wpa_state_machine *sm, + struct wpa_eapol_ie_parse *kde) +{ +#ifdef CONFIG_IEEE80211BE + int i; + unsigned int n_links = 0; + + if (sm->mld_assoc_link_id < 0) + return 0; + + /* MLD MAC address must be the same */ + if (!kde->mac_addr || + !ether_addr_equal(kde->mac_addr, sm->peer_mld_addr)) { + wpa_printf(MSG_DEBUG, "RSN: MLD: Invalid MLD address"); + return -1; + } + + /* Find matching link ID and the MAC address for each link */ + for_each_link(kde->valid_mlo_links, i) { + /* + * Each entry should contain the link information and the MAC + * address. + */ + if (kde->mlo_link_len[i] != 1 + ETH_ALEN) { + wpa_printf(MSG_DEBUG, + "RSN: MLD: Invalid MLO Link (ID %u) KDE len=%zu", + i, kde->mlo_link_len[i]); + return -1; + } + + if (!sm->mld_links[i].valid || i == sm->mld_assoc_link_id) { + wpa_printf(MSG_DEBUG, + "RSN: MLD: Invalid link ID=%u", i); + return -1; + } + + if (!ether_addr_equal(sm->mld_links[i].peer_addr, + kde->mlo_link[i] + 1)) { + wpa_printf(MSG_DEBUG, + "RSN: MLD: invalid MAC address=" MACSTR + " expected " MACSTR " (link ID %u)", + MAC2STR(kde->mlo_link[i] + 1), + MAC2STR(sm->mld_links[i].peer_addr), i); + return -1; + } + + n_links++; + } + + /* Must have the same number of MLO links (excluding the local one) */ + if (n_links != sm->n_mld_affiliated_links) { + wpa_printf(MSG_DEBUG, + "RSN: MLD: Expecting %u MLD links in msg 2, but got %u", + sm->n_mld_affiliated_links, n_links); + return -1; + } +#endif /* CONFIG_IEEE80211BE */ + + return 0; +} + + +SM_STATE(WPA_PTK, PTKCALCNEGOTIATING) +{ + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + struct wpa_ptk PTK; + int ok = 0, psk_found = 0; + const u8 *pmk = NULL; + size_t pmk_len; + int ft; + const u8 *eapol_key_ie, *key_data, *mic; + u16 key_info, ver, key_data_length; + size_t mic_len, eapol_key_ie_len; + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + struct wpa_eapol_ie_parse kde; + int vlan_id = 0; + int owe_ptk_workaround = !!wpa_auth->conf.owe_ptk_workaround; + u8 pmk_r0[PMK_LEN_MAX], pmk_r0_name[WPA_PMK_NAME_LEN]; + u8 pmk_r1[PMK_LEN_MAX]; + size_t key_len; + u8 *key_data_buf = NULL; + size_t key_data_buf_len = 0; + bool derive_kdk, no_kdk = false; + + SM_ENTRY_MA(WPA_PTK, PTKCALCNEGOTIATING, wpa_ptk); + sm->EAPOLKeyReceived = false; + sm->update_snonce = false; + os_memset(&PTK, 0, sizeof(PTK)); + + mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len); + + derive_kdk = sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF); + + /* WPA with IEEE 802.1X: use the derived PMK from EAP + * WPA-PSK: iterate through possible PSKs and select the one matching + * the packet */ + for (;;) { + if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) && + !wpa_key_mgmt_sae(sm->wpa_key_mgmt)) { + pmk = wpa_auth_get_psk(sm->wpa_auth, sm->addr, + sm->p2p_dev_addr, pmk, &pmk_len, + &vlan_id); + if (!pmk) + break; + psk_found = 1; +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt)) { + os_memcpy(sm->xxkey, pmk, pmk_len); + sm->xxkey_len = pmk_len; + } +#endif /* CONFIG_IEEE80211R_AP */ + } else { + pmk = sm->PMK; + pmk_len = sm->pmk_len; + } + + if ((!pmk || !pmk_len) && sm->pmksa) { + wpa_printf(MSG_DEBUG, "WPA: Use PMK from PMKSA cache"); + pmk = sm->pmksa->pmk; + pmk_len = sm->pmksa->pmk_len; + } + + no_kdk = false; + try_without_kdk: + if (wpa_derive_ptk(sm, sm->SNonce, pmk, pmk_len, &PTK, + owe_ptk_workaround == 2, pmk_r0, pmk_r1, + pmk_r0_name, &key_len, no_kdk) < 0) + break; + + if (mic_len && + wpa_verify_key_mic(sm->wpa_key_mgmt, pmk_len, &PTK, + sm->last_rx_eapol_key, + sm->last_rx_eapol_key_len) == 0) { + if (sm->PMK != pmk) { + os_memcpy(sm->PMK, pmk, pmk_len); + sm->pmk_len = pmk_len; + } + ok = 1; + break; + } + +#ifdef CONFIG_FILS + if (!mic_len && + wpa_aead_decrypt(sm, &PTK, sm->last_rx_eapol_key, + sm->last_rx_eapol_key_len, NULL) == 0) { + ok = 1; + break; + } +#endif /* CONFIG_FILS */ + +#ifdef CONFIG_OWE + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OWE && pmk_len > 32 && + owe_ptk_workaround == 1) { + wpa_printf(MSG_DEBUG, + "OWE: Try PTK derivation workaround with SHA256"); + owe_ptk_workaround = 2; + continue; + } +#endif /* CONFIG_OWE */ + + /* Some deployed STAs that advertise SecureLTF support in the + * RSNXE in (Re)Association Request frames, do not derive KDK + * during PTK generation. Try to work around this by checking if + * a PTK derived without KDK would result in a matching MIC. */ + if (!sm->wpa_auth->conf.force_kdk_derivation && + derive_kdk && !no_kdk) { + wpa_printf(MSG_DEBUG, + "Try new PTK derivation without KDK as a workaround"); + no_kdk = true; + goto try_without_kdk; + } + + if (!wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) || + wpa_key_mgmt_sae(sm->wpa_key_mgmt)) + break; + } + + if (no_kdk && ok) { + /* The workaround worked, so allow the 4-way handshake to be + * completed with the PTK that was derived without the KDK. */ + wpa_printf(MSG_DEBUG, + "PTK without KDK worked - misbehaving STA " + MACSTR, MAC2STR(sm->addr)); + } + + if (!ok && wpa_key_mgmt_wpa_psk_no_sae(sm->wpa_key_mgmt) && + wpa_auth->conf.radius_psk && wpa_auth->cb->request_radius_psk && + !sm->waiting_radius_psk) { + wpa_printf(MSG_DEBUG, "No PSK available - ask RADIUS server"); + wpa_auth->cb->request_radius_psk(wpa_auth->cb_ctx, sm->addr, + sm->wpa_key_mgmt, + sm->ANonce, + sm->last_rx_eapol_key, + sm->last_rx_eapol_key_len); + sm->waiting_radius_psk = 1; + goto out; + } + + if (!ok) { + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "invalid MIC in msg 2/4 of 4-Way Handshake"); + if (psk_found) + wpa_auth_psk_failure_report(sm->wpa_auth, sm->addr); + goto out; + } + + /* + * Note: last_rx_eapol_key length fields have already been validated in + * wpa_receive(). + */ + hdr = (struct ieee802_1x_hdr *) sm->last_rx_eapol_key; + key = (struct wpa_eapol_key *) (hdr + 1); + mic = (u8 *) (key + 1); + key_info = WPA_GET_BE16(key->key_info); + key_data = mic + mic_len + 2; + key_data_length = WPA_GET_BE16(mic + mic_len); + if (key_data_length > sm->last_rx_eapol_key_len - sizeof(*hdr) - + sizeof(*key) - mic_len - 2) + goto out; + + ver = key_info & WPA_KEY_INFO_TYPE_MASK; + if (mic_len && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) { + if (ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES && + ver != WPA_KEY_INFO_TYPE_AES_128_CMAC && + !wpa_use_aes_key_wrap(sm->wpa_key_mgmt)) { + wpa_printf(MSG_INFO, + "Unsupported EAPOL-Key Key Data field encryption"); + goto out; + } + + if (key_data_length < 8 || key_data_length % 8) { + wpa_printf(MSG_INFO, + "RSN: Unsupported AES-WRAP len %u", + key_data_length); + goto out; + } + key_data_length -= 8; /* AES-WRAP adds 8 bytes */ + key_data_buf = os_malloc(key_data_length); + if (!key_data_buf) + goto out; + key_data_buf_len = key_data_length; + if (aes_unwrap(PTK.kek, PTK.kek_len, key_data_length / 8, + key_data, key_data_buf)) { + bin_clear_free(key_data_buf, key_data_buf_len); + wpa_printf(MSG_INFO, + "RSN: AES unwrap failed - could not decrypt EAPOL-Key key data"); + goto out; + } + key_data = key_data_buf; + wpa_hexdump_key(MSG_DEBUG, "RSN: Decrypted EAPOL-Key Key Data", + key_data, key_data_length); + } + + if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "received EAPOL-Key msg 2/4 with invalid Key Data contents"); + goto out; + } + if (kde.rsn_ie) { + eapol_key_ie = kde.rsn_ie; + eapol_key_ie_len = kde.rsn_ie_len; + } else if (kde.osen) { + eapol_key_ie = kde.osen; + eapol_key_ie_len = kde.osen_len; + } else { + eapol_key_ie = kde.wpa_ie; + eapol_key_ie_len = kde.wpa_ie_len; + } + ft = sm->wpa == WPA_VERSION_WPA2 && wpa_key_mgmt_ft(sm->wpa_key_mgmt); + if (!sm->wpa_ie || + wpa_compare_rsn_ie(ft, sm->wpa_ie, sm->wpa_ie_len, + eapol_key_ie, eapol_key_ie_len)) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "WPA IE from (Re)AssocReq did not match with msg 2/4"); + if (sm->wpa_ie) { + wpa_hexdump(MSG_DEBUG, "WPA IE in AssocReq", + sm->wpa_ie, sm->wpa_ie_len); + } + wpa_hexdump(MSG_DEBUG, "WPA IE in msg 2/4", + eapol_key_ie, eapol_key_ie_len); + /* MLME-DEAUTHENTICATE.request */ + wpa_sta_disconnect(wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + goto out; + } + if ((!sm->rsnxe && kde.rsnxe) || + (sm->rsnxe && !kde.rsnxe) || + (sm->rsnxe && kde.rsnxe && + (sm->rsnxe_len != kde.rsnxe_len || + os_memcmp(sm->rsnxe, kde.rsnxe, sm->rsnxe_len) != 0))) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "RSNXE from (Re)AssocReq did not match the one in EAPOL-Key msg 2/4"); + wpa_hexdump(MSG_DEBUG, "RSNXE in AssocReq", + sm->rsnxe, sm->rsnxe_len); + wpa_hexdump(MSG_DEBUG, "RSNXE in EAPOL-Key msg 2/4", + kde.rsnxe, kde.rsnxe_len); + /* MLME-DEAUTHENTICATE.request */ + wpa_sta_disconnect(wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + goto out; + } +#ifdef CONFIG_OCV + if (wpa_auth_uses_ocv(sm)) { + struct wpa_channel_info ci; + int tx_chanwidth; + int tx_seg1_idx; + enum oci_verify_result res; + + if (wpa_channel_info(wpa_auth, &ci) != 0) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "Failed to get channel info to validate received OCI in EAPOL-Key 2/4"); + goto out; + } + + if (get_sta_tx_parameters(sm, + channel_width_to_int(ci.chanwidth), + ci.seg1_idx, &tx_chanwidth, + &tx_seg1_idx) < 0) + goto out; + + res = ocv_verify_tx_params(kde.oci, kde.oci_len, &ci, + tx_chanwidth, tx_seg1_idx); + if (wpa_auth_uses_ocv(sm) == 2 && res == OCI_NOT_FOUND) { + /* Work around misbehaving STAs */ + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "Disable OCV with a STA that does not send OCI"); + wpa_auth_set_ocv(sm, 0); + } else if (res != OCI_SUCCESS) { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "OCV failed: %s", ocv_errorstr); + if (wpa_auth->conf.msg_ctx) + wpa_msg(wpa_auth->conf.msg_ctx, MSG_INFO, + OCV_FAILURE "addr=" MACSTR + " frame=eapol-key-m2 error=%s", + MAC2STR(wpa_auth_get_spa(sm)), + ocv_errorstr); + goto out; + } + } +#endif /* CONFIG_OCV */ +#ifdef CONFIG_IEEE80211R_AP + if (ft && ft_check_msg_2_of_4(wpa_auth, sm, &kde) < 0) { + wpa_sta_disconnect(wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + goto out; + } +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_P2P + if (kde.ip_addr_req && kde.ip_addr_req[0] && + wpa_auth->ip_pool && WPA_GET_BE32(sm->ip_addr) == 0) { + int idx; + wpa_printf(MSG_DEBUG, + "P2P: IP address requested in EAPOL-Key exchange"); + idx = bitfield_get_first_zero(wpa_auth->ip_pool); + if (idx >= 0) { + u32 start = WPA_GET_BE32(wpa_auth->conf.ip_addr_start); + bitfield_set(wpa_auth->ip_pool, idx); + sm->ip_addr_bit = idx; + WPA_PUT_BE32(sm->ip_addr, start + idx); + wpa_printf(MSG_DEBUG, + "P2P: Assigned IP address %u.%u.%u.%u to " + MACSTR " (bit %u)", + sm->ip_addr[0], sm->ip_addr[1], + sm->ip_addr[2], sm->ip_addr[3], + MAC2STR(wpa_auth_get_spa(sm)), + sm->ip_addr_bit); + } + } +#endif /* CONFIG_P2P */ + +#ifdef CONFIG_DPP2 + if (DPP_VERSION > 1 && kde.dpp_kde) { + wpa_printf(MSG_DEBUG, + "DPP: peer Protocol Version %u Flags 0x%x", + kde.dpp_kde[0], kde.dpp_kde[1]); + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP && + wpa_auth->conf.dpp_pfs != 2 && + (kde.dpp_kde[1] & DPP_KDE_PFS_ALLOWED) && + !sm->dpp_z) { + wpa_printf(MSG_INFO, + "DPP: Peer indicated it supports PFS and local configuration allows this, but PFS was not negotiated for the association"); + wpa_sta_disconnect(wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + goto out; + } + } +#endif /* CONFIG_DPP2 */ + + if (wpa_auth_validate_ml_kdes_m2(sm, &kde) < 0) { + wpa_sta_disconnect(wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + return; + } + + if (vlan_id && wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) && + wpa_auth_update_vlan(wpa_auth, sm->addr, vlan_id) < 0) { + wpa_sta_disconnect(wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + goto out; + } + + sm->pending_1_of_4_timeout = 0; + eloop_cancel_timeout(wpa_send_eapol_timeout, sm->wpa_auth, sm); + + if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) && sm->PMK != pmk) { + /* PSK may have changed from the previous choice, so update + * state machine data based on whatever PSK was selected here. + */ + os_memcpy(sm->PMK, pmk, PMK_LEN); + sm->pmk_len = PMK_LEN; + } + + sm->MICVerified = true; + +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt) && !sm->ft_completed) { + wpa_printf(MSG_DEBUG, "FT: Store PMK-R0/PMK-R1"); + wpa_auth_ft_store_keys(sm, pmk_r0, pmk_r1, pmk_r0_name, + key_len); + } +#endif /* CONFIG_IEEE80211R_AP */ + + os_memcpy(&sm->PTK, &PTK, sizeof(PTK)); + forced_memzero(&PTK, sizeof(PTK)); + sm->PTK_valid = true; +out: + forced_memzero(pmk_r0, sizeof(pmk_r0)); + forced_memzero(pmk_r1, sizeof(pmk_r1)); + bin_clear_free(key_data_buf, key_data_buf_len); +} + + +SM_STATE(WPA_PTK, PTKCALCNEGOTIATING2) +{ + SM_ENTRY_MA(WPA_PTK, PTKCALCNEGOTIATING2, wpa_ptk); + sm->TimeoutCtr = 0; +} + + +static int ieee80211w_kde_len(struct wpa_state_machine *sm) +{ + size_t len = 0; + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + + if (sm->mgmt_frame_prot) { + len += 2 + RSN_SELECTOR_LEN + WPA_IGTK_KDE_PREFIX_LEN; + len += wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher); + } + + if (wpa_auth->conf.tx_bss_auth) + wpa_auth = wpa_auth->conf.tx_bss_auth; + if (sm->mgmt_frame_prot && sm->wpa_auth->conf.beacon_prot) { + len += 2 + RSN_SELECTOR_LEN + WPA_BIGTK_KDE_PREFIX_LEN; + len += wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher); + } + + return len; +} + + +static u8 * ieee80211w_kde_add(struct wpa_state_machine *sm, u8 *pos) +{ + struct wpa_igtk_kde igtk; + struct wpa_bigtk_kde bigtk; + struct wpa_group *gsm = sm->group; + u8 rsc[WPA_KEY_RSC_LEN]; + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + struct wpa_auth_config *conf = &wpa_auth->conf; + size_t len = wpa_cipher_key_len(conf->group_mgmt_cipher); + + if (!sm->mgmt_frame_prot) + return pos; + +#ifdef CONFIG_IEEE80211BE + if (sm->mld_assoc_link_id >= 0) + return pos; /* Use per-link MLO KDEs instead */ +#endif /* CONFIG_IEEE80211BE */ + + igtk.keyid[0] = gsm->GN_igtk; + igtk.keyid[1] = 0; + if (gsm->wpa_group_state != WPA_GROUP_SETKEYSDONE || + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_igtk, rsc) < 0) + os_memset(igtk.pn, 0, sizeof(igtk.pn)); + else + os_memcpy(igtk.pn, rsc, sizeof(igtk.pn)); + os_memcpy(igtk.igtk, gsm->IGTK[gsm->GN_igtk - 4], len); + if (conf->disable_gtk || sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random IGTK to each STA to prevent use of + * IGTK in the BSS. + */ + if (random_get_bytes(igtk.igtk, len) < 0) + return pos; + } + pos = wpa_add_kde(pos, RSN_KEY_DATA_IGTK, + (const u8 *) &igtk, WPA_IGTK_KDE_PREFIX_LEN + len, + NULL, 0); + forced_memzero(&igtk, sizeof(igtk)); + + if (wpa_auth->conf.tx_bss_auth) { + wpa_auth = wpa_auth->conf.tx_bss_auth; + conf = &wpa_auth->conf; + len = wpa_cipher_key_len(conf->group_mgmt_cipher); + gsm = wpa_auth->group; + } + + if (!sm->wpa_auth->conf.beacon_prot) + return pos; + + bigtk.keyid[0] = gsm->GN_bigtk; + bigtk.keyid[1] = 0; + if (gsm->wpa_group_state != WPA_GROUP_SETKEYSDONE || + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_bigtk, rsc) < 0) + os_memset(bigtk.pn, 0, sizeof(bigtk.pn)); + else + os_memcpy(bigtk.pn, rsc, sizeof(bigtk.pn)); + os_memcpy(bigtk.bigtk, gsm->BIGTK[gsm->GN_bigtk - 6], len); + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random BIGTK to each OSEN STA to prevent use + * of BIGTK in the BSS. + */ + if (random_get_bytes(bigtk.bigtk, len) < 0) + return pos; + } + pos = wpa_add_kde(pos, RSN_KEY_DATA_BIGTK, + (const u8 *) &bigtk, WPA_BIGTK_KDE_PREFIX_LEN + len, + NULL, 0); + forced_memzero(&bigtk, sizeof(bigtk)); + + return pos; +} + + +static int ocv_oci_len(struct wpa_state_machine *sm) +{ +#ifdef CONFIG_OCV + if (wpa_auth_uses_ocv(sm)) + return OCV_OCI_KDE_LEN; +#endif /* CONFIG_OCV */ + return 0; +} + + +static int ocv_oci_add(struct wpa_state_machine *sm, u8 **argpos, + unsigned int freq) +{ +#ifdef CONFIG_OCV + struct wpa_channel_info ci; + + if (!wpa_auth_uses_ocv(sm)) + return 0; + + if (wpa_channel_info(sm->wpa_auth, &ci) != 0) { + wpa_printf(MSG_WARNING, + "Failed to get channel info for OCI element"); + return -1; + } +#ifdef CONFIG_TESTING_OPTIONS + if (freq) { + wpa_printf(MSG_INFO, + "TEST: Override OCI KDE frequency %d -> %u MHz", + ci.frequency, freq); + ci.frequency = freq; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + return ocv_insert_oci_kde(&ci, argpos); +#else /* CONFIG_OCV */ + return 0; +#endif /* CONFIG_OCV */ +} + + +#ifdef CONFIG_TESTING_OPTIONS +static u8 * replace_ie(const char *name, const u8 *old_buf, size_t *len, u8 eid, + const u8 *ie, size_t ie_len) +{ + const u8 *elem; + u8 *buf; + + wpa_printf(MSG_DEBUG, "TESTING: %s EAPOL override", name); + wpa_hexdump(MSG_DEBUG, "TESTING: wpa_ie before override", + old_buf, *len); + buf = os_malloc(*len + ie_len); + if (!buf) + return NULL; + os_memcpy(buf, old_buf, *len); + elem = get_ie(buf, *len, eid); + if (elem) { + u8 elem_len = 2 + elem[1]; + + os_memmove((void *) elem, elem + elem_len, + *len - (elem - buf) - elem_len); + *len -= elem_len; + } + os_memcpy(buf + *len, ie, ie_len); + *len += ie_len; + wpa_hexdump(MSG_DEBUG, "TESTING: wpa_ie after EAPOL override", + buf, *len); + + return buf; +} +#endif /* CONFIG_TESTING_OPTIONS */ + + +#ifdef CONFIG_IEEE80211BE + +void wpa_auth_ml_get_key_info(struct wpa_authenticator *a, + struct wpa_auth_ml_link_key_info *info, + bool mgmt_frame_prot, bool beacon_prot) +{ + struct wpa_group *gsm = a->group; + u8 rsc[WPA_KEY_RSC_LEN]; + + wpa_printf(MSG_DEBUG, + "MLD: Get group key info: link_id=%u, IGTK=%u, BIGTK=%u", + info->link_id, mgmt_frame_prot, beacon_prot); + + info->gtkidx = gsm->GN & 0x03; + info->gtk = gsm->GTK[gsm->GN - 1]; + info->gtk_len = gsm->GTK_len; + + if (wpa_auth_get_seqnum(a, NULL, gsm->GN, rsc) < 0) + os_memset(info->pn, 0, sizeof(info->pn)); + else + os_memcpy(info->pn, rsc, sizeof(info->pn)); + + if (!mgmt_frame_prot) + return; + + info->igtkidx = gsm->GN_igtk; + info->igtk = gsm->IGTK[gsm->GN_igtk - 4]; + info->igtk_len = wpa_cipher_key_len(a->conf.group_mgmt_cipher); + + if (wpa_auth_get_seqnum(a, NULL, gsm->GN_igtk, rsc) < 0) + os_memset(info->ipn, 0, sizeof(info->ipn)); + else + os_memcpy(info->ipn, rsc, sizeof(info->ipn)); + + if (!beacon_prot) + return; + + if (a->conf.tx_bss_auth) { + a = a->conf.tx_bss_auth; + gsm = a->group; + } + + info->bigtkidx = gsm->GN_bigtk; + info->bigtk = gsm->BIGTK[gsm->GN_bigtk - 6]; + + if (wpa_auth_get_seqnum(a, NULL, gsm->GN_bigtk, rsc) < 0) + os_memset(info->bipn, 0, sizeof(info->bipn)); + else + os_memcpy(info->bipn, rsc, sizeof(info->bipn)); +} + + +static void wpa_auth_get_ml_key_info(struct wpa_authenticator *wpa_auth, + struct wpa_auth_ml_key_info *info) +{ + if (!wpa_auth->cb->get_ml_key_info) + return; + + wpa_auth->cb->get_ml_key_info(wpa_auth->cb_ctx, info); +} + + +static size_t wpa_auth_ml_group_kdes_len(struct wpa_state_machine *sm) +{ + struct wpa_authenticator *wpa_auth; + size_t kde_len = 0; + int link_id; + + if (sm->mld_assoc_link_id < 0) + return 0; + + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + if (!sm->mld_links[link_id].valid) + continue; + + wpa_auth = sm->mld_links[link_id].wpa_auth; + if (!wpa_auth || !wpa_auth->group) + continue; + + /* MLO GTK KDE + * Header + Key ID + Tx + LinkID + PN + GTK */ + kde_len += KDE_HDR_LEN + 1 + RSN_PN_LEN; + kde_len += wpa_auth->group->GTK_len; + + if (!sm->mgmt_frame_prot) + continue; + + if (wpa_auth->conf.tx_bss_auth) + wpa_auth = wpa_auth->conf.tx_bss_auth; + + /* MLO IGTK KDE + * Header + Key ID + IPN + LinkID + IGTK */ + kde_len += KDE_HDR_LEN + WPA_IGTK_KDE_PREFIX_LEN + 1; + kde_len += wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher); + + if (!wpa_auth->conf.beacon_prot) + continue; + + /* MLO BIGTK KDE + * Header + Key ID + BIPN + LinkID + BIGTK */ + kde_len += KDE_HDR_LEN + WPA_BIGTK_KDE_PREFIX_LEN + 1; + kde_len += wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher); + } + + wpa_printf(MSG_DEBUG, "MLO Group KDEs len = %zu", kde_len); + + return kde_len; +} + + +static u8 * wpa_auth_ml_group_kdes(struct wpa_state_machine *sm, u8 *pos) +{ + struct wpa_auth_ml_key_info ml_key_info; + unsigned int i, link_id; + u8 *start = pos; + + /* First fetch the key information from all the authenticators */ + os_memset(&ml_key_info, 0, sizeof(ml_key_info)); + ml_key_info.n_mld_links = sm->n_mld_affiliated_links + 1; + + /* + * Assume that management frame protection and beacon protection are the + * same on all links. + */ + ml_key_info.mgmt_frame_prot = sm->mgmt_frame_prot; + ml_key_info.beacon_prot = sm->wpa_auth->conf.beacon_prot; + + for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + if (!sm->mld_links[link_id].valid) + continue; + + ml_key_info.links[i++].link_id = link_id; + } + + wpa_auth_get_ml_key_info(sm->wpa_auth, &ml_key_info); + + /* Add MLO GTK KDEs */ + for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + if (!sm->mld_links[link_id].valid || + !ml_key_info.links[i].gtk_len) + continue; + + wpa_printf(MSG_DEBUG, "RSN: MLO GTK: link=%u", link_id); + wpa_hexdump_key(MSG_DEBUG, "RSN: MLO GTK", + ml_key_info.links[i].gtk, + ml_key_info.links[i].gtk_len); + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = RSN_SELECTOR_LEN + 1 + 6 + + ml_key_info.links[i].gtk_len; + + RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_GTK); + pos += RSN_SELECTOR_LEN; + + *pos++ = (ml_key_info.links[i].gtkidx & 0x3) | (link_id << 4); + + os_memcpy(pos, ml_key_info.links[i].pn, 6); + pos += 6; + + os_memcpy(pos, ml_key_info.links[i].gtk, + ml_key_info.links[i].gtk_len); + pos += ml_key_info.links[i].gtk_len; + + i++; + } + + if (!sm->mgmt_frame_prot) { + wpa_printf(MSG_DEBUG, "RSN: MLO Group KDE len = %ld", + pos - start); + return pos; + } + + /* Add MLO IGTK KDEs */ + for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + if (!sm->mld_links[link_id].valid || + !ml_key_info.links[i].igtk_len) + continue; + + wpa_printf(MSG_DEBUG, "RSN: MLO IGTK: link=%u", link_id); + wpa_hexdump_key(MSG_DEBUG, "RSN: MLO IGTK", + ml_key_info.links[i].igtk, + ml_key_info.links[i].igtk_len); + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = RSN_SELECTOR_LEN + 2 + 1 + + sizeof(ml_key_info.links[i].ipn) + + ml_key_info.links[i].igtk_len; + + RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_IGTK); + pos += RSN_SELECTOR_LEN; + + /* Add the Key ID */ + *pos++ = ml_key_info.links[i].igtkidx; + *pos++ = 0; + + /* Add the IPN */ + os_memcpy(pos, ml_key_info.links[i].ipn, + sizeof(ml_key_info.links[i].ipn)); + pos += sizeof(ml_key_info.links[i].ipn); + + *pos++ = ml_key_info.links[i].link_id << 4; + + os_memcpy(pos, ml_key_info.links[i].igtk, + ml_key_info.links[i].igtk_len); + pos += ml_key_info.links[i].igtk_len; + + i++; + } + + if (!sm->wpa_auth->conf.beacon_prot) { + wpa_printf(MSG_DEBUG, "RSN: MLO Group KDE len = %ld", + pos - start); + return pos; + } + + /* Add MLO BIGTK KDEs */ + for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + if (!sm->mld_links[link_id].valid || + !ml_key_info.links[i].bigtk || + !ml_key_info.links[i].igtk_len) + continue; + + wpa_printf(MSG_DEBUG, "RSN: MLO BIGTK: link=%u", link_id); + wpa_hexdump_key(MSG_DEBUG, "RSN: MLO BIGTK", + ml_key_info.links[i].bigtk, + ml_key_info.links[i].igtk_len); + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = RSN_SELECTOR_LEN + 2 + 1 + + sizeof(ml_key_info.links[i].bipn) + + ml_key_info.links[i].igtk_len; + + RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_BIGTK); + pos += RSN_SELECTOR_LEN; + + /* Add the Key ID */ + *pos++ = ml_key_info.links[i].bigtkidx; + *pos++ = 0; + + /* Add the BIPN */ + os_memcpy(pos, ml_key_info.links[i].bipn, + sizeof(ml_key_info.links[i].bipn)); + pos += sizeof(ml_key_info.links[i].bipn); + + *pos++ = ml_key_info.links[i].link_id << 4; + + os_memcpy(pos, ml_key_info.links[i].bigtk, + ml_key_info.links[i].igtk_len); + pos += ml_key_info.links[i].igtk_len; + + i++; + } + + wpa_printf(MSG_DEBUG, "RSN: MLO Group KDE len = %ld", pos - start); + return pos; +} + +#endif /* CONFIG_IEEE80211BE */ + + +static size_t wpa_auth_ml_kdes_len(struct wpa_state_machine *sm) +{ + size_t kde_len = 0; + +#ifdef CONFIG_IEEE80211BE + unsigned int link_id; + + if (sm->mld_assoc_link_id < 0) + return 0; + + /* For the MAC Address KDE */ + kde_len = 2 + RSN_SELECTOR_LEN + ETH_ALEN; + + /* MLO Link KDE for each link */ + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + struct wpa_authenticator *wpa_auth; + const u8 *ie; + + wpa_auth = wpa_get_link_auth(sm->wpa_auth, link_id); + if (!wpa_auth) + continue; + + kde_len += 2 + RSN_SELECTOR_LEN + 1 + ETH_ALEN; + ie = get_ie(wpa_auth->wpa_ie, wpa_auth->wpa_ie_len, + WLAN_EID_RSN); + if (ie) + kde_len += 2 + ie[1]; + ie = get_ie(wpa_auth->wpa_ie, wpa_auth->wpa_ie_len, + WLAN_EID_RSNX); + if (ie) + kde_len += 2 + ie[1]; + } + + kde_len += wpa_auth_ml_group_kdes_len(sm); +#endif /* CONFIG_IEEE80211BE */ + + return kde_len; +} + + +static u8 * wpa_auth_ml_kdes(struct wpa_state_machine *sm, u8 *pos) +{ +#ifdef CONFIG_IEEE80211BE + u8 link_id; + u8 *start = pos; + + if (sm->mld_assoc_link_id < 0) + return pos; + + wpa_printf(MSG_DEBUG, "RSN: MLD: Adding MAC Address KDE"); + pos = wpa_add_kde(pos, RSN_KEY_DATA_MAC_ADDR, + sm->wpa_auth->mld_addr, ETH_ALEN, NULL, 0); + + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + struct wpa_authenticator *wpa_auth; + const u8 *rsne, *rsnxe; + size_t rsne_len, rsnxe_len; + + wpa_auth = wpa_get_link_auth(sm->wpa_auth, link_id); + if (!wpa_auth) + continue; + + rsne = get_ie(wpa_auth->wpa_ie, wpa_auth->wpa_ie_len, + WLAN_EID_RSN); + rsne_len = rsne ? 2 + rsne[1] : 0; + + rsnxe = get_ie(wpa_auth->wpa_ie, wpa_auth->wpa_ie_len, + WLAN_EID_RSNX); + rsnxe_len = rsnxe ? 2 + rsnxe[1] : 0; + + wpa_printf(MSG_DEBUG, + "RSN: MLO Link: link=%u, len=%zu", link_id, + RSN_SELECTOR_LEN + 1 + ETH_ALEN + + rsne_len + rsnxe_len); + + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = RSN_SELECTOR_LEN + 1 + ETH_ALEN + + rsne_len + rsnxe_len; + + RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_LINK); + pos += RSN_SELECTOR_LEN; + + /* Add the Link Information */ + *pos = link_id; + if (rsne_len) + *pos |= RSN_MLO_LINK_KDE_LI_RSNE_INFO; + if (rsnxe_len) + *pos |= RSN_MLO_LINK_KDE_LI_RSNXE_INFO; + + pos++; + os_memcpy(pos, wpa_auth->addr, ETH_ALEN); + pos += ETH_ALEN; + + if (rsne_len) { + os_memcpy(pos, rsne, rsne_len); + pos += rsne_len; + } + + if (rsnxe_len) { + os_memcpy(pos, rsnxe, rsnxe_len); + pos += rsnxe_len; + } + } + + wpa_printf(MSG_DEBUG, "RSN: MLO Link KDE len = %ld", pos - start); + pos = wpa_auth_ml_group_kdes(sm, pos); +#endif /* CONFIG_IEEE80211BE */ + + return pos; +} + + +SM_STATE(WPA_PTK, PTKINITNEGOTIATING) +{ + u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde = NULL, *pos, stub_gtk[32]; + size_t gtk_len, kde_len = 0, wpa_ie_len; + struct wpa_group *gsm = sm->group; + u8 *wpa_ie; + int secure, gtkidx, encr = 0; + u8 *wpa_ie_buf = NULL, *wpa_ie_buf2 = NULL; + u8 hdr[2]; + struct wpa_auth_config *conf = &sm->wpa_auth->conf; +#ifdef CONFIG_IEEE80211BE + bool is_mld = sm->mld_assoc_link_id >= 0; +#else /* CONFIG_IEEE80211BE */ + bool is_mld = false; +#endif /* CONFIG_IEEE80211BE */ + + SM_ENTRY_MA(WPA_PTK, PTKINITNEGOTIATING, wpa_ptk); + sm->TimeoutEvt = false; + + sm->TimeoutCtr++; + if (conf->wpa_disable_eapol_key_retries && sm->TimeoutCtr > 1) { + /* Do not allow retransmission of EAPOL-Key msg 3/4 */ + return; + } + if (sm->TimeoutCtr > conf->wpa_pairwise_update_count) { + /* No point in sending the EAPOL-Key - we will disconnect + * immediately following this. */ + return; + } + + /* Send EAPOL(1, 1, 1, Pair, P, RSC, ANonce, MIC(PTK), RSNIE, [MDIE], + GTK[GN], IGTK, [BIGTK], [FTIE], [TIE * 2]) + */ + os_memset(rsc, 0, WPA_KEY_RSC_LEN); + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, rsc); + /* If FT is used, wpa_auth->wpa_ie includes both RSNIE and MDIE */ + wpa_ie = sm->wpa_auth->wpa_ie; + wpa_ie_len = sm->wpa_auth->wpa_ie_len; + if (sm->wpa == WPA_VERSION_WPA && (conf->wpa & WPA_PROTO_RSN) && + wpa_ie_len > wpa_ie[1] + 2U && wpa_ie[0] == WLAN_EID_RSN) { + /* WPA-only STA, remove RSN IE and possible MDIE */ + wpa_ie = wpa_ie + wpa_ie[1] + 2; + if (wpa_ie[0] == WLAN_EID_RSNX) + wpa_ie = wpa_ie + wpa_ie[1] + 2; + if (wpa_ie[0] == WLAN_EID_MOBILITY_DOMAIN) + wpa_ie = wpa_ie + wpa_ie[1] + 2; + wpa_ie_len = wpa_ie[1] + 2; + } +#ifdef CONFIG_TESTING_OPTIONS + if (conf->rsne_override_eapol_set) { + wpa_ie_buf2 = replace_ie( + "RSNE", wpa_ie, &wpa_ie_len, WLAN_EID_RSN, + conf->rsne_override_eapol, + conf->rsne_override_eapol_len); + if (!wpa_ie_buf2) + goto done; + wpa_ie = wpa_ie_buf2; + } + if (conf->rsnxe_override_eapol_set) { + wpa_ie_buf = replace_ie( + "RSNXE", wpa_ie, &wpa_ie_len, WLAN_EID_RSNX, + conf->rsnxe_override_eapol, + conf->rsnxe_override_eapol_len); + if (!wpa_ie_buf) + goto done; + wpa_ie = wpa_ie_buf; + } +#endif /* CONFIG_TESTING_OPTIONS */ + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "sending 3/4 msg of 4-Way Handshake"); + if (sm->wpa == WPA_VERSION_WPA2) { + if (sm->use_ext_key_id && sm->TimeoutCtr == 1 && + wpa_auth_set_key(sm->wpa_auth, 0, + wpa_cipher_to_alg(sm->pairwise), + sm->addr, + sm->keyidx_active, sm->PTK.tk, + wpa_cipher_key_len(sm->pairwise), + KEY_FLAG_PAIRWISE_RX)) { + wpa_sta_disconnect(sm->wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + return; + } + +#ifdef CONFIG_PASN + if (sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, + WLAN_RSNX_CAPAB_SECURE_LTF) && + wpa_auth_set_ltf_keyseed(sm->wpa_auth, sm->addr, + sm->PTK.ltf_keyseed, + sm->PTK.ltf_keyseed_len)) { + wpa_printf(MSG_ERROR, + "WPA: Failed to set LTF keyseed to driver"); + wpa_sta_disconnect(sm->wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + return; + } +#endif /* CONFIG_PASN */ + + /* WPA2 send GTK in the 4-way handshake */ + secure = 1; + gtk = gsm->GTK[gsm->GN - 1]; + gtk_len = gsm->GTK_len; + if (conf->disable_gtk || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random GTK to each STA to prevent use + * of GTK in the BSS. + */ + if (random_get_bytes(stub_gtk, gtk_len) < 0) + goto done; + gtk = stub_gtk; + } + gtkidx = gsm->GN; + _rsc = rsc; + encr = 1; + } else { + /* WPA does not include GTK in msg 3/4 */ + secure = 0; + gtk = NULL; + gtk_len = 0; + gtkidx = 0; + _rsc = NULL; + if (sm->rx_eapol_key_secure) { + /* + * It looks like Windows 7 supplicant tries to use + * Secure bit in msg 2/4 after having reported Michael + * MIC failure and it then rejects the 4-way handshake + * if msg 3/4 does not set Secure bit. Work around this + * by setting the Secure bit here even in the case of + * WPA if the supplicant used it first. + */ + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "STA used Secure bit in WPA msg 2/4 - set Secure for 3/4 as workaround"); + secure = 1; + } + } + + kde_len = wpa_ie_len + ieee80211w_kde_len(sm) + ocv_oci_len(sm); + + if (sm->use_ext_key_id) + kde_len += 2 + RSN_SELECTOR_LEN + 2; + + if (gtk) + kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len; +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + kde_len += 2 + PMKID_LEN; /* PMKR1Name into RSN IE */ + kde_len += 300; /* FTIE + 2 * TIE */ + } +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_P2P + if (WPA_GET_BE32(sm->ip_addr) > 0) + kde_len += 2 + RSN_SELECTOR_LEN + 3 * 4; +#endif /* CONFIG_P2P */ + + if (conf->transition_disable) + kde_len += 2 + RSN_SELECTOR_LEN + 1; + +#ifdef CONFIG_DPP2 + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP) + kde_len += 2 + RSN_SELECTOR_LEN + 2; +#endif /* CONFIG_DPP2 */ + + kde_len += wpa_auth_ml_kdes_len(sm); + + if (sm->ssid_protection) + kde_len += 2 + conf->ssid_len; + +#ifdef CONFIG_TESTING_OPTIONS + if (conf->eapol_m3_elements) + kde_len += wpabuf_len(conf->eapol_m3_elements); +#endif /* CONFIG_TESTING_OPTIONS */ + + kde = os_malloc(kde_len); + if (!kde) + goto done; + + pos = kde; + if (!is_mld) { + os_memcpy(pos, wpa_ie, wpa_ie_len); + pos += wpa_ie_len; + } +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + int res; + size_t elen; + + elen = pos - kde; + res = wpa_insert_pmkid(kde, &elen, sm->pmk_r1_name, true); + if (res < 0) { + wpa_printf(MSG_ERROR, + "FT: Failed to insert PMKR1Name into RSN IE in EAPOL-Key data"); + goto done; + } + pos -= wpa_ie_len; + pos += elen; + } +#endif /* CONFIG_IEEE80211R_AP */ + hdr[1] = 0; + + if (sm->use_ext_key_id) { + hdr[0] = sm->keyidx_active & 0x01; + pos = wpa_add_kde(pos, RSN_KEY_DATA_KEYID, hdr, 2, NULL, 0); + } + + if (gtk && !is_mld) { + hdr[0] = gtkidx & 0x03; + pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2, + gtk, gtk_len); + } + pos = ieee80211w_kde_add(sm, pos); + if (ocv_oci_add(sm, &pos, conf->oci_freq_override_eapol_m3) < 0) + goto done; + +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + int res; + + if (sm->assoc_resp_ftie && + kde + kde_len - pos >= 2 + sm->assoc_resp_ftie[1]) { + os_memcpy(pos, sm->assoc_resp_ftie, + 2 + sm->assoc_resp_ftie[1]); + res = 2 + sm->assoc_resp_ftie[1]; + } else { + res = wpa_write_ftie(conf, sm->wpa_key_mgmt, + sm->xxkey_len, + conf->r0_key_holder, + conf->r0_key_holder_len, + NULL, NULL, pos, + kde + kde_len - pos, + NULL, 0, 0); + } + if (res < 0) { + wpa_printf(MSG_ERROR, + "FT: Failed to insert FTIE into EAPOL-Key Key Data"); + goto done; + } + pos += res; + + /* TIE[ReassociationDeadline] (TU) */ + *pos++ = WLAN_EID_TIMEOUT_INTERVAL; + *pos++ = 5; + *pos++ = WLAN_TIMEOUT_REASSOC_DEADLINE; + WPA_PUT_LE32(pos, conf->reassociation_deadline); + pos += 4; + + /* TIE[KeyLifetime] (seconds) */ + *pos++ = WLAN_EID_TIMEOUT_INTERVAL; + *pos++ = 5; + *pos++ = WLAN_TIMEOUT_KEY_LIFETIME; + WPA_PUT_LE32(pos, conf->r0_key_lifetime); + pos += 4; + } +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_P2P + if (WPA_GET_BE32(sm->ip_addr) > 0) { + u8 addr[3 * 4]; + os_memcpy(addr, sm->ip_addr, 4); + os_memcpy(addr + 4, conf->ip_addr_mask, 4); + os_memcpy(addr + 8, conf->ip_addr_go, 4); + pos = wpa_add_kde(pos, WFA_KEY_DATA_IP_ADDR_ALLOC, + addr, sizeof(addr), NULL, 0); + } +#endif /* CONFIG_P2P */ + + if (conf->transition_disable) + pos = wpa_add_kde(pos, WFA_KEY_DATA_TRANSITION_DISABLE, + &conf->transition_disable, 1, NULL, 0); + +#ifdef CONFIG_DPP2 + if (DPP_VERSION > 1 && sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP) { + u8 payload[2]; + + payload[0] = DPP_VERSION; /* Protocol Version */ + payload[1] = 0; /* Flags */ + if (conf->dpp_pfs == 0) + payload[1] |= DPP_KDE_PFS_ALLOWED; + else if (conf->dpp_pfs == 1) + payload[1] |= DPP_KDE_PFS_ALLOWED | + DPP_KDE_PFS_REQUIRED; + pos = wpa_add_kde(pos, WFA_KEY_DATA_DPP, + payload, sizeof(payload), NULL, 0); + } +#endif /* CONFIG_DPP2 */ + + pos = wpa_auth_ml_kdes(sm, pos); + + if (sm->ssid_protection) { + *pos++ = WLAN_EID_SSID; + *pos++ = conf->ssid_len; + os_memcpy(pos, conf->ssid, conf->ssid_len); + pos += conf->ssid_len; + } + +#ifdef CONFIG_TESTING_OPTIONS + if (conf->eapol_m3_elements) { + os_memcpy(pos, wpabuf_head(conf->eapol_m3_elements), + wpabuf_len(conf->eapol_m3_elements)); + pos += wpabuf_len(conf->eapol_m3_elements); + } + + if (conf->eapol_m3_no_encrypt) + encr = 0; +#endif /* CONFIG_TESTING_OPTIONS */ + + wpa_send_eapol(sm->wpa_auth, sm, + (secure ? WPA_KEY_INFO_SECURE : 0) | + (wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len) ? + WPA_KEY_INFO_MIC : 0) | + WPA_KEY_INFO_ACK | WPA_KEY_INFO_INSTALL | + WPA_KEY_INFO_KEY_TYPE, + _rsc, sm->ANonce, kde, pos - kde, 0, encr); +done: + bin_clear_free(kde, kde_len); + os_free(wpa_ie_buf); + os_free(wpa_ie_buf2); +} + + +static int wpa_auth_validate_ml_kdes_m4(struct wpa_state_machine *sm) +{ +#ifdef CONFIG_IEEE80211BE + const struct ieee802_1x_hdr *hdr; + const struct wpa_eapol_key *key; + struct wpa_eapol_ie_parse kde; + const u8 *key_data, *mic; + u16 key_data_length; + size_t mic_len; + + if (sm->mld_assoc_link_id < 0) + return 0; + + /* + * Note: last_rx_eapol_key length fields have already been validated in + * wpa_receive(). + */ + mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len); + + hdr = (const struct ieee802_1x_hdr *) sm->last_rx_eapol_key; + key = (const struct wpa_eapol_key *) (hdr + 1); + mic = (const u8 *) (key + 1); + key_data = mic + mic_len + 2; + key_data_length = WPA_GET_BE16(mic + mic_len); + if (key_data_length > sm->last_rx_eapol_key_len - sizeof(*hdr) - + sizeof(*key) - mic_len - 2) + return -1; + + if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) { + wpa_auth_vlogger(sm->wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "received EAPOL-Key msg 4/4 with invalid Key Data contents"); + return -1; + } + + /* MLD MAC address must be the same */ + if (!kde.mac_addr || + !ether_addr_equal(kde.mac_addr, sm->peer_mld_addr)) { + wpa_printf(MSG_DEBUG, + "MLD: Mismatching or missing MLD address in EAPOL-Key msg 4/4"); + return -1; + } + + wpa_printf(MSG_DEBUG, "MLD: MLD address in EAPOL-Key msg 4/4: " MACSTR, + MAC2STR(kde.mac_addr)); +#endif /* CONFIG_IEEE80211BE */ + + return 0; +} + + +SM_STATE(WPA_PTK, PTKINITDONE) +{ + SM_ENTRY_MA(WPA_PTK, PTKINITDONE, wpa_ptk); + sm->EAPOLKeyReceived = false; + + if (wpa_auth_validate_ml_kdes_m4(sm) < 0) { + wpa_sta_disconnect(sm->wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + return; + } + + if (sm->Pair) { + enum wpa_alg alg = wpa_cipher_to_alg(sm->pairwise); + int klen = wpa_cipher_key_len(sm->pairwise); + int res; + + if (sm->use_ext_key_id) + res = wpa_auth_set_key(sm->wpa_auth, 0, 0, sm->addr, + sm->keyidx_active, NULL, 0, + KEY_FLAG_PAIRWISE_RX_TX_MODIFY); + else + res = wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, + 0, sm->PTK.tk, klen, + KEY_FLAG_PAIRWISE_RX_TX); + if (res) { + wpa_sta_disconnect(sm->wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + return; + } + +#ifdef CONFIG_PASN + if (sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, + WLAN_RSNX_CAPAB_SECURE_LTF) && + wpa_auth_set_ltf_keyseed(sm->wpa_auth, sm->addr, + sm->PTK.ltf_keyseed, + sm->PTK.ltf_keyseed_len)) { + wpa_printf(MSG_ERROR, + "WPA: Failed to set LTF keyseed to driver"); + wpa_sta_disconnect(sm->wpa_auth, sm->addr, + WLAN_REASON_PREV_AUTH_NOT_VALID); + return; + } +#endif /* CONFIG_PASN */ + + /* FIX: MLME-SetProtection.Request(TA, Tx_Rx) */ + sm->pairwise_set = true; + + wpa_auth_set_ptk_rekey_timer(sm); + wpa_auth_store_ptksa(sm->wpa_auth, sm->addr, sm->pairwise, + dot11RSNAConfigPMKLifetime, &sm->PTK); + + if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) || + sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OWE) { + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, + WPA_EAPOL_authorized, 1); + } + } + + if (0 /* IBSS == TRUE */) { + sm->keycount++; + if (sm->keycount == 2) { + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, + WPA_EAPOL_portValid, 1); + } + } else { + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_portValid, + 1); + } + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_keyAvailable, + false); + wpa_auth_set_eapol(sm->wpa_auth, sm->addr, WPA_EAPOL_keyDone, true); + if (sm->wpa == WPA_VERSION_WPA) + sm->PInitAKeys = true; + else + sm->has_GTK = true; + wpa_auth_vlogger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "pairwise key handshake completed (%s)", + sm->wpa == WPA_VERSION_WPA ? "WPA" : "RSN"); + wpa_msg(sm->wpa_auth->conf.msg_ctx, MSG_INFO, "EAPOL-4WAY-HS-COMPLETED " + MACSTR, MAC2STR(sm->addr)); + +#ifdef CONFIG_IEEE80211R_AP + wpa_ft_push_pmk_r1(sm->wpa_auth, wpa_auth_get_spa(sm)); +#endif /* CONFIG_IEEE80211R_AP */ + + sm->ptkstart_without_success = 0; +} + + +SM_STEP(WPA_PTK) +{ + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + struct wpa_auth_config *conf = &wpa_auth->conf; + + if (sm->Init) + SM_ENTER(WPA_PTK, INITIALIZE); + else if (sm->Disconnect + /* || FIX: dot11RSNAConfigSALifetime timeout */) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "WPA_PTK: sm->Disconnect"); + SM_ENTER(WPA_PTK, DISCONNECT); + } + else if (sm->DeauthenticationRequest) + SM_ENTER(WPA_PTK, DISCONNECTED); + else if (sm->AuthenticationRequest) + SM_ENTER(WPA_PTK, AUTHENTICATION); + else if (sm->ReAuthenticationRequest) + SM_ENTER(WPA_PTK, AUTHENTICATION2); + else if (sm->PTKRequest) { + if (wpa_auth_sm_ptk_update(sm) < 0) + SM_ENTER(WPA_PTK, DISCONNECTED); + else + SM_ENTER(WPA_PTK, PTKSTART); + } else switch (sm->wpa_ptk_state) { + case WPA_PTK_INITIALIZE: + break; + case WPA_PTK_DISCONNECT: + SM_ENTER(WPA_PTK, DISCONNECTED); + break; + case WPA_PTK_DISCONNECTED: + SM_ENTER(WPA_PTK, INITIALIZE); + break; + case WPA_PTK_AUTHENTICATION: + SM_ENTER(WPA_PTK, AUTHENTICATION2); + break; + case WPA_PTK_AUTHENTICATION2: + if (wpa_key_mgmt_wpa_ieee8021x(sm->wpa_key_mgmt) && + wpa_auth_get_eapol(wpa_auth, sm->addr, + WPA_EAPOL_keyRun)) + SM_ENTER(WPA_PTK, INITPMK); + else if (wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OWE + /* FIX: && 802.1X::keyRun */) + SM_ENTER(WPA_PTK, INITPSK); + else if (sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP) + SM_ENTER(WPA_PTK, INITPMK); + break; + case WPA_PTK_INITPMK: + if (wpa_auth_get_eapol(wpa_auth, sm->addr, + WPA_EAPOL_keyAvailable)) { + SM_ENTER(WPA_PTK, PTKSTART); +#ifdef CONFIG_DPP + } else if (sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP && sm->pmksa) { + SM_ENTER(WPA_PTK, PTKSTART); +#endif /* CONFIG_DPP */ + } else { + wpa_auth->dot11RSNA4WayHandshakeFailures++; + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "INITPMK - keyAvailable = false"); + SM_ENTER(WPA_PTK, DISCONNECT); + } + break; + case WPA_PTK_INITPSK: + if (wpa_auth_get_psk(wpa_auth, sm->addr, sm->p2p_dev_addr, + NULL, NULL, NULL)) { + SM_ENTER(WPA_PTK, PTKSTART); +#ifdef CONFIG_SAE + } else if (wpa_auth_uses_sae(sm) && sm->pmksa) { + SM_ENTER(WPA_PTK, PTKSTART); +#endif /* CONFIG_SAE */ + } else if (wpa_key_mgmt_wpa_psk_no_sae(sm->wpa_key_mgmt) && + wpa_auth->conf.radius_psk) { + wpa_printf(MSG_DEBUG, + "INITPSK: No PSK yet available for STA - use RADIUS later"); + SM_ENTER(WPA_PTK, PTKSTART); + } else { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "no PSK configured for the STA"); + wpa_auth->dot11RSNA4WayHandshakeFailures++; + SM_ENTER(WPA_PTK, DISCONNECT); + } + break; + case WPA_PTK_PTKSTART: + if (sm->EAPOLKeyReceived && !sm->EAPOLKeyRequest && + sm->EAPOLKeyPairwise) + SM_ENTER(WPA_PTK, PTKCALCNEGOTIATING); + else if (sm->TimeoutCtr > conf->wpa_pairwise_update_count) { + wpa_auth->dot11RSNA4WayHandshakeFailures++; + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "PTKSTART: Retry limit %u reached", + conf->wpa_pairwise_update_count); + sm->disconnect_reason = + WLAN_REASON_4WAY_HANDSHAKE_TIMEOUT; + SM_ENTER(WPA_PTK, DISCONNECT); + } else if (sm->TimeoutEvt) + SM_ENTER(WPA_PTK, PTKSTART); + break; + case WPA_PTK_PTKCALCNEGOTIATING: + if (sm->MICVerified) + SM_ENTER(WPA_PTK, PTKCALCNEGOTIATING2); + else if (sm->EAPOLKeyReceived && !sm->EAPOLKeyRequest && + sm->EAPOLKeyPairwise) + SM_ENTER(WPA_PTK, PTKCALCNEGOTIATING); + else if (sm->TimeoutEvt) + SM_ENTER(WPA_PTK, PTKSTART); + break; + case WPA_PTK_PTKCALCNEGOTIATING2: + SM_ENTER(WPA_PTK, PTKINITNEGOTIATING); + break; + case WPA_PTK_PTKINITNEGOTIATING: + if (sm->update_snonce) + SM_ENTER(WPA_PTK, PTKCALCNEGOTIATING); + else if (sm->EAPOLKeyReceived && !sm->EAPOLKeyRequest && + sm->EAPOLKeyPairwise && sm->MICVerified) + SM_ENTER(WPA_PTK, PTKINITDONE); + else if (sm->TimeoutCtr > + conf->wpa_pairwise_update_count || + (conf->wpa_disable_eapol_key_retries && + sm->TimeoutCtr > 1)) { + wpa_auth->dot11RSNA4WayHandshakeFailures++; + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "PTKINITNEGOTIATING: Retry limit %u reached", + conf->wpa_pairwise_update_count); + sm->disconnect_reason = + WLAN_REASON_4WAY_HANDSHAKE_TIMEOUT; + SM_ENTER(WPA_PTK, DISCONNECT); + } else if (sm->TimeoutEvt) + SM_ENTER(WPA_PTK, PTKINITNEGOTIATING); + break; + case WPA_PTK_PTKINITDONE: + break; + } +} + + +SM_STATE(WPA_PTK_GROUP, IDLE) +{ + SM_ENTRY_MA(WPA_PTK_GROUP, IDLE, wpa_ptk_group); + if (sm->Init) { + /* Init flag is not cleared here, so avoid busy + * loop by claiming nothing changed. */ + sm->changed = false; + } + sm->GTimeoutCtr = 0; +} + + +SM_STATE(WPA_PTK_GROUP, REKEYNEGOTIATING) +{ + u8 rsc[WPA_KEY_RSC_LEN]; + struct wpa_group *gsm = sm->group; + const u8 *kde = NULL; + u8 *kde_buf = NULL, *pos, hdr[2]; + size_t kde_len = 0; + u8 *gtk, stub_gtk[32]; + struct wpa_auth_config *conf = &sm->wpa_auth->conf; + bool is_mld = false; + +#ifdef CONFIG_IEEE80211BE + is_mld = sm->mld_assoc_link_id >= 0; +#endif /* CONFIG_IEEE80211BE */ + + SM_ENTRY_MA(WPA_PTK_GROUP, REKEYNEGOTIATING, wpa_ptk_group); + + sm->GTimeoutCtr++; + if (conf->wpa_disable_eapol_key_retries && sm->GTimeoutCtr > 1) { + /* Do not allow retransmission of EAPOL-Key group msg 1/2 */ + return; + } + if (sm->GTimeoutCtr > conf->wpa_group_update_count) { + /* No point in sending the EAPOL-Key - we will disconnect + * immediately following this. */ + return; + } + + if (sm->wpa == WPA_VERSION_WPA) + sm->PInitAKeys = false; + sm->TimeoutEvt = false; + /* Send EAPOL(1, 1, 1, !Pair, G, RSC, GNonce, MIC(PTK), GTK[GN]) */ + os_memset(rsc, 0, WPA_KEY_RSC_LEN); + if (gsm->wpa_group_state == WPA_GROUP_SETKEYSDONE) + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, rsc); + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "sending 1/2 msg of Group Key Handshake"); + + gtk = gsm->GTK[gsm->GN - 1]; + if (conf->disable_gtk || sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random GTK to each STA to prevent use + * of GTK in the BSS. + */ + if (random_get_bytes(stub_gtk, gsm->GTK_len) < 0) + return; + gtk = stub_gtk; + } + + if (sm->wpa == WPA_VERSION_WPA2 && !is_mld) { + kde_len = 2 + RSN_SELECTOR_LEN + 2 + gsm->GTK_len + + ieee80211w_kde_len(sm) + ocv_oci_len(sm); + kde_buf = os_malloc(kde_len); + if (!kde_buf) + return; + + kde = pos = kde_buf; + hdr[0] = gsm->GN & 0x03; + hdr[1] = 0; + pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2, + gtk, gsm->GTK_len); + pos = ieee80211w_kde_add(sm, pos); + if (ocv_oci_add(sm, &pos, + conf->oci_freq_override_eapol_g1) < 0) { + os_free(kde_buf); + return; + } + kde_len = pos - kde; +#ifdef CONFIG_IEEE80211BE + } else if (sm->wpa == WPA_VERSION_WPA2 && is_mld) { + kde_len = wpa_auth_ml_group_kdes_len(sm); + if (kde_len) { + kde_buf = os_malloc(kde_len); + if (!kde_buf) + return; + + kde = pos = kde_buf; + pos = wpa_auth_ml_group_kdes(sm, pos); + kde_len = pos - kde_buf; + } +#endif /* CONFIG_IEEE80211BE */ + } else { + kde = gtk; + kde_len = gsm->GTK_len; + } + + wpa_send_eapol(sm->wpa_auth, sm, + WPA_KEY_INFO_SECURE | + (wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len) ? + WPA_KEY_INFO_MIC : 0) | + WPA_KEY_INFO_ACK | + (!sm->Pair ? WPA_KEY_INFO_INSTALL : 0), + rsc, NULL, kde, kde_len, gsm->GN, 1); + + bin_clear_free(kde_buf, kde_len); +} + + +SM_STATE(WPA_PTK_GROUP, REKEYESTABLISHED) +{ + struct wpa_authenticator *wpa_auth = sm->wpa_auth; +#ifdef CONFIG_OCV + const u8 *key_data, *mic; + struct ieee802_1x_hdr *hdr; + struct wpa_eapol_key *key; + struct wpa_eapol_ie_parse kde; + size_t mic_len; + u16 key_data_length; +#endif /* CONFIG_OCV */ + + SM_ENTRY_MA(WPA_PTK_GROUP, REKEYESTABLISHED, wpa_ptk_group); + sm->EAPOLKeyReceived = false; + +#ifdef CONFIG_OCV + mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len); + + /* + * Note: last_rx_eapol_key length fields have already been validated in + * wpa_receive(). + */ + hdr = (struct ieee802_1x_hdr *) sm->last_rx_eapol_key; + key = (struct wpa_eapol_key *) (hdr + 1); + mic = (u8 *) (key + 1); + key_data = mic + mic_len + 2; + key_data_length = WPA_GET_BE16(mic + mic_len); + if (key_data_length > sm->last_rx_eapol_key_len - sizeof(*hdr) - + sizeof(*key) - mic_len - 2) + return; + + if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "received EAPOL-Key group msg 2/2 with invalid Key Data contents"); + return; + } + + if (wpa_auth_uses_ocv(sm)) { + struct wpa_channel_info ci; + int tx_chanwidth; + int tx_seg1_idx; + + if (wpa_channel_info(wpa_auth, &ci) != 0) { + wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "Failed to get channel info to validate received OCI in EAPOL-Key group 2/2"); + return; + } + + if (get_sta_tx_parameters(sm, + channel_width_to_int(ci.chanwidth), + ci.seg1_idx, &tx_chanwidth, + &tx_seg1_idx) < 0) + return; + + if (ocv_verify_tx_params(kde.oci, kde.oci_len, &ci, + tx_chanwidth, tx_seg1_idx) != + OCI_SUCCESS) { + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), + LOGGER_INFO, + "OCV failed: %s", ocv_errorstr); + if (wpa_auth->conf.msg_ctx) + wpa_msg(wpa_auth->conf.msg_ctx, MSG_INFO, + OCV_FAILURE "addr=" MACSTR + " frame=eapol-key-g2 error=%s", + MAC2STR(wpa_auth_get_spa(sm)), + ocv_errorstr); + return; + } + } +#endif /* CONFIG_OCV */ + + if (sm->GUpdateStationKeys) + wpa_gkeydone_sta(sm); + sm->GTimeoutCtr = 0; + /* FIX: MLME.SetProtection.Request(TA, Tx_Rx) */ + wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "group key handshake completed (%s)", + sm->wpa == WPA_VERSION_WPA ? "WPA" : "RSN"); + sm->has_GTK = true; +} + + +SM_STATE(WPA_PTK_GROUP, KEYERROR) +{ + SM_ENTRY_MA(WPA_PTK_GROUP, KEYERROR, wpa_ptk_group); + if (sm->GUpdateStationKeys) + wpa_gkeydone_sta(sm); + if (sm->wpa_auth->conf.no_disconnect_on_group_keyerror && + sm->wpa == WPA_VERSION_WPA2) { + wpa_auth_vlogger(sm->wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "group key handshake failed after %u tries - allow STA to remain connected", + sm->wpa_auth->conf.wpa_group_update_count); + return; + } + sm->Disconnect = true; + sm->disconnect_reason = WLAN_REASON_GROUP_KEY_UPDATE_TIMEOUT; + wpa_auth_vlogger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO, + "group key handshake failed (%s) after %u tries", + sm->wpa == WPA_VERSION_WPA ? "WPA" : "RSN", + sm->wpa_auth->conf.wpa_group_update_count); +} + + +SM_STEP(WPA_PTK_GROUP) +{ + if (sm->Init || sm->PtkGroupInit) { + SM_ENTER(WPA_PTK_GROUP, IDLE); + sm->PtkGroupInit = false; + } else switch (sm->wpa_ptk_group_state) { + case WPA_PTK_GROUP_IDLE: + if (sm->GUpdateStationKeys || + (sm->wpa == WPA_VERSION_WPA && sm->PInitAKeys)) + SM_ENTER(WPA_PTK_GROUP, REKEYNEGOTIATING); + break; + case WPA_PTK_GROUP_REKEYNEGOTIATING: + if (sm->EAPOLKeyReceived && !sm->EAPOLKeyRequest && + !sm->EAPOLKeyPairwise && sm->MICVerified) + SM_ENTER(WPA_PTK_GROUP, REKEYESTABLISHED); + else if (sm->GTimeoutCtr > + sm->wpa_auth->conf.wpa_group_update_count || + (sm->wpa_auth->conf.wpa_disable_eapol_key_retries && + sm->GTimeoutCtr > 1)) + SM_ENTER(WPA_PTK_GROUP, KEYERROR); + else if (sm->TimeoutEvt) + SM_ENTER(WPA_PTK_GROUP, REKEYNEGOTIATING); + break; + case WPA_PTK_GROUP_KEYERROR: + SM_ENTER(WPA_PTK_GROUP, IDLE); + break; + case WPA_PTK_GROUP_REKEYESTABLISHED: + SM_ENTER(WPA_PTK_GROUP, IDLE); + break; + } +} + + +static int wpa_gtk_update(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + struct wpa_auth_config *conf = &wpa_auth->conf; + int ret = 0; + size_t len; + + os_memcpy(group->GNonce, group->Counter, WPA_NONCE_LEN); + inc_byte_array(group->Counter, WPA_NONCE_LEN); + if (wpa_gmk_to_gtk(group->GMK, "Group key expansion", + wpa_auth->addr, group->GNonce, + group->GTK[group->GN - 1], group->GTK_len) < 0) + ret = -1; + wpa_hexdump_key(MSG_DEBUG, "GTK", + group->GTK[group->GN - 1], group->GTK_len); + + if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + len = wpa_cipher_key_len(conf->group_mgmt_cipher); + os_memcpy(group->GNonce, group->Counter, WPA_NONCE_LEN); + inc_byte_array(group->Counter, WPA_NONCE_LEN); + if (wpa_gmk_to_gtk(group->GMK, "IGTK key expansion", + wpa_auth->addr, group->GNonce, + group->IGTK[group->GN_igtk - 4], len) < 0) + ret = -1; + wpa_hexdump_key(MSG_DEBUG, "IGTK", + group->IGTK[group->GN_igtk - 4], len); + } + + if (!wpa_auth->non_tx_beacon_prot && + conf->ieee80211w == NO_MGMT_FRAME_PROTECTION) + return ret; + if (!conf->beacon_prot) + return ret; + + if (wpa_auth->conf.tx_bss_auth) { + group = wpa_auth->conf.tx_bss_auth->group; + if (group->bigtk_set) + return ret; + wpa_printf(MSG_DEBUG, "Set up BIGTK for TX BSS"); + } + + len = wpa_cipher_key_len(conf->group_mgmt_cipher); + os_memcpy(group->GNonce, group->Counter, WPA_NONCE_LEN); + inc_byte_array(group->Counter, WPA_NONCE_LEN); + if (wpa_gmk_to_gtk(group->GMK, "BIGTK key expansion", + wpa_auth->addr, group->GNonce, + group->BIGTK[group->GN_bigtk - 6], len) < 0) + return -1; + group->bigtk_set = true; + wpa_hexdump_key(MSG_DEBUG, "BIGTK", + group->BIGTK[group->GN_bigtk - 6], len); + + return ret; +} + + +static void wpa_group_gtk_init(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + wpa_printf(MSG_DEBUG, + "WPA: group state machine entering state GTK_INIT (VLAN-ID %d)", + group->vlan_id); + group->changed = false; /* GInit is not cleared here; avoid loop */ + group->wpa_group_state = WPA_GROUP_GTK_INIT; + + /* GTK[0..N] = 0 */ + os_memset(group->GTK, 0, sizeof(group->GTK)); + group->GN = 1; + group->GM = 2; + group->GN_igtk = 4; + group->GM_igtk = 5; + group->GN_bigtk = 6; + group->GM_bigtk = 7; + /* GTK[GN] = CalcGTK() */ + wpa_gtk_update(wpa_auth, group); +} + + +static int wpa_group_update_sta(struct wpa_state_machine *sm, void *ctx) +{ + if (ctx != NULL && ctx != sm->group) + return 0; + + if (sm->wpa_ptk_state != WPA_PTK_PTKINITDONE) { + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "Not in PTKINITDONE; skip Group Key update"); + sm->GUpdateStationKeys = false; + return 0; + } + if (sm->GUpdateStationKeys) { + /* + * This should not really happen, so add a debug log entry. + * Since we clear the GKeyDoneStations before the loop, the + * station needs to be counted here anyway. + */ + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "GUpdateStationKeys was already set when marking station for GTK rekeying"); + } + + /* Do not rekey GTK/IGTK when STA is in WNM-Sleep Mode */ + if (sm->is_wnmsleep) + return 0; + + sm->group->GKeyDoneStations++; + sm->GUpdateStationKeys = true; + + wpa_sm_step(sm); + return 0; +} + + +#ifdef CONFIG_WNM_AP +/* update GTK when exiting WNM-Sleep Mode */ +void wpa_wnmsleep_rekey_gtk(struct wpa_state_machine *sm) +{ + if (!sm || sm->is_wnmsleep) + return; + + wpa_group_update_sta(sm, NULL); +} + + +void wpa_set_wnmsleep(struct wpa_state_machine *sm, int flag) +{ + if (sm) + sm->is_wnmsleep = !!flag; +} + + +int wpa_wnmsleep_gtk_subelem(struct wpa_state_machine *sm, u8 *pos) +{ + struct wpa_auth_config *conf = &sm->wpa_auth->conf; + struct wpa_group *gsm = sm->group; + u8 *start = pos; + + /* + * GTK subelement: + * Sub-elem ID[1] | Length[1] | Key Info[2] | Key Length[1] | RSC[8] | + * Key[5..32] + */ + *pos++ = WNM_SLEEP_SUBELEM_GTK; + *pos++ = 11 + gsm->GTK_len; + /* Key ID in B0-B1 of Key Info */ + WPA_PUT_LE16(pos, gsm->GN & 0x03); + pos += 2; + *pos++ = gsm->GTK_len; + if (wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, pos) != 0) + return 0; + pos += 8; + os_memcpy(pos, gsm->GTK[gsm->GN - 1], gsm->GTK_len); + if (conf->disable_gtk || sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random GTK to each STA to prevent use + * of GTK in the BSS. + */ + if (random_get_bytes(pos, gsm->GTK_len) < 0) + return 0; + } + pos += gsm->GTK_len; + + wpa_printf(MSG_DEBUG, "WNM: GTK Key ID %u in WNM-Sleep Mode exit", + gsm->GN); + wpa_hexdump_key(MSG_DEBUG, "WNM: GTK in WNM-Sleep Mode exit", + gsm->GTK[gsm->GN - 1], gsm->GTK_len); + + return pos - start; +} + + +int wpa_wnmsleep_igtk_subelem(struct wpa_state_machine *sm, u8 *pos) +{ + struct wpa_auth_config *conf = &sm->wpa_auth->conf; + struct wpa_group *gsm = sm->group; + u8 *start = pos; + size_t len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher); + + /* + * IGTK subelement: + * Sub-elem ID[1] | Length[1] | KeyID[2] | PN[6] | Key[16] + */ + *pos++ = WNM_SLEEP_SUBELEM_IGTK; + *pos++ = 2 + 6 + len; + WPA_PUT_LE16(pos, gsm->GN_igtk); + pos += 2; + if (wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_igtk, pos) != 0) + return 0; + pos += 6; + + os_memcpy(pos, gsm->IGTK[gsm->GN_igtk - 4], len); + if (conf->disable_gtk || sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random IGTK to each STA to prevent use + * of IGTK in the BSS. + */ + if (random_get_bytes(pos, len) < 0) + return 0; + } + pos += len; + + wpa_printf(MSG_DEBUG, "WNM: IGTK Key ID %u in WNM-Sleep Mode exit", + gsm->GN_igtk); + wpa_hexdump_key(MSG_DEBUG, "WNM: IGTK in WNM-Sleep Mode exit", + gsm->IGTK[gsm->GN_igtk - 4], len); + + return pos - start; +} + + +int wpa_wnmsleep_bigtk_subelem(struct wpa_state_machine *sm, u8 *pos) +{ + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + struct wpa_group *gsm = wpa_auth->group; + u8 *start = pos; + size_t len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher); + + /* + * BIGTK subelement: + * Sub-elem ID[1] | Length[1] | KeyID[2] | PN[6] | Key[16] + */ + *pos++ = WNM_SLEEP_SUBELEM_BIGTK; + *pos++ = 2 + 6 + len; + WPA_PUT_LE16(pos, gsm->GN_bigtk); + pos += 2; + if (wpa_auth_get_seqnum(wpa_auth, NULL, gsm->GN_bigtk, pos) != 0) + return 0; + pos += 6; + + os_memcpy(pos, gsm->BIGTK[gsm->GN_bigtk - 6], len); + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random BIGTK to each STA to prevent use + * of BIGTK in the BSS. + */ + if (random_get_bytes(pos, len) < 0) + return 0; + } + pos += len; + + wpa_printf(MSG_DEBUG, "WNM: BIGTK Key ID %u in WNM-Sleep Mode exit", + gsm->GN_bigtk); + wpa_hexdump_key(MSG_DEBUG, "WNM: BIGTK in WNM-Sleep Mode exit", + gsm->BIGTK[gsm->GN_bigtk - 6], len); + + return pos - start; +} + +#endif /* CONFIG_WNM_AP */ + + +static void wpa_group_update_gtk(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + int tmp; + + tmp = group->GM; + group->GM = group->GN; + group->GN = tmp; + tmp = group->GM_igtk; + group->GM_igtk = group->GN_igtk; + group->GN_igtk = tmp; + tmp = group->GM_bigtk; + group->GM_bigtk = group->GN_bigtk; + group->GN_bigtk = tmp; + /* "GKeyDoneStations = GNoStations" is done in more robust way by + * counting the STAs that are marked with GUpdateStationKeys instead of + * including all STAs that could be in not-yet-completed state. */ + wpa_gtk_update(wpa_auth, group); +} + + +static void wpa_group_setkeys(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + wpa_printf(MSG_DEBUG, + "WPA: group state machine entering state SETKEYS (VLAN-ID %d)", + group->vlan_id); + group->changed = true; + group->wpa_group_state = WPA_GROUP_SETKEYS; + group->GTKReKey = false; + +#ifdef CONFIG_IEEE80211BE + if (wpa_auth->is_ml) + goto skip_update; +#endif /* CONFIG_IEEE80211BE */ + + wpa_group_update_gtk(wpa_auth, group); + + if (group->GKeyDoneStations) { + wpa_printf(MSG_DEBUG, + "wpa_group_setkeys: Unexpected GKeyDoneStations=%d when starting new GTK rekey", + group->GKeyDoneStations); + group->GKeyDoneStations = 0; + } + +#ifdef CONFIG_IEEE80211BE +skip_update: +#endif /* CONFIG_IEEE80211BE */ + wpa_auth_for_each_sta(wpa_auth, wpa_group_update_sta, group); + wpa_printf(MSG_DEBUG, "wpa_group_setkeys: GKeyDoneStations=%d", + group->GKeyDoneStations); +} + + +static int wpa_group_config_group_keys(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + struct wpa_auth_config *conf = &wpa_auth->conf; + int ret = 0; + + if (wpa_auth_set_key(wpa_auth, group->vlan_id, + wpa_cipher_to_alg(conf->wpa_group), + broadcast_ether_addr, group->GN, + group->GTK[group->GN - 1], group->GTK_len, + KEY_FLAG_GROUP_TX_DEFAULT) < 0) + ret = -1; + + if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + enum wpa_alg alg; + size_t len; + + alg = wpa_cipher_to_alg(conf->group_mgmt_cipher); + len = wpa_cipher_key_len(conf->group_mgmt_cipher); + + if (ret == 0 && + wpa_auth_set_key(wpa_auth, group->vlan_id, alg, + broadcast_ether_addr, group->GN_igtk, + group->IGTK[group->GN_igtk - 4], len, + KEY_FLAG_GROUP_TX_DEFAULT) < 0) + ret = -1; + + if (ret || !conf->beacon_prot) + return ret; + if (wpa_auth->conf.tx_bss_auth) { + wpa_auth = wpa_auth->conf.tx_bss_auth; + group = wpa_auth->group; + if (!group->bigtk_set || group->bigtk_configured) + return ret; + } + if (wpa_auth_set_key(wpa_auth, group->vlan_id, alg, + broadcast_ether_addr, group->GN_bigtk, + group->BIGTK[group->GN_bigtk - 6], len, + KEY_FLAG_GROUP_TX_DEFAULT) < 0) + ret = -1; + else + group->bigtk_configured = true; + } + + return ret; +} + + +static int wpa_group_disconnect_cb(struct wpa_state_machine *sm, void *ctx) +{ + if (sm->group == ctx) { + wpa_printf(MSG_DEBUG, "WPA: Mark STA " MACSTR + " for disconnection due to fatal failure", + MAC2STR(wpa_auth_get_spa(sm))); + sm->Disconnect = true; + } + + return 0; +} + + +static void wpa_group_fatal_failure(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + wpa_printf(MSG_DEBUG, + "WPA: group state machine entering state FATAL_FAILURE"); + group->changed = true; + group->wpa_group_state = WPA_GROUP_FATAL_FAILURE; + wpa_auth_for_each_sta(wpa_auth, wpa_group_disconnect_cb, group); +} + + +static int wpa_group_setkeysdone(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + wpa_printf(MSG_DEBUG, + "WPA: group state machine entering state SETKEYSDONE (VLAN-ID %d)", + group->vlan_id); + group->changed = true; + group->wpa_group_state = WPA_GROUP_SETKEYSDONE; + + if (wpa_group_config_group_keys(wpa_auth, group) < 0) { + wpa_group_fatal_failure(wpa_auth, group); + return -1; + } + + return 0; +} + + +static void wpa_group_sm_step(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + if (group->GInit) { + wpa_group_gtk_init(wpa_auth, group); + } else if (group->wpa_group_state == WPA_GROUP_FATAL_FAILURE) { + /* Do not allow group operations */ + } else if (group->wpa_group_state == WPA_GROUP_GTK_INIT && + group->GTKAuthenticator) { + wpa_group_setkeysdone(wpa_auth, group); + } else if (group->wpa_group_state == WPA_GROUP_SETKEYSDONE && + group->GTKReKey) { + wpa_group_setkeys(wpa_auth, group); + } else if (group->wpa_group_state == WPA_GROUP_SETKEYS) { + if (group->GKeyDoneStations == 0) + wpa_group_setkeysdone(wpa_auth, group); + else if (group->GTKReKey) + wpa_group_setkeys(wpa_auth, group); + } +} + + +static void wpa_clear_changed(struct wpa_state_machine *sm) +{ +#ifdef CONFIG_IEEE80211BE + int link_id; +#endif /* CONFIG_IEEE80211BE */ + + sm->changed = false; + sm->wpa_auth->group->changed = false; + +#ifdef CONFIG_IEEE80211BE + for_each_sm_auth(sm, link_id) + sm->mld_links[link_id].wpa_auth->group->changed = false; +#endif /* CONFIG_IEEE80211BE */ +} + + +static void wpa_group_sm_step_links(struct wpa_state_machine *sm) +{ +#ifdef CONFIG_IEEE80211BE + int link_id; +#endif /* CONFIG_IEEE80211BE */ + + if (!sm || !sm->wpa_auth) + return; + wpa_group_sm_step(sm->wpa_auth, sm->wpa_auth->group); + +#ifdef CONFIG_IEEE80211BE + for_each_sm_auth(sm, link_id) { + wpa_group_sm_step(sm->mld_links[link_id].wpa_auth, + sm->mld_links[link_id].wpa_auth->group); + } +#endif /* CONFIG_IEEE80211BE */ +} + + +static bool wpa_group_sm_changed(struct wpa_state_machine *sm) +{ +#ifdef CONFIG_IEEE80211BE + int link_id; +#endif /* CONFIG_IEEE80211BE */ + bool changed; + + if (!sm || !sm->wpa_auth) + return false; + changed = sm->wpa_auth->group->changed; + +#ifdef CONFIG_IEEE80211BE + for_each_sm_auth(sm, link_id) + changed |= sm->mld_links[link_id].wpa_auth->group->changed; +#endif /* CONFIG_IEEE80211BE */ + + return changed; +} + + +static int wpa_sm_step(struct wpa_state_machine *sm) +{ + if (!sm) + return 0; + + if (sm->in_step_loop) { + /* This should not happen, but if it does, make sure we do not + * end up freeing the state machine too early by exiting the + * recursive call. */ + wpa_printf(MSG_ERROR, "WPA: wpa_sm_step() called recursively"); + return 0; + } + + sm->in_step_loop = 1; + do { + if (sm->pending_deinit) + break; + + wpa_clear_changed(sm); + + SM_STEP_RUN(WPA_PTK); + if (sm->pending_deinit) + break; + SM_STEP_RUN(WPA_PTK_GROUP); + if (sm->pending_deinit) + break; + wpa_group_sm_step_links(sm); + } while (sm->changed || wpa_group_sm_changed(sm)); + sm->in_step_loop = 0; + + if (sm->pending_deinit) { + wpa_printf(MSG_DEBUG, + "WPA: Completing pending STA state machine deinit for " + MACSTR, MAC2STR(wpa_auth_get_spa(sm))); + wpa_free_sta_sm(sm); + return 1; + } + return 0; +} + + +static void wpa_sm_call_step(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_state_machine *sm = eloop_ctx; + wpa_sm_step(sm); +} + + +void wpa_auth_sm_notify(struct wpa_state_machine *sm) +{ + if (!sm) + return; + eloop_register_timeout(0, 0, wpa_sm_call_step, sm, NULL); +} + + +void wpa_gtk_rekey(struct wpa_authenticator *wpa_auth) +{ + int tmp, i; + struct wpa_group *group; + + if (!wpa_auth) + return; + + group = wpa_auth->group; + + for (i = 0; i < 2; i++) { + tmp = group->GM; + group->GM = group->GN; + group->GN = tmp; + tmp = group->GM_igtk; + group->GM_igtk = group->GN_igtk; + group->GN_igtk = tmp; + if (!wpa_auth->conf.tx_bss_auth) { + tmp = group->GM_bigtk; + group->GM_bigtk = group->GN_bigtk; + group->GN_bigtk = tmp; + } + wpa_gtk_update(wpa_auth, group); + wpa_group_config_group_keys(wpa_auth, group); + } +} + + +static const char * wpa_bool_txt(int val) +{ + return val ? "TRUE" : "FALSE"; +} + + +#define RSN_SUITE "%02x-%02x-%02x-%d" +#define RSN_SUITE_ARG(s) \ +((s) >> 24) & 0xff, ((s) >> 16) & 0xff, ((s) >> 8) & 0xff, (s) & 0xff + +int wpa_get_mib(struct wpa_authenticator *wpa_auth, char *buf, size_t buflen) +{ + struct wpa_auth_config *conf; + int len = 0, ret; + char pmkid_txt[PMKID_LEN * 2 + 1]; +#ifdef CONFIG_RSN_PREAUTH + const int preauth = 1; +#else /* CONFIG_RSN_PREAUTH */ + const int preauth = 0; +#endif /* CONFIG_RSN_PREAUTH */ + + if (!wpa_auth) + return len; + conf = &wpa_auth->conf; + + ret = os_snprintf(buf + len, buflen - len, + "dot11RSNAOptionImplemented=TRUE\n" + "dot11RSNAPreauthenticationImplemented=%s\n" + "dot11RSNAEnabled=%s\n" + "dot11RSNAPreauthenticationEnabled=%s\n", + wpa_bool_txt(preauth), + wpa_bool_txt(conf->wpa & WPA_PROTO_RSN), + wpa_bool_txt(conf->rsn_preauth)); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + wpa_snprintf_hex(pmkid_txt, sizeof(pmkid_txt), + wpa_auth->dot11RSNAPMKIDUsed, PMKID_LEN); + + ret = os_snprintf( + buf + len, buflen - len, + "dot11RSNAConfigVersion=%u\n" + "dot11RSNAConfigPairwiseKeysSupported=9999\n" + /* FIX: dot11RSNAConfigGroupCipher */ + /* FIX: dot11RSNAConfigGroupRekeyMethod */ + /* FIX: dot11RSNAConfigGroupRekeyTime */ + /* FIX: dot11RSNAConfigGroupRekeyPackets */ + "dot11RSNAConfigGroupRekeyStrict=%u\n" + "dot11RSNAConfigGroupUpdateCount=%u\n" + "dot11RSNAConfigPairwiseUpdateCount=%u\n" + "dot11RSNAConfigGroupCipherSize=%u\n" + "dot11RSNAConfigPMKLifetime=%u\n" + "dot11RSNAConfigPMKReauthThreshold=%u\n" + "dot11RSNAConfigNumberOfPTKSAReplayCounters=0\n" + "dot11RSNAConfigSATimeout=%u\n" + "dot11RSNAAuthenticationSuiteSelected=" RSN_SUITE "\n" + "dot11RSNAPairwiseCipherSelected=" RSN_SUITE "\n" + "dot11RSNAGroupCipherSelected=" RSN_SUITE "\n" + "dot11RSNAPMKIDUsed=%s\n" + "dot11RSNAAuthenticationSuiteRequested=" RSN_SUITE "\n" + "dot11RSNAPairwiseCipherRequested=" RSN_SUITE "\n" + "dot11RSNAGroupCipherRequested=" RSN_SUITE "\n" + "dot11RSNATKIPCounterMeasuresInvoked=%u\n" + "dot11RSNA4WayHandshakeFailures=%u\n" + "dot11RSNAConfigNumberOfGTKSAReplayCounters=0\n", + RSN_VERSION, + !!conf->wpa_strict_rekey, + conf->wpa_group_update_count, + conf->wpa_pairwise_update_count, + wpa_cipher_key_len(conf->wpa_group) * 8, + dot11RSNAConfigPMKLifetime, + dot11RSNAConfigPMKReauthThreshold, + dot11RSNAConfigSATimeout, + RSN_SUITE_ARG(wpa_auth->dot11RSNAAuthenticationSuiteSelected), + RSN_SUITE_ARG(wpa_auth->dot11RSNAPairwiseCipherSelected), + RSN_SUITE_ARG(wpa_auth->dot11RSNAGroupCipherSelected), + pmkid_txt, + RSN_SUITE_ARG(wpa_auth->dot11RSNAAuthenticationSuiteRequested), + RSN_SUITE_ARG(wpa_auth->dot11RSNAPairwiseCipherRequested), + RSN_SUITE_ARG(wpa_auth->dot11RSNAGroupCipherRequested), + wpa_auth->dot11RSNATKIPCounterMeasuresInvoked, + wpa_auth->dot11RSNA4WayHandshakeFailures); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* TODO: dot11RSNAConfigPairwiseCiphersTable */ + /* TODO: dot11RSNAConfigAuthenticationSuitesTable */ + + /* Private MIB */ + ret = os_snprintf(buf + len, buflen - len, "hostapdWPAGroupState=%d\n", + wpa_auth->group->wpa_group_state); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + return len; +} + + +int wpa_get_mib_sta(struct wpa_state_machine *sm, char *buf, size_t buflen) +{ + int len = 0, ret; + u32 pairwise = 0; + + if (!sm) + return 0; + + /* TODO: FF-FF-FF-FF-FF-FF entry for broadcast/multicast stats */ + + /* dot11RSNAStatsEntry */ + + pairwise = wpa_cipher_to_suite(sm->wpa == WPA_VERSION_WPA2 ? + WPA_PROTO_RSN : WPA_PROTO_WPA, + sm->pairwise); + if (pairwise == 0) + return 0; + + ret = os_snprintf( + buf + len, buflen - len, + /* TODO: dot11RSNAStatsIndex */ + "dot11RSNAStatsSTAAddress=" MACSTR "\n" + "dot11RSNAStatsVersion=1\n" + "dot11RSNAStatsSelectedPairwiseCipher=" RSN_SUITE "\n" + /* TODO: dot11RSNAStatsTKIPICVErrors */ + "dot11RSNAStatsTKIPLocalMICFailures=%u\n" + "dot11RSNAStatsTKIPRemoteMICFailures=%u\n" + /* TODO: dot11RSNAStatsCCMPReplays */ + /* TODO: dot11RSNAStatsCCMPDecryptErrors */ + /* TODO: dot11RSNAStatsTKIPReplays */, + MAC2STR(sm->addr), + RSN_SUITE_ARG(pairwise), + sm->dot11RSNAStatsTKIPLocalMICFailures, + sm->dot11RSNAStatsTKIPRemoteMICFailures); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + /* Private MIB */ + ret = os_snprintf(buf + len, buflen - len, + "wpa=%d\n" + "AKMSuiteSelector=" RSN_SUITE "\n" + "hostapdWPAPTKState=%d\n" + "hostapdWPAPTKGroupState=%d\n" + "hostapdMFPR=%d\n", + sm->wpa, + RSN_SUITE_ARG(wpa_akm_to_suite(sm->wpa_key_mgmt)), + sm->wpa_ptk_state, + sm->wpa_ptk_group_state, + sm->mfpr); + if (os_snprintf_error(buflen - len, ret)) + return len; + len += ret; + + return len; +} + + +void wpa_auth_countermeasures_start(struct wpa_authenticator *wpa_auth) +{ + if (wpa_auth) + wpa_auth->dot11RSNATKIPCounterMeasuresInvoked++; +} + + +int wpa_auth_pairwise_set(struct wpa_state_machine *sm) +{ + return sm && sm->pairwise_set; +} + + +int wpa_auth_get_pairwise(struct wpa_state_machine *sm) +{ + return sm->pairwise; +} + + +const u8 * wpa_auth_get_pmk(struct wpa_state_machine *sm, int *len) +{ + if (!sm) + return NULL; + *len = sm->pmk_len; + return sm->PMK; +} + + +const u8 * wpa_auth_get_dpp_pkhash(struct wpa_state_machine *sm) +{ + if (!sm || !sm->pmksa) + return NULL; + return sm->pmksa->dpp_pkhash; +} + + +int wpa_auth_sta_key_mgmt(struct wpa_state_machine *sm) +{ + if (!sm) + return -1; + return sm->wpa_key_mgmt; +} + + +int wpa_auth_sta_wpa_version(struct wpa_state_machine *sm) +{ + if (!sm) + return 0; + return sm->wpa; +} + + +int wpa_auth_sta_ft_tk_already_set(struct wpa_state_machine *sm) +{ + if (!sm || !wpa_key_mgmt_ft(sm->wpa_key_mgmt)) + return 0; + return sm->tk_already_set; +} + + +int wpa_auth_sta_fils_tk_already_set(struct wpa_state_machine *sm) +{ + if (!sm || !wpa_key_mgmt_fils(sm->wpa_key_mgmt)) + return 0; + return sm->tk_already_set; +} + + +int wpa_auth_sta_clear_pmksa(struct wpa_state_machine *sm, + struct rsn_pmksa_cache_entry *entry) +{ + if (!sm || sm->pmksa != entry) + return -1; + sm->pmksa = NULL; + return 0; +} + + +struct rsn_pmksa_cache_entry * +wpa_auth_sta_get_pmksa(struct wpa_state_machine *sm) +{ + return sm ? sm->pmksa : NULL; +} + + +void wpa_auth_sta_local_mic_failure_report(struct wpa_state_machine *sm) +{ + if (sm) + sm->dot11RSNAStatsTKIPLocalMICFailures++; +} + + +const u8 * wpa_auth_get_wpa_ie(struct wpa_authenticator *wpa_auth, size_t *len) +{ + if (!wpa_auth) + return NULL; + *len = wpa_auth->wpa_ie_len; + return wpa_auth->wpa_ie; +} + + +int wpa_auth_pmksa_add(struct wpa_state_machine *sm, const u8 *pmk, + unsigned int pmk_len, + int session_timeout, struct eapol_state_machine *eapol) +{ + if (!sm || sm->wpa != WPA_VERSION_WPA2 || + sm->wpa_auth->conf.disable_pmksa_caching) + return -1; + +#ifdef CONFIG_IEEE80211R_AP + if (pmk_len >= 2 * PMK_LEN && wpa_key_mgmt_ft(sm->wpa_key_mgmt) && + wpa_key_mgmt_wpa_ieee8021x(sm->wpa_key_mgmt) && + !wpa_key_mgmt_sha384(sm->wpa_key_mgmt)) { + /* Cache MPMK/XXKey instead of initial part from MSK */ + pmk = pmk + PMK_LEN; + pmk_len = PMK_LEN; + } else +#endif /* CONFIG_IEEE80211R_AP */ + if (wpa_key_mgmt_sha384(sm->wpa_key_mgmt)) { + if (pmk_len > PMK_LEN_SUITE_B_192) + pmk_len = PMK_LEN_SUITE_B_192; + } else if (pmk_len > PMK_LEN) { + pmk_len = PMK_LEN; + } + + wpa_hexdump_key(MSG_DEBUG, "RSN: Cache PMK", pmk, pmk_len); + if (pmksa_cache_auth_add(sm->wpa_auth->pmksa, pmk, pmk_len, NULL, + sm->PTK.kck, sm->PTK.kck_len, + wpa_auth_get_aa(sm), + wpa_auth_get_spa(sm), session_timeout, + eapol, sm->wpa_key_mgmt)) + return 0; + + return -1; +} + + +int wpa_auth_pmksa_add_preauth(struct wpa_authenticator *wpa_auth, + const u8 *pmk, size_t len, const u8 *sta_addr, + int session_timeout, + struct eapol_state_machine *eapol) +{ + if (!wpa_auth) + return -1; + + wpa_hexdump_key(MSG_DEBUG, "RSN: Cache PMK from preauth", pmk, len); + if (pmksa_cache_auth_add(wpa_auth->pmksa, pmk, len, NULL, + NULL, 0, + wpa_auth->addr, + sta_addr, session_timeout, eapol, + WPA_KEY_MGMT_IEEE8021X)) + return 0; + + return -1; +} + + +int wpa_auth_pmksa_add_sae(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *pmk, size_t pmk_len, const u8 *pmkid, + int akmp) +{ + if (wpa_auth->conf.disable_pmksa_caching) + return -1; + + wpa_hexdump_key(MSG_DEBUG, "RSN: Cache PMK from SAE", pmk, pmk_len); + if (!akmp) + akmp = WPA_KEY_MGMT_SAE; + if (pmksa_cache_auth_add(wpa_auth->pmksa, pmk, pmk_len, pmkid, + NULL, 0, wpa_auth->addr, addr, 0, NULL, akmp)) + return 0; + + return -1; +} + + +void wpa_auth_add_sae_pmkid(struct wpa_state_machine *sm, const u8 *pmkid) +{ + os_memcpy(sm->pmkid, pmkid, PMKID_LEN); + sm->pmkid_set = 1; +} + + +int wpa_auth_pmksa_add2(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *pmk, size_t pmk_len, const u8 *pmkid, + int session_timeout, int akmp, const u8 *dpp_pkhash) +{ + struct rsn_pmksa_cache_entry *entry; + + if (!wpa_auth || wpa_auth->conf.disable_pmksa_caching) + return -1; + + wpa_hexdump_key(MSG_DEBUG, "RSN: Cache PMK (3)", pmk, PMK_LEN); + entry = pmksa_cache_auth_add(wpa_auth->pmksa, pmk, pmk_len, pmkid, + NULL, 0, wpa_auth->addr, addr, session_timeout, + NULL, akmp); + if (!entry) + return -1; + + if (dpp_pkhash) + entry->dpp_pkhash = os_memdup(dpp_pkhash, SHA256_MAC_LEN); + + return 0; +} + + +void wpa_auth_pmksa_remove(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr) +{ + struct rsn_pmksa_cache_entry *pmksa; + + if (!wpa_auth || !wpa_auth->pmksa) + return; + pmksa = pmksa_cache_auth_get(wpa_auth->pmksa, sta_addr, NULL); + if (pmksa) { + wpa_printf(MSG_DEBUG, "WPA: Remove PMKSA cache entry for " + MACSTR " based on request", MAC2STR(sta_addr)); + pmksa_cache_free_entry(wpa_auth->pmksa, pmksa); + } +} + + +int wpa_auth_pmksa_list(struct wpa_authenticator *wpa_auth, char *buf, + size_t len) +{ + if (!wpa_auth || !wpa_auth->pmksa) + return 0; + return pmksa_cache_auth_list(wpa_auth->pmksa, buf, len); +} + + +void wpa_auth_pmksa_flush(struct wpa_authenticator *wpa_auth) +{ + if (wpa_auth && wpa_auth->pmksa) + pmksa_cache_auth_flush(wpa_auth->pmksa); +} + + +#ifdef CONFIG_PMKSA_CACHE_EXTERNAL +#ifdef CONFIG_MESH + +int wpa_auth_pmksa_list_mesh(struct wpa_authenticator *wpa_auth, const u8 *addr, + char *buf, size_t len) +{ + if (!wpa_auth || !wpa_auth->pmksa) + return 0; + + return pmksa_cache_auth_list_mesh(wpa_auth->pmksa, addr, buf, len); +} + + +struct rsn_pmksa_cache_entry * +wpa_auth_pmksa_create_entry(const u8 *aa, const u8 *spa, const u8 *pmk, + size_t pmk_len, int akmp, + const u8 *pmkid, int expiration) +{ + struct rsn_pmksa_cache_entry *entry; + struct os_reltime now; + + entry = pmksa_cache_auth_create_entry(pmk, pmk_len, pmkid, NULL, 0, aa, + spa, 0, NULL, akmp); + if (!entry) + return NULL; + + os_get_reltime(&now); + entry->expiration = now.sec + expiration; + return entry; +} + + +int wpa_auth_pmksa_add_entry(struct wpa_authenticator *wpa_auth, + struct rsn_pmksa_cache_entry *entry) +{ + int ret; + + if (!wpa_auth || !wpa_auth->pmksa) + return -1; + + ret = pmksa_cache_auth_add_entry(wpa_auth->pmksa, entry); + if (ret < 0) + wpa_printf(MSG_DEBUG, + "RSN: Failed to store external PMKSA cache for " + MACSTR, MAC2STR(entry->spa)); + + return ret; +} + +#endif /* CONFIG_MESH */ +#endif /* CONFIG_PMKSA_CACHE_EXTERNAL */ + + +struct rsn_pmksa_cache * +wpa_auth_get_pmksa_cache(struct wpa_authenticator *wpa_auth) +{ + if (!wpa_auth || !wpa_auth->pmksa) + return NULL; + return wpa_auth->pmksa; +} + + +struct rsn_pmksa_cache_entry * +wpa_auth_pmksa_get(struct wpa_authenticator *wpa_auth, const u8 *sta_addr, + const u8 *pmkid) +{ + if (!wpa_auth || !wpa_auth->pmksa) + return NULL; + return pmksa_cache_auth_get(wpa_auth->pmksa, sta_addr, pmkid); +} + + +void wpa_auth_pmksa_set_to_sm(struct rsn_pmksa_cache_entry *pmksa, + struct wpa_state_machine *sm, + struct wpa_authenticator *wpa_auth, + u8 *pmkid, u8 *pmk, size_t *pmk_len) +{ + if (!sm) + return; + + sm->pmksa = pmksa; + os_memcpy(pmk, pmksa->pmk, pmksa->pmk_len); + *pmk_len = pmksa->pmk_len; + os_memcpy(pmkid, pmksa->pmkid, PMKID_LEN); + os_memcpy(wpa_auth->dot11RSNAPMKIDUsed, pmksa->pmkid, PMKID_LEN); +} + + +/* + * Remove and free the group from wpa_authenticator. This is triggered by a + * callback to make sure nobody is currently iterating the group list while it + * gets modified. + */ +static void wpa_group_free(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + struct wpa_group *prev = wpa_auth->group; + + wpa_printf(MSG_DEBUG, "WPA: Remove group state machine for VLAN-ID %d", + group->vlan_id); + + while (prev) { + if (prev->next == group) { + /* This never frees the special first group as needed */ + prev->next = group->next; + os_free(group); + break; + } + prev = prev->next; + } + +} + + +/* Increase the reference counter for group */ +static void wpa_group_get(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + /* Skip the special first group */ + if (wpa_auth->group == group) + return; + + group->references++; +} + + +/* Decrease the reference counter and maybe free the group */ +static void wpa_group_put(struct wpa_authenticator *wpa_auth, + struct wpa_group *group) +{ + /* Skip the special first group */ + if (wpa_auth->group == group) + return; + + group->references--; + if (group->references) + return; + wpa_group_free(wpa_auth, group); +} + + +/* + * Add a group that has its references counter set to zero. Caller needs to + * call wpa_group_get() on the return value to mark the entry in use. + */ +static struct wpa_group * +wpa_auth_add_group(struct wpa_authenticator *wpa_auth, int vlan_id) +{ + struct wpa_group *group; + + if (!wpa_auth || !wpa_auth->group) + return NULL; + + wpa_printf(MSG_DEBUG, "WPA: Add group state machine for VLAN-ID %d", + vlan_id); + group = wpa_group_init(wpa_auth, vlan_id, 0); + if (!group) + return NULL; + + group->next = wpa_auth->group->next; + wpa_auth->group->next = group; + + return group; +} + + +/* + * Enforce that the group state machine for the VLAN is running, increase + * reference counter as interface is up. References might have been increased + * even if a negative value is returned. + * Returns: -1 on error (group missing, group already failed); otherwise, 0 + */ +int wpa_auth_ensure_group(struct wpa_authenticator *wpa_auth, int vlan_id) +{ + struct wpa_group *group; + + if (!wpa_auth) + return 0; + + group = wpa_auth->group; + while (group) { + if (group->vlan_id == vlan_id) + break; + group = group->next; + } + + if (!group) { + group = wpa_auth_add_group(wpa_auth, vlan_id); + if (!group) + return -1; + } + + wpa_printf(MSG_DEBUG, + "WPA: Ensure group state machine running for VLAN ID %d", + vlan_id); + + wpa_group_get(wpa_auth, group); + group->num_setup_iface++; + + if (group->wpa_group_state == WPA_GROUP_FATAL_FAILURE) + return -1; + + return 0; +} + + +/* + * Decrease reference counter, expected to be zero afterwards. + * returns: -1 on error (group not found, group in fail state) + * -2 if wpa_group is still referenced + * 0 else + */ +int wpa_auth_release_group(struct wpa_authenticator *wpa_auth, int vlan_id) +{ + struct wpa_group *group; + int ret = 0; + + if (!wpa_auth) + return 0; + + group = wpa_auth->group; + while (group) { + if (group->vlan_id == vlan_id) + break; + group = group->next; + } + + if (!group) + return -1; + + wpa_printf(MSG_DEBUG, + "WPA: Try stopping group state machine for VLAN ID %d", + vlan_id); + + if (group->num_setup_iface <= 0) { + wpa_printf(MSG_ERROR, + "WPA: wpa_auth_release_group called more often than wpa_auth_ensure_group for VLAN ID %d, skipping.", + vlan_id); + return -1; + } + group->num_setup_iface--; + + if (group->wpa_group_state == WPA_GROUP_FATAL_FAILURE) + ret = -1; + + if (group->references > 1) { + wpa_printf(MSG_DEBUG, + "WPA: Cannot stop group state machine for VLAN ID %d as references are still hold", + vlan_id); + ret = -2; + } + + wpa_group_put(wpa_auth, group); + + return ret; +} + + +int wpa_auth_sta_set_vlan(struct wpa_state_machine *sm, int vlan_id) +{ + struct wpa_group *group; + + if (!sm || !sm->wpa_auth) + return 0; + + group = sm->wpa_auth->group; + while (group) { + if (group->vlan_id == vlan_id) + break; + group = group->next; + } + + if (!group) { + group = wpa_auth_add_group(sm->wpa_auth, vlan_id); + if (!group) + return -1; + } + + if (sm->group == group) + return 0; + + if (group->wpa_group_state == WPA_GROUP_FATAL_FAILURE) + return -1; + + wpa_printf(MSG_DEBUG, "WPA: Moving STA " MACSTR + " to use group state machine for VLAN ID %d", + MAC2STR(wpa_auth_get_spa(sm)), vlan_id); + + wpa_group_get(sm->wpa_auth, group); + wpa_group_put(sm->wpa_auth, sm->group); + sm->group = group; + + return 0; +} + + +void wpa_auth_eapol_key_tx_status(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int ack) +{ + if (!wpa_auth || !sm) + return; + wpa_printf(MSG_DEBUG, "WPA: EAPOL-Key TX status for STA " MACSTR + " ack=%d", MAC2STR(wpa_auth_get_spa(sm)), ack); + if (sm->pending_1_of_4_timeout && ack) { + /* + * Some deployed supplicant implementations update their SNonce + * for each EAPOL-Key 2/4 message even within the same 4-way + * handshake and then fail to use the first SNonce when + * deriving the PTK. This results in unsuccessful 4-way + * handshake whenever the relatively short initial timeout is + * reached and EAPOL-Key 1/4 is retransmitted. Try to work + * around this by increasing the timeout now that we know that + * the station has received the frame. + */ + int timeout_ms = eapol_key_timeout_subseq; + wpa_printf(MSG_DEBUG, + "WPA: Increase initial EAPOL-Key 1/4 timeout by %u ms because of acknowledged frame", + timeout_ms); + eloop_cancel_timeout(wpa_send_eapol_timeout, wpa_auth, sm); + eloop_register_timeout(timeout_ms / 1000, + (timeout_ms % 1000) * 1000, + wpa_send_eapol_timeout, wpa_auth, sm); + } + +#ifdef CONFIG_TESTING_OPTIONS + if (sm->eapol_status_cb) { + sm->eapol_status_cb(sm->eapol_status_cb_ctx1, + sm->eapol_status_cb_ctx2); + sm->eapol_status_cb = NULL; + } +#endif /* CONFIG_TESTING_OPTIONS */ +} + + +int wpa_auth_uses_sae(struct wpa_state_machine *sm) +{ + if (!sm) + return 0; + return wpa_key_mgmt_sae(sm->wpa_key_mgmt); +} + + +int wpa_auth_uses_ft_sae(struct wpa_state_machine *sm) +{ + if (!sm) + return 0; + return sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE || + sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY; +} + + +#ifdef CONFIG_P2P +int wpa_auth_get_ip_addr(struct wpa_state_machine *sm, u8 *addr) +{ + if (!sm || WPA_GET_BE32(sm->ip_addr) == 0) + return -1; + os_memcpy(addr, sm->ip_addr, 4); + return 0; +} +#endif /* CONFIG_P2P */ + + +int wpa_auth_radius_das_disconnect_pmksa(struct wpa_authenticator *wpa_auth, + struct radius_das_attrs *attr) +{ + return pmksa_cache_auth_radius_das_disconnect(wpa_auth->pmksa, attr); +} + + +void wpa_auth_reconfig_group_keys(struct wpa_authenticator *wpa_auth) +{ + struct wpa_group *group; + + if (!wpa_auth) + return; + for (group = wpa_auth->group; group; group = group->next) + wpa_group_config_group_keys(wpa_auth, group); +} + + +#ifdef CONFIG_FILS + +struct wpa_auth_fils_iter_data { + struct wpa_authenticator *auth; + const u8 *cache_id; + struct rsn_pmksa_cache_entry *pmksa; + const u8 *spa; + const u8 *pmkid; +}; + + +static int wpa_auth_fils_iter(struct wpa_authenticator *a, void *ctx) +{ + struct wpa_auth_fils_iter_data *data = ctx; + + if (a == data->auth || !a->conf.fils_cache_id_set || + os_memcmp(a->conf.fils_cache_id, data->cache_id, + FILS_CACHE_ID_LEN) != 0) + return 0; + data->pmksa = pmksa_cache_auth_get(a->pmksa, data->spa, data->pmkid); + return data->pmksa != NULL; +} + + +struct rsn_pmksa_cache_entry * +wpa_auth_pmksa_get_fils_cache_id(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr, const u8 *pmkid) +{ + struct wpa_auth_fils_iter_data idata; + + if (!wpa_auth->conf.fils_cache_id_set) + return NULL; + idata.auth = wpa_auth; + idata.cache_id = wpa_auth->conf.fils_cache_id; + idata.pmksa = NULL; + idata.spa = sta_addr; + idata.pmkid = pmkid; + wpa_auth_for_each_auth(wpa_auth, wpa_auth_fils_iter, &idata); + return idata.pmksa; +} + + +#ifdef CONFIG_IEEE80211R_AP +int wpa_auth_write_fte(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + u8 *buf, size_t len) +{ + struct wpa_auth_config *conf = &wpa_auth->conf; + + return wpa_write_ftie(conf, sm->wpa_key_mgmt, sm->xxkey_len, + conf->r0_key_holder, conf->r0_key_holder_len, + NULL, NULL, buf, len, NULL, 0, 0); +} +#endif /* CONFIG_IEEE80211R_AP */ + + +void wpa_auth_get_fils_aead_params(struct wpa_state_machine *sm, + u8 *fils_anonce, u8 *fils_snonce, + u8 *fils_kek, size_t *fils_kek_len) +{ + os_memcpy(fils_anonce, sm->ANonce, WPA_NONCE_LEN); + os_memcpy(fils_snonce, sm->SNonce, WPA_NONCE_LEN); + os_memcpy(fils_kek, sm->PTK.kek, WPA_KEK_MAX_LEN); + *fils_kek_len = sm->PTK.kek_len; +} + + +void wpa_auth_add_fils_pmk_pmkid(struct wpa_state_machine *sm, const u8 *pmk, + size_t pmk_len, const u8 *pmkid) +{ + os_memcpy(sm->PMK, pmk, pmk_len); + sm->pmk_len = pmk_len; + os_memcpy(sm->pmkid, pmkid, PMKID_LEN); + sm->pmkid_set = 1; +} + +#endif /* CONFIG_FILS */ + + +void wpa_auth_set_auth_alg(struct wpa_state_machine *sm, u16 auth_alg) +{ + if (sm) + sm->auth_alg = auth_alg; +} + + +#ifdef CONFIG_DPP2 +void wpa_auth_set_dpp_z(struct wpa_state_machine *sm, const struct wpabuf *z) +{ + if (sm) { + wpabuf_clear_free(sm->dpp_z); + sm->dpp_z = z ? wpabuf_dup(z) : NULL; + } +} +#endif /* CONFIG_DPP2 */ + + +void wpa_auth_set_ssid_protection(struct wpa_state_machine *sm, bool val) +{ + if (sm) + sm->ssid_protection = val; +} + + +void wpa_auth_set_transition_disable(struct wpa_authenticator *wpa_auth, + u8 val) +{ + if (wpa_auth) + wpa_auth->conf.transition_disable = val; +} + + +#ifdef CONFIG_TESTING_OPTIONS + +int wpa_auth_resend_m1(struct wpa_state_machine *sm, int change_anonce, + void (*cb)(void *ctx1, void *ctx2), + void *ctx1, void *ctx2) +{ + const u8 *anonce = sm->ANonce; + u8 anonce_buf[WPA_NONCE_LEN]; + + if (change_anonce) { + if (random_get_bytes(anonce_buf, WPA_NONCE_LEN)) + return -1; + anonce = anonce_buf; + } + + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "sending 1/4 msg of 4-Way Handshake (TESTING)"); + wpa_send_eapol(sm->wpa_auth, sm, + WPA_KEY_INFO_ACK | WPA_KEY_INFO_KEY_TYPE, NULL, + anonce, NULL, 0, 0, 0); + return 0; +} + + +int wpa_auth_resend_m3(struct wpa_state_machine *sm, + void (*cb)(void *ctx1, void *ctx2), + void *ctx1, void *ctx2) +{ + u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde, *pos; + u8 *opos; + size_t gtk_len, kde_len; + struct wpa_auth_config *conf = &sm->wpa_auth->conf; + struct wpa_group *gsm = sm->group; + u8 *wpa_ie; + int wpa_ie_len, secure, gtkidx, encr = 0; + u8 hdr[2]; + + /* Send EAPOL(1, 1, 1, Pair, P, RSC, ANonce, MIC(PTK), RSNIE, [MDIE], + GTK[GN], IGTK, [BIGTK], [FTIE], [TIE * 2]) + */ + + /* Use 0 RSC */ + os_memset(rsc, 0, WPA_KEY_RSC_LEN); + /* If FT is used, wpa_auth->wpa_ie includes both RSNIE and MDIE */ + wpa_ie = sm->wpa_auth->wpa_ie; + wpa_ie_len = sm->wpa_auth->wpa_ie_len; + if (sm->wpa == WPA_VERSION_WPA && + (sm->wpa_auth->conf.wpa & WPA_PROTO_RSN) && + wpa_ie_len > wpa_ie[1] + 2 && wpa_ie[0] == WLAN_EID_RSN) { + /* WPA-only STA, remove RSN IE and possible MDIE */ + wpa_ie = wpa_ie + wpa_ie[1] + 2; + if (wpa_ie[0] == WLAN_EID_RSNX) + wpa_ie = wpa_ie + wpa_ie[1] + 2; + if (wpa_ie[0] == WLAN_EID_MOBILITY_DOMAIN) + wpa_ie = wpa_ie + wpa_ie[1] + 2; + wpa_ie_len = wpa_ie[1] + 2; + } + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "sending 3/4 msg of 4-Way Handshake (TESTING)"); + if (sm->wpa == WPA_VERSION_WPA2) { + /* WPA2 send GTK in the 4-way handshake */ + secure = 1; + gtk = gsm->GTK[gsm->GN - 1]; + gtk_len = gsm->GTK_len; + gtkidx = gsm->GN; + _rsc = rsc; + encr = 1; + } else { + /* WPA does not include GTK in msg 3/4 */ + secure = 0; + gtk = NULL; + gtk_len = 0; + _rsc = NULL; + if (sm->rx_eapol_key_secure) { + /* + * It looks like Windows 7 supplicant tries to use + * Secure bit in msg 2/4 after having reported Michael + * MIC failure and it then rejects the 4-way handshake + * if msg 3/4 does not set Secure bit. Work around this + * by setting the Secure bit here even in the case of + * WPA if the supplicant used it first. + */ + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), + LOGGER_DEBUG, + "STA used Secure bit in WPA msg 2/4 - set Secure for 3/4 as workaround"); + secure = 1; + } + } + + kde_len = wpa_ie_len + ieee80211w_kde_len(sm) + ocv_oci_len(sm); + + if (sm->use_ext_key_id) + kde_len += 2 + RSN_SELECTOR_LEN + 2; + + if (gtk) + kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len; +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + kde_len += 2 + PMKID_LEN; /* PMKR1Name into RSN IE */ + kde_len += 300; /* FTIE + 2 * TIE */ + } +#endif /* CONFIG_IEEE80211R_AP */ + kde = os_malloc(kde_len); + if (!kde) + return -1; + + pos = kde; + os_memcpy(pos, wpa_ie, wpa_ie_len); + pos += wpa_ie_len; +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + int res; + size_t elen; + + elen = pos - kde; + res = wpa_insert_pmkid(kde, &elen, sm->pmk_r1_name, true); + if (res < 0) { + wpa_printf(MSG_ERROR, + "FT: Failed to insert PMKR1Name into RSN IE in EAPOL-Key data"); + os_free(kde); + return -1; + } + pos -= wpa_ie_len; + pos += elen; + } +#endif /* CONFIG_IEEE80211R_AP */ + hdr[1] = 0; + + if (sm->use_ext_key_id) { + hdr[0] = sm->keyidx_active & 0x01; + pos = wpa_add_kde(pos, RSN_KEY_DATA_KEYID, hdr, 2, NULL, 0); + } + + if (gtk) { + hdr[0] = gtkidx & 0x03; + pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2, + gtk, gtk_len); + } + opos = pos; + pos = ieee80211w_kde_add(sm, pos); + if (pos - opos >= 2 + RSN_SELECTOR_LEN + WPA_IGTK_KDE_PREFIX_LEN) { + /* skip KDE header and keyid */ + opos += 2 + RSN_SELECTOR_LEN + 2; + os_memset(opos, 0, 6); /* clear PN */ + } + if (ocv_oci_add(sm, &pos, conf->oci_freq_override_eapol_m3) < 0) { + os_free(kde); + return -1; + } + +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + int res; + + if (sm->assoc_resp_ftie && + kde + kde_len - pos >= 2 + sm->assoc_resp_ftie[1]) { + os_memcpy(pos, sm->assoc_resp_ftie, + 2 + sm->assoc_resp_ftie[1]); + res = 2 + sm->assoc_resp_ftie[1]; + } else { + res = wpa_write_ftie(conf, sm->wpa_key_mgmt, + sm->xxkey_len, + conf->r0_key_holder, + conf->r0_key_holder_len, + NULL, NULL, pos, + kde + kde_len - pos, + NULL, 0, 0); + } + if (res < 0) { + wpa_printf(MSG_ERROR, + "FT: Failed to insert FTIE into EAPOL-Key Key Data"); + os_free(kde); + return -1; + } + pos += res; + + /* TIE[ReassociationDeadline] (TU) */ + *pos++ = WLAN_EID_TIMEOUT_INTERVAL; + *pos++ = 5; + *pos++ = WLAN_TIMEOUT_REASSOC_DEADLINE; + WPA_PUT_LE32(pos, conf->reassociation_deadline); + pos += 4; + + /* TIE[KeyLifetime] (seconds) */ + *pos++ = WLAN_EID_TIMEOUT_INTERVAL; + *pos++ = 5; + *pos++ = WLAN_TIMEOUT_KEY_LIFETIME; + WPA_PUT_LE32(pos, conf->r0_key_lifetime); + pos += 4; + } +#endif /* CONFIG_IEEE80211R_AP */ + + wpa_send_eapol(sm->wpa_auth, sm, + (secure ? WPA_KEY_INFO_SECURE : 0) | + (wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len) ? + WPA_KEY_INFO_MIC : 0) | + WPA_KEY_INFO_ACK | WPA_KEY_INFO_INSTALL | + WPA_KEY_INFO_KEY_TYPE, + _rsc, sm->ANonce, kde, pos - kde, 0, encr); + bin_clear_free(kde, kde_len); + return 0; +} + + +int wpa_auth_resend_group_m1(struct wpa_state_machine *sm, + void (*cb)(void *ctx1, void *ctx2), + void *ctx1, void *ctx2) +{ + u8 rsc[WPA_KEY_RSC_LEN]; + struct wpa_auth_config *conf = &sm->wpa_auth->conf; + struct wpa_group *gsm = sm->group; + const u8 *kde; + u8 *kde_buf = NULL, *pos, hdr[2]; + u8 *opos; + size_t kde_len; + u8 *gtk; + + /* Send EAPOL(1, 1, 1, !Pair, G, RSC, GNonce, MIC(PTK), GTK[GN]) */ + os_memset(rsc, 0, WPA_KEY_RSC_LEN); + /* Use 0 RSC */ + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "sending 1/2 msg of Group Key Handshake (TESTING)"); + + gtk = gsm->GTK[gsm->GN - 1]; + if (sm->wpa == WPA_VERSION_WPA2) { + kde_len = 2 + RSN_SELECTOR_LEN + 2 + gsm->GTK_len + + ieee80211w_kde_len(sm) + ocv_oci_len(sm); + kde_buf = os_malloc(kde_len); + if (!kde_buf) + return -1; + + kde = pos = kde_buf; + hdr[0] = gsm->GN & 0x03; + hdr[1] = 0; + pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2, + gtk, gsm->GTK_len); + opos = pos; + pos = ieee80211w_kde_add(sm, pos); + if (pos - opos >= + 2 + RSN_SELECTOR_LEN + WPA_IGTK_KDE_PREFIX_LEN) { + /* skip KDE header and keyid */ + opos += 2 + RSN_SELECTOR_LEN + 2; + os_memset(opos, 0, 6); /* clear PN */ + } + if (ocv_oci_add(sm, &pos, + conf->oci_freq_override_eapol_g1) < 0) { + os_free(kde_buf); + return -1; + } + kde_len = pos - kde; + } else { + kde = gtk; + kde_len = gsm->GTK_len; + } + + sm->eapol_status_cb = cb; + sm->eapol_status_cb_ctx1 = ctx1; + sm->eapol_status_cb_ctx2 = ctx2; + + wpa_send_eapol(sm->wpa_auth, sm, + WPA_KEY_INFO_SECURE | + (wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len) ? + WPA_KEY_INFO_MIC : 0) | + WPA_KEY_INFO_ACK | + (!sm->Pair ? WPA_KEY_INFO_INSTALL : 0), + rsc, NULL, kde, kde_len, gsm->GN, 1); + + bin_clear_free(kde_buf, kde_len); + return 0; +} + + +int wpa_auth_rekey_gtk(struct wpa_authenticator *wpa_auth) +{ + if (!wpa_auth) + return -1; + eloop_cancel_timeout(wpa_rekey_gtk, + wpa_get_primary_auth(wpa_auth), NULL); + return eloop_register_timeout(0, 0, wpa_rekey_gtk, + wpa_get_primary_auth(wpa_auth), NULL); +} + + +int wpa_auth_rekey_ptk(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm) +{ + if (!wpa_auth || !sm) + return -1; + wpa_auth_logger(wpa_auth, sm->addr, LOGGER_DEBUG, "rekeying PTK"); + wpa_request_new_ptk(sm); + wpa_sm_step(sm); + return 0; +} + + +void wpa_auth_set_ft_rsnxe_used(struct wpa_authenticator *wpa_auth, int val) +{ + if (wpa_auth) + wpa_auth->conf.ft_rsnxe_used = val; +} + + +void wpa_auth_set_ocv_override_freq(struct wpa_authenticator *wpa_auth, + enum wpa_auth_ocv_override_frame frame, + unsigned int freq) +{ + if (!wpa_auth) + return; + switch (frame) { + case WPA_AUTH_OCV_OVERRIDE_EAPOL_M3: + wpa_auth->conf.oci_freq_override_eapol_m3 = freq; + break; + case WPA_AUTH_OCV_OVERRIDE_EAPOL_G1: + wpa_auth->conf.oci_freq_override_eapol_g1 = freq; + break; + case WPA_AUTH_OCV_OVERRIDE_FT_ASSOC: + wpa_auth->conf.oci_freq_override_ft_assoc = freq; + break; + case WPA_AUTH_OCV_OVERRIDE_FILS_ASSOC: + wpa_auth->conf.oci_freq_override_fils_assoc = freq; + break; + } +} + +#endif /* CONFIG_TESTING_OPTIONS */ + + +void wpa_auth_sta_radius_psk_resp(struct wpa_state_machine *sm, bool success) +{ + if (!sm->waiting_radius_psk) { + wpa_printf(MSG_DEBUG, + "Ignore RADIUS PSK response for " MACSTR + " that did not wait one", + MAC2STR(sm->addr)); + return; + } + + wpa_printf(MSG_DEBUG, "RADIUS PSK response for " MACSTR " (%s)", + MAC2STR(sm->addr), success ? "success" : "fail"); + sm->waiting_radius_psk = 0; + + if (success) { + /* Try to process the EAPOL-Key msg 2/4 again */ + sm->EAPOLKeyReceived = true; + } else { + sm->Disconnect = true; + } + + eloop_register_timeout(0, 0, wpa_sm_call_step, sm, NULL); +} + + +void wpa_auth_set_ml_info(struct wpa_state_machine *sm, + u8 mld_assoc_link_id, struct mld_info *info) +{ +#ifdef CONFIG_IEEE80211BE + unsigned int link_id; + + if (!info) + return; + + os_memset(sm->mld_links, 0, sizeof(sm->mld_links)); + sm->n_mld_affiliated_links = 0; + + wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG, + "MLD: Initialization"); + + os_memcpy(sm->peer_mld_addr, info->common_info.mld_addr, ETH_ALEN); + + sm->mld_assoc_link_id = mld_assoc_link_id; + + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) { + struct mld_link_info *link = &info->links[link_id]; + struct mld_link *sm_link = &sm->mld_links[link_id]; + struct wpa_get_link_auth_ctx ctx; + + sm_link->valid = link->valid; + if (!link->valid) + continue; + + os_memcpy(sm_link->peer_addr, link->peer_addr, ETH_ALEN); + + wpa_printf(MSG_DEBUG, + "WPA_AUTH: MLD: id=%u, peer=" MACSTR, + link_id, + MAC2STR(sm_link->peer_addr)); + + if (link_id != mld_assoc_link_id) { + sm->n_mld_affiliated_links++; + ctx.addr = link->local_addr; + ctx.mld_addr = NULL; + ctx.link_id = -1; + ctx.wpa_auth = NULL; + wpa_auth_for_each_auth(sm->wpa_auth, + wpa_get_link_sta_auth, &ctx); + if (ctx.wpa_auth) { + sm_link->wpa_auth = ctx.wpa_auth; + wpa_group_get(sm_link->wpa_auth, + sm_link->wpa_auth->group); + } + } else { + sm_link->wpa_auth = sm->wpa_auth; + } + + if (!sm_link->wpa_auth) + wpa_printf(MSG_ERROR, + "Unable to find authenticator object for ML STA " + MACSTR " on link id %d", + MAC2STR(sm->wpa_auth->mld_addr), + link_id); + } +#endif /* CONFIG_IEEE80211BE */ +} diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h new file mode 100644 index 0000000..b588f9f --- /dev/null +++ b/src/ap/wpa_auth.h @@ -0,0 +1,658 @@ +/* + * hostapd - IEEE 802.11i-2004 / WPA Authenticator + * Copyright (c) 2004-2022, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPA_AUTH_H +#define WPA_AUTH_H + +#include "common/defs.h" +#include "common/eapol_common.h" +#include "common/wpa_common.h" +#include "common/ieee802_11_defs.h" + +struct vlan_description; +struct mld_info; + +#define MAX_OWN_IE_OVERRIDE 256 + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif /* _MSC_VER */ + +/* IEEE Std 802.11r-2008, 11A.10.3 - Remote request/response frame definition + */ +struct ft_rrb_frame { + u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */ + u8 packet_type; /* FT_PACKET_REQUEST/FT_PACKET_RESPONSE */ + le16 action_length; /* little endian length of action_frame */ + u8 ap_address[ETH_ALEN]; + /* + * Followed by action_length bytes of FT Action frame (from Category + * field to the end of Action Frame body. + */ +} STRUCT_PACKED; + +#define RSN_REMOTE_FRAME_TYPE_FT_RRB 1 + +#define FT_PACKET_REQUEST 0 +#define FT_PACKET_RESPONSE 1 + +/* Vendor-specific types for R0KH-R1KH protocol; not defined in 802.11r. These + * use OUI Extended EtherType as the encapsulating format. */ +#define FT_PACKET_R0KH_R1KH_PULL 0x01 +#define FT_PACKET_R0KH_R1KH_RESP 0x02 +#define FT_PACKET_R0KH_R1KH_PUSH 0x03 +#define FT_PACKET_R0KH_R1KH_SEQ_REQ 0x04 +#define FT_PACKET_R0KH_R1KH_SEQ_RESP 0x05 + +/* packet layout + * IEEE 802 extended OUI ethertype frame header + * u16 authlen (little endian) + * multiple of struct ft_rrb_tlv (authenticated only, length = authlen) + * multiple of struct ft_rrb_tlv (AES-SIV encrypted, AES-SIV needs an extra + * blocksize length) + * + * AES-SIV AAD; + * source MAC address (6) + * authenticated-only TLVs (authlen) + * subtype (1; FT_PACKET_*) + */ + +#define FT_RRB_NONCE_LEN 16 + +#define FT_RRB_LAST_EMPTY 0 /* placeholder or padding */ + +#define FT_RRB_SEQ 1 /* struct ft_rrb_seq */ +#define FT_RRB_NONCE 2 /* size FT_RRB_NONCE_LEN */ +#define FT_RRB_TIMESTAMP 3 /* le32 unix seconds */ + +#define FT_RRB_R0KH_ID 4 /* FT_R0KH_ID_MAX_LEN */ +#define FT_RRB_R1KH_ID 5 /* FT_R1KH_ID_LEN */ +#define FT_RRB_S1KH_ID 6 /* ETH_ALEN */ + +#define FT_RRB_PMK_R0_NAME 7 /* WPA_PMK_NAME_LEN */ +#define FT_RRB_PMK_R0 8 /* PMK_LEN */ +#define FT_RRB_PMK_R1_NAME 9 /* WPA_PMK_NAME_LEN */ +#define FT_RRB_PMK_R1 10 /* PMK_LEN */ + +#define FT_RRB_PAIRWISE 11 /* le16 */ +#define FT_RRB_EXPIRES_IN 12 /* le16 seconds */ + +#define FT_RRB_VLAN_UNTAGGED 13 /* le16 */ +#define FT_RRB_VLAN_TAGGED 14 /* n times le16 */ + +#define FT_RRB_IDENTITY 15 +#define FT_RRB_RADIUS_CUI 16 +#define FT_RRB_SESSION_TIMEOUT 17 /* le32 seconds */ + +struct ft_rrb_tlv { + le16 type; + le16 len; + /* followed by data of length len */ +} STRUCT_PACKED; + +struct ft_rrb_seq { + le32 dom; + le32 seq; + le32 ts; +} STRUCT_PACKED; + +/* session TLVs: + * required: PMK_R1, PMK_R1_NAME, PAIRWISE + * optional: VLAN_UNTAGGED, VLAN_TAGGED, EXPIRES_IN, IDENTITY, RADIUS_CUI, + * SESSION_TIMEOUT + * + * pull frame TLVs: + * auth: + * required: SEQ, NONCE, R0KH_ID, R1KH_ID + * encrypted: + * required: PMK_R0_NAME, S1KH_ID + * + * response frame TLVs: + * auth: + * required: SEQ, NONCE, R0KH_ID, R1KH_ID + * encrypted: + * required: S1KH_ID + * optional: session TLVs + * + * push frame TLVs: + * auth: + * required: SEQ, R0KH_ID, R1KH_ID + * encrypted: + * required: S1KH_ID, PMK_R0_NAME, session TLVs + * + * sequence number request frame TLVs: + * auth: + * required: R0KH_ID, R1KH_ID, NONCE + * + * sequence number response frame TLVs: + * auth: + * required: SEQ, NONCE, R0KH_ID, R1KH_ID + */ + +#ifdef _MSC_VER +#pragma pack(pop) +#endif /* _MSC_VER */ + + +/* per STA state machine data */ + +struct wpa_authenticator; +struct wpa_state_machine; +struct rsn_pmksa_cache_entry; +struct eapol_state_machine; +struct ft_remote_seq; +struct wpa_channel_info; + + +struct ft_remote_r0kh { + struct ft_remote_r0kh *next; + u8 addr[ETH_ALEN]; + u8 id[FT_R0KH_ID_MAX_LEN]; + size_t id_len; + u8 key[32]; + struct ft_remote_seq *seq; +}; + + +struct ft_remote_r1kh { + struct ft_remote_r1kh *next; + u8 addr[ETH_ALEN]; + u8 id[FT_R1KH_ID_LEN]; + u8 key[32]; + struct ft_remote_seq *seq; +}; + + +struct wpa_auth_config { + void *msg_ctx; + int wpa; + int extended_key_id; + int wpa_key_mgmt; + int wpa_pairwise; + int wpa_group; + int wpa_group_rekey; + int wpa_strict_rekey; + int wpa_gmk_rekey; + int wpa_ptk_rekey; + int wpa_deny_ptk0_rekey; + u32 wpa_group_update_count; + u32 wpa_pairwise_update_count; + int wpa_disable_eapol_key_retries; + int rsn_pairwise; + int rsn_preauth; + int eapol_version; + int wmm_enabled; + int wmm_uapsd; + int disable_pmksa_caching; + int okc; + int tx_status; + enum mfp_options ieee80211w; + int beacon_prot; + int group_mgmt_cipher; + int sae_require_mfp; +#ifdef CONFIG_OCV + int ocv; /* Operating Channel Validation */ +#endif /* CONFIG_OCV */ + u8 ssid[SSID_MAX_LEN]; + size_t ssid_len; +#ifdef CONFIG_IEEE80211R_AP + u8 mobility_domain[MOBILITY_DOMAIN_ID_LEN]; + u8 r0_key_holder[FT_R0KH_ID_MAX_LEN]; + size_t r0_key_holder_len; + u8 r1_key_holder[FT_R1KH_ID_LEN]; + u32 r0_key_lifetime; /* PMK-R0 lifetime seconds */ + int rkh_pos_timeout; + int rkh_neg_timeout; + int rkh_pull_timeout; /* ms */ + int rkh_pull_retries; + int r1_max_key_lifetime; + u32 reassociation_deadline; + struct ft_remote_r0kh **r0kh_list; + struct ft_remote_r1kh **r1kh_list; + int pmk_r1_push; + int ft_over_ds; + int ft_psk_generate_local; +#endif /* CONFIG_IEEE80211R_AP */ + int disable_gtk; + int ap_mlme; +#ifdef CONFIG_TESTING_OPTIONS + double corrupt_gtk_rekey_mic_probability; + u8 own_ie_override[MAX_OWN_IE_OVERRIDE]; + size_t own_ie_override_len; + u8 rsne_override_eapol[MAX_OWN_IE_OVERRIDE]; + size_t rsne_override_eapol_len; + u8 rsnxe_override_eapol[MAX_OWN_IE_OVERRIDE]; + size_t rsnxe_override_eapol_len; + u8 rsne_override_ft[MAX_OWN_IE_OVERRIDE]; + size_t rsne_override_ft_len; + u8 rsnxe_override_ft[MAX_OWN_IE_OVERRIDE]; + size_t rsnxe_override_ft_len; + u8 gtk_rsc_override[WPA_KEY_RSC_LEN]; + u8 igtk_rsc_override[WPA_KEY_RSC_LEN]; + unsigned int rsne_override_eapol_set:1; + unsigned int rsnxe_override_eapol_set:1; + unsigned int rsne_override_ft_set:1; + unsigned int rsnxe_override_ft_set:1; + unsigned int gtk_rsc_override_set:1; + unsigned int igtk_rsc_override_set:1; + int ft_rsnxe_used; + bool delay_eapol_tx; + struct wpabuf *eapol_m1_elements; + struct wpabuf *eapol_m3_elements; + bool eapol_m3_no_encrypt; +#endif /* CONFIG_TESTING_OPTIONS */ + unsigned int oci_freq_override_eapol_m3; + unsigned int oci_freq_override_eapol_g1; + unsigned int oci_freq_override_ft_assoc; + unsigned int oci_freq_override_fils_assoc; +#ifdef CONFIG_P2P + u8 ip_addr_go[4]; + u8 ip_addr_mask[4]; + u8 ip_addr_start[4]; + u8 ip_addr_end[4]; +#endif /* CONFIG_P2P */ +#ifdef CONFIG_FILS + unsigned int fils_cache_id_set:1; + u8 fils_cache_id[FILS_CACHE_ID_LEN]; +#endif /* CONFIG_FILS */ + enum sae_pwe sae_pwe; + bool sae_pk; + + unsigned int secure_ltf:1; + unsigned int secure_rtt:1; + unsigned int prot_range_neg:1; + + int owe_ptk_workaround; + u8 transition_disable; +#ifdef CONFIG_DPP2 + int dpp_pfs; +#endif /* CONFIG_DPP2 */ + + /* + * If set Key Derivation Key should be derived as part of PMK to + * PTK derivation regardless of advertised capabilities. + */ + bool force_kdk_derivation; + + bool radius_psk; + + bool no_disconnect_on_group_keyerror; + + /* Pointer to Multi-BSSID transmitted BSS authenticator instance. + * Set only in nontransmitted BSSs, i.e., is NULL for transmitted BSS + * and in BSSs that are not part of a Multi-BSSID set. */ + struct wpa_authenticator *tx_bss_auth; + +#ifdef CONFIG_IEEE80211BE + const u8 *mld_addr; + int link_id; + struct wpa_authenticator *first_link_auth; +#endif /* CONFIG_IEEE80211BE */ + + bool ssid_protection; +}; + +typedef enum { + LOGGER_DEBUG, LOGGER_INFO, LOGGER_WARNING +} logger_level; + +typedef enum { + WPA_EAPOL_portEnabled, WPA_EAPOL_portValid, WPA_EAPOL_authorized, + WPA_EAPOL_portControl_Auto, WPA_EAPOL_keyRun, WPA_EAPOL_keyAvailable, + WPA_EAPOL_keyDone, WPA_EAPOL_inc_EapolFramesTx +} wpa_eapol_variable; + +struct wpa_auth_ml_key_info { + unsigned int n_mld_links; + bool mgmt_frame_prot; + bool beacon_prot; + + struct wpa_auth_ml_link_key_info { + u8 link_id; + + u8 gtkidx; + u8 gtk_len; + u8 pn[6]; + const u8 *gtk; + + u8 igtkidx; + u8 igtk_len; + const u8 *igtk; + u8 ipn[6]; + + u8 bigtkidx; + const u8 *bigtk; + u8 bipn[6]; + } links[MAX_NUM_MLD_LINKS]; +}; + +struct wpa_auth_callbacks { + void (*logger)(void *ctx, const u8 *addr, logger_level level, + const char *txt); + void (*disconnect)(void *ctx, const u8 *addr, u16 reason); + int (*mic_failure_report)(void *ctx, const u8 *addr); + void (*psk_failure_report)(void *ctx, const u8 *addr); + void (*set_eapol)(void *ctx, const u8 *addr, wpa_eapol_variable var, + int value); + int (*get_eapol)(void *ctx, const u8 *addr, wpa_eapol_variable var); + const u8 * (*get_psk)(void *ctx, const u8 *addr, const u8 *p2p_dev_addr, + const u8 *prev_psk, size_t *psk_len, + int *vlan_id); + int (*get_msk)(void *ctx, const u8 *addr, u8 *msk, size_t *len); + int (*set_key)(void *ctx, int vlan_id, enum wpa_alg alg, + const u8 *addr, int idx, u8 *key, size_t key_len, + enum key_flag key_flag); + int (*get_seqnum)(void *ctx, const u8 *addr, int idx, u8 *seq); + int (*send_eapol)(void *ctx, const u8 *addr, const u8 *data, + size_t data_len, int encrypt); + int (*get_sta_count)(void *ctx); + int (*for_each_sta)(void *ctx, int (*cb)(struct wpa_state_machine *sm, + void *ctx), void *cb_ctx); + int (*for_each_auth)(void *ctx, int (*cb)(struct wpa_authenticator *a, + void *ctx), void *cb_ctx); + int (*send_ether)(void *ctx, const u8 *dst, u16 proto, const u8 *data, + size_t data_len); + int (*send_oui)(void *ctx, const u8 *dst, u8 oui_suffix, const u8 *data, + size_t data_len); + int (*channel_info)(void *ctx, struct wpa_channel_info *ci); + int (*update_vlan)(void *ctx, const u8 *addr, int vlan_id); + int (*get_sta_tx_params)(void *ctx, const u8 *addr, + int ap_max_chanwidth, int ap_seg1_idx, + int *bandwidth, int *seg1_idx); + void (*store_ptksa)(void *ctx, const u8 *addr, int cipher, + u32 life_time, const struct wpa_ptk *ptk); + void (*clear_ptksa)(void *ctx, const u8 *addr, int cipher); + void (*request_radius_psk)(void *ctx, const u8 *addr, int key_mgmt, + const u8 *anonce, + const u8 *eapol, size_t eapol_len); +#ifdef CONFIG_IEEE80211R_AP + struct wpa_state_machine * (*add_sta)(void *ctx, const u8 *sta_addr); + int (*add_sta_ft)(void *ctx, const u8 *sta_addr); + int (*set_vlan)(void *ctx, const u8 *sta_addr, + struct vlan_description *vlan); + int (*get_vlan)(void *ctx, const u8 *sta_addr, + struct vlan_description *vlan); + int (*set_identity)(void *ctx, const u8 *sta_addr, + const u8 *identity, size_t identity_len); + size_t (*get_identity)(void *ctx, const u8 *sta_addr, const u8 **buf); + int (*set_radius_cui)(void *ctx, const u8 *sta_addr, + const u8 *radius_cui, size_t radius_cui_len); + size_t (*get_radius_cui)(void *ctx, const u8 *sta_addr, const u8 **buf); + void (*set_session_timeout)(void *ctx, const u8 *sta_addr, + int session_timeout); + int (*get_session_timeout)(void *ctx, const u8 *sta_addr); + + int (*send_ft_action)(void *ctx, const u8 *dst, + const u8 *data, size_t data_len); + int (*add_tspec)(void *ctx, const u8 *sta_addr, u8 *tspec_ie, + size_t tspec_ielen); +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_MESH + int (*start_ampe)(void *ctx, const u8 *sta_addr); +#endif /* CONFIG_MESH */ +#ifdef CONFIG_PASN + int (*set_ltf_keyseed)(void *ctx, const u8 *addr, const u8 *ltf_keyseed, + size_t ltf_keyseed_len); +#endif /* CONFIG_PASN */ +#ifdef CONFIG_IEEE80211BE + int (*get_ml_key_info)(void *ctx, struct wpa_auth_ml_key_info *info); +#endif /* CONFIG_IEEE80211BE */ + int (*get_drv_flags)(void *ctx, u64 *drv_flags, u64 *drv_flags2); +}; + +struct wpa_authenticator * wpa_init(const u8 *addr, + struct wpa_auth_config *conf, + const struct wpa_auth_callbacks *cb, + void *cb_ctx); +int wpa_init_keys(struct wpa_authenticator *wpa_auth); +void wpa_deinit(struct wpa_authenticator *wpa_auth); +int wpa_reconfig(struct wpa_authenticator *wpa_auth, + struct wpa_auth_config *conf); + +enum wpa_validate_result { + WPA_IE_OK, WPA_INVALID_IE, WPA_INVALID_GROUP, WPA_INVALID_PAIRWISE, + WPA_INVALID_AKMP, WPA_NOT_ENABLED, WPA_ALLOC_FAIL, + WPA_MGMT_FRAME_PROTECTION_VIOLATION, WPA_INVALID_MGMT_GROUP_CIPHER, + WPA_INVALID_MDIE, WPA_INVALID_PROTO, WPA_INVALID_PMKID, + WPA_DENIED_OTHER_REASON +}; + +enum wpa_validate_result +wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int freq, + const u8 *wpa_ie, size_t wpa_ie_len, + const u8 *rsnxe, size_t rsnxe_len, + const u8 *mdie, size_t mdie_len, + const u8 *owe_dh, size_t owe_dh_len, + struct wpa_state_machine *assoc_sm); +int wpa_validate_osen(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + const u8 *osen_ie, size_t osen_ie_len); +int wpa_auth_uses_mfp(struct wpa_state_machine *sm); +void wpa_auth_set_ocv(struct wpa_state_machine *sm, int ocv); +int wpa_auth_uses_ocv(struct wpa_state_machine *sm); +struct wpa_state_machine * +wpa_auth_sta_init(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *p2p_dev_addr); +int wpa_auth_sta_associated(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm); +void wpa_auth_sta_no_wpa(struct wpa_state_machine *sm); +void wpa_auth_sta_deinit(struct wpa_state_machine *sm); +void wpa_receive(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + u8 *data, size_t data_len); +enum wpa_event { + WPA_AUTH, WPA_ASSOC, WPA_DISASSOC, WPA_DEAUTH, WPA_REAUTH, + WPA_REAUTH_EAPOL, WPA_ASSOC_FT, WPA_ASSOC_FILS, WPA_DRV_STA_REMOVED +}; +void wpa_remove_ptk(struct wpa_state_machine *sm); +int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event); +void wpa_auth_sm_notify(struct wpa_state_machine *sm); +void wpa_gtk_rekey(struct wpa_authenticator *wpa_auth); +int wpa_get_mib(struct wpa_authenticator *wpa_auth, char *buf, size_t buflen); +int wpa_get_mib_sta(struct wpa_state_machine *sm, char *buf, size_t buflen); +void wpa_auth_countermeasures_start(struct wpa_authenticator *wpa_auth); +int wpa_auth_pairwise_set(struct wpa_state_machine *sm); +int wpa_auth_get_pairwise(struct wpa_state_machine *sm); +const u8 * wpa_auth_get_pmk(struct wpa_state_machine *sm, int *len); +const u8 * wpa_auth_get_dpp_pkhash(struct wpa_state_machine *sm); +int wpa_auth_sta_key_mgmt(struct wpa_state_machine *sm); +int wpa_auth_sta_wpa_version(struct wpa_state_machine *sm); +int wpa_auth_sta_ft_tk_already_set(struct wpa_state_machine *sm); +int wpa_auth_sta_fils_tk_already_set(struct wpa_state_machine *sm); +int wpa_auth_sta_clear_pmksa(struct wpa_state_machine *sm, + struct rsn_pmksa_cache_entry *entry); +struct rsn_pmksa_cache_entry * +wpa_auth_sta_get_pmksa(struct wpa_state_machine *sm); +void wpa_auth_sta_local_mic_failure_report(struct wpa_state_machine *sm); +const u8 * wpa_auth_get_wpa_ie(struct wpa_authenticator *wpa_auth, + size_t *len); +int wpa_auth_pmksa_add(struct wpa_state_machine *sm, const u8 *pmk, + unsigned int pmk_len, + int session_timeout, struct eapol_state_machine *eapol); +int wpa_auth_pmksa_add_preauth(struct wpa_authenticator *wpa_auth, + const u8 *pmk, size_t len, const u8 *sta_addr, + int session_timeout, + struct eapol_state_machine *eapol); +int wpa_auth_pmksa_add_sae(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *pmk, size_t pmk_len, const u8 *pmkid, + int akmp); +void wpa_auth_add_sae_pmkid(struct wpa_state_machine *sm, const u8 *pmkid); +int wpa_auth_pmksa_add2(struct wpa_authenticator *wpa_auth, const u8 *addr, + const u8 *pmk, size_t pmk_len, const u8 *pmkid, + int session_timeout, int akmp, const u8 *dpp_pkhash); +void wpa_auth_pmksa_remove(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr); +int wpa_auth_pmksa_list(struct wpa_authenticator *wpa_auth, char *buf, + size_t len); +void wpa_auth_pmksa_flush(struct wpa_authenticator *wpa_auth); +int wpa_auth_pmksa_list_mesh(struct wpa_authenticator *wpa_auth, const u8 *addr, + char *buf, size_t len); +struct rsn_pmksa_cache_entry * +wpa_auth_pmksa_create_entry(const u8 *aa, const u8 *spa, const u8 *pmk, + size_t pmk_len, int akmp, + const u8 *pmkid, int expiration); +int wpa_auth_pmksa_add_entry(struct wpa_authenticator *wpa_auth, + struct rsn_pmksa_cache_entry *entry); +struct rsn_pmksa_cache * +wpa_auth_get_pmksa_cache(struct wpa_authenticator *wpa_auth); +struct rsn_pmksa_cache_entry * +wpa_auth_pmksa_get(struct wpa_authenticator *wpa_auth, const u8 *sta_addr, + const u8 *pmkid); +struct rsn_pmksa_cache_entry * +wpa_auth_pmksa_get_fils_cache_id(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr, const u8 *pmkid); +void wpa_auth_pmksa_set_to_sm(struct rsn_pmksa_cache_entry *pmksa, + struct wpa_state_machine *sm, + struct wpa_authenticator *wpa_auth, + u8 *pmkid, u8 *pmk, size_t *pmk_len); +int wpa_auth_sta_set_vlan(struct wpa_state_machine *sm, int vlan_id); +void wpa_auth_eapol_key_tx_status(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int ack); + +#ifdef CONFIG_IEEE80211R_AP +u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos, + size_t max_len, int auth_alg, + const u8 *req_ies, size_t req_ies_len, + int omit_rsnxe); +void wpa_ft_process_auth(struct wpa_state_machine *sm, + u16 auth_transaction, const u8 *ies, size_t ies_len, + void (*cb)(void *ctx, const u8 *dst, + u16 auth_transaction, u16 resp, + const u8 *ies, size_t ies_len), + void *ctx); +int wpa_ft_validate_reassoc(struct wpa_state_machine *sm, const u8 *ies, + size_t ies_len); +int wpa_ft_action_rx(struct wpa_state_machine *sm, const u8 *data, size_t len); +int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr, + const u8 *data, size_t data_len); +void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr, + const u8 *dst_addr, u8 oui_suffix, const u8 *data, + size_t data_len); +void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr); +void wpa_ft_deinit(struct wpa_authenticator *wpa_auth); +void wpa_ft_sta_deinit(struct wpa_state_machine *sm); +int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth, + const u8 *spa, const u8 *pmk_r1_name, + u8 *pmk_r1, size_t *pmk_r1_len, int *pairwise, + struct vlan_description *vlan, + const u8 **identity, size_t *identity_len, + const u8 **radius_cui, size_t *radius_cui_len, + int *session_timeout); + +#endif /* CONFIG_IEEE80211R_AP */ + +void wpa_wnmsleep_rekey_gtk(struct wpa_state_machine *sm); +void wpa_set_wnmsleep(struct wpa_state_machine *sm, int flag); +int wpa_wnmsleep_gtk_subelem(struct wpa_state_machine *sm, u8 *pos); +int wpa_wnmsleep_igtk_subelem(struct wpa_state_machine *sm, u8 *pos); +int wpa_wnmsleep_bigtk_subelem(struct wpa_state_machine *sm, u8 *pos); + +int wpa_auth_uses_sae(struct wpa_state_machine *sm); +int wpa_auth_uses_ft_sae(struct wpa_state_machine *sm); + +int wpa_auth_get_ip_addr(struct wpa_state_machine *sm, u8 *addr); + +struct radius_das_attrs; +int wpa_auth_radius_das_disconnect_pmksa(struct wpa_authenticator *wpa_auth, + struct radius_das_attrs *attr); +void wpa_auth_reconfig_group_keys(struct wpa_authenticator *wpa_auth); + +int wpa_auth_ensure_group(struct wpa_authenticator *wpa_auth, int vlan_id); +int wpa_auth_release_group(struct wpa_authenticator *wpa_auth, int vlan_id); +int fils_auth_pmk_to_ptk(struct wpa_state_machine *sm, const u8 *pmk, + size_t pmk_len, const u8 *snonce, const u8 *anonce, + const u8 *dhss, size_t dhss_len, + struct wpabuf *g_sta, struct wpabuf *g_ap); +int fils_decrypt_assoc(struct wpa_state_machine *sm, const u8 *fils_session, + const struct ieee80211_mgmt *mgmt, size_t frame_len, + u8 *pos, size_t left); +int fils_encrypt_assoc(struct wpa_state_machine *sm, u8 *buf, + size_t current_len, size_t max_len, + const struct wpabuf *hlp); +int fils_set_tk(struct wpa_state_machine *sm); +u8 * hostapd_eid_assoc_fils_session(struct wpa_state_machine *sm, u8 *eid, + const u8 *fils_session, + struct wpabuf *fils_hlp_resp); +const u8 * wpa_fils_validate_fils_session(struct wpa_state_machine *sm, + const u8 *ies, size_t ies_len, + const u8 *fils_session); +int wpa_fils_validate_key_confirm(struct wpa_state_machine *sm, const u8 *ies, + size_t ies_len); + +int get_sta_tx_parameters(struct wpa_state_machine *sm, int ap_max_chanwidth, + int ap_seg1_idx, int *bandwidth, int *seg1_idx); + +int wpa_auth_write_fte(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + u8 *buf, size_t len); +void wpa_auth_get_fils_aead_params(struct wpa_state_machine *sm, + u8 *fils_anonce, u8 *fils_snonce, + u8 *fils_kek, size_t *fils_kek_len); +void wpa_auth_add_fils_pmk_pmkid(struct wpa_state_machine *sm, const u8 *pmk, + size_t pmk_len, const u8 *pmkid); +u8 * wpa_auth_write_assoc_resp_owe(struct wpa_state_machine *sm, + u8 *pos, size_t max_len, + const u8 *req_ies, size_t req_ies_len); +u8 * wpa_auth_write_assoc_resp_fils(struct wpa_state_machine *sm, + u8 *pos, size_t max_len, + const u8 *req_ies, size_t req_ies_len); +bool wpa_auth_write_fd_rsn_info(struct wpa_authenticator *wpa_auth, + u8 *fd_rsn_info); +void wpa_auth_set_auth_alg(struct wpa_state_machine *sm, u16 auth_alg); +void wpa_auth_set_dpp_z(struct wpa_state_machine *sm, const struct wpabuf *z); +void wpa_auth_set_ssid_protection(struct wpa_state_machine *sm, bool val); +void wpa_auth_set_transition_disable(struct wpa_authenticator *wpa_auth, + u8 val); + +int wpa_auth_resend_m1(struct wpa_state_machine *sm, int change_anonce, + void (*cb)(void *ctx1, void *ctx2), + void *ctx1, void *ctx2); +int wpa_auth_resend_m3(struct wpa_state_machine *sm, + void (*cb)(void *ctx1, void *ctx2), + void *ctx1, void *ctx2); +int wpa_auth_resend_group_m1(struct wpa_state_machine *sm, + void (*cb)(void *ctx1, void *ctx2), + void *ctx1, void *ctx2); +int wpa_auth_rekey_ptk(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm); +int wpa_auth_rekey_gtk(struct wpa_authenticator *wpa_auth); +int hostapd_wpa_auth_send_eapol(void *ctx, const u8 *addr, + const u8 *data, size_t data_len, + int encrypt); +void wpa_auth_set_ptk_rekey_timer(struct wpa_state_machine *sm); +void wpa_auth_set_ft_rsnxe_used(struct wpa_authenticator *wpa_auth, int val); + +enum wpa_auth_ocv_override_frame { + WPA_AUTH_OCV_OVERRIDE_EAPOL_M3, + WPA_AUTH_OCV_OVERRIDE_EAPOL_G1, + WPA_AUTH_OCV_OVERRIDE_FT_ASSOC, + WPA_AUTH_OCV_OVERRIDE_FILS_ASSOC, +}; +void wpa_auth_set_ocv_override_freq(struct wpa_authenticator *wpa_auth, + enum wpa_auth_ocv_override_frame frame, + unsigned int freq); + +void wpa_auth_sta_radius_psk_resp(struct wpa_state_machine *sm, bool success); + +void wpa_auth_set_ml_info(struct wpa_state_machine *sm, + u8 mld_assoc_link_id, struct mld_info *info); +void wpa_auth_ml_get_key_info(struct wpa_authenticator *a, + struct wpa_auth_ml_link_key_info *info, + bool mgmt_frame_prot, bool beacon_prot); + +void wpa_release_link_auth_ref(struct wpa_state_machine *sm, + int release_link_id); + +#define for_each_sm_auth(sm, link_id) \ + for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) \ + if (sm->mld_links[link_id].valid && \ + sm->mld_links[link_id].wpa_auth && \ + sm->wpa_auth != sm->mld_links[link_id].wpa_auth) + +#endif /* WPA_AUTH_H */ diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c new file mode 100644 index 0000000..de16c31 --- /dev/null +++ b/src/ap/wpa_auth_ft.c @@ -0,0 +1,4997 @@ +/* + * hostapd - IEEE 802.11r - Fast BSS Transition + * Copyright (c) 2004-2018, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "utils/list.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "common/ocv.h" +#include "common/wpa_ctrl.h" +#include "drivers/driver.h" +#include "crypto/aes.h" +#include "crypto/aes_siv.h" +#include "crypto/aes_wrap.h" +#include "crypto/sha384.h" +#include "crypto/sha512.h" +#include "crypto/random.h" +#include "ap_config.h" +#include "ieee802_11.h" +#include "wmm.h" +#include "wpa_auth.h" +#include "wpa_auth_i.h" +#include "pmksa_cache_auth.h" + + +#ifdef CONFIG_IEEE80211R_AP + +const unsigned int ftRRBseqTimeout = 10; +const unsigned int ftRRBmaxQueueLen = 100; + +/* TODO: make these configurable */ +static const int dot11RSNAConfigPMKLifetime = 43200; + + +static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm, + const u8 *current_ap, const u8 *sta_addr, + u16 status, const u8 *resp_ies, + size_t resp_ies_len); +static void ft_finish_pull(struct wpa_state_machine *sm); +static void wpa_ft_expire_pull(void *eloop_ctx, void *timeout_ctx); +static void wpa_ft_rrb_seq_timeout(void *eloop_ctx, void *timeout_ctx); + +struct tlv_list { + u16 type; + size_t len; + const u8 *data; +}; + + +/** + * wpa_ft_rrb_decrypt - Decrypt FT RRB message + * @key: AES-SIV key for AEAD + * @key_len: Length of key in octets + * @enc: Pointer to encrypted TLVs + * @enc_len: Length of encrypted TLVs in octets + * @auth: Pointer to authenticated TLVs + * @auth_len: Length of authenticated TLVs in octets + * @src_addr: MAC address of the frame sender + * @type: Vendor-specific subtype of the RRB frame (FT_PACKET_*) + * @plain: Pointer to return the pointer to the allocated plaintext buffer; + * needs to be freed by the caller if not NULL; + * will only be returned on success + * @plain_len: Pointer to return the length of the allocated plaintext buffer + * in octets + * Returns: 0 on success, -1 on error + */ +static int wpa_ft_rrb_decrypt(const u8 *key, const size_t key_len, + const u8 *enc, size_t enc_len, + const u8 *auth, const size_t auth_len, + const u8 *src_addr, u8 type, + u8 **plain, size_t *plain_size) +{ + const u8 *ad[3] = { src_addr, auth, &type }; + size_t ad_len[3] = { ETH_ALEN, auth_len, sizeof(type) }; + + wpa_printf(MSG_DEBUG, "FT(RRB): src_addr=" MACSTR " type=%u", + MAC2STR(src_addr), type); + wpa_hexdump_key(MSG_DEBUG, "FT(RRB): decrypt using key", key, key_len); + wpa_hexdump(MSG_DEBUG, "FT(RRB): encrypted TLVs", enc, enc_len); + wpa_hexdump(MSG_DEBUG, "FT(RRB): authenticated TLVs", auth, auth_len); + + if (!key) { /* skip decryption */ + *plain = os_memdup(enc, enc_len); + if (enc_len > 0 && !*plain) + goto err; + + *plain_size = enc_len; + + return 0; + } + + *plain = NULL; + + /* SIV overhead */ + if (enc_len < AES_BLOCK_SIZE) + goto err; + + *plain = os_zalloc(enc_len - AES_BLOCK_SIZE); + if (!*plain) + goto err; + + if (aes_siv_decrypt(key, key_len, enc, enc_len, 3, ad, ad_len, + *plain) < 0) { + if (enc_len < AES_BLOCK_SIZE + 2) + goto err; + + /* Try to work around Ethernet devices that add extra + * two octet padding even if the frame is longer than + * the minimum Ethernet frame. */ + enc_len -= 2; + if (aes_siv_decrypt(key, key_len, enc, enc_len, 3, ad, ad_len, + *plain) < 0) + goto err; + } + + *plain_size = enc_len - AES_BLOCK_SIZE; + wpa_hexdump_key(MSG_DEBUG, "FT(RRB): decrypted TLVs", + *plain, *plain_size); + return 0; +err: + os_free(*plain); + *plain = NULL; + *plain_size = 0; + + wpa_printf(MSG_ERROR, "FT(RRB): Failed to decrypt"); + + return -1; +} + + +/* get first tlv record in packet matching type + * @data (decrypted) packet + * @return 0 on success else -1 + */ +static int wpa_ft_rrb_get_tlv(const u8 *plain, size_t plain_len, + u16 type, size_t *tlv_len, const u8 **tlv_data) +{ + const struct ft_rrb_tlv *f; + size_t left; + le16 type16; + size_t len; + + left = plain_len; + type16 = host_to_le16(type); + + while (left >= sizeof(*f)) { + f = (const struct ft_rrb_tlv *) plain; + + left -= sizeof(*f); + plain += sizeof(*f); + len = le_to_host16(f->len); + + if (left < len) { + wpa_printf(MSG_DEBUG, "FT: RRB message truncated"); + break; + } + + if (f->type == type16) { + *tlv_len = len; + *tlv_data = plain; + return 0; + } + + left -= len; + plain += len; + } + + return -1; +} + + +static void wpa_ft_rrb_dump(const u8 *plain, const size_t plain_len) +{ + const struct ft_rrb_tlv *f; + size_t left; + size_t len; + + left = plain_len; + + wpa_printf(MSG_DEBUG, "FT: RRB dump message"); + while (left >= sizeof(*f)) { + f = (const struct ft_rrb_tlv *) plain; + + left -= sizeof(*f); + plain += sizeof(*f); + len = le_to_host16(f->len); + + wpa_printf(MSG_DEBUG, "FT: RRB TLV type = %d, len = %zu", + le_to_host16(f->type), len); + + if (left < len) { + wpa_printf(MSG_DEBUG, + "FT: RRB message truncated: left %zu bytes, need %zu", + left, len); + break; + } + + wpa_hexdump(MSG_DEBUG, "FT: RRB TLV data", plain, len); + + left -= len; + plain += len; + } + + if (left > 0) + wpa_hexdump(MSG_DEBUG, "FT: RRB TLV padding", plain, left); + + wpa_printf(MSG_DEBUG, "FT: RRB dump message end"); +} + + +static int cmp_int(const void *a, const void *b) +{ + int x, y; + + x = *((int *) a); + y = *((int *) b); + return x - y; +} + + +static int wpa_ft_rrb_get_tlv_vlan(const u8 *plain, const size_t plain_len, + struct vlan_description *vlan) +{ + struct ft_rrb_tlv *f; + size_t left; + size_t len; + int taggedidx; + int vlan_id; + int type; + + left = plain_len; + taggedidx = 0; + os_memset(vlan, 0, sizeof(*vlan)); + + while (left >= sizeof(*f)) { + f = (struct ft_rrb_tlv *) plain; + + left -= sizeof(*f); + plain += sizeof(*f); + + len = le_to_host16(f->len); + type = le_to_host16(f->type); + + if (left < len) { + wpa_printf(MSG_DEBUG, "FT: RRB message truncated"); + return -1; + } + + if (type != FT_RRB_VLAN_UNTAGGED && type != FT_RRB_VLAN_TAGGED) + goto skip; + + if (type == FT_RRB_VLAN_UNTAGGED && len != sizeof(le16)) { + wpa_printf(MSG_DEBUG, + "FT: RRB VLAN_UNTAGGED invalid length"); + return -1; + } + + if (type == FT_RRB_VLAN_TAGGED && len % sizeof(le16) != 0) { + wpa_printf(MSG_DEBUG, + "FT: RRB VLAN_TAGGED invalid length"); + return -1; + } + + while (len >= sizeof(le16)) { + vlan_id = WPA_GET_LE16(plain); + plain += sizeof(le16); + left -= sizeof(le16); + len -= sizeof(le16); + + if (vlan_id <= 0 || vlan_id > MAX_VLAN_ID) { + wpa_printf(MSG_DEBUG, + "FT: RRB VLAN ID invalid %d", + vlan_id); + continue; + } + + if (type == FT_RRB_VLAN_UNTAGGED) + vlan->untagged = vlan_id; + + if (type == FT_RRB_VLAN_TAGGED && + taggedidx < MAX_NUM_TAGGED_VLAN) { + vlan->tagged[taggedidx] = vlan_id; + taggedidx++; + } else if (type == FT_RRB_VLAN_TAGGED) { + wpa_printf(MSG_DEBUG, "FT: RRB too many VLANs"); + } + } + + skip: + left -= len; + plain += len; + } + + if (taggedidx) + qsort(vlan->tagged, taggedidx, sizeof(int), cmp_int); + + vlan->notempty = vlan->untagged || vlan->tagged[0]; + + return 0; +} + + +static size_t wpa_ft_tlv_len(const struct tlv_list *tlvs) +{ + size_t tlv_len = 0; + int i; + + if (!tlvs) + return 0; + + for (i = 0; tlvs[i].type != FT_RRB_LAST_EMPTY; i++) { + tlv_len += sizeof(struct ft_rrb_tlv); + tlv_len += tlvs[i].len; + } + + return tlv_len; +} + + +static size_t wpa_ft_tlv_lin(const struct tlv_list *tlvs, u8 *start, + u8 *endpos) +{ + int i; + size_t tlv_len; + struct ft_rrb_tlv *hdr; + u8 *pos; + + if (!tlvs) + return 0; + + tlv_len = 0; + pos = start; + for (i = 0; tlvs[i].type != FT_RRB_LAST_EMPTY; i++) { + if (tlv_len + sizeof(*hdr) > (size_t) (endpos - start)) + return tlv_len; + tlv_len += sizeof(*hdr); + hdr = (struct ft_rrb_tlv *) pos; + hdr->type = host_to_le16(tlvs[i].type); + hdr->len = host_to_le16(tlvs[i].len); + pos = start + tlv_len; + + if (tlv_len + tlvs[i].len > (size_t) (endpos - start)) + return tlv_len; + if (tlvs[i].len == 0) + continue; + tlv_len += tlvs[i].len; + os_memcpy(pos, tlvs[i].data, tlvs[i].len); + pos = start + tlv_len; + } + + return tlv_len; +} + + +static size_t wpa_ft_vlan_len(const struct vlan_description *vlan) +{ + size_t tlv_len = 0; + int i; + + if (!vlan || !vlan->notempty) + return 0; + + if (vlan->untagged) { + tlv_len += sizeof(struct ft_rrb_tlv); + tlv_len += sizeof(le16); + } + if (vlan->tagged[0]) + tlv_len += sizeof(struct ft_rrb_tlv); + for (i = 0; i < MAX_NUM_TAGGED_VLAN && vlan->tagged[i]; i++) + tlv_len += sizeof(le16); + + return tlv_len; +} + + +static size_t wpa_ft_vlan_lin(const struct vlan_description *vlan, + u8 *start, u8 *endpos) +{ + size_t tlv_len; + int i, len; + struct ft_rrb_tlv *hdr; + u8 *pos = start; + + if (!vlan || !vlan->notempty) + return 0; + + tlv_len = 0; + if (vlan->untagged) { + tlv_len += sizeof(*hdr); + if (start + tlv_len > endpos) + return tlv_len; + hdr = (struct ft_rrb_tlv *) pos; + hdr->type = host_to_le16(FT_RRB_VLAN_UNTAGGED); + hdr->len = host_to_le16(sizeof(le16)); + pos = start + tlv_len; + + tlv_len += sizeof(le16); + if (start + tlv_len > endpos) + return tlv_len; + WPA_PUT_LE16(pos, vlan->untagged); + pos = start + tlv_len; + } + + if (!vlan->tagged[0]) + return tlv_len; + + tlv_len += sizeof(*hdr); + if (start + tlv_len > endpos) + return tlv_len; + hdr = (struct ft_rrb_tlv *) pos; + hdr->type = host_to_le16(FT_RRB_VLAN_TAGGED); + len = 0; /* len is computed below */ + pos = start + tlv_len; + + for (i = 0; i < MAX_NUM_TAGGED_VLAN && vlan->tagged[i]; i++) { + tlv_len += sizeof(le16); + if (start + tlv_len > endpos) + break; + len += sizeof(le16); + WPA_PUT_LE16(pos, vlan->tagged[i]); + pos = start + tlv_len; + } + + hdr->len = host_to_le16(len); + + return tlv_len; +} + + +static int wpa_ft_rrb_lin(const struct tlv_list *tlvs1, + const struct tlv_list *tlvs2, + const struct vlan_description *vlan, + u8 **plain, size_t *plain_len) +{ + u8 *pos, *endpos; + size_t tlv_len; + + tlv_len = wpa_ft_tlv_len(tlvs1); + tlv_len += wpa_ft_tlv_len(tlvs2); + tlv_len += wpa_ft_vlan_len(vlan); + + *plain_len = tlv_len; + *plain = os_zalloc(tlv_len); + if (!*plain) { + wpa_printf(MSG_ERROR, "FT: Failed to allocate plaintext"); + goto err; + } + + pos = *plain; + endpos = *plain + tlv_len; + pos += wpa_ft_tlv_lin(tlvs1, pos, endpos); + pos += wpa_ft_tlv_lin(tlvs2, pos, endpos); + pos += wpa_ft_vlan_lin(vlan, pos, endpos); + + /* validity check */ + if (pos != endpos) { + wpa_printf(MSG_ERROR, "FT: Length error building RRB"); + goto err; + } + + return 0; + +err: + os_free(*plain); + *plain = NULL; + *plain_len = 0; + return -1; +} + + +static int wpa_ft_rrb_encrypt(const u8 *key, const size_t key_len, + const u8 *plain, const size_t plain_len, + const u8 *auth, const size_t auth_len, + const u8 *src_addr, u8 type, u8 *enc) +{ + const u8 *ad[3] = { src_addr, auth, &type }; + size_t ad_len[3] = { ETH_ALEN, auth_len, sizeof(type) }; + + wpa_printf(MSG_DEBUG, "FT(RRB): src_addr=" MACSTR " type=%u", + MAC2STR(src_addr), type); + wpa_hexdump_key(MSG_DEBUG, "FT(RRB): plaintext message", + plain, plain_len); + wpa_hexdump_key(MSG_DEBUG, "FT(RRB): encrypt using key", key, key_len); + wpa_hexdump(MSG_DEBUG, "FT(RRB): authenticated TLVs", auth, auth_len); + + if (!key) { + /* encryption not needed, return plaintext as packet */ + os_memcpy(enc, plain, plain_len); + } else if (aes_siv_encrypt(key, key_len, plain, plain_len, + 3, ad, ad_len, enc) < 0) { + wpa_printf(MSG_ERROR, "FT: Failed to encrypt RRB-OUI message"); + return -1; + } + wpa_hexdump(MSG_DEBUG, "FT(RRB): encrypted TLVs", + enc, plain_len + AES_BLOCK_SIZE); + + return 0; +} + + +/** + * wpa_ft_rrb_build - Build and encrypt an FT RRB message + * @key: AES-SIV key for AEAD + * @key_len: Length of key in octets + * @tlvs_enc0: First set of to-be-encrypted TLVs + * @tlvs_enc1: Second set of to-be-encrypted TLVs + * @tlvs_auth: Set of to-be-authenticated TLVs + * @src_addr: MAC address of the frame sender + * @type: Vendor-specific subtype of the RRB frame (FT_PACKET_*) + * @packet Pointer to return the pointer to the allocated packet buffer; + * needs to be freed by the caller if not null; + * will only be returned on success + * @packet_len: Pointer to return the length of the allocated buffer in octets + * Returns: 0 on success, -1 on error + */ +static int wpa_ft_rrb_build(const u8 *key, const size_t key_len, + const struct tlv_list *tlvs_enc0, + const struct tlv_list *tlvs_enc1, + const struct tlv_list *tlvs_auth, + const struct vlan_description *vlan, + const u8 *src_addr, u8 type, + u8 **packet, size_t *packet_len) +{ + u8 *plain = NULL, *auth = NULL, *pos, *tmp; + size_t plain_len = 0, auth_len = 0; + int ret = -1; + size_t pad_len = 0; + + *packet = NULL; + if (wpa_ft_rrb_lin(tlvs_enc0, tlvs_enc1, vlan, &plain, &plain_len) < 0) + goto out; + + if (wpa_ft_rrb_lin(tlvs_auth, NULL, NULL, &auth, &auth_len) < 0) + goto out; + + *packet_len = sizeof(u16) + auth_len + plain_len; + if (key) + *packet_len += AES_BLOCK_SIZE; +#define RRB_MIN_MSG_LEN 64 + if (*packet_len < RRB_MIN_MSG_LEN) { + pad_len = RRB_MIN_MSG_LEN - *packet_len; + if (pad_len < sizeof(struct ft_rrb_tlv)) + pad_len = sizeof(struct ft_rrb_tlv); + wpa_printf(MSG_DEBUG, + "FT: Pad message to minimum Ethernet frame length (%d --> %d)", + (int) *packet_len, (int) (*packet_len + pad_len)); + *packet_len += pad_len; + tmp = os_realloc(auth, auth_len + pad_len); + if (!tmp) + goto out; + auth = tmp; + pos = auth + auth_len; + WPA_PUT_LE16(pos, FT_RRB_LAST_EMPTY); + pos += 2; + WPA_PUT_LE16(pos, pad_len - sizeof(struct ft_rrb_tlv)); + pos += 2; + os_memset(pos, 0, pad_len - sizeof(struct ft_rrb_tlv)); + auth_len += pad_len; + + } + *packet = os_zalloc(*packet_len); + if (!*packet) + goto out; + + pos = *packet; + WPA_PUT_LE16(pos, auth_len); + pos += 2; + os_memcpy(pos, auth, auth_len); + pos += auth_len; + if (wpa_ft_rrb_encrypt(key, key_len, plain, plain_len, auth, + auth_len, src_addr, type, pos) < 0) + goto out; + wpa_hexdump(MSG_MSGDUMP, "FT: RRB frame payload", *packet, *packet_len); + + ret = 0; + +out: + bin_clear_free(plain, plain_len); + os_free(auth); + + if (ret) { + wpa_printf(MSG_ERROR, "FT: Failed to build RRB-OUI message"); + os_free(*packet); + *packet = NULL; + *packet_len = 0; + } + + return ret; +} + + +#define RRB_GET_SRC(srcfield, type, field, txt, checklength) do { \ + if (wpa_ft_rrb_get_tlv(srcfield, srcfield##_len, type, \ + &f_##field##_len, &f_##field) < 0 || \ + (checklength > 0 && ((size_t) checklength) != f_##field##_len)) { \ + wpa_printf(MSG_INFO, "FT: Missing required " #field \ + " in %s from " MACSTR, txt, MAC2STR(src_addr)); \ + wpa_ft_rrb_dump(srcfield, srcfield##_len); \ + goto out; \ + } \ +} while (0) + +#define RRB_GET(type, field, txt, checklength) \ + RRB_GET_SRC(plain, type, field, txt, checklength) +#define RRB_GET_AUTH(type, field, txt, checklength) \ + RRB_GET_SRC(auth, type, field, txt, checklength) + +#define RRB_GET_OPTIONAL_SRC(srcfield, type, field, txt, checklength) do { \ + if (wpa_ft_rrb_get_tlv(srcfield, srcfield##_len, type, \ + &f_##field##_len, &f_##field) < 0 || \ + (checklength > 0 && ((size_t) checklength) != f_##field##_len)) { \ + wpa_printf(MSG_DEBUG, "FT: Missing optional " #field \ + " in %s from " MACSTR, txt, MAC2STR(src_addr)); \ + f_##field##_len = 0; \ + f_##field = NULL; \ + } \ +} while (0) + +#define RRB_GET_OPTIONAL(type, field, txt, checklength) \ + RRB_GET_OPTIONAL_SRC(plain, type, field, txt, checklength) +#define RRB_GET_OPTIONAL_AUTH(type, field, txt, checklength) \ + RRB_GET_OPTIONAL_SRC(auth, type, field, txt, checklength) + +static int wpa_ft_rrb_send(struct wpa_authenticator *wpa_auth, const u8 *dst, + const u8 *data, size_t data_len) +{ + if (wpa_auth->cb->send_ether == NULL) + return -1; + wpa_printf(MSG_DEBUG, "FT: RRB send to " MACSTR, MAC2STR(dst)); + return wpa_auth->cb->send_ether(wpa_auth->cb_ctx, dst, ETH_P_RRB, + data, data_len); +} + + +static int wpa_ft_rrb_oui_send(struct wpa_authenticator *wpa_auth, + const u8 *dst, u8 oui_suffix, + const u8 *data, size_t data_len) +{ + if (!wpa_auth->cb->send_oui) + return -1; + wpa_printf(MSG_DEBUG, "FT: RRB-OUI type %u send to " MACSTR " (len=%u)", + oui_suffix, MAC2STR(dst), (unsigned int) data_len); + return wpa_auth->cb->send_oui(wpa_auth->cb_ctx, dst, oui_suffix, data, + data_len); +} + + +static int wpa_ft_action_send(struct wpa_authenticator *wpa_auth, + const u8 *dst, const u8 *data, size_t data_len) +{ + if (wpa_auth->cb->send_ft_action == NULL) + return -1; + return wpa_auth->cb->send_ft_action(wpa_auth->cb_ctx, dst, + data, data_len); +} + + +static const u8 * wpa_ft_get_psk(struct wpa_authenticator *wpa_auth, + const u8 *addr, const u8 *p2p_dev_addr, + const u8 *prev_psk) +{ + if (wpa_auth->cb->get_psk == NULL) + return NULL; + return wpa_auth->cb->get_psk(wpa_auth->cb_ctx, addr, p2p_dev_addr, + prev_psk, NULL, NULL); +} + + +static struct wpa_state_machine * +wpa_ft_add_sta(struct wpa_authenticator *wpa_auth, const u8 *sta_addr) +{ + if (wpa_auth->cb->add_sta == NULL) + return NULL; + return wpa_auth->cb->add_sta(wpa_auth->cb_ctx, sta_addr); +} + + +static int wpa_ft_set_vlan(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr, struct vlan_description *vlan) +{ + if (!wpa_auth->cb->set_vlan) + return -1; + return wpa_auth->cb->set_vlan(wpa_auth->cb_ctx, sta_addr, vlan); +} + + +static int wpa_ft_get_vlan(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr, struct vlan_description *vlan) +{ + if (!wpa_auth->cb->get_vlan) + return -1; + return wpa_auth->cb->get_vlan(wpa_auth->cb_ctx, sta_addr, vlan); +} + + +static int +wpa_ft_set_identity(struct wpa_authenticator *wpa_auth, const u8 *sta_addr, + const u8 *identity, size_t identity_len) +{ + if (!wpa_auth->cb->set_identity) + return -1; + return wpa_auth->cb->set_identity(wpa_auth->cb_ctx, sta_addr, identity, + identity_len); +} + + +static size_t +wpa_ft_get_identity(struct wpa_authenticator *wpa_auth, const u8 *sta_addr, + const u8 **buf) +{ + *buf = NULL; + if (!wpa_auth->cb->get_identity) + return 0; + return wpa_auth->cb->get_identity(wpa_auth->cb_ctx, sta_addr, buf); +} + + +static int +wpa_ft_set_radius_cui(struct wpa_authenticator *wpa_auth, const u8 *sta_addr, + const u8 *radius_cui, size_t radius_cui_len) +{ + if (!wpa_auth->cb->set_radius_cui) + return -1; + return wpa_auth->cb->set_radius_cui(wpa_auth->cb_ctx, sta_addr, + radius_cui, radius_cui_len); +} + + +static size_t +wpa_ft_get_radius_cui(struct wpa_authenticator *wpa_auth, const u8 *sta_addr, + const u8 **buf) +{ + *buf = NULL; + if (!wpa_auth->cb->get_radius_cui) + return 0; + return wpa_auth->cb->get_radius_cui(wpa_auth->cb_ctx, sta_addr, buf); +} + + +static void +wpa_ft_set_session_timeout(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr, int session_timeout) +{ + if (!wpa_auth->cb->set_session_timeout) + return; + wpa_auth->cb->set_session_timeout(wpa_auth->cb_ctx, sta_addr, + session_timeout); +} + + +static int +wpa_ft_get_session_timeout(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr) +{ + if (!wpa_auth->cb->get_session_timeout) + return 0; + return wpa_auth->cb->get_session_timeout(wpa_auth->cb_ctx, sta_addr); +} + + +static int wpa_ft_add_tspec(struct wpa_authenticator *wpa_auth, + const u8 *sta_addr, + u8 *tspec_ie, size_t tspec_ielen) +{ + if (wpa_auth->cb->add_tspec == NULL) { + wpa_printf(MSG_DEBUG, "FT: add_tspec is not initialized"); + return -1; + } + return wpa_auth->cb->add_tspec(wpa_auth->cb_ctx, sta_addr, tspec_ie, + tspec_ielen); +} + + +#ifdef CONFIG_OCV +static int wpa_channel_info(struct wpa_authenticator *wpa_auth, + struct wpa_channel_info *ci) +{ + if (!wpa_auth->cb->channel_info) + return -1; + return wpa_auth->cb->channel_info(wpa_auth->cb_ctx, ci); +} +#endif /* CONFIG_OCV */ + + +int wpa_write_mdie(struct wpa_auth_config *conf, u8 *buf, size_t len) +{ + u8 *pos = buf; + u8 capab; + if (len < 2 + sizeof(struct rsn_mdie)) + return -1; + + *pos++ = WLAN_EID_MOBILITY_DOMAIN; + *pos++ = MOBILITY_DOMAIN_ID_LEN + 1; + os_memcpy(pos, conf->mobility_domain, MOBILITY_DOMAIN_ID_LEN); + pos += MOBILITY_DOMAIN_ID_LEN; + capab = 0; + if (conf->ft_over_ds) + capab |= RSN_FT_CAPAB_FT_OVER_DS; + *pos++ = capab; + + return pos - buf; +} + + +int wpa_write_ftie(struct wpa_auth_config *conf, int key_mgmt, size_t key_len, + const u8 *r0kh_id, size_t r0kh_id_len, + const u8 *anonce, const u8 *snonce, + u8 *buf, size_t len, const u8 *subelem, + size_t subelem_len, int rsnxe_used) +{ + u8 *pos = buf, *ielen; + size_t hdrlen; + u16 mic_control = rsnxe_used ? FTE_MIC_CTRL_RSNXE_USED : 0; + + if (key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + key_len == SHA256_MAC_LEN) + hdrlen = sizeof(struct rsn_ftie); + else if (key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + key_len == SHA384_MAC_LEN) + hdrlen = sizeof(struct rsn_ftie_sha384); + else if (key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + key_len == SHA512_MAC_LEN) + hdrlen = sizeof(struct rsn_ftie_sha512); + else if (wpa_key_mgmt_sha384(key_mgmt)) + hdrlen = sizeof(struct rsn_ftie_sha384); + else + hdrlen = sizeof(struct rsn_ftie); + + if (len < 2 + hdrlen + 2 + FT_R1KH_ID_LEN + 2 + r0kh_id_len + + subelem_len) + return -1; + + *pos++ = WLAN_EID_FAST_BSS_TRANSITION; + ielen = pos++; + + if (key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + key_len == SHA512_MAC_LEN) { + struct rsn_ftie_sha512 *hdr = (struct rsn_ftie_sha512 *) pos; + + os_memset(hdr, 0, sizeof(*hdr)); + pos += sizeof(*hdr); + mic_control |= FTE_MIC_LEN_32 << FTE_MIC_CTRL_MIC_LEN_SHIFT; + WPA_PUT_LE16(hdr->mic_control, mic_control); + if (anonce) + os_memcpy(hdr->anonce, anonce, WPA_NONCE_LEN); + if (snonce) + os_memcpy(hdr->snonce, snonce, WPA_NONCE_LEN); + } else if ((key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + key_len == SHA384_MAC_LEN) || + wpa_key_mgmt_sha384(key_mgmt)) { + struct rsn_ftie_sha384 *hdr = (struct rsn_ftie_sha384 *) pos; + + os_memset(hdr, 0, sizeof(*hdr)); + pos += sizeof(*hdr); + mic_control |= FTE_MIC_LEN_24 << FTE_MIC_CTRL_MIC_LEN_SHIFT; + WPA_PUT_LE16(hdr->mic_control, mic_control); + if (anonce) + os_memcpy(hdr->anonce, anonce, WPA_NONCE_LEN); + if (snonce) + os_memcpy(hdr->snonce, snonce, WPA_NONCE_LEN); + } else { + struct rsn_ftie *hdr = (struct rsn_ftie *) pos; + + os_memset(hdr, 0, sizeof(*hdr)); + pos += sizeof(*hdr); + mic_control |= FTE_MIC_LEN_16 << FTE_MIC_CTRL_MIC_LEN_SHIFT; + WPA_PUT_LE16(hdr->mic_control, mic_control); + if (anonce) + os_memcpy(hdr->anonce, anonce, WPA_NONCE_LEN); + if (snonce) + os_memcpy(hdr->snonce, snonce, WPA_NONCE_LEN); + } + + /* Optional Parameters */ + *pos++ = FTIE_SUBELEM_R1KH_ID; + *pos++ = FT_R1KH_ID_LEN; + os_memcpy(pos, conf->r1_key_holder, FT_R1KH_ID_LEN); + pos += FT_R1KH_ID_LEN; + + if (r0kh_id) { + *pos++ = FTIE_SUBELEM_R0KH_ID; + *pos++ = r0kh_id_len; + os_memcpy(pos, r0kh_id, r0kh_id_len); + pos += r0kh_id_len; + } + + if (subelem) { + os_memcpy(pos, subelem, subelem_len); + pos += subelem_len; + } + + *ielen = pos - buf - 2; + + return pos - buf; +} + + +/* A packet to be handled after seq response */ +struct ft_remote_item { + struct dl_list list; + + u8 nonce[FT_RRB_NONCE_LEN]; + struct os_reltime nonce_ts; + + u8 src_addr[ETH_ALEN]; + u8 *enc; + size_t enc_len; + u8 *auth; + size_t auth_len; + int (*cb)(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + int no_defer); +}; + + +static void wpa_ft_rrb_seq_free(struct ft_remote_item *item) +{ + eloop_cancel_timeout(wpa_ft_rrb_seq_timeout, ELOOP_ALL_CTX, item); + dl_list_del(&item->list); + bin_clear_free(item->enc, item->enc_len); + os_free(item->auth); + os_free(item); +} + + +static void wpa_ft_rrb_seq_flush(struct wpa_authenticator *wpa_auth, + struct ft_remote_seq *rkh_seq, int cb) +{ + struct ft_remote_item *item, *n; + + dl_list_for_each_safe(item, n, &rkh_seq->rx.queue, + struct ft_remote_item, list) { + if (cb && item->cb) + item->cb(wpa_auth, item->src_addr, item->enc, + item->enc_len, item->auth, item->auth_len, 1); + wpa_ft_rrb_seq_free(item); + } +} + + +static void wpa_ft_rrb_seq_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct ft_remote_item *item = timeout_ctx; + + wpa_ft_rrb_seq_free(item); +} + + +static int +wpa_ft_rrb_seq_req(struct wpa_authenticator *wpa_auth, + struct ft_remote_seq *rkh_seq, const u8 *src_addr, + const u8 *f_r0kh_id, size_t f_r0kh_id_len, + const u8 *f_r1kh_id, const u8 *key, size_t key_len, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + int (*cb)(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + int no_defer)) +{ + struct ft_remote_item *item = NULL; + u8 *packet = NULL; + size_t packet_len; + struct tlv_list seq_req_auth[] = { + { .type = FT_RRB_NONCE, .len = FT_RRB_NONCE_LEN, + .data = NULL /* to be filled: item->nonce */ }, + { .type = FT_RRB_R0KH_ID, .len = f_r0kh_id_len, + .data = f_r0kh_id }, + { .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN, + .data = f_r1kh_id }, + { .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL }, + }; + + if (dl_list_len(&rkh_seq->rx.queue) >= ftRRBmaxQueueLen) { + wpa_printf(MSG_DEBUG, "FT: Sequence number queue too long"); + goto err; + } + + wpa_printf(MSG_DEBUG, "FT: Send sequence number request from " MACSTR + " to " MACSTR, + MAC2STR(wpa_auth->addr), MAC2STR(src_addr)); + item = os_zalloc(sizeof(*item)); + if (!item) + goto err; + + os_memcpy(item->src_addr, src_addr, ETH_ALEN); + item->cb = cb; + + if (random_get_bytes(item->nonce, FT_RRB_NONCE_LEN) < 0) { + wpa_printf(MSG_DEBUG, "FT: Seq num nonce: out of random bytes"); + goto err; + } + + if (os_get_reltime(&item->nonce_ts) < 0) + goto err; + + if (enc && enc_len > 0) { + item->enc = os_memdup(enc, enc_len); + item->enc_len = enc_len; + if (!item->enc) + goto err; + } + + if (auth && auth_len > 0) { + item->auth = os_memdup(auth, auth_len); + item->auth_len = auth_len; + if (!item->auth) + goto err; + } + + eloop_register_timeout(ftRRBseqTimeout, 0, wpa_ft_rrb_seq_timeout, + wpa_auth, item); + + seq_req_auth[0].data = item->nonce; + + if (wpa_ft_rrb_build(key, key_len, NULL, NULL, seq_req_auth, NULL, + wpa_auth->addr, FT_PACKET_R0KH_R1KH_SEQ_REQ, + &packet, &packet_len) < 0) { + item = NULL; /* some other seq resp might still accept this */ + goto err; + } + + dl_list_add(&rkh_seq->rx.queue, &item->list); + + wpa_ft_rrb_oui_send(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_REQ, + packet, packet_len); + + os_free(packet); + + return 0; +err: + wpa_printf(MSG_DEBUG, "FT: Failed to send sequence number request"); + if (item) { + os_free(item->auth); + bin_clear_free(item->enc, item->enc_len); + os_free(item); + } + + return -1; +} + + +#define FT_RRB_SEQ_OK 0 +#define FT_RRB_SEQ_DROP 1 +#define FT_RRB_SEQ_DEFER 2 + +static int +wpa_ft_rrb_seq_chk(struct ft_remote_seq *rkh_seq, const u8 *src_addr, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + const char *msgtype, int no_defer) +{ + const u8 *f_seq; + size_t f_seq_len; + const struct ft_rrb_seq *msg_both; + u32 msg_seq, msg_off, rkh_off; + struct os_reltime now; + unsigned int i; + + RRB_GET_AUTH(FT_RRB_SEQ, seq, msgtype, sizeof(*msg_both)); + wpa_hexdump(MSG_DEBUG, "FT: sequence number", f_seq, f_seq_len); + msg_both = (const struct ft_rrb_seq *) f_seq; + + if (rkh_seq->rx.num_last == 0) { + /* first packet from remote */ + goto defer; + } + + if (le_to_host32(msg_both->dom) != rkh_seq->rx.dom) { + /* remote might have rebooted */ + goto defer; + } + + if (os_get_reltime(&now) == 0) { + u32 msg_ts_now_remote, msg_ts_off; + struct os_reltime now_remote; + + os_reltime_sub(&now, &rkh_seq->rx.time_offset, &now_remote); + msg_ts_now_remote = now_remote.sec; + msg_ts_off = le_to_host32(msg_both->ts) - + (msg_ts_now_remote - ftRRBseqTimeout); + if (msg_ts_off > 2 * ftRRBseqTimeout) + goto defer; + } + + msg_seq = le_to_host32(msg_both->seq); + rkh_off = rkh_seq->rx.last[rkh_seq->rx.offsetidx]; + msg_off = msg_seq - rkh_off; + if (msg_off > 0xC0000000) + goto out; /* too old message, drop it */ + + if (msg_off <= 0x40000000) { + for (i = 0; i < rkh_seq->rx.num_last; i++) { + if (rkh_seq->rx.last[i] == msg_seq) + goto out; /* duplicate message, drop it */ + } + + return FT_RRB_SEQ_OK; + } + +defer: + if (no_defer) + goto out; + + wpa_printf(MSG_DEBUG, "FT: Possibly invalid sequence number in %s from " + MACSTR, msgtype, MAC2STR(src_addr)); + + return FT_RRB_SEQ_DEFER; +out: + wpa_printf(MSG_DEBUG, "FT: Invalid sequence number in %s from " MACSTR, + msgtype, MAC2STR(src_addr)); + + return FT_RRB_SEQ_DROP; +} + + +static void +wpa_ft_rrb_seq_accept(struct wpa_authenticator *wpa_auth, + struct ft_remote_seq *rkh_seq, const u8 *src_addr, + const u8 *auth, size_t auth_len, + const char *msgtype) +{ + const u8 *f_seq; + size_t f_seq_len; + const struct ft_rrb_seq *msg_both; + u32 msg_seq, msg_off, min_off, rkh_off; + int minidx = 0; + unsigned int i; + + RRB_GET_AUTH(FT_RRB_SEQ, seq, msgtype, sizeof(*msg_both)); + msg_both = (const struct ft_rrb_seq *) f_seq; + + msg_seq = le_to_host32(msg_both->seq); + + if (rkh_seq->rx.num_last < FT_REMOTE_SEQ_BACKLOG) { + rkh_seq->rx.last[rkh_seq->rx.num_last] = msg_seq; + rkh_seq->rx.num_last++; + return; + } + + rkh_off = rkh_seq->rx.last[rkh_seq->rx.offsetidx]; + for (i = 0; i < rkh_seq->rx.num_last; i++) { + msg_off = rkh_seq->rx.last[i] - rkh_off; + min_off = rkh_seq->rx.last[minidx] - rkh_off; + if (msg_off < min_off && i != rkh_seq->rx.offsetidx) + minidx = i; + } + rkh_seq->rx.last[rkh_seq->rx.offsetidx] = msg_seq; + rkh_seq->rx.offsetidx = minidx; + + return; +out: + /* RRB_GET_AUTH should never fail here as + * wpa_ft_rrb_seq_chk() verified FT_RRB_SEQ presence. */ + wpa_printf(MSG_ERROR, "FT: %s() failed", __func__); +} + + +static int wpa_ft_new_seq(struct ft_remote_seq *rkh_seq, + struct ft_rrb_seq *f_seq) +{ + struct os_reltime now; + + if (os_get_reltime(&now) < 0) + return -1; + + if (!rkh_seq->tx.dom) { + if (random_get_bytes((u8 *) &rkh_seq->tx.seq, + sizeof(rkh_seq->tx.seq))) { + wpa_printf(MSG_ERROR, + "FT: Failed to get random data for sequence number initialization"); + rkh_seq->tx.seq = now.usec; + } + if (random_get_bytes((u8 *) &rkh_seq->tx.dom, + sizeof(rkh_seq->tx.dom))) { + wpa_printf(MSG_ERROR, + "FT: Failed to get random data for sequence number initialization"); + rkh_seq->tx.dom = now.usec; + } + rkh_seq->tx.dom |= 1; + } + + f_seq->dom = host_to_le32(rkh_seq->tx.dom); + f_seq->seq = host_to_le32(rkh_seq->tx.seq); + f_seq->ts = host_to_le32(now.sec); + + rkh_seq->tx.seq++; + + return 0; +} + + +struct wpa_ft_pmk_r0_sa { + struct dl_list list; + u8 pmk_r0[PMK_LEN_MAX]; + size_t pmk_r0_len; + u8 pmk_r0_name[WPA_PMK_NAME_LEN]; + u8 spa[ETH_ALEN]; + int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */ + struct vlan_description *vlan; + os_time_t expiration; /* 0 for no expiration */ + u8 *identity; + size_t identity_len; + u8 *radius_cui; + size_t radius_cui_len; + os_time_t session_timeout; /* 0 for no expiration */ + /* TODO: radius_class, EAP type */ + int pmk_r1_pushed; +}; + +struct wpa_ft_pmk_r1_sa { + struct dl_list list; + u8 pmk_r1[PMK_LEN_MAX]; + size_t pmk_r1_len; + u8 pmk_r1_name[WPA_PMK_NAME_LEN]; + u8 spa[ETH_ALEN]; + int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */ + struct vlan_description *vlan; + u8 *identity; + size_t identity_len; + u8 *radius_cui; + size_t radius_cui_len; + os_time_t session_timeout; /* 0 for no expiration */ + /* TODO: radius_class, EAP type */ +}; + +struct wpa_ft_pmk_cache { + struct dl_list pmk_r0; /* struct wpa_ft_pmk_r0_sa */ + struct dl_list pmk_r1; /* struct wpa_ft_pmk_r1_sa */ +}; + + +static void wpa_ft_expire_pmk_r0(void *eloop_ctx, void *timeout_ctx); +static void wpa_ft_expire_pmk_r1(void *eloop_ctx, void *timeout_ctx); + + +static void wpa_ft_free_pmk_r0(struct wpa_ft_pmk_r0_sa *r0) +{ + if (!r0) + return; + + dl_list_del(&r0->list); + eloop_cancel_timeout(wpa_ft_expire_pmk_r0, r0, NULL); + + os_memset(r0->pmk_r0, 0, PMK_LEN_MAX); + os_free(r0->vlan); + os_free(r0->identity); + os_free(r0->radius_cui); + os_free(r0); +} + + +static void wpa_ft_expire_pmk_r0(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_ft_pmk_r0_sa *r0 = eloop_ctx; + struct os_reltime now; + int expires_in; + int session_timeout; + + os_get_reltime(&now); + + if (!r0) + return; + + expires_in = r0->expiration - now.sec; + session_timeout = r0->session_timeout - now.sec; + /* conditions to remove from cache: + * a) r0->expiration is set and hit + * -or- + * b) r0->session_timeout is set and hit + */ + if ((!r0->expiration || expires_in > 0) && + (!r0->session_timeout || session_timeout > 0)) { + wpa_printf(MSG_ERROR, + "FT: %s() called for non-expired entry %p", + __func__, r0); + eloop_cancel_timeout(wpa_ft_expire_pmk_r0, r0, NULL); + if (r0->expiration && expires_in > 0) + eloop_register_timeout(expires_in + 1, 0, + wpa_ft_expire_pmk_r0, r0, NULL); + if (r0->session_timeout && session_timeout > 0) + eloop_register_timeout(session_timeout + 1, 0, + wpa_ft_expire_pmk_r0, r0, NULL); + return; + } + + wpa_ft_free_pmk_r0(r0); +} + + +static void wpa_ft_free_pmk_r1(struct wpa_ft_pmk_r1_sa *r1) +{ + if (!r1) + return; + + dl_list_del(&r1->list); + eloop_cancel_timeout(wpa_ft_expire_pmk_r1, r1, NULL); + + os_memset(r1->pmk_r1, 0, PMK_LEN_MAX); + os_free(r1->vlan); + os_free(r1->identity); + os_free(r1->radius_cui); + os_free(r1); +} + + +static void wpa_ft_expire_pmk_r1(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_ft_pmk_r1_sa *r1 = eloop_ctx; + + wpa_ft_free_pmk_r1(r1); +} + + +struct wpa_ft_pmk_cache * wpa_ft_pmk_cache_init(void) +{ + struct wpa_ft_pmk_cache *cache; + + cache = os_zalloc(sizeof(*cache)); + if (cache) { + dl_list_init(&cache->pmk_r0); + dl_list_init(&cache->pmk_r1); + } + + return cache; +} + + +void wpa_ft_pmk_cache_deinit(struct wpa_ft_pmk_cache *cache) +{ + struct wpa_ft_pmk_r0_sa *r0, *r0prev; + struct wpa_ft_pmk_r1_sa *r1, *r1prev; + + dl_list_for_each_safe(r0, r0prev, &cache->pmk_r0, + struct wpa_ft_pmk_r0_sa, list) + wpa_ft_free_pmk_r0(r0); + + dl_list_for_each_safe(r1, r1prev, &cache->pmk_r1, + struct wpa_ft_pmk_r1_sa, list) + wpa_ft_free_pmk_r1(r1); + + os_free(cache); +} + + +static int wpa_ft_store_pmk_r0(struct wpa_authenticator *wpa_auth, + const u8 *spa, const u8 *pmk_r0, + size_t pmk_r0_len, + const u8 *pmk_r0_name, int pairwise, + const struct vlan_description *vlan, + int expires_in, int session_timeout, + const u8 *identity, size_t identity_len, + const u8 *radius_cui, size_t radius_cui_len) +{ + struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache; + struct wpa_ft_pmk_r0_sa *r0; + struct os_reltime now; + + /* TODO: add limit on number of entries in cache */ + os_get_reltime(&now); + + r0 = os_zalloc(sizeof(*r0)); + if (r0 == NULL) + return -1; + + os_memcpy(r0->pmk_r0, pmk_r0, pmk_r0_len); + r0->pmk_r0_len = pmk_r0_len; + os_memcpy(r0->pmk_r0_name, pmk_r0_name, WPA_PMK_NAME_LEN); + os_memcpy(r0->spa, spa, ETH_ALEN); + r0->pairwise = pairwise; + if (expires_in > 0) + r0->expiration = now.sec + expires_in; + if (vlan && vlan->notempty) { + r0->vlan = os_zalloc(sizeof(*vlan)); + if (!r0->vlan) { + bin_clear_free(r0, sizeof(*r0)); + return -1; + } + *r0->vlan = *vlan; + } + if (identity) { + r0->identity = os_malloc(identity_len); + if (r0->identity) { + os_memcpy(r0->identity, identity, identity_len); + r0->identity_len = identity_len; + } + } + if (radius_cui) { + r0->radius_cui = os_malloc(radius_cui_len); + if (r0->radius_cui) { + os_memcpy(r0->radius_cui, radius_cui, radius_cui_len); + r0->radius_cui_len = radius_cui_len; + } + } + if (session_timeout > 0) + r0->session_timeout = now.sec + session_timeout; + + dl_list_add(&cache->pmk_r0, &r0->list); + if (expires_in > 0) + eloop_register_timeout(expires_in + 1, 0, wpa_ft_expire_pmk_r0, + r0, NULL); + if (session_timeout > 0) + eloop_register_timeout(session_timeout + 1, 0, + wpa_ft_expire_pmk_r0, r0, NULL); + + return 0; +} + + +static int wpa_ft_fetch_pmk_r0(struct wpa_authenticator *wpa_auth, + const u8 *spa, const u8 *pmk_r0_name, + const struct wpa_ft_pmk_r0_sa **r0_out) +{ + struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache; + struct wpa_ft_pmk_r0_sa *r0; + struct os_reltime now; + + os_get_reltime(&now); + dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) { + if (ether_addr_equal(r0->spa, spa) && + os_memcmp_const(r0->pmk_r0_name, pmk_r0_name, + WPA_PMK_NAME_LEN) == 0) { + *r0_out = r0; + return 0; + } + } + + *r0_out = NULL; + return -1; +} + + +static int wpa_ft_store_pmk_r1(struct wpa_authenticator *wpa_auth, + const u8 *spa, const u8 *pmk_r1, + size_t pmk_r1_len, + const u8 *pmk_r1_name, int pairwise, + const struct vlan_description *vlan, + int expires_in, int session_timeout, + const u8 *identity, size_t identity_len, + const u8 *radius_cui, size_t radius_cui_len) +{ + struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache; + int max_expires_in = wpa_auth->conf.r1_max_key_lifetime; + struct wpa_ft_pmk_r1_sa *r1; + struct os_reltime now; + + /* TODO: limit on number of entries in cache */ + os_get_reltime(&now); + + if (max_expires_in && (max_expires_in < expires_in || expires_in == 0)) + expires_in = max_expires_in; + + r1 = os_zalloc(sizeof(*r1)); + if (r1 == NULL) + return -1; + + os_memcpy(r1->pmk_r1, pmk_r1, pmk_r1_len); + r1->pmk_r1_len = pmk_r1_len; + os_memcpy(r1->pmk_r1_name, pmk_r1_name, WPA_PMK_NAME_LEN); + os_memcpy(r1->spa, spa, ETH_ALEN); + r1->pairwise = pairwise; + if (vlan && vlan->notempty) { + r1->vlan = os_zalloc(sizeof(*vlan)); + if (!r1->vlan) { + bin_clear_free(r1, sizeof(*r1)); + return -1; + } + *r1->vlan = *vlan; + } + if (identity) { + r1->identity = os_malloc(identity_len); + if (r1->identity) { + os_memcpy(r1->identity, identity, identity_len); + r1->identity_len = identity_len; + } + } + if (radius_cui) { + r1->radius_cui = os_malloc(radius_cui_len); + if (r1->radius_cui) { + os_memcpy(r1->radius_cui, radius_cui, radius_cui_len); + r1->radius_cui_len = radius_cui_len; + } + } + if (session_timeout > 0) + r1->session_timeout = now.sec + session_timeout; + + dl_list_add(&cache->pmk_r1, &r1->list); + + if (expires_in > 0) + eloop_register_timeout(expires_in + 1, 0, wpa_ft_expire_pmk_r1, + r1, NULL); + if (session_timeout > 0) + eloop_register_timeout(session_timeout + 1, 0, + wpa_ft_expire_pmk_r1, r1, NULL); + + return 0; +} + + +int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth, + const u8 *spa, const u8 *pmk_r1_name, + u8 *pmk_r1, size_t *pmk_r1_len, int *pairwise, + struct vlan_description *vlan, + const u8 **identity, size_t *identity_len, + const u8 **radius_cui, size_t *radius_cui_len, + int *session_timeout) +{ + struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache; + struct wpa_ft_pmk_r1_sa *r1; + struct os_reltime now; + + os_get_reltime(&now); + + dl_list_for_each(r1, &cache->pmk_r1, struct wpa_ft_pmk_r1_sa, list) { + if (ether_addr_equal(r1->spa, spa) && + os_memcmp_const(r1->pmk_r1_name, pmk_r1_name, + WPA_PMK_NAME_LEN) == 0) { + os_memcpy(pmk_r1, r1->pmk_r1, r1->pmk_r1_len); + *pmk_r1_len = r1->pmk_r1_len; + if (pairwise) + *pairwise = r1->pairwise; + if (vlan && r1->vlan) + *vlan = *r1->vlan; + if (vlan && !r1->vlan) + os_memset(vlan, 0, sizeof(*vlan)); + if (identity && identity_len) { + *identity = r1->identity; + *identity_len = r1->identity_len; + } + if (radius_cui && radius_cui_len) { + *radius_cui = r1->radius_cui; + *radius_cui_len = r1->radius_cui_len; + } + if (session_timeout && r1->session_timeout > now.sec) + *session_timeout = r1->session_timeout - + now.sec; + else if (session_timeout && r1->session_timeout) + *session_timeout = 1; + else if (session_timeout) + *session_timeout = 0; + return 0; + } + } + + return -1; +} + + +static int wpa_ft_rrb_init_r0kh_seq(struct ft_remote_r0kh *r0kh) +{ + if (r0kh->seq) + return 0; + + r0kh->seq = os_zalloc(sizeof(*r0kh->seq)); + if (!r0kh->seq) { + wpa_printf(MSG_DEBUG, "FT: Failed to allocate r0kh->seq"); + return -1; + } + + dl_list_init(&r0kh->seq->rx.queue); + + return 0; +} + + +static void wpa_ft_rrb_lookup_r0kh(struct wpa_authenticator *wpa_auth, + const u8 *f_r0kh_id, size_t f_r0kh_id_len, + struct ft_remote_r0kh **r0kh_out, + struct ft_remote_r0kh **r0kh_wildcard) +{ + struct ft_remote_r0kh *r0kh; + + *r0kh_wildcard = NULL; + *r0kh_out = NULL; + + if (wpa_auth->conf.r0kh_list) + r0kh = *wpa_auth->conf.r0kh_list; + else + r0kh = NULL; + for (; r0kh; r0kh = r0kh->next) { + if (r0kh->id_len == 1 && r0kh->id[0] == '*') + *r0kh_wildcard = r0kh; + if (f_r0kh_id && r0kh->id_len == f_r0kh_id_len && + os_memcmp_const(f_r0kh_id, r0kh->id, f_r0kh_id_len) == 0) + *r0kh_out = r0kh; + } + + if (!*r0kh_out && !*r0kh_wildcard) + wpa_printf(MSG_DEBUG, "FT: No matching R0KH found"); + + if (*r0kh_out && wpa_ft_rrb_init_r0kh_seq(*r0kh_out) < 0) + *r0kh_out = NULL; +} + + +static int wpa_ft_rrb_init_r1kh_seq(struct ft_remote_r1kh *r1kh) +{ + if (r1kh->seq) + return 0; + + r1kh->seq = os_zalloc(sizeof(*r1kh->seq)); + if (!r1kh->seq) { + wpa_printf(MSG_DEBUG, "FT: Failed to allocate r1kh->seq"); + return -1; + } + + dl_list_init(&r1kh->seq->rx.queue); + + return 0; +} + + +static void wpa_ft_rrb_lookup_r1kh(struct wpa_authenticator *wpa_auth, + const u8 *f_r1kh_id, + struct ft_remote_r1kh **r1kh_out, + struct ft_remote_r1kh **r1kh_wildcard) +{ + struct ft_remote_r1kh *r1kh; + + *r1kh_wildcard = NULL; + *r1kh_out = NULL; + + if (wpa_auth->conf.r1kh_list) + r1kh = *wpa_auth->conf.r1kh_list; + else + r1kh = NULL; + for (; r1kh; r1kh = r1kh->next) { + if (is_zero_ether_addr(r1kh->addr) && + is_zero_ether_addr(r1kh->id)) + *r1kh_wildcard = r1kh; + if (f_r1kh_id && + os_memcmp_const(r1kh->id, f_r1kh_id, FT_R1KH_ID_LEN) == 0) + *r1kh_out = r1kh; + } + + if (!*r1kh_out && !*r1kh_wildcard) + wpa_printf(MSG_DEBUG, "FT: No matching R1KH found"); + + if (*r1kh_out && wpa_ft_rrb_init_r1kh_seq(*r1kh_out) < 0) + *r1kh_out = NULL; +} + + +static int wpa_ft_rrb_check_r0kh(struct wpa_authenticator *wpa_auth, + const u8 *f_r0kh_id, size_t f_r0kh_id_len) +{ + if (f_r0kh_id_len != wpa_auth->conf.r0_key_holder_len || + os_memcmp_const(f_r0kh_id, wpa_auth->conf.r0_key_holder, + f_r0kh_id_len) != 0) + return -1; + + return 0; +} + + +static int wpa_ft_rrb_check_r1kh(struct wpa_authenticator *wpa_auth, + const u8 *f_r1kh_id) +{ + if (os_memcmp_const(f_r1kh_id, wpa_auth->conf.r1_key_holder, + FT_R1KH_ID_LEN) != 0) + return -1; + + return 0; +} + + +static void wpa_ft_rrb_del_r0kh(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_authenticator *wpa_auth = eloop_ctx; + struct ft_remote_r0kh *r0kh, *prev = NULL; + + if (!wpa_auth->conf.r0kh_list) + return; + + for (r0kh = *wpa_auth->conf.r0kh_list; r0kh; r0kh = r0kh->next) { + if (r0kh == timeout_ctx) + break; + prev = r0kh; + } + if (!r0kh) + return; + if (prev) + prev->next = r0kh->next; + else + *wpa_auth->conf.r0kh_list = r0kh->next; + if (r0kh->seq) + wpa_ft_rrb_seq_flush(wpa_auth, r0kh->seq, 0); + os_free(r0kh->seq); + os_free(r0kh); +} + + +static void wpa_ft_rrb_r0kh_replenish(struct wpa_authenticator *wpa_auth, + struct ft_remote_r0kh *r0kh, int timeout) +{ + if (timeout > 0) + eloop_replenish_timeout(timeout, 0, wpa_ft_rrb_del_r0kh, + wpa_auth, r0kh); +} + + +static void wpa_ft_rrb_r0kh_timeout(struct wpa_authenticator *wpa_auth, + struct ft_remote_r0kh *r0kh, int timeout) +{ + eloop_cancel_timeout(wpa_ft_rrb_del_r0kh, wpa_auth, r0kh); + + if (timeout > 0) + eloop_register_timeout(timeout, 0, wpa_ft_rrb_del_r0kh, + wpa_auth, r0kh); +} + + +static struct ft_remote_r0kh * +wpa_ft_rrb_add_r0kh(struct wpa_authenticator *wpa_auth, + struct ft_remote_r0kh *r0kh_wildcard, + const u8 *src_addr, const u8 *r0kh_id, size_t id_len, + int timeout) +{ + struct ft_remote_r0kh *r0kh; + + if (!wpa_auth->conf.r0kh_list) + return NULL; + + r0kh = os_zalloc(sizeof(*r0kh)); + if (!r0kh) + return NULL; + + if (src_addr) + os_memcpy(r0kh->addr, src_addr, sizeof(r0kh->addr)); + + if (id_len > FT_R0KH_ID_MAX_LEN) + id_len = FT_R0KH_ID_MAX_LEN; + os_memcpy(r0kh->id, r0kh_id, id_len); + r0kh->id_len = id_len; + + os_memcpy(r0kh->key, r0kh_wildcard->key, sizeof(r0kh->key)); + + r0kh->next = *wpa_auth->conf.r0kh_list; + *wpa_auth->conf.r0kh_list = r0kh; + + if (timeout > 0) + eloop_register_timeout(timeout, 0, wpa_ft_rrb_del_r0kh, + wpa_auth, r0kh); + + if (wpa_ft_rrb_init_r0kh_seq(r0kh) < 0) + return NULL; + + return r0kh; +} + + +static void wpa_ft_rrb_del_r1kh(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_authenticator *wpa_auth = eloop_ctx; + struct ft_remote_r1kh *r1kh, *prev = NULL; + + if (!wpa_auth->conf.r1kh_list) + return; + + for (r1kh = *wpa_auth->conf.r1kh_list; r1kh; r1kh = r1kh->next) { + if (r1kh == timeout_ctx) + break; + prev = r1kh; + } + if (!r1kh) + return; + if (prev) + prev->next = r1kh->next; + else + *wpa_auth->conf.r1kh_list = r1kh->next; + if (r1kh->seq) + wpa_ft_rrb_seq_flush(wpa_auth, r1kh->seq, 0); + os_free(r1kh->seq); + os_free(r1kh); +} + + +static void wpa_ft_rrb_r1kh_replenish(struct wpa_authenticator *wpa_auth, + struct ft_remote_r1kh *r1kh, int timeout) +{ + if (timeout > 0) + eloop_replenish_timeout(timeout, 0, wpa_ft_rrb_del_r1kh, + wpa_auth, r1kh); +} + + +static struct ft_remote_r1kh * +wpa_ft_rrb_add_r1kh(struct wpa_authenticator *wpa_auth, + struct ft_remote_r1kh *r1kh_wildcard, + const u8 *src_addr, const u8 *r1kh_id, int timeout) +{ + struct ft_remote_r1kh *r1kh; + + if (!wpa_auth->conf.r1kh_list) + return NULL; + + r1kh = os_zalloc(sizeof(*r1kh)); + if (!r1kh) + return NULL; + + os_memcpy(r1kh->addr, src_addr, sizeof(r1kh->addr)); + os_memcpy(r1kh->id, r1kh_id, sizeof(r1kh->id)); + os_memcpy(r1kh->key, r1kh_wildcard->key, sizeof(r1kh->key)); + r1kh->next = *wpa_auth->conf.r1kh_list; + *wpa_auth->conf.r1kh_list = r1kh; + + if (timeout > 0) + eloop_register_timeout(timeout, 0, wpa_ft_rrb_del_r1kh, + wpa_auth, r1kh); + + if (wpa_ft_rrb_init_r1kh_seq(r1kh) < 0) + return NULL; + + return r1kh; +} + + +void wpa_ft_sta_deinit(struct wpa_state_machine *sm) +{ + eloop_cancel_timeout(wpa_ft_expire_pull, sm, NULL); +} + + +static void wpa_ft_deinit_seq(struct wpa_authenticator *wpa_auth) +{ + struct ft_remote_r0kh *r0kh; + struct ft_remote_r1kh *r1kh; + + eloop_cancel_timeout(wpa_ft_rrb_seq_timeout, wpa_auth, ELOOP_ALL_CTX); + + if (wpa_auth->conf.r0kh_list) + r0kh = *wpa_auth->conf.r0kh_list; + else + r0kh = NULL; + for (; r0kh; r0kh = r0kh->next) { + if (!r0kh->seq) + continue; + wpa_ft_rrb_seq_flush(wpa_auth, r0kh->seq, 0); + os_free(r0kh->seq); + r0kh->seq = NULL; + } + + if (wpa_auth->conf.r1kh_list) + r1kh = *wpa_auth->conf.r1kh_list; + else + r1kh = NULL; + for (; r1kh; r1kh = r1kh->next) { + if (!r1kh->seq) + continue; + wpa_ft_rrb_seq_flush(wpa_auth, r1kh->seq, 0); + os_free(r1kh->seq); + r1kh->seq = NULL; + } +} + + +static void wpa_ft_deinit_rkh_tmp(struct wpa_authenticator *wpa_auth) +{ + struct ft_remote_r0kh *r0kh, *r0kh_next, *r0kh_prev = NULL; + struct ft_remote_r1kh *r1kh, *r1kh_next, *r1kh_prev = NULL; + + if (wpa_auth->conf.r0kh_list) + r0kh = *wpa_auth->conf.r0kh_list; + else + r0kh = NULL; + while (r0kh) { + r0kh_next = r0kh->next; + if (eloop_cancel_timeout(wpa_ft_rrb_del_r0kh, wpa_auth, + r0kh) > 0) { + if (r0kh_prev) + r0kh_prev->next = r0kh_next; + else + *wpa_auth->conf.r0kh_list = r0kh_next; + os_free(r0kh); + } else { + r0kh_prev = r0kh; + } + r0kh = r0kh_next; + } + + if (wpa_auth->conf.r1kh_list) + r1kh = *wpa_auth->conf.r1kh_list; + else + r1kh = NULL; + while (r1kh) { + r1kh_next = r1kh->next; + if (eloop_cancel_timeout(wpa_ft_rrb_del_r1kh, wpa_auth, + r1kh) > 0) { + if (r1kh_prev) + r1kh_prev->next = r1kh_next; + else + *wpa_auth->conf.r1kh_list = r1kh_next; + os_free(r1kh); + } else { + r1kh_prev = r1kh; + } + r1kh = r1kh_next; + } +} + + +void wpa_ft_deinit(struct wpa_authenticator *wpa_auth) +{ + wpa_ft_deinit_seq(wpa_auth); + wpa_ft_deinit_rkh_tmp(wpa_auth); +} + + +static void wpa_ft_block_r0kh(struct wpa_authenticator *wpa_auth, + const u8 *f_r0kh_id, size_t f_r0kh_id_len) +{ + struct ft_remote_r0kh *r0kh, *r0kh_wildcard; + + if (!wpa_auth->conf.rkh_neg_timeout) + return; + + wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len, + &r0kh, &r0kh_wildcard); + + if (!r0kh_wildcard) { + /* r0kh removed after neg_timeout and might need re-adding */ + return; + } + + wpa_hexdump(MSG_DEBUG, "FT: Temporarily block R0KH-ID", + f_r0kh_id, f_r0kh_id_len); + + if (r0kh) { + wpa_ft_rrb_r0kh_timeout(wpa_auth, r0kh, + wpa_auth->conf.rkh_neg_timeout); + os_memset(r0kh->addr, 0, ETH_ALEN); + } else + wpa_ft_rrb_add_r0kh(wpa_auth, r0kh_wildcard, NULL, f_r0kh_id, + f_r0kh_id_len, + wpa_auth->conf.rkh_neg_timeout); +} + + +static void wpa_ft_expire_pull(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_state_machine *sm = eloop_ctx; + + wpa_printf(MSG_DEBUG, "FT: Timeout pending pull request for " MACSTR, + MAC2STR(sm->addr)); + if (sm->ft_pending_pull_left_retries <= 0) + wpa_ft_block_r0kh(sm->wpa_auth, sm->r0kh_id, sm->r0kh_id_len); + + /* cancel multiple timeouts */ + eloop_cancel_timeout(wpa_ft_expire_pull, sm, NULL); + ft_finish_pull(sm); +} + + +static int wpa_ft_pull_pmk_r1(struct wpa_state_machine *sm, + const u8 *ies, size_t ies_len, + const u8 *pmk_r0_name) +{ + struct ft_remote_r0kh *r0kh, *r0kh_wildcard; + u8 *packet = NULL; + const u8 *key, *f_r1kh_id = sm->wpa_auth->conf.r1_key_holder; + size_t packet_len, key_len; + struct ft_rrb_seq f_seq; + int tsecs, tusecs, first; + struct wpabuf *ft_pending_req_ies; + int r0kh_timeout; + struct tlv_list req_enc[] = { + { .type = FT_RRB_PMK_R0_NAME, .len = WPA_PMK_NAME_LEN, + .data = pmk_r0_name }, + { .type = FT_RRB_S1KH_ID, .len = ETH_ALEN, + .data = sm->addr }, + { .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL }, + }; + struct tlv_list req_auth[] = { + { .type = FT_RRB_NONCE, .len = FT_RRB_NONCE_LEN, + .data = sm->ft_pending_pull_nonce }, + { .type = FT_RRB_SEQ, .len = sizeof(f_seq), + .data = (u8 *) &f_seq }, + { .type = FT_RRB_R0KH_ID, .len = sm->r0kh_id_len, + .data = sm->r0kh_id }, + { .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN, + .data = f_r1kh_id }, + { .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL }, + }; + + if (sm->ft_pending_pull_left_retries <= 0) + return -1; + first = sm->ft_pending_pull_left_retries == + sm->wpa_auth->conf.rkh_pull_retries; + sm->ft_pending_pull_left_retries--; + + wpa_ft_rrb_lookup_r0kh(sm->wpa_auth, sm->r0kh_id, sm->r0kh_id_len, + &r0kh, &r0kh_wildcard); + + /* Keep r0kh sufficiently long in the list for seq num check */ + r0kh_timeout = sm->wpa_auth->conf.rkh_pull_timeout / 1000 + + 1 + ftRRBseqTimeout; + if (r0kh) { + wpa_ft_rrb_r0kh_replenish(sm->wpa_auth, r0kh, r0kh_timeout); + } else if (r0kh_wildcard) { + wpa_printf(MSG_DEBUG, "FT: Using wildcard R0KH-ID"); + /* r0kh->addr: updated by SEQ_RESP and wpa_ft_expire_pull */ + r0kh = wpa_ft_rrb_add_r0kh(sm->wpa_auth, r0kh_wildcard, + r0kh_wildcard->addr, + sm->r0kh_id, sm->r0kh_id_len, + r0kh_timeout); + } + if (r0kh == NULL) { + wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID", + sm->r0kh_id, sm->r0kh_id_len); + return -1; + } + if (is_zero_ether_addr(r0kh->addr)) { + wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID is temporarily blocked", + sm->r0kh_id, sm->r0kh_id_len); + return -1; + } + if (ether_addr_equal(r0kh->addr, sm->wpa_auth->addr)) { + wpa_printf(MSG_DEBUG, + "FT: R0KH-ID points to self - no matching key available"); + return -1; + } + + key = r0kh->key; + key_len = sizeof(r0kh->key); + + if (r0kh->seq->rx.num_last == 0) { + /* A sequence request will be sent out anyway when pull + * response is received. Send it out now to avoid one RTT. */ + wpa_ft_rrb_seq_req(sm->wpa_auth, r0kh->seq, r0kh->addr, + r0kh->id, r0kh->id_len, f_r1kh_id, key, + key_len, NULL, 0, NULL, 0, NULL); + } + + wpa_printf(MSG_DEBUG, "FT: Send PMK-R1 pull request from " MACSTR + " to remote R0KH address " MACSTR, + MAC2STR(sm->wpa_auth->addr), MAC2STR(r0kh->addr)); + + if (first && + random_get_bytes(sm->ft_pending_pull_nonce, FT_RRB_NONCE_LEN) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to get random data for " + "nonce"); + return -1; + } + + if (wpa_ft_new_seq(r0kh->seq, &f_seq) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to get seq num"); + return -1; + } + + if (wpa_ft_rrb_build(key, key_len, req_enc, NULL, req_auth, NULL, + sm->wpa_auth->addr, FT_PACKET_R0KH_R1KH_PULL, + &packet, &packet_len) < 0) + return -1; + + ft_pending_req_ies = wpabuf_alloc_copy(ies, ies_len); + wpabuf_free(sm->ft_pending_req_ies); + sm->ft_pending_req_ies = ft_pending_req_ies; + if (!sm->ft_pending_req_ies) { + os_free(packet); + return -1; + } + + tsecs = sm->wpa_auth->conf.rkh_pull_timeout / 1000; + tusecs = (sm->wpa_auth->conf.rkh_pull_timeout % 1000) * 1000; + eloop_register_timeout(tsecs, tusecs, wpa_ft_expire_pull, sm, NULL); + + wpa_ft_rrb_oui_send(sm->wpa_auth, r0kh->addr, FT_PACKET_R0KH_R1KH_PULL, + packet, packet_len); + + os_free(packet); + + return 0; +} + + +int wpa_ft_store_pmk_fils(struct wpa_state_machine *sm, + const u8 *pmk_r0, const u8 *pmk_r0_name) +{ + int expires_in = sm->wpa_auth->conf.r0_key_lifetime; + struct vlan_description vlan; + const u8 *identity, *radius_cui; + size_t identity_len, radius_cui_len; + int session_timeout; + size_t pmk_r0_len = wpa_key_mgmt_sha384(sm->wpa_key_mgmt) ? + SHA384_MAC_LEN : PMK_LEN; + + if (wpa_ft_get_vlan(sm->wpa_auth, sm->addr, &vlan) < 0) { + wpa_printf(MSG_DEBUG, "FT: vlan not available for STA " MACSTR, + MAC2STR(sm->addr)); + return -1; + } + + identity_len = wpa_ft_get_identity(sm->wpa_auth, sm->addr, &identity); + radius_cui_len = wpa_ft_get_radius_cui(sm->wpa_auth, sm->addr, + &radius_cui); + session_timeout = wpa_ft_get_session_timeout(sm->wpa_auth, sm->addr); + + return wpa_ft_store_pmk_r0(sm->wpa_auth, sm->addr, pmk_r0, pmk_r0_len, + pmk_r0_name, sm->pairwise, &vlan, expires_in, + session_timeout, identity, identity_len, + radius_cui, radius_cui_len); +} + + +int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, struct wpa_ptk *ptk, + u8 *pmk_r0, u8 *pmk_r1, u8 *pmk_r0_name, + size_t *key_len, size_t kdk_len) +{ + size_t pmk_r0_len, pmk_r1_len; + u8 ptk_name[WPA_PMK_NAME_LEN]; + const u8 *mdid = sm->wpa_auth->conf.mobility_domain; + const u8 *r0kh = sm->wpa_auth->conf.r0_key_holder; + size_t r0kh_len = sm->wpa_auth->conf.r0_key_holder_len; + const u8 *r1kh = sm->wpa_auth->conf.r1_key_holder; + const u8 *ssid = sm->wpa_auth->conf.ssid; + size_t ssid_len = sm->wpa_auth->conf.ssid_len; + const u8 *mpmk; + size_t mpmk_len; + + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + (sm->xxkey_len == SHA256_MAC_LEN || + sm->xxkey_len == SHA384_MAC_LEN || + sm->xxkey_len == SHA512_MAC_LEN)) + pmk_r0_len = sm->xxkey_len; + else if (wpa_key_mgmt_sha384(sm->wpa_key_mgmt)) + pmk_r0_len = SHA384_MAC_LEN; + else + pmk_r0_len = PMK_LEN; + *key_len = pmk_r1_len = pmk_r0_len; + + if (sm->xxkey_len > 0) { + mpmk = sm->xxkey; + mpmk_len = sm->xxkey_len; + } else if (sm->pmksa) { + mpmk = sm->pmksa->pmk; + mpmk_len = sm->pmksa->pmk_len; + } else { + wpa_printf(MSG_DEBUG, "FT: XXKey not available for key " + "derivation"); + return -1; + } + + if (wpa_derive_pmk_r0(mpmk, mpmk_len, ssid, ssid_len, mdid, + r0kh, r0kh_len, sm->addr, + pmk_r0, pmk_r0_name, + sm->wpa_key_mgmt) < 0 || + wpa_derive_pmk_r1(pmk_r0, pmk_r0_len, pmk_r0_name, r1kh, sm->addr, + pmk_r1, sm->pmk_r1_name) < 0) + return -1; + + return wpa_pmk_r1_to_ptk(pmk_r1, pmk_r1_len, sm->SNonce, sm->ANonce, + sm->addr, sm->wpa_auth->addr, sm->pmk_r1_name, + ptk, ptk_name, sm->wpa_key_mgmt, sm->pairwise, + kdk_len); +} + + +void wpa_auth_ft_store_keys(struct wpa_state_machine *sm, const u8 *pmk_r0, + const u8 *pmk_r1, const u8 *pmk_r0_name, + size_t key_len) +{ + int psk_local = sm->wpa_auth->conf.ft_psk_generate_local; + int expires_in = sm->wpa_auth->conf.r0_key_lifetime; + struct vlan_description vlan; + const u8 *identity, *radius_cui; + size_t identity_len, radius_cui_len; + int session_timeout; + + if (psk_local && wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt)) + return; + + if (wpa_ft_get_vlan(sm->wpa_auth, sm->addr, &vlan) < 0) { + wpa_printf(MSG_DEBUG, "FT: vlan not available for STA " MACSTR, + MAC2STR(sm->addr)); + return; + } + + identity_len = wpa_ft_get_identity(sm->wpa_auth, sm->addr, &identity); + radius_cui_len = wpa_ft_get_radius_cui(sm->wpa_auth, sm->addr, + &radius_cui); + session_timeout = wpa_ft_get_session_timeout(sm->wpa_auth, sm->addr); + + + wpa_ft_store_pmk_r0(sm->wpa_auth, sm->addr, pmk_r0, key_len, + pmk_r0_name, + sm->pairwise, &vlan, expires_in, + session_timeout, identity, identity_len, + radius_cui, radius_cui_len); + wpa_ft_store_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1, key_len, + sm->pmk_r1_name, sm->pairwise, &vlan, + expires_in, session_timeout, identity, + identity_len, radius_cui, radius_cui_len); +} + + +static inline int wpa_auth_get_seqnum(struct wpa_authenticator *wpa_auth, + const u8 *addr, int idx, u8 *seq) +{ + if (wpa_auth->cb->get_seqnum == NULL) + return -1; + return wpa_auth->cb->get_seqnum(wpa_auth->cb_ctx, addr, idx, seq); +} + + +static u8 * wpa_ft_gtk_subelem(struct wpa_state_machine *sm, size_t *len) +{ + u8 *subelem; + struct wpa_auth_config *conf = &sm->wpa_auth->conf; + struct wpa_group *gsm = sm->group; + size_t subelem_len, pad_len; + const u8 *key; + size_t key_len; + u8 keybuf[WPA_GTK_MAX_LEN]; + const u8 *kek; + size_t kek_len; + + if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) { + kek = sm->PTK.kek2; + kek_len = sm->PTK.kek2_len; + } else { + kek = sm->PTK.kek; + kek_len = sm->PTK.kek_len; + } + + key_len = gsm->GTK_len; + if (key_len > sizeof(keybuf)) + return NULL; + + /* + * Pad key for AES Key Wrap if it is not multiple of 8 bytes or is less + * than 16 bytes. + */ + pad_len = key_len % 8; + if (pad_len) + pad_len = 8 - pad_len; + if (key_len + pad_len < 16) + pad_len += 8; + if (pad_len && key_len < sizeof(keybuf)) { + os_memcpy(keybuf, gsm->GTK[gsm->GN - 1], key_len); + if (conf->disable_gtk || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random GTK to each STA to prevent use + * of GTK in the BSS. + */ + if (random_get_bytes(keybuf, key_len) < 0) + return NULL; + } + os_memset(keybuf + key_len, 0, pad_len); + keybuf[key_len] = 0xdd; + key_len += pad_len; + key = keybuf; + } else if (conf->disable_gtk || sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random GTK to each STA to prevent use of GTK + * in the BSS. + */ + if (random_get_bytes(keybuf, key_len) < 0) + return NULL; + key = keybuf; + } else { + key = gsm->GTK[gsm->GN - 1]; + } + + /* + * Sub-elem ID[1] | Length[1] | Key Info[2] | Key Length[1] | RSC[8] | + * Key[5..32]. + */ + subelem_len = 13 + key_len + 8; + subelem = os_zalloc(subelem_len); + if (subelem == NULL) + return NULL; + + subelem[0] = FTIE_SUBELEM_GTK; + subelem[1] = 11 + key_len + 8; + /* Key ID in B0-B1 of Key Info */ + WPA_PUT_LE16(&subelem[2], gsm->GN & 0x03); + subelem[4] = gsm->GTK_len; + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, subelem + 5); + if (aes_wrap(kek, kek_len, key_len / 8, key, subelem + 13)) { + wpa_printf(MSG_DEBUG, + "FT: GTK subelem encryption failed: kek_len=%d", + (int) kek_len); + forced_memzero(keybuf, sizeof(keybuf)); + os_free(subelem); + return NULL; + } + + forced_memzero(keybuf, sizeof(keybuf)); + *len = subelem_len; + return subelem; +} + + +static u8 * wpa_ft_igtk_subelem(struct wpa_state_machine *sm, size_t *len) +{ + u8 *subelem, *pos; + struct wpa_auth_config *conf = &sm->wpa_auth->conf; + struct wpa_group *gsm = sm->group; + size_t subelem_len; + const u8 *kek, *igtk; + size_t kek_len; + size_t igtk_len; + u8 stub_igtk[WPA_IGTK_MAX_LEN]; + + if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) { + kek = sm->PTK.kek2; + kek_len = sm->PTK.kek2_len; + } else { + kek = sm->PTK.kek; + kek_len = sm->PTK.kek_len; + } + + igtk_len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher); + + /* Sub-elem ID[1] | Length[1] | KeyID[2] | IPN[6] | Key Length[1] | + * Key[16+8] */ + subelem_len = 1 + 1 + 2 + 6 + 1 + igtk_len + 8; + subelem = os_zalloc(subelem_len); + if (subelem == NULL) + return NULL; + + pos = subelem; + *pos++ = FTIE_SUBELEM_IGTK; + *pos++ = subelem_len - 2; + WPA_PUT_LE16(pos, gsm->GN_igtk); + pos += 2; + wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_igtk, pos); + pos += 6; + *pos++ = igtk_len; + igtk = gsm->IGTK[gsm->GN_igtk - 4]; + if (conf->disable_gtk || sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random IGTK to each STA to prevent use of + * IGTK in the BSS. + */ + if (random_get_bytes(stub_igtk, igtk_len / 8) < 0) { + os_free(subelem); + return NULL; + } + igtk = stub_igtk; + } + if (aes_wrap(kek, kek_len, igtk_len / 8, igtk, pos)) { + wpa_printf(MSG_DEBUG, + "FT: IGTK subelem encryption failed: kek_len=%d", + (int) kek_len); + os_free(subelem); + return NULL; + } + + *len = subelem_len; + return subelem; +} + + +static u8 * wpa_ft_bigtk_subelem(struct wpa_state_machine *sm, size_t *len) +{ + u8 *subelem, *pos; + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + struct wpa_group *gsm = wpa_auth->group; + size_t subelem_len; + const u8 *kek, *bigtk; + size_t kek_len; + size_t bigtk_len; + u8 stub_bigtk[WPA_IGTK_MAX_LEN]; + + if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) { + kek = sm->PTK.kek2; + kek_len = sm->PTK.kek2_len; + } else { + kek = sm->PTK.kek; + kek_len = sm->PTK.kek_len; + } + + bigtk_len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher); + + /* Sub-elem ID[1] | Length[1] | KeyID[2] | BIPN[6] | Key Length[1] | + * Key[16+8] */ + subelem_len = 1 + 1 + 2 + 6 + 1 + bigtk_len + 8; + subelem = os_zalloc(subelem_len); + if (subelem == NULL) + return NULL; + + pos = subelem; + *pos++ = FTIE_SUBELEM_BIGTK; + *pos++ = subelem_len - 2; + WPA_PUT_LE16(pos, gsm->GN_bigtk); + pos += 2; + wpa_auth_get_seqnum(wpa_auth, NULL, gsm->GN_bigtk, pos); + pos += 6; + *pos++ = bigtk_len; + bigtk = gsm->BIGTK[gsm->GN_bigtk - 6]; + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) { + /* + * Provide unique random BIGTK to each OSEN STA to prevent use + * of BIGTK in the BSS. + */ + if (random_get_bytes(stub_bigtk, bigtk_len / 8) < 0) { + os_free(subelem); + return NULL; + } + bigtk = stub_bigtk; + } + if (aes_wrap(kek, kek_len, bigtk_len / 8, bigtk, pos)) { + wpa_printf(MSG_DEBUG, + "FT: BIGTK subelem encryption failed: kek_len=%d", + (int) kek_len); + os_free(subelem); + return NULL; + } + + *len = subelem_len; + return subelem; +} + + +static u8 * wpa_ft_process_rdie(struct wpa_state_machine *sm, + u8 *pos, u8 *end, u8 id, u8 descr_count, + const u8 *ies, size_t ies_len) +{ + struct ieee802_11_elems parse; + struct rsn_rdie *rdie; + + wpa_printf(MSG_DEBUG, "FT: Resource Request: id=%d descr_count=%d", + id, descr_count); + wpa_hexdump(MSG_MSGDUMP, "FT: Resource descriptor IE(s)", + ies, ies_len); + + if (end - pos < (int) sizeof(*rdie)) { + wpa_printf(MSG_ERROR, "FT: Not enough room for response RDIE"); + return pos; + } + + *pos++ = WLAN_EID_RIC_DATA; + *pos++ = sizeof(*rdie); + rdie = (struct rsn_rdie *) pos; + rdie->id = id; + rdie->descr_count = 0; + rdie->status_code = host_to_le16(WLAN_STATUS_SUCCESS); + pos += sizeof(*rdie); + + if (ieee802_11_parse_elems((u8 *) ies, ies_len, &parse, 1) == + ParseFailed) { + wpa_printf(MSG_DEBUG, "FT: Failed to parse request IEs"); + rdie->status_code = + host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE); + return pos; + } + + if (parse.wmm_tspec) { + struct wmm_tspec_element *tspec; + + if (parse.wmm_tspec_len + 2 < (int) sizeof(*tspec)) { + wpa_printf(MSG_DEBUG, "FT: Too short WMM TSPEC IE " + "(%d)", (int) parse.wmm_tspec_len); + rdie->status_code = + host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE); + return pos; + } + if (end - pos < (int) sizeof(*tspec)) { + wpa_printf(MSG_ERROR, "FT: Not enough room for " + "response TSPEC"); + rdie->status_code = + host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE); + return pos; + } + tspec = (struct wmm_tspec_element *) pos; + os_memcpy(tspec, parse.wmm_tspec - 2, sizeof(*tspec)); + } + +#ifdef NEED_AP_MLME + if (parse.wmm_tspec && sm->wpa_auth->conf.ap_mlme) { + int res; + + res = wmm_process_tspec((struct wmm_tspec_element *) pos); + wpa_printf(MSG_DEBUG, "FT: ADDTS processing result: %d", res); + if (res == WMM_ADDTS_STATUS_INVALID_PARAMETERS) + rdie->status_code = + host_to_le16(WLAN_STATUS_INVALID_PARAMETERS); + else if (res == WMM_ADDTS_STATUS_REFUSED) + rdie->status_code = + host_to_le16(WLAN_STATUS_REQUEST_DECLINED); + else { + /* TSPEC accepted; include updated TSPEC in response */ + rdie->descr_count = 1; + pos += sizeof(struct wmm_tspec_element); + } + return pos; + } +#endif /* NEED_AP_MLME */ + + if (parse.wmm_tspec && !sm->wpa_auth->conf.ap_mlme) { + int res; + + res = wpa_ft_add_tspec(sm->wpa_auth, sm->addr, pos, + sizeof(struct wmm_tspec_element)); + if (res >= 0) { + if (res) + rdie->status_code = host_to_le16(res); + else { + /* TSPEC accepted; include updated TSPEC in + * response */ + rdie->descr_count = 1; + pos += sizeof(struct wmm_tspec_element); + } + return pos; + } + } + + wpa_printf(MSG_DEBUG, "FT: No supported resource requested"); + rdie->status_code = host_to_le16(WLAN_STATUS_UNSPECIFIED_FAILURE); + return pos; +} + + +static u8 * wpa_ft_process_ric(struct wpa_state_machine *sm, u8 *pos, u8 *end, + const u8 *ric, size_t ric_len) +{ + const u8 *rpos, *start; + const struct rsn_rdie *rdie; + + wpa_hexdump(MSG_MSGDUMP, "FT: RIC Request", ric, ric_len); + + rpos = ric; + while (rpos + sizeof(*rdie) < ric + ric_len) { + if (rpos[0] != WLAN_EID_RIC_DATA || rpos[1] < sizeof(*rdie) || + rpos + 2 + rpos[1] > ric + ric_len) + break; + rdie = (const struct rsn_rdie *) (rpos + 2); + rpos += 2 + rpos[1]; + start = rpos; + + while (rpos + 2 <= ric + ric_len && + rpos + 2 + rpos[1] <= ric + ric_len) { + if (rpos[0] == WLAN_EID_RIC_DATA) + break; + rpos += 2 + rpos[1]; + } + pos = wpa_ft_process_rdie(sm, pos, end, rdie->id, + rdie->descr_count, + start, rpos - start); + } + + return pos; +} + + +u8 * wpa_sm_write_assoc_resp_ies(struct wpa_state_machine *sm, u8 *pos, + size_t max_len, int auth_alg, + const u8 *req_ies, size_t req_ies_len, + int omit_rsnxe) +{ + u8 *end, *mdie, *ftie, *rsnie = NULL, *r0kh_id, *subelem = NULL; + u8 *fte_mic, *elem_count; + size_t mdie_len, ftie_len, rsnie_len = 0, r0kh_id_len, subelem_len = 0; + u8 rsnxe_buf[10], *rsnxe = rsnxe_buf; + size_t rsnxe_len; + int rsnxe_used; + int res; + struct wpa_auth_config *conf; + struct wpa_ft_ies parse; + u8 *ric_start; + u8 *anonce, *snonce; + const u8 *kck; + size_t kck_len; + size_t key_len; + + if (sm == NULL) + return pos; + + conf = &sm->wpa_auth->conf; + + if (!wpa_key_mgmt_ft(sm->wpa_key_mgmt)) + return pos; + + end = pos + max_len; + +#ifdef CONFIG_TESTING_OPTIONS + if (auth_alg == WLAN_AUTH_FT && + sm->wpa_auth->conf.rsne_override_ft_set) { + wpa_printf(MSG_DEBUG, + "TESTING: RSNE FT override for MIC calculation"); + rsnie = sm->wpa_auth->conf.rsne_override_ft; + rsnie_len = sm->wpa_auth->conf.rsne_override_ft_len; + if (end - pos < (long int) rsnie_len) + return pos; + os_memcpy(pos, rsnie, rsnie_len); + rsnie = pos; + pos += rsnie_len; + if (rsnie_len > PMKID_LEN && sm->pmk_r1_name_valid) { + int idx; + + /* Replace all 0xff PMKID with the valid PMKR1Name */ + for (idx = 0; idx < PMKID_LEN; idx++) { + if (rsnie[rsnie_len - 1 - idx] != 0xff) + break; + } + if (idx == PMKID_LEN) + os_memcpy(&rsnie[rsnie_len - PMKID_LEN], + sm->pmk_r1_name, WPA_PMK_NAME_LEN); + } + } else +#endif /* CONFIG_TESTING_OPTIONS */ + if (auth_alg == WLAN_AUTH_FT || + ((auth_alg == WLAN_AUTH_FILS_SK || + auth_alg == WLAN_AUTH_FILS_SK_PFS || + auth_alg == WLAN_AUTH_FILS_PK) && + (sm->wpa_key_mgmt & (WPA_KEY_MGMT_FT_FILS_SHA256 | + WPA_KEY_MGMT_FT_FILS_SHA384)))) { + if (!sm->pmk_r1_name_valid) { + wpa_printf(MSG_ERROR, + "FT: PMKR1Name is not valid for Assoc Resp RSNE"); + return NULL; + } + wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name for Assoc Resp RSNE", + sm->pmk_r1_name, WPA_PMK_NAME_LEN); + /* + * RSN (only present if this is a Reassociation Response and + * part of a fast BSS transition; or if this is a + * (Re)Association Response frame during an FT initial mobility + * domain association using FILS) + */ + res = wpa_write_rsn_ie(conf, pos, end - pos, sm->pmk_r1_name); + if (res < 0) + return NULL; + rsnie = pos; + rsnie_len = res; + pos += res; + } + + /* Mobility Domain Information */ + res = wpa_write_mdie(conf, pos, end - pos); + if (res < 0) + return NULL; + mdie = pos; + mdie_len = res; + pos += res; + + /* Fast BSS Transition Information */ + if (auth_alg == WLAN_AUTH_FT) { + subelem = wpa_ft_gtk_subelem(sm, &subelem_len); + if (!subelem) { + wpa_printf(MSG_DEBUG, + "FT: Failed to add GTK subelement"); + return NULL; + } + r0kh_id = sm->r0kh_id; + r0kh_id_len = sm->r0kh_id_len; + anonce = sm->ANonce; + snonce = sm->SNonce; + if (sm->mgmt_frame_prot) { + u8 *igtk; + size_t igtk_len; + u8 *nbuf; + igtk = wpa_ft_igtk_subelem(sm, &igtk_len); + if (igtk == NULL) { + wpa_printf(MSG_DEBUG, + "FT: Failed to add IGTK subelement"); + os_free(subelem); + return NULL; + } + nbuf = os_realloc(subelem, subelem_len + igtk_len); + if (nbuf == NULL) { + os_free(subelem); + os_free(igtk); + return NULL; + } + subelem = nbuf; + os_memcpy(subelem + subelem_len, igtk, igtk_len); + subelem_len += igtk_len; + os_free(igtk); + } + if (sm->mgmt_frame_prot && conf->beacon_prot) { + u8 *bigtk; + size_t bigtk_len; + u8 *nbuf; + + bigtk = wpa_ft_bigtk_subelem(sm, &bigtk_len); + if (!bigtk) { + wpa_printf(MSG_DEBUG, + "FT: Failed to add BIGTK subelement"); + os_free(subelem); + return NULL; + } + nbuf = os_realloc(subelem, subelem_len + bigtk_len); + if (!nbuf) { + os_free(subelem); + os_free(bigtk); + return NULL; + } + subelem = nbuf; + os_memcpy(subelem + subelem_len, bigtk, bigtk_len); + subelem_len += bigtk_len; + os_free(bigtk); + } +#ifdef CONFIG_OCV + if (wpa_auth_uses_ocv(sm)) { + struct wpa_channel_info ci; + u8 *nbuf, *ocipos; + + if (wpa_channel_info(sm->wpa_auth, &ci) != 0) { + wpa_printf(MSG_WARNING, + "Failed to get channel info for OCI element"); + os_free(subelem); + return NULL; + } +#ifdef CONFIG_TESTING_OPTIONS + if (conf->oci_freq_override_ft_assoc) { + wpa_printf(MSG_INFO, + "TEST: Override OCI frequency %d -> %u MHz", + ci.frequency, + conf->oci_freq_override_ft_assoc); + ci.frequency = conf->oci_freq_override_ft_assoc; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + subelem_len += 2 + OCV_OCI_LEN; + nbuf = os_realloc(subelem, subelem_len); + if (!nbuf) { + os_free(subelem); + return NULL; + } + subelem = nbuf; + + ocipos = subelem + subelem_len - 2 - OCV_OCI_LEN; + *ocipos++ = FTIE_SUBELEM_OCI; + *ocipos++ = OCV_OCI_LEN; + if (ocv_insert_oci(&ci, &ocipos) < 0) { + os_free(subelem); + return NULL; + } + } +#endif /* CONFIG_OCV */ + } else { + r0kh_id = conf->r0_key_holder; + r0kh_id_len = conf->r0_key_holder_len; + anonce = NULL; + snonce = NULL; + } + rsnxe_used = (auth_alg == WLAN_AUTH_FT) && + (conf->sae_pwe == SAE_PWE_HASH_TO_ELEMENT || + conf->sae_pwe == SAE_PWE_BOTH); +#ifdef CONFIG_TESTING_OPTIONS + if (sm->wpa_auth->conf.ft_rsnxe_used) { + rsnxe_used = sm->wpa_auth->conf.ft_rsnxe_used == 1; + wpa_printf(MSG_DEBUG, "TESTING: FT: Force RSNXE Used %d", + rsnxe_used); + } +#endif /* CONFIG_TESTING_OPTIONS */ + key_len = sm->xxkey_len; + if (!key_len) + key_len = sm->pmk_r1_len; + if (!key_len && sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + sm->wpa_auth->cb->get_psk) { + size_t psk_len; + + if (sm->wpa_auth->cb->get_psk(sm->wpa_auth->cb_ctx, + sm->addr, sm->p2p_dev_addr, + NULL, &psk_len, NULL)) + key_len = psk_len; + } + res = wpa_write_ftie(conf, sm->wpa_key_mgmt, key_len, + r0kh_id, r0kh_id_len, + anonce, snonce, pos, end - pos, + subelem, subelem_len, rsnxe_used); + os_free(subelem); + if (res < 0) + return NULL; + ftie = pos; + ftie_len = res; + pos += res; + + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + key_len == SHA512_MAC_LEN) { + struct rsn_ftie_sha512 *_ftie = + (struct rsn_ftie_sha512 *) (ftie + 2); + + fte_mic = _ftie->mic; + elem_count = &_ftie->mic_control[1]; + } else if ((sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + key_len == SHA384_MAC_LEN) || + wpa_key_mgmt_sha384(sm->wpa_key_mgmt)) { + struct rsn_ftie_sha384 *_ftie = + (struct rsn_ftie_sha384 *) (ftie + 2); + + fte_mic = _ftie->mic; + elem_count = &_ftie->mic_control[1]; + } else { + struct rsn_ftie *_ftie = (struct rsn_ftie *) (ftie + 2); + + fte_mic = _ftie->mic; + elem_count = &_ftie->mic_control[1]; + } + if (auth_alg == WLAN_AUTH_FT) + *elem_count = 3; /* Information element count */ + + ric_start = pos; + if (wpa_ft_parse_ies(req_ies, req_ies_len, &parse, + sm->wpa_key_mgmt, false) == 0 && parse.ric) { + pos = wpa_ft_process_ric(sm, pos, end, parse.ric, + parse.ric_len); + if (auth_alg == WLAN_AUTH_FT) + *elem_count += + ieee802_11_ie_count(ric_start, + pos - ric_start); + } + if (ric_start == pos) + ric_start = NULL; + + if (omit_rsnxe) { + rsnxe_len = 0; + } else { + res = wpa_write_rsnxe(&sm->wpa_auth->conf, rsnxe, + sizeof(rsnxe_buf)); + if (res < 0) { + pos = NULL; + goto fail; + } + rsnxe_len = res; + } +#ifdef CONFIG_TESTING_OPTIONS + if (auth_alg == WLAN_AUTH_FT && + sm->wpa_auth->conf.rsnxe_override_ft_set) { + wpa_printf(MSG_DEBUG, + "TESTING: RSNXE FT override for MIC calculation"); + rsnxe = sm->wpa_auth->conf.rsnxe_override_ft; + rsnxe_len = sm->wpa_auth->conf.rsnxe_override_ft_len; + } +#endif /* CONFIG_TESTING_OPTIONS */ + if (auth_alg == WLAN_AUTH_FT && rsnxe_len) + *elem_count += 1; + + if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) { + kck = sm->PTK.kck2; + kck_len = sm->PTK.kck2_len; + } else { + kck = sm->PTK.kck; + kck_len = sm->PTK.kck_len; + } + if (auth_alg == WLAN_AUTH_FT && + wpa_ft_mic(sm->wpa_key_mgmt, kck, kck_len, + sm->addr, sm->wpa_auth->addr, 6, + mdie, mdie_len, ftie, ftie_len, + rsnie, rsnie_len, + ric_start, ric_start ? pos - ric_start : 0, + rsnxe_len ? rsnxe : NULL, rsnxe_len, + NULL, + fte_mic) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC"); + pos = NULL; + goto fail; + } + + os_free(sm->assoc_resp_ftie); + sm->assoc_resp_ftie = os_malloc(ftie_len); + if (!sm->assoc_resp_ftie) { + pos = NULL; + goto fail; + } + os_memcpy(sm->assoc_resp_ftie, ftie, ftie_len); + +fail: + wpa_ft_parse_ies_free(&parse); + return pos; +} + + +static inline int wpa_auth_set_key(struct wpa_authenticator *wpa_auth, + int vlan_id, + enum wpa_alg alg, const u8 *addr, int idx, + u8 *key, size_t key_len, + enum key_flag key_flag) +{ + if (wpa_auth->cb->set_key == NULL) + return -1; + return wpa_auth->cb->set_key(wpa_auth->cb_ctx, vlan_id, alg, addr, idx, + key, key_len, key_flag); +} + + +#ifdef CONFIG_PASN +static inline int wpa_auth_set_ltf_keyseed(struct wpa_authenticator *wpa_auth, + const u8 *peer_addr, + const u8 *ltf_keyseed, + size_t ltf_keyseed_len) +{ + if (!wpa_auth->cb->set_ltf_keyseed) + return -1; + return wpa_auth->cb->set_ltf_keyseed(wpa_auth->cb_ctx, peer_addr, + ltf_keyseed, ltf_keyseed_len); +} +#endif /* CONFIG_PASN */ + + +static inline int wpa_auth_add_sta_ft(struct wpa_authenticator *wpa_auth, + const u8 *addr) +{ + if (!wpa_auth->cb->add_sta_ft) + return -1; + return wpa_auth->cb->add_sta_ft(wpa_auth->cb_ctx, addr); +} + + +void wpa_ft_install_ptk(struct wpa_state_machine *sm, int retry) +{ + enum wpa_alg alg; + int klen; + + /* MLME-SETKEYS.request(PTK) */ + alg = wpa_cipher_to_alg(sm->pairwise); + klen = wpa_cipher_key_len(sm->pairwise); + if (!wpa_cipher_valid_pairwise(sm->pairwise)) { + wpa_printf(MSG_DEBUG, "FT: Unknown pairwise alg 0x%x - skip " + "PTK configuration", sm->pairwise); + return; + } + + if (sm->tk_already_set) { + /* Must avoid TK reconfiguration to prevent clearing of TX/RX + * PN in the driver */ + wpa_printf(MSG_DEBUG, + "FT: Do not re-install same PTK to the driver"); + return; + } + + if (!retry) + wpa_auth_add_sta_ft(sm->wpa_auth, sm->addr); + + /* FIX: add STA entry to kernel/driver here? The set_key will fail + * most likely without this.. At the moment, STA entry is added only + * after association has been completed. This function will be called + * again after association to get the PTK configured, but that could be + * optimized by adding the STA entry earlier. + */ + if (wpa_auth_set_key(sm->wpa_auth, 0, alg, sm->addr, sm->keyidx_active, + sm->PTK.tk, klen, KEY_FLAG_PAIRWISE_RX_TX)) + return; + +#ifdef CONFIG_PASN + if (sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF) && + wpa_auth_set_ltf_keyseed(sm->wpa_auth, sm->addr, + sm->PTK.ltf_keyseed, + sm->PTK.ltf_keyseed_len)) { + wpa_printf(MSG_ERROR, + "FT: Failed to set LTF keyseed to driver"); + return; + } +#endif /* CONFIG_PASN */ + + /* FIX: MLME-SetProtection.Request(TA, Tx_Rx) */ + sm->pairwise_set = true; + sm->tk_already_set = true; + + wpa_auth_store_ptksa(sm->wpa_auth, sm->addr, sm->pairwise, + dot11RSNAConfigPMKLifetime, &sm->PTK); +} + + +/* Derive PMK-R1 from PSK, check all available PSK */ +static int wpa_ft_psk_pmk_r1(struct wpa_state_machine *sm, + const u8 *req_pmk_r1_name, + u8 *out_pmk_r1, int *out_pairwise, + struct vlan_description *out_vlan, + const u8 **out_identity, size_t *out_identity_len, + const u8 **out_radius_cui, + size_t *out_radius_cui_len, + int *out_session_timeout) +{ + const u8 *pmk = NULL; + u8 pmk_r0[PMK_LEN], pmk_r0_name[WPA_PMK_NAME_LEN]; + u8 pmk_r1[PMK_LEN], pmk_r1_name[WPA_PMK_NAME_LEN]; + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + const u8 *mdid = wpa_auth->conf.mobility_domain; + const u8 *r0kh = sm->r0kh_id; + size_t r0kh_len = sm->r0kh_id_len; + const u8 *r1kh = wpa_auth->conf.r1_key_holder; + const u8 *ssid = wpa_auth->conf.ssid; + size_t ssid_len = wpa_auth->conf.ssid_len; + int pairwise; + + pairwise = sm->pairwise; + + for (;;) { + pmk = wpa_ft_get_psk(wpa_auth, sm->addr, sm->p2p_dev_addr, + pmk); + if (pmk == NULL) + break; + + if (wpa_derive_pmk_r0(pmk, PMK_LEN, ssid, ssid_len, mdid, r0kh, + r0kh_len, sm->addr, + pmk_r0, pmk_r0_name, + WPA_KEY_MGMT_FT_PSK) < 0 || + wpa_derive_pmk_r1(pmk_r0, PMK_LEN, pmk_r0_name, r1kh, + sm->addr, pmk_r1, pmk_r1_name) < 0 || + os_memcmp_const(pmk_r1_name, req_pmk_r1_name, + WPA_PMK_NAME_LEN) != 0) + continue; + + /* We found a PSK that matches the requested pmk_r1_name */ + wpa_printf(MSG_DEBUG, + "FT: Found PSK to generate PMK-R1 locally"); + os_memcpy(out_pmk_r1, pmk_r1, PMK_LEN); + if (out_pairwise) + *out_pairwise = pairwise; + os_memcpy(sm->PMK, pmk, PMK_LEN); + sm->pmk_len = PMK_LEN; + if (out_vlan && + wpa_ft_get_vlan(sm->wpa_auth, sm->addr, out_vlan) < 0) { + wpa_printf(MSG_DEBUG, "FT: vlan not available for STA " + MACSTR, MAC2STR(sm->addr)); + return -1; + } + + if (out_identity && out_identity_len) { + *out_identity_len = wpa_ft_get_identity( + sm->wpa_auth, sm->addr, out_identity); + } + + if (out_radius_cui && out_radius_cui_len) { + *out_radius_cui_len = wpa_ft_get_radius_cui( + sm->wpa_auth, sm->addr, out_radius_cui); + } + + if (out_session_timeout) { + *out_session_timeout = wpa_ft_get_session_timeout( + sm->wpa_auth, sm->addr); + } + + return 0; + } + + wpa_printf(MSG_DEBUG, + "FT: Did not find PSK to generate PMK-R1 locally"); + return -1; +} + + +/* Detect the configuration the station asked for. + * Required to detect FT-PSK and pairwise cipher. + */ +static int wpa_ft_set_key_mgmt(struct wpa_state_machine *sm, + struct wpa_ft_ies *parse) +{ + int key_mgmt, ciphers; + + if (sm->wpa_key_mgmt) + return 0; + + key_mgmt = parse->key_mgmt & sm->wpa_auth->conf.wpa_key_mgmt; + if (!key_mgmt) { + wpa_printf(MSG_DEBUG, "FT: Invalid key mgmt (0x%x) from " + MACSTR, parse->key_mgmt, MAC2STR(sm->addr)); + return -1; + } + if (key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_IEEE8021X; +#ifdef CONFIG_SHA384 + else if (key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X_SHA384) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_IEEE8021X_SHA384; +#endif /* CONFIG_SHA384 */ + else if (key_mgmt & WPA_KEY_MGMT_FT_PSK) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_PSK; +#ifdef CONFIG_FILS + else if (key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA256) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_FILS_SHA256; + else if (key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA384) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_FILS_SHA384; +#endif /* CONFIG_FILS */ + ciphers = parse->pairwise_cipher & sm->wpa_auth->conf.rsn_pairwise; + if (!ciphers) { + wpa_printf(MSG_DEBUG, "FT: Invalid pairwise cipher (0x%x) from " + MACSTR, + parse->pairwise_cipher, MAC2STR(sm->addr)); + return -1; + } + sm->pairwise = wpa_pick_pairwise_cipher(ciphers, 0); + + return 0; +} + + +static int wpa_ft_local_derive_pmk_r1(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + const u8 *r0kh_id, size_t r0kh_id_len, + const u8 *req_pmk_r0_name, + u8 *out_pmk_r1_name, + u8 *out_pmk_r1, int *out_pairwise, + struct vlan_description *vlan, + const u8 **identity, size_t *identity_len, + const u8 **radius_cui, + size_t *radius_cui_len, + int *out_session_timeout, + size_t *pmk_r1_len) +{ + struct wpa_auth_config *conf = &wpa_auth->conf; + const struct wpa_ft_pmk_r0_sa *r0; + int expires_in = 0; + int session_timeout = 0; + struct os_reltime now; + + if (conf->r0_key_holder_len != r0kh_id_len || + os_memcmp(conf->r0_key_holder, r0kh_id, conf->r0_key_holder_len) != + 0) + return -1; /* not our R0KH-ID */ + + wpa_printf(MSG_DEBUG, "FT: STA R0KH-ID matching local configuration"); + if (wpa_ft_fetch_pmk_r0(sm->wpa_auth, sm->addr, req_pmk_r0_name, &r0) < + 0) + return -1; /* no matching PMKR0Name in local cache */ + + wpa_printf(MSG_DEBUG, "FT: Requested PMKR0Name found in local cache"); + + if (wpa_derive_pmk_r1(r0->pmk_r0, r0->pmk_r0_len, r0->pmk_r0_name, + conf->r1_key_holder, + sm->addr, out_pmk_r1, out_pmk_r1_name) < 0) + return -1; + + os_get_reltime(&now); + if (r0->expiration) + expires_in = r0->expiration - now.sec; + + if (r0->session_timeout) + session_timeout = r0->session_timeout - now.sec; + + wpa_ft_store_pmk_r1(wpa_auth, sm->addr, out_pmk_r1, r0->pmk_r0_len, + out_pmk_r1_name, + sm->pairwise, r0->vlan, expires_in, session_timeout, + r0->identity, r0->identity_len, + r0->radius_cui, r0->radius_cui_len); + + *out_pairwise = sm->pairwise; + if (vlan) { + if (r0->vlan) + *vlan = *r0->vlan; + else + os_memset(vlan, 0, sizeof(*vlan)); + } + + if (identity && identity_len) { + *identity = r0->identity; + *identity_len = r0->identity_len; + } + + if (radius_cui && radius_cui_len) { + *radius_cui = r0->radius_cui; + *radius_cui_len = r0->radius_cui_len; + } + + *out_session_timeout = session_timeout; + + *pmk_r1_len = r0->pmk_r0_len; + + return 0; +} + + +static int wpa_ft_process_auth_req(struct wpa_state_machine *sm, + const u8 *ies, size_t ies_len, + u8 **resp_ies, size_t *resp_ies_len) +{ + struct rsn_mdie *mdie; + u8 pmk_r1[PMK_LEN_MAX], pmk_r1_name[WPA_PMK_NAME_LEN]; + u8 ptk_name[WPA_PMK_NAME_LEN]; + struct wpa_auth_config *conf; + struct wpa_ft_ies parse; + size_t buflen; + int ret; + u8 *pos, *end; + int pairwise, session_timeout = 0; + struct vlan_description vlan; + const u8 *identity, *radius_cui; + size_t identity_len = 0, radius_cui_len = 0; + size_t pmk_r1_len, kdk_len, len; + int retval = WLAN_STATUS_UNSPECIFIED_FAILURE; + + *resp_ies = NULL; + *resp_ies_len = 0; + + sm->pmk_r1_name_valid = 0; + conf = &sm->wpa_auth->conf; + + wpa_hexdump(MSG_DEBUG, "FT: Received authentication frame IEs", + ies, ies_len); + + if (wpa_ft_parse_ies(ies, ies_len, &parse, 0, false)) { + wpa_printf(MSG_DEBUG, "FT: Failed to parse FT IEs"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + mdie = (struct rsn_mdie *) parse.mdie; + if (mdie == NULL || parse.mdie_len < sizeof(*mdie) || + os_memcmp(mdie->mobility_domain, + sm->wpa_auth->conf.mobility_domain, + MOBILITY_DOMAIN_ID_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: Invalid MDIE"); + retval = WLAN_STATUS_INVALID_MDIE; + goto out; + } + + if (!parse.ftie || parse.ftie_len < sizeof(struct rsn_ftie)) { + wpa_printf(MSG_DEBUG, "FT: Invalid FTIE"); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + if (parse.r0kh_id == NULL) { + wpa_printf(MSG_DEBUG, "FT: Invalid FTIE - no R0KH-ID"); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + wpa_hexdump(MSG_DEBUG, "FT: STA R0KH-ID", + parse.r0kh_id, parse.r0kh_id_len); + os_memcpy(sm->r0kh_id, parse.r0kh_id, parse.r0kh_id_len); + sm->r0kh_id_len = parse.r0kh_id_len; + + if (parse.rsn_pmkid == NULL) { + wpa_printf(MSG_DEBUG, "FT: No PMKID in RSNIE"); + retval = WLAN_STATUS_INVALID_PMKID; + goto out; + } + + if (wpa_ft_set_key_mgmt(sm, &parse) < 0) + goto out; + + wpa_hexdump(MSG_DEBUG, "FT: Requested PMKR0Name", + parse.rsn_pmkid, WPA_PMK_NAME_LEN); + + if (conf->ft_psk_generate_local && + wpa_key_mgmt_ft_psk(sm->wpa_key_mgmt)) { + if (wpa_derive_pmk_r1_name(parse.rsn_pmkid, + sm->wpa_auth->conf.r1_key_holder, + sm->addr, pmk_r1_name, PMK_LEN) < 0) + goto out; + if (wpa_ft_psk_pmk_r1(sm, pmk_r1_name, pmk_r1, &pairwise, + &vlan, &identity, &identity_len, + &radius_cui, &radius_cui_len, + &session_timeout) < 0) { + retval = WLAN_STATUS_INVALID_PMKID; + goto out; + } + pmk_r1_len = PMK_LEN; + wpa_printf(MSG_DEBUG, + "FT: Generated PMK-R1 for FT-PSK locally"); + goto pmk_r1_derived; + } + + /* Need to test all possible hash algorithms for FT-SAE-EXT-KEY since + * the key length is not yet known. For other AKMs, only the length + * identified by the AKM is used. */ + for (len = SHA256_MAC_LEN; len <= SHA512_MAC_LEN; len += 16) { + if (parse.key_mgmt != WPA_KEY_MGMT_FT_SAE_EXT_KEY && + ((wpa_key_mgmt_sha384(parse.key_mgmt) && + len != SHA384_MAC_LEN) || + (!wpa_key_mgmt_sha384(parse.key_mgmt) && + len != SHA256_MAC_LEN))) + continue; + if (wpa_derive_pmk_r1_name(parse.rsn_pmkid, + sm->wpa_auth->conf.r1_key_holder, + sm->addr, pmk_r1_name, len) < 0) + continue; + + if (wpa_ft_fetch_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1_name, + pmk_r1, &pmk_r1_len, &pairwise, &vlan, + &identity, &identity_len, &radius_cui, + &radius_cui_len, + &session_timeout) == 0) { + wpa_printf(MSG_DEBUG, + "FT: Found PMKR1Name (using SHA%zu) from local cache", + pmk_r1_len * 8); + goto pmk_r1_derived; + } + } + + wpa_printf(MSG_DEBUG, + "FT: No PMK-R1 available in local cache for the requested PMKR1Name"); + if (wpa_ft_local_derive_pmk_r1(sm->wpa_auth, sm, + parse.r0kh_id, parse.r0kh_id_len, + parse.rsn_pmkid, + pmk_r1_name, pmk_r1, &pairwise, + &vlan, &identity, &identity_len, + &radius_cui, &radius_cui_len, + &session_timeout, &pmk_r1_len) == 0) { + wpa_printf(MSG_DEBUG, + "FT: Generated PMK-R1 based on local PMK-R0"); + goto pmk_r1_derived; + } + + if (wpa_ft_pull_pmk_r1(sm, ies, ies_len, parse.rsn_pmkid) < 0) { + wpa_printf(MSG_DEBUG, + "FT: Did not have matching PMK-R1 and either unknown or blocked R0KH-ID or NAK from R0KH"); + retval = WLAN_STATUS_INVALID_PMKID; + goto out; + } + + retval = -1; /* Status pending */ + goto out; + +pmk_r1_derived: + wpa_hexdump_key(MSG_DEBUG, "FT: Selected PMK-R1", pmk_r1, pmk_r1_len); + sm->pmk_r1_name_valid = 1; + os_memcpy(sm->pmk_r1_name, pmk_r1_name, WPA_PMK_NAME_LEN); + os_memcpy(sm->pmk_r1, pmk_r1, pmk_r1_len); + sm->pmk_r1_len = pmk_r1_len; + + if (random_get_bytes(sm->ANonce, WPA_NONCE_LEN)) { + wpa_printf(MSG_DEBUG, "FT: Failed to get random data for " + "ANonce"); + goto out; + } + + /* Now that we know the correct PMK-R1 length and as such, the length + * of the MIC field, fetch the SNonce. */ + if (pmk_r1_len == SHA512_MAC_LEN) { + const struct rsn_ftie_sha512 *ftie; + + ftie = (const struct rsn_ftie_sha512 *) parse.ftie; + if (!ftie || parse.ftie_len < sizeof(*ftie)) { + wpa_printf(MSG_DEBUG, "FT: Invalid FTIE"); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN); + } else if (pmk_r1_len == SHA384_MAC_LEN) { + const struct rsn_ftie_sha384 *ftie; + + ftie = (const struct rsn_ftie_sha384 *) parse.ftie; + if (!ftie || parse.ftie_len < sizeof(*ftie)) { + wpa_printf(MSG_DEBUG, "FT: Invalid FTIE"); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN); + } else { + const struct rsn_ftie *ftie; + + ftie = (const struct rsn_ftie *) parse.ftie; + if (!ftie || parse.ftie_len < sizeof(*ftie)) { + wpa_printf(MSG_DEBUG, "FT: Invalid FTIE"); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN); + } + + wpa_hexdump(MSG_DEBUG, "FT: Received SNonce", + sm->SNonce, WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Generated ANonce", + sm->ANonce, WPA_NONCE_LEN); + + if (sm->wpa_auth->conf.force_kdk_derivation || + (sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF))) + kdk_len = WPA_KDK_MAX_LEN; + else + kdk_len = 0; + + if (wpa_pmk_r1_to_ptk(pmk_r1, pmk_r1_len, sm->SNonce, sm->ANonce, + sm->addr, sm->wpa_auth->addr, pmk_r1_name, + &sm->PTK, ptk_name, parse.key_mgmt, + pairwise, kdk_len) < 0) + goto out; + +#ifdef CONFIG_PASN + if (sm->wpa_auth->conf.secure_ltf && + ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF) && + wpa_ltf_keyseed(&sm->PTK, parse.key_mgmt, pairwise)) { + wpa_printf(MSG_DEBUG, "FT: Failed to derive LTF keyseed"); + goto out; + } +#endif /* CONFIG_PASN */ + + sm->pairwise = pairwise; + sm->PTK_valid = true; + sm->tk_already_set = false; + wpa_ft_install_ptk(sm, 0); + + if (wpa_ft_set_vlan(sm->wpa_auth, sm->addr, &vlan) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to configure VLAN"); + goto out; + } + if (wpa_ft_set_identity(sm->wpa_auth, sm->addr, + identity, identity_len) < 0 || + wpa_ft_set_radius_cui(sm->wpa_auth, sm->addr, + radius_cui, radius_cui_len) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to configure identity/CUI"); + goto out; + } + wpa_ft_set_session_timeout(sm->wpa_auth, sm->addr, session_timeout); + + buflen = 2 + sizeof(struct rsn_mdie) + 2 + sizeof(struct rsn_ftie) + + 2 + FT_R1KH_ID_LEN + 200; + *resp_ies = os_zalloc(buflen); + if (*resp_ies == NULL) + goto fail; + + pos = *resp_ies; + end = *resp_ies + buflen; + + ret = wpa_write_rsn_ie(conf, pos, end - pos, parse.rsn_pmkid); + if (ret < 0) + goto fail; + pos += ret; + + ret = wpa_write_mdie(conf, pos, end - pos); + if (ret < 0) + goto fail; + pos += ret; + + ret = wpa_write_ftie(conf, parse.key_mgmt, pmk_r1_len, + parse.r0kh_id, parse.r0kh_id_len, + sm->ANonce, sm->SNonce, pos, end - pos, NULL, 0, + 0); + if (ret < 0) + goto fail; + pos += ret; + + *resp_ies_len = pos - *resp_ies; + + retval = WLAN_STATUS_SUCCESS; + goto out; +fail: + os_free(*resp_ies); + *resp_ies = NULL; +out: + wpa_ft_parse_ies_free(&parse); + return retval; +} + + +void wpa_ft_process_auth(struct wpa_state_machine *sm, + u16 auth_transaction, const u8 *ies, size_t ies_len, + void (*cb)(void *ctx, const u8 *dst, + u16 auth_transaction, u16 status, + const u8 *ies, size_t ies_len), + void *ctx) +{ + u16 status; + u8 *resp_ies; + size_t resp_ies_len; + int res; + + if (sm == NULL) { + wpa_printf(MSG_DEBUG, "FT: Received authentication frame, but " + "WPA SM not available"); + return; + } + + wpa_printf(MSG_DEBUG, "FT: Received authentication frame: STA=" MACSTR + " BSSID=" MACSTR " transaction=%d", + MAC2STR(sm->addr), MAC2STR(sm->wpa_auth->addr), + auth_transaction); + sm->ft_pending_cb = cb; + sm->ft_pending_cb_ctx = ctx; + sm->ft_pending_auth_transaction = auth_transaction; + sm->ft_pending_pull_left_retries = sm->wpa_auth->conf.rkh_pull_retries; + res = wpa_ft_process_auth_req(sm, ies, ies_len, &resp_ies, + &resp_ies_len); + if (res < 0) { + wpa_printf(MSG_DEBUG, "FT: Callback postponed until response is available"); + return; + } + status = res; + + wpa_printf(MSG_DEBUG, "FT: FT authentication response: dst=" MACSTR + " auth_transaction=%d status=%u (%s)", + MAC2STR(sm->addr), auth_transaction + 1, status, + status2str(status)); + wpa_hexdump(MSG_DEBUG, "FT: Response IEs", resp_ies, resp_ies_len); + cb(ctx, sm->addr, auth_transaction + 1, status, resp_ies, resp_ies_len); + os_free(resp_ies); +} + + +int wpa_ft_validate_reassoc(struct wpa_state_machine *sm, const u8 *ies, + size_t ies_len) +{ + struct wpa_ft_ies parse; + struct rsn_mdie *mdie; + u8 mic[WPA_EAPOL_KEY_MIC_MAX_LEN]; + size_t mic_len; + unsigned int count; + const u8 *kck; + size_t kck_len; + struct wpa_auth_config *conf; + int retval = WLAN_STATUS_UNSPECIFIED_FAILURE; + + if (sm == NULL) + return WLAN_STATUS_UNSPECIFIED_FAILURE; + + conf = &sm->wpa_auth->conf; + + wpa_hexdump(MSG_DEBUG, "FT: Reassoc Req IEs", ies, ies_len); + + if (wpa_ft_parse_ies(ies, ies_len, &parse, sm->wpa_key_mgmt, + false) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to parse FT IEs"); + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + if (parse.rsn == NULL) { + wpa_printf(MSG_DEBUG, "FT: No RSNIE in Reassoc Req"); + goto out; + } + + if (parse.rsn_pmkid == NULL) { + wpa_printf(MSG_DEBUG, "FT: No PMKID in RSNIE"); + retval = WLAN_STATUS_INVALID_PMKID; + goto out; + } + + if (os_memcmp_const(parse.rsn_pmkid, sm->pmk_r1_name, WPA_PMK_NAME_LEN) + != 0) { + wpa_printf(MSG_DEBUG, "FT: PMKID in Reassoc Req did not match " + "with the PMKR1Name derived from auth request"); + retval = WLAN_STATUS_INVALID_PMKID; + goto out; + } + + mdie = (struct rsn_mdie *) parse.mdie; + if (mdie == NULL || parse.mdie_len < sizeof(*mdie) || + os_memcmp(mdie->mobility_domain, conf->mobility_domain, + MOBILITY_DOMAIN_ID_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: Invalid MDIE"); + retval = WLAN_STATUS_INVALID_MDIE; + goto out; + } + + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + sm->pmk_r1_len == SHA512_MAC_LEN) + mic_len = 32; + else if ((sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY && + sm->pmk_r1_len == SHA384_MAC_LEN) || + wpa_key_mgmt_sha384(sm->wpa_key_mgmt)) + mic_len = 24; + else + mic_len = 16; + + if (!parse.ftie || !parse.fte_anonce || !parse.fte_snonce || + parse.fte_mic_len != mic_len) { + wpa_printf(MSG_DEBUG, + "FT: Invalid FTE (fte_mic_len=%zu mic_len=%zu)", + parse.fte_mic_len, mic_len); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + if (os_memcmp(parse.fte_snonce, sm->SNonce, WPA_NONCE_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: SNonce mismatch in FTIE"); + wpa_hexdump(MSG_DEBUG, "FT: Received SNonce", + parse.fte_snonce, WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Expected SNonce", + sm->SNonce, WPA_NONCE_LEN); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + if (os_memcmp(parse.fte_anonce, sm->ANonce, WPA_NONCE_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: ANonce mismatch in FTIE"); + wpa_hexdump(MSG_DEBUG, "FT: Received ANonce", + parse.fte_anonce, WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Expected ANonce", + sm->ANonce, WPA_NONCE_LEN); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + if (parse.r0kh_id == NULL) { + wpa_printf(MSG_DEBUG, "FT: No R0KH-ID subelem in FTIE"); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + if (parse.r0kh_id_len != sm->r0kh_id_len || + os_memcmp_const(parse.r0kh_id, sm->r0kh_id, parse.r0kh_id_len) != 0) + { + wpa_printf(MSG_DEBUG, "FT: R0KH-ID in FTIE did not match with " + "the current R0KH-ID"); + wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID in FTIE", + parse.r0kh_id, parse.r0kh_id_len); + wpa_hexdump(MSG_DEBUG, "FT: The current R0KH-ID", + sm->r0kh_id, sm->r0kh_id_len); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + if (parse.r1kh_id == NULL) { + wpa_printf(MSG_DEBUG, "FT: No R1KH-ID subelem in FTIE"); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + if (os_memcmp_const(parse.r1kh_id, conf->r1_key_holder, + FT_R1KH_ID_LEN) != 0) { + wpa_printf(MSG_DEBUG, "FT: Unknown R1KH-ID used in " + "ReassocReq"); + wpa_hexdump(MSG_DEBUG, "FT: R1KH-ID in FTIE", + parse.r1kh_id, FT_R1KH_ID_LEN); + wpa_hexdump(MSG_DEBUG, "FT: Expected R1KH-ID", + conf->r1_key_holder, FT_R1KH_ID_LEN); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + if (parse.rsn_pmkid == NULL || + os_memcmp_const(parse.rsn_pmkid, sm->pmk_r1_name, WPA_PMK_NAME_LEN)) + { + wpa_printf(MSG_DEBUG, "FT: No matching PMKR1Name (PMKID) in " + "RSNIE (pmkid=%d)", !!parse.rsn_pmkid); + retval = WLAN_STATUS_INVALID_PMKID; + goto out; + } + + count = 3; + if (parse.ric) + count += ieee802_11_ie_count(parse.ric, parse.ric_len); + if (parse.rsnxe) + count++; + if (parse.fte_elem_count != count) { + wpa_printf(MSG_DEBUG, "FT: Unexpected IE count in MIC " + "Control: received %u expected %u", + parse.fte_elem_count, count); + goto out; + } + + if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) { + kck = sm->PTK.kck2; + kck_len = sm->PTK.kck2_len; + } else { + kck = sm->PTK.kck; + kck_len = sm->PTK.kck_len; + } + if (wpa_ft_mic(sm->wpa_key_mgmt, kck, kck_len, + sm->addr, sm->wpa_auth->addr, 5, + parse.mdie - 2, parse.mdie_len + 2, + parse.ftie - 2, parse.ftie_len + 2, + parse.rsn - 2, parse.rsn_len + 2, + parse.ric, parse.ric_len, + parse.rsnxe ? parse.rsnxe - 2 : NULL, + parse.rsnxe ? parse.rsnxe_len + 2 : 0, + NULL, + mic) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC"); + goto out; + } + + if (os_memcmp_const(mic, parse.fte_mic, mic_len) != 0) { + wpa_printf(MSG_DEBUG, "FT: Invalid MIC in FTIE"); + wpa_printf(MSG_DEBUG, "FT: addr=" MACSTR " auth_addr=" MACSTR, + MAC2STR(sm->addr), MAC2STR(sm->wpa_auth->addr)); + wpa_hexdump(MSG_MSGDUMP, "FT: Received MIC", + parse.fte_mic, mic_len); + wpa_hexdump(MSG_MSGDUMP, "FT: Calculated MIC", mic, mic_len); + wpa_hexdump(MSG_MSGDUMP, "FT: MDIE", + parse.mdie - 2, parse.mdie_len + 2); + wpa_hexdump(MSG_MSGDUMP, "FT: FTIE", + parse.ftie - 2, parse.ftie_len + 2); + wpa_hexdump(MSG_MSGDUMP, "FT: RSN", + parse.rsn - 2, parse.rsn_len + 2); + wpa_hexdump(MSG_MSGDUMP, "FT: RSNXE", + parse.rsnxe ? parse.rsnxe - 2 : NULL, + parse.rsnxe ? parse.rsnxe_len + 2 : 0); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + + if (parse.fte_rsnxe_used && + (conf->sae_pwe == SAE_PWE_HASH_TO_ELEMENT || + conf->sae_pwe == SAE_PWE_BOTH) && + !parse.rsnxe) { + wpa_printf(MSG_INFO, + "FT: FTE indicated that STA uses RSNXE, but RSNXE was not included"); + retval = -1; /* discard request */ + goto out; + } + +#ifdef CONFIG_OCV + if (wpa_auth_uses_ocv(sm)) { + struct wpa_channel_info ci; + int tx_chanwidth; + int tx_seg1_idx; + enum oci_verify_result res; + + if (wpa_channel_info(sm->wpa_auth, &ci) != 0) { + wpa_printf(MSG_WARNING, + "Failed to get channel info to validate received OCI in (Re)Assoc Request"); + goto out; + } + + if (get_sta_tx_parameters(sm, + channel_width_to_int(ci.chanwidth), + ci.seg1_idx, &tx_chanwidth, + &tx_seg1_idx) < 0) + goto out; + + res = ocv_verify_tx_params(parse.oci, parse.oci_len, &ci, + tx_chanwidth, tx_seg1_idx); + if (wpa_auth_uses_ocv(sm) == 2 && res == OCI_NOT_FOUND) { + /* Work around misbehaving STAs */ + wpa_printf(MSG_INFO, + "Disable OCV with a STA that does not send OCI"); + wpa_auth_set_ocv(sm, 0); + } else if (res != OCI_SUCCESS) { + wpa_printf(MSG_WARNING, "OCV failed: %s", ocv_errorstr); + if (sm->wpa_auth->conf.msg_ctx) + wpa_msg(sm->wpa_auth->conf.msg_ctx, MSG_INFO, + OCV_FAILURE "addr=" MACSTR + " frame=ft-reassoc-req error=%s", + MAC2STR(sm->addr), ocv_errorstr); + retval = WLAN_STATUS_INVALID_FTIE; + goto out; + } + } +#endif /* CONFIG_OCV */ + + retval = WLAN_STATUS_SUCCESS; +out: + wpa_ft_parse_ies_free(&parse); + return retval; +} + + +int wpa_ft_action_rx(struct wpa_state_machine *sm, const u8 *data, size_t len) +{ + const u8 *sta_addr, *target_ap; + const u8 *ies; + size_t ies_len; + u8 action; + struct ft_rrb_frame *frame; + + if (sm == NULL) + return -1; + + /* + * data: Category[1] Action[1] STA_Address[6] Target_AP_Address[6] + * FT Request action frame body[variable] + */ + + if (len < 14) { + wpa_printf(MSG_DEBUG, "FT: Too short FT Action frame " + "(len=%lu)", (unsigned long) len); + return -1; + } + + action = data[1]; + sta_addr = data + 2; + target_ap = data + 8; + ies = data + 14; + ies_len = len - 14; + + wpa_printf(MSG_DEBUG, "FT: Received FT Action frame (STA=" MACSTR + " Target AP=" MACSTR " Action=%d)", + MAC2STR(sta_addr), MAC2STR(target_ap), action); + + if (!ether_addr_equal(sta_addr, sm->addr)) { + wpa_printf(MSG_DEBUG, "FT: Mismatch in FT Action STA address: " + "STA=" MACSTR " STA-Address=" MACSTR, + MAC2STR(sm->addr), MAC2STR(sta_addr)); + return -1; + } + + /* + * Do some validity checking on the target AP address (not own and not + * broadcast. This could be extended to filter based on a list of known + * APs in the MD (if such a list were configured). + */ + if ((target_ap[0] & 0x01) || + ether_addr_equal(target_ap, sm->wpa_auth->addr)) { + wpa_printf(MSG_DEBUG, "FT: Invalid Target AP in FT Action " + "frame"); + return -1; + } + + wpa_hexdump(MSG_MSGDUMP, "FT: Action frame body", ies, ies_len); + + if (!sm->wpa_auth->conf.ft_over_ds) { + wpa_printf(MSG_DEBUG, "FT: Over-DS option disabled - reject"); + return -1; + } + + /* RRB - Forward action frame to the target AP */ + frame = os_malloc(sizeof(*frame) + len); + if (frame == NULL) + return -1; + frame->frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB; + frame->packet_type = FT_PACKET_REQUEST; + frame->action_length = host_to_le16(len); + os_memcpy(frame->ap_address, sm->wpa_auth->addr, ETH_ALEN); + os_memcpy(frame + 1, data, len); + + wpa_ft_rrb_send(sm->wpa_auth, target_ap, (u8 *) frame, + sizeof(*frame) + len); + os_free(frame); + + return 0; +} + + +static void wpa_ft_rrb_rx_request_cb(void *ctx, const u8 *dst, + u16 auth_transaction, u16 resp, + const u8 *ies, size_t ies_len) +{ + struct wpa_state_machine *sm = ctx; + wpa_printf(MSG_DEBUG, "FT: Over-the-DS RX request cb for " MACSTR, + MAC2STR(sm->addr)); + wpa_ft_send_rrb_auth_resp(sm, sm->ft_pending_current_ap, sm->addr, + WLAN_STATUS_SUCCESS, ies, ies_len); +} + + +static int wpa_ft_rrb_rx_request(struct wpa_authenticator *wpa_auth, + const u8 *current_ap, const u8 *sta_addr, + const u8 *body, size_t len) +{ + struct wpa_state_machine *sm; + u16 status; + u8 *resp_ies; + size_t resp_ies_len; + int res; + + sm = wpa_ft_add_sta(wpa_auth, sta_addr); + if (sm == NULL) { + wpa_printf(MSG_DEBUG, "FT: Failed to add new STA based on " + "RRB Request"); + return -1; + } + + wpa_hexdump(MSG_MSGDUMP, "FT: RRB Request Frame body", body, len); + + sm->ft_pending_cb = wpa_ft_rrb_rx_request_cb; + sm->ft_pending_cb_ctx = sm; + os_memcpy(sm->ft_pending_current_ap, current_ap, ETH_ALEN); + sm->ft_pending_pull_left_retries = sm->wpa_auth->conf.rkh_pull_retries; + res = wpa_ft_process_auth_req(sm, body, len, &resp_ies, + &resp_ies_len); + if (res < 0) { + wpa_printf(MSG_DEBUG, "FT: No immediate response available - wait for pull response"); + return 0; + } + status = res; + + res = wpa_ft_send_rrb_auth_resp(sm, current_ap, sta_addr, status, + resp_ies, resp_ies_len); + os_free(resp_ies); + return res; +} + + +static int wpa_ft_send_rrb_auth_resp(struct wpa_state_machine *sm, + const u8 *current_ap, const u8 *sta_addr, + u16 status, const u8 *resp_ies, + size_t resp_ies_len) +{ + struct wpa_authenticator *wpa_auth = sm->wpa_auth; + size_t rlen; + struct ft_rrb_frame *frame; + u8 *pos; + + wpa_printf(MSG_DEBUG, "FT: RRB authentication response: STA=" MACSTR + " CurrentAP=" MACSTR " status=%u (%s)", + MAC2STR(sm->addr), MAC2STR(current_ap), status, + status2str(status)); + wpa_hexdump(MSG_DEBUG, "FT: Response IEs", resp_ies, resp_ies_len); + + /* RRB - Forward action frame response to the Current AP */ + + /* + * data: Category[1] Action[1] STA_Address[6] Target_AP_Address[6] + * Status_Code[2] FT Request action frame body[variable] + */ + rlen = 2 + 2 * ETH_ALEN + 2 + resp_ies_len; + + frame = os_malloc(sizeof(*frame) + rlen); + if (frame == NULL) + return -1; + frame->frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB; + frame->packet_type = FT_PACKET_RESPONSE; + frame->action_length = host_to_le16(rlen); + os_memcpy(frame->ap_address, wpa_auth->addr, ETH_ALEN); + pos = (u8 *) (frame + 1); + *pos++ = WLAN_ACTION_FT; + *pos++ = 2; /* Action: Response */ + os_memcpy(pos, sta_addr, ETH_ALEN); + pos += ETH_ALEN; + os_memcpy(pos, wpa_auth->addr, ETH_ALEN); + pos += ETH_ALEN; + WPA_PUT_LE16(pos, status); + pos += 2; + if (resp_ies) + os_memcpy(pos, resp_ies, resp_ies_len); + + wpa_ft_rrb_send(wpa_auth, current_ap, (u8 *) frame, + sizeof(*frame) + rlen); + os_free(frame); + + return 0; +} + + +static int wpa_ft_rrb_build_r0(const u8 *key, const size_t key_len, + const struct tlv_list *tlvs, + const struct wpa_ft_pmk_r0_sa *pmk_r0, + const u8 *r1kh_id, const u8 *s1kh_id, + const struct tlv_list *tlv_auth, + const u8 *src_addr, u8 type, + u8 **packet, size_t *packet_len) +{ + u8 pmk_r1[PMK_LEN_MAX]; + size_t pmk_r1_len = pmk_r0->pmk_r0_len; + u8 pmk_r1_name[WPA_PMK_NAME_LEN]; + u8 f_pairwise[sizeof(le16)]; + u8 f_expires_in[sizeof(le16)]; + u8 f_session_timeout[sizeof(le32)]; + int expires_in; + int session_timeout; + struct os_reltime now; + int ret; + struct tlv_list sess_tlv[] = { + { .type = FT_RRB_PMK_R1, .len = pmk_r1_len, + .data = pmk_r1 }, + { .type = FT_RRB_PMK_R1_NAME, .len = sizeof(pmk_r1_name), + .data = pmk_r1_name }, + { .type = FT_RRB_PAIRWISE, .len = sizeof(f_pairwise), + .data = f_pairwise }, + { .type = FT_RRB_EXPIRES_IN, .len = sizeof(f_expires_in), + .data = f_expires_in }, + { .type = FT_RRB_IDENTITY, .len = pmk_r0->identity_len, + .data = pmk_r0->identity }, + { .type = FT_RRB_RADIUS_CUI, .len = pmk_r0->radius_cui_len, + .data = pmk_r0->radius_cui }, + { .type = FT_RRB_SESSION_TIMEOUT, + .len = sizeof(f_session_timeout), + .data = f_session_timeout }, + { .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL }, + }; + + wpa_printf(MSG_DEBUG, "FT: Derive PMK-R1 for peer AP"); + if (wpa_derive_pmk_r1(pmk_r0->pmk_r0, pmk_r0->pmk_r0_len, + pmk_r0->pmk_r0_name, r1kh_id, + s1kh_id, pmk_r1, pmk_r1_name) < 0) + return -1; + WPA_PUT_LE16(f_pairwise, pmk_r0->pairwise); + + os_get_reltime(&now); + if (pmk_r0->expiration > now.sec) + expires_in = pmk_r0->expiration - now.sec; + else if (pmk_r0->expiration) + expires_in = 1; + else + expires_in = 0; + WPA_PUT_LE16(f_expires_in, expires_in); + + if (pmk_r0->session_timeout > now.sec) + session_timeout = pmk_r0->session_timeout - now.sec; + else if (pmk_r0->session_timeout) + session_timeout = 1; + else + session_timeout = 0; + WPA_PUT_LE32(f_session_timeout, session_timeout); + + ret = wpa_ft_rrb_build(key, key_len, tlvs, sess_tlv, tlv_auth, + pmk_r0->vlan, src_addr, type, + packet, packet_len); + + forced_memzero(pmk_r1, sizeof(pmk_r1)); + + return ret; +} + + +static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + int no_defer) +{ + const char *msgtype = "pull request"; + u8 *plain = NULL, *packet = NULL; + size_t plain_len = 0, packet_len = 0; + struct ft_remote_r1kh *r1kh, *r1kh_wildcard; + const u8 *key; + size_t key_len; + int seq_ret; + const u8 *f_nonce, *f_r0kh_id, *f_r1kh_id, *f_s1kh_id, *f_pmk_r0_name; + size_t f_nonce_len, f_r0kh_id_len, f_r1kh_id_len, f_s1kh_id_len; + size_t f_pmk_r0_name_len; + const struct wpa_ft_pmk_r0_sa *r0; + int ret; + struct tlv_list resp[2]; + struct tlv_list resp_auth[5]; + struct ft_rrb_seq f_seq; + + wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull"); + + RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, msgtype, -1); + wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID", f_r0kh_id, f_r0kh_id_len); + + if (wpa_ft_rrb_check_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len)) { + wpa_printf(MSG_DEBUG, "FT: R0KH-ID mismatch"); + goto out; + } + + RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, msgtype, FT_R1KH_ID_LEN); + wpa_printf(MSG_DEBUG, "FT: R1KH-ID=" MACSTR, MAC2STR(f_r1kh_id)); + + wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh, &r1kh_wildcard); + if (r1kh) { + key = r1kh->key; + key_len = sizeof(r1kh->key); + } else if (r1kh_wildcard) { + wpa_printf(MSG_DEBUG, "FT: Using wildcard R1KH-ID"); + key = r1kh_wildcard->key; + key_len = sizeof(r1kh_wildcard->key); + } else { + goto out; + } + + RRB_GET_AUTH(FT_RRB_NONCE, nonce, "pull request", FT_RRB_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: nonce", f_nonce, f_nonce_len); + + seq_ret = FT_RRB_SEQ_DROP; + if (r1kh) + seq_ret = wpa_ft_rrb_seq_chk(r1kh->seq, src_addr, enc, enc_len, + auth, auth_len, msgtype, no_defer); + if (!no_defer && r1kh_wildcard && + (!r1kh || !ether_addr_equal(r1kh->addr, src_addr))) { + /* wildcard: r1kh-id unknown or changed addr -> do a seq req */ + seq_ret = FT_RRB_SEQ_DEFER; + } + + if (seq_ret == FT_RRB_SEQ_DROP) + goto out; + + if (wpa_ft_rrb_decrypt(key, key_len, enc, enc_len, auth, auth_len, + src_addr, FT_PACKET_R0KH_R1KH_PULL, + &plain, &plain_len) < 0) + goto out; + + if (!r1kh) + r1kh = wpa_ft_rrb_add_r1kh(wpa_auth, r1kh_wildcard, src_addr, + f_r1kh_id, + wpa_auth->conf.rkh_pos_timeout); + if (!r1kh) + goto out; + + if (seq_ret == FT_RRB_SEQ_DEFER) { + wpa_ft_rrb_seq_req(wpa_auth, r1kh->seq, src_addr, f_r0kh_id, + f_r0kh_id_len, f_r1kh_id, key, key_len, + enc, enc_len, auth, auth_len, + &wpa_ft_rrb_rx_pull); + goto out; + } + + wpa_ft_rrb_seq_accept(wpa_auth, r1kh->seq, src_addr, auth, auth_len, + msgtype); + wpa_ft_rrb_r1kh_replenish(wpa_auth, r1kh, + wpa_auth->conf.rkh_pos_timeout); + + RRB_GET(FT_RRB_PMK_R0_NAME, pmk_r0_name, msgtype, WPA_PMK_NAME_LEN); + wpa_hexdump(MSG_DEBUG, "FT: PMKR0Name", f_pmk_r0_name, + f_pmk_r0_name_len); + + RRB_GET(FT_RRB_S1KH_ID, s1kh_id, msgtype, ETH_ALEN); + wpa_printf(MSG_DEBUG, "FT: S1KH-ID=" MACSTR, MAC2STR(f_s1kh_id)); + + if (wpa_ft_new_seq(r1kh->seq, &f_seq) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to get seq num"); + goto out; + } + + wpa_printf(MSG_DEBUG, "FT: Send PMK-R1 pull response from " MACSTR + " to " MACSTR, + MAC2STR(wpa_auth->addr), MAC2STR(src_addr)); + + resp[0].type = FT_RRB_S1KH_ID; + resp[0].len = f_s1kh_id_len; + resp[0].data = f_s1kh_id; + resp[1].type = FT_RRB_LAST_EMPTY; + resp[1].len = 0; + resp[1].data = NULL; + + resp_auth[0].type = FT_RRB_NONCE; + resp_auth[0].len = f_nonce_len; + resp_auth[0].data = f_nonce; + resp_auth[1].type = FT_RRB_SEQ; + resp_auth[1].len = sizeof(f_seq); + resp_auth[1].data = (u8 *) &f_seq; + resp_auth[2].type = FT_RRB_R0KH_ID; + resp_auth[2].len = f_r0kh_id_len; + resp_auth[2].data = f_r0kh_id; + resp_auth[3].type = FT_RRB_R1KH_ID; + resp_auth[3].len = f_r1kh_id_len; + resp_auth[3].data = f_r1kh_id; + resp_auth[4].type = FT_RRB_LAST_EMPTY; + resp_auth[4].len = 0; + resp_auth[4].data = NULL; + + if (wpa_ft_fetch_pmk_r0(wpa_auth, f_s1kh_id, f_pmk_r0_name, &r0) < 0) { + wpa_printf(MSG_DEBUG, "FT: No matching PMK-R0-Name found"); + ret = wpa_ft_rrb_build(key, key_len, resp, NULL, resp_auth, + NULL, wpa_auth->addr, + FT_PACKET_R0KH_R1KH_RESP, + &packet, &packet_len); + } else { + ret = wpa_ft_rrb_build_r0(key, key_len, resp, r0, f_r1kh_id, + f_s1kh_id, resp_auth, wpa_auth->addr, + FT_PACKET_R0KH_R1KH_RESP, + &packet, &packet_len); + } + + if (!ret) + wpa_ft_rrb_oui_send(wpa_auth, src_addr, + FT_PACKET_R0KH_R1KH_RESP, packet, + packet_len); + +out: + os_free(plain); + os_free(packet); + + return 0; +} + + +/* @returns 0 on success + * -1 on error + * -2 if FR_RRB_PAIRWISE is missing + */ +static int wpa_ft_rrb_rx_r1(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, u8 type, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + const char *msgtype, u8 *s1kh_id_out, + int (*cb)(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + int no_defer)) +{ + u8 *plain = NULL; + size_t plain_len = 0; + struct ft_remote_r0kh *r0kh, *r0kh_wildcard; + const u8 *key; + size_t key_len; + int seq_ret; + const u8 *f_r1kh_id, *f_s1kh_id, *f_r0kh_id; + const u8 *f_pmk_r1_name, *f_pairwise, *f_pmk_r1; + const u8 *f_expires_in; + size_t f_r1kh_id_len, f_s1kh_id_len, f_r0kh_id_len; + const u8 *f_identity, *f_radius_cui; + const u8 *f_session_timeout; + size_t f_pmk_r1_name_len, f_pairwise_len, f_pmk_r1_len; + size_t f_expires_in_len; + size_t f_identity_len, f_radius_cui_len; + size_t f_session_timeout_len; + int pairwise; + int ret = -1; + int expires_in; + int session_timeout; + struct vlan_description vlan; + size_t pmk_r1_len; + + RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, msgtype, -1); + wpa_hexdump(MSG_DEBUG, "FT: R0KH-ID", f_r0kh_id, f_r0kh_id_len); + + RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, msgtype, FT_R1KH_ID_LEN); + wpa_printf(MSG_DEBUG, "FT: R1KH-ID=" MACSTR, MAC2STR(f_r1kh_id)); + + if (wpa_ft_rrb_check_r1kh(wpa_auth, f_r1kh_id)) { + wpa_printf(MSG_DEBUG, "FT: R1KH-ID mismatch"); + goto out; + } + + wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len, &r0kh, + &r0kh_wildcard); + if (r0kh) { + key = r0kh->key; + key_len = sizeof(r0kh->key); + } else if (r0kh_wildcard) { + wpa_printf(MSG_DEBUG, "FT: Using wildcard R0KH-ID"); + key = r0kh_wildcard->key; + key_len = sizeof(r0kh_wildcard->key); + } else { + goto out; + } + + seq_ret = FT_RRB_SEQ_DROP; + if (r0kh) { + seq_ret = wpa_ft_rrb_seq_chk(r0kh->seq, src_addr, enc, enc_len, + auth, auth_len, msgtype, + cb ? 0 : 1); + } + if (cb && r0kh_wildcard && + (!r0kh || !ether_addr_equal(r0kh->addr, src_addr))) { + /* wildcard: r0kh-id unknown or changed addr -> do a seq req */ + seq_ret = FT_RRB_SEQ_DEFER; + } + + if (seq_ret == FT_RRB_SEQ_DROP) + goto out; + + if (wpa_ft_rrb_decrypt(key, key_len, enc, enc_len, auth, auth_len, + src_addr, type, &plain, &plain_len) < 0) + goto out; + + if (!r0kh) + r0kh = wpa_ft_rrb_add_r0kh(wpa_auth, r0kh_wildcard, src_addr, + f_r0kh_id, f_r0kh_id_len, + wpa_auth->conf.rkh_pos_timeout); + if (!r0kh) + goto out; + + if (seq_ret == FT_RRB_SEQ_DEFER) { + wpa_ft_rrb_seq_req(wpa_auth, r0kh->seq, src_addr, f_r0kh_id, + f_r0kh_id_len, f_r1kh_id, key, key_len, + enc, enc_len, auth, auth_len, cb); + goto out; + } + + wpa_ft_rrb_seq_accept(wpa_auth, r0kh->seq, src_addr, auth, auth_len, + msgtype); + wpa_ft_rrb_r0kh_replenish(wpa_auth, r0kh, + wpa_auth->conf.rkh_pos_timeout); + + RRB_GET(FT_RRB_S1KH_ID, s1kh_id, msgtype, ETH_ALEN); + wpa_printf(MSG_DEBUG, "FT: S1KH-ID=" MACSTR, MAC2STR(f_s1kh_id)); + + if (s1kh_id_out) + os_memcpy(s1kh_id_out, f_s1kh_id, ETH_ALEN); + + ret = -2; + RRB_GET(FT_RRB_PAIRWISE, pairwise, msgtype, sizeof(le16)); + wpa_hexdump(MSG_DEBUG, "FT: pairwise", f_pairwise, f_pairwise_len); + + ret = -1; + RRB_GET(FT_RRB_PMK_R1_NAME, pmk_r1_name, msgtype, WPA_PMK_NAME_LEN); + wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", + f_pmk_r1_name, WPA_PMK_NAME_LEN); + + pmk_r1_len = PMK_LEN; + if (wpa_ft_rrb_get_tlv(plain, plain_len, FT_RRB_PMK_R1, &f_pmk_r1_len, + &f_pmk_r1) == 0 && + (f_pmk_r1_len == PMK_LEN || f_pmk_r1_len == SHA384_MAC_LEN || + f_pmk_r1_len == SHA512_MAC_LEN)) + pmk_r1_len = f_pmk_r1_len; + RRB_GET(FT_RRB_PMK_R1, pmk_r1, msgtype, pmk_r1_len); + wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1", f_pmk_r1, pmk_r1_len); + + pairwise = WPA_GET_LE16(f_pairwise); + + RRB_GET_OPTIONAL(FT_RRB_EXPIRES_IN, expires_in, msgtype, + sizeof(le16)); + if (f_expires_in) + expires_in = WPA_GET_LE16(f_expires_in); + else + expires_in = 0; + + wpa_printf(MSG_DEBUG, "FT: PMK-R1 %s - expires_in=%d", msgtype, + expires_in); + + if (wpa_ft_rrb_get_tlv_vlan(plain, plain_len, &vlan) < 0) { + wpa_printf(MSG_DEBUG, "FT: Cannot parse vlan"); + wpa_ft_rrb_dump(plain, plain_len); + goto out; + } + + wpa_printf(MSG_DEBUG, "FT: vlan %d%s", + le_to_host16(vlan.untagged), vlan.tagged[0] ? "+" : ""); + + RRB_GET_OPTIONAL(FT_RRB_IDENTITY, identity, msgtype, -1); + if (f_identity) + wpa_hexdump_ascii(MSG_DEBUG, "FT: Identity", f_identity, + f_identity_len); + + RRB_GET_OPTIONAL(FT_RRB_RADIUS_CUI, radius_cui, msgtype, -1); + if (f_radius_cui) + wpa_hexdump_ascii(MSG_DEBUG, "FT: CUI", f_radius_cui, + f_radius_cui_len); + + RRB_GET_OPTIONAL(FT_RRB_SESSION_TIMEOUT, session_timeout, msgtype, + sizeof(le32)); + if (f_session_timeout) + session_timeout = WPA_GET_LE32(f_session_timeout); + else + session_timeout = 0; + wpa_printf(MSG_DEBUG, "FT: session_timeout %d", session_timeout); + + if (wpa_ft_store_pmk_r1(wpa_auth, f_s1kh_id, f_pmk_r1, pmk_r1_len, + f_pmk_r1_name, + pairwise, &vlan, expires_in, session_timeout, + f_identity, f_identity_len, f_radius_cui, + f_radius_cui_len) < 0) + goto out; + + ret = 0; +out: + bin_clear_free(plain, plain_len); + + return ret; + +} + + +static void ft_finish_pull(struct wpa_state_machine *sm) +{ + int res; + u8 *resp_ies; + size_t resp_ies_len; + u16 status; + + if (!sm->ft_pending_cb || !sm->ft_pending_req_ies) + return; + + res = wpa_ft_process_auth_req(sm, wpabuf_head(sm->ft_pending_req_ies), + wpabuf_len(sm->ft_pending_req_ies), + &resp_ies, &resp_ies_len); + if (res < 0) { + /* this loop is broken by ft_pending_pull_left_retries */ + wpa_printf(MSG_DEBUG, + "FT: Callback postponed until response is available"); + return; + } + wpabuf_free(sm->ft_pending_req_ies); + sm->ft_pending_req_ies = NULL; + status = res; + wpa_printf(MSG_DEBUG, "FT: Postponed auth callback result for " MACSTR + " - status %u", MAC2STR(sm->addr), status); + + sm->ft_pending_cb(sm->ft_pending_cb_ctx, sm->addr, + sm->ft_pending_auth_transaction + 1, status, + resp_ies, resp_ies_len); + os_free(resp_ies); +} + + +struct ft_get_sta_ctx { + const u8 *nonce; + const u8 *s1kh_id; + struct wpa_state_machine *sm; +}; + + +static int ft_get_sta_cb(struct wpa_state_machine *sm, void *ctx) +{ + struct ft_get_sta_ctx *info = ctx; + + if ((info->s1kh_id && + !ether_addr_equal(info->s1kh_id, sm->addr)) || + os_memcmp(info->nonce, sm->ft_pending_pull_nonce, + FT_RRB_NONCE_LEN) != 0 || + sm->ft_pending_cb == NULL || sm->ft_pending_req_ies == NULL) + return 0; + + info->sm = sm; + + return 1; +} + + +static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + int no_defer) +{ + const char *msgtype = "pull response"; + int nak, ret = -1; + struct ft_get_sta_ctx ctx; + u8 s1kh_id[ETH_ALEN]; + const u8 *f_nonce; + size_t f_nonce_len; + + wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull response"); + + RRB_GET_AUTH(FT_RRB_NONCE, nonce, msgtype, FT_RRB_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: nonce", f_nonce, f_nonce_len); + + os_memset(&ctx, 0, sizeof(ctx)); + ctx.nonce = f_nonce; + if (!wpa_auth_for_each_sta(wpa_auth, ft_get_sta_cb, &ctx)) { + /* nonce not found */ + wpa_printf(MSG_DEBUG, "FT: Invalid nonce"); + return -1; + } + + ret = wpa_ft_rrb_rx_r1(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_RESP, + enc, enc_len, auth, auth_len, msgtype, s1kh_id, + no_defer ? NULL : &wpa_ft_rrb_rx_resp); + if (ret == -2) { + ret = 0; + nak = 1; + } else { + nak = 0; + } + if (ret < 0) + return -1; + + ctx.s1kh_id = s1kh_id; + if (wpa_auth_for_each_sta(wpa_auth, ft_get_sta_cb, &ctx)) { + wpa_printf(MSG_DEBUG, + "FT: Response to a pending pull request for " MACSTR, + MAC2STR(ctx.sm->addr)); + eloop_cancel_timeout(wpa_ft_expire_pull, ctx.sm, NULL); + if (nak) + ctx.sm->ft_pending_pull_left_retries = 0; + ft_finish_pull(ctx.sm); + } + +out: + return ret; +} + + +static int wpa_ft_rrb_rx_push(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, int no_defer) +{ + const char *msgtype = "push"; + + wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 push"); + + if (wpa_ft_rrb_rx_r1(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_PUSH, + enc, enc_len, auth, auth_len, msgtype, NULL, + no_defer ? NULL : wpa_ft_rrb_rx_push) < 0) + return -1; + + return 0; +} + + +static int wpa_ft_rrb_rx_seq(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, int type, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + struct ft_remote_seq **rkh_seq, + u8 **key, size_t *key_len, + struct ft_remote_r0kh **r0kh_out, + struct ft_remote_r1kh **r1kh_out, + struct ft_remote_r0kh **r0kh_wildcard_out, + struct ft_remote_r1kh **r1kh_wildcard_out) +{ + struct ft_remote_r0kh *r0kh = NULL; + struct ft_remote_r1kh *r1kh = NULL; + const u8 *f_r0kh_id, *f_r1kh_id; + size_t f_r0kh_id_len, f_r1kh_id_len; + int to_r0kh, to_r1kh; + u8 *plain = NULL; + size_t plain_len = 0; + struct ft_remote_r0kh *r0kh_wildcard; + struct ft_remote_r1kh *r1kh_wildcard; + + RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, "seq", -1); + RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, "seq", FT_R1KH_ID_LEN); + + to_r0kh = !wpa_ft_rrb_check_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len); + to_r1kh = !wpa_ft_rrb_check_r1kh(wpa_auth, f_r1kh_id); + + if (to_r0kh && to_r1kh) { + wpa_printf(MSG_DEBUG, "FT: seq - local R0KH-ID and R1KH-ID"); + goto out; + } + + if (!to_r0kh && !to_r1kh) { + wpa_printf(MSG_DEBUG, "FT: seq - remote R0KH-ID and R1KH-ID"); + goto out; + } + + if (!to_r0kh) { + wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len, + &r0kh, &r0kh_wildcard); + if (!r0kh_wildcard && + (!r0kh || !ether_addr_equal(r0kh->addr, src_addr))) { + wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID", + f_r0kh_id, f_r0kh_id_len); + goto out; + } + if (r0kh) { + *key = r0kh->key; + *key_len = sizeof(r0kh->key); + } else { + *key = r0kh_wildcard->key; + *key_len = sizeof(r0kh_wildcard->key); + } + } + + if (!to_r1kh) { + wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh, + &r1kh_wildcard); + if (!r1kh_wildcard && + (!r1kh || !ether_addr_equal(r1kh->addr, src_addr))) { + wpa_hexdump(MSG_DEBUG, "FT: Did not find R1KH-ID", + f_r1kh_id, FT_R1KH_ID_LEN); + goto out; + } + if (r1kh) { + *key = r1kh->key; + *key_len = sizeof(r1kh->key); + } else { + *key = r1kh_wildcard->key; + *key_len = sizeof(r1kh_wildcard->key); + } + } + + if (wpa_ft_rrb_decrypt(*key, *key_len, enc, enc_len, auth, auth_len, + src_addr, type, &plain, &plain_len) < 0) + goto out; + + os_free(plain); + + if (!to_r0kh) { + if (!r0kh) + r0kh = wpa_ft_rrb_add_r0kh(wpa_auth, r0kh_wildcard, + src_addr, f_r0kh_id, + f_r0kh_id_len, + ftRRBseqTimeout); + if (!r0kh) + goto out; + + wpa_ft_rrb_r0kh_replenish(wpa_auth, r0kh, ftRRBseqTimeout); + *rkh_seq = r0kh->seq; + if (r0kh_out) + *r0kh_out = r0kh; + if (r0kh_wildcard_out) + *r0kh_wildcard_out = r0kh_wildcard; + } + + if (!to_r1kh) { + if (!r1kh) + r1kh = wpa_ft_rrb_add_r1kh(wpa_auth, r1kh_wildcard, + src_addr, f_r1kh_id, + ftRRBseqTimeout); + if (!r1kh) + goto out; + + wpa_ft_rrb_r1kh_replenish(wpa_auth, r1kh, ftRRBseqTimeout); + *rkh_seq = r1kh->seq; + if (r1kh_out) + *r1kh_out = r1kh; + if (r1kh_wildcard_out) + *r1kh_wildcard_out = r1kh_wildcard; + } + + return 0; +out: + return -1; +} + + +static int wpa_ft_rrb_rx_seq_req(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + int no_defer) +{ + int ret = -1; + struct ft_rrb_seq f_seq; + const u8 *f_nonce, *f_r0kh_id, *f_r1kh_id; + size_t f_nonce_len, f_r0kh_id_len, f_r1kh_id_len; + struct ft_remote_seq *rkh_seq = NULL; + u8 *packet = NULL, *key = NULL; + size_t packet_len = 0, key_len = 0; + struct tlv_list seq_resp_auth[5]; + + wpa_printf(MSG_DEBUG, "FT: Received sequence number request"); + + if (wpa_ft_rrb_rx_seq(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_REQ, + enc, enc_len, auth, auth_len, &rkh_seq, &key, + &key_len, NULL, NULL, NULL, NULL) < 0) + goto out; + + RRB_GET_AUTH(FT_RRB_NONCE, nonce, "seq request", FT_RRB_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: seq request - nonce", f_nonce, f_nonce_len); + + RRB_GET_AUTH(FT_RRB_R0KH_ID, r0kh_id, "seq", -1); + RRB_GET_AUTH(FT_RRB_R1KH_ID, r1kh_id, "seq", FT_R1KH_ID_LEN); + + if (wpa_ft_new_seq(rkh_seq, &f_seq) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to get seq num"); + goto out; + } + + wpa_printf(MSG_DEBUG, "FT: Send sequence number response from " MACSTR + " to " MACSTR, + MAC2STR(wpa_auth->addr), MAC2STR(src_addr)); + + seq_resp_auth[0].type = FT_RRB_NONCE; + seq_resp_auth[0].len = f_nonce_len; + seq_resp_auth[0].data = f_nonce; + seq_resp_auth[1].type = FT_RRB_SEQ; + seq_resp_auth[1].len = sizeof(f_seq); + seq_resp_auth[1].data = (u8 *) &f_seq; + seq_resp_auth[2].type = FT_RRB_R0KH_ID; + seq_resp_auth[2].len = f_r0kh_id_len; + seq_resp_auth[2].data = f_r0kh_id; + seq_resp_auth[3].type = FT_RRB_R1KH_ID; + seq_resp_auth[3].len = FT_R1KH_ID_LEN; + seq_resp_auth[3].data = f_r1kh_id; + seq_resp_auth[4].type = FT_RRB_LAST_EMPTY; + seq_resp_auth[4].len = 0; + seq_resp_auth[4].data = NULL; + + if (wpa_ft_rrb_build(key, key_len, NULL, NULL, seq_resp_auth, NULL, + wpa_auth->addr, FT_PACKET_R0KH_R1KH_SEQ_RESP, + &packet, &packet_len) < 0) + goto out; + + wpa_ft_rrb_oui_send(wpa_auth, src_addr, + FT_PACKET_R0KH_R1KH_SEQ_RESP, packet, + packet_len); + +out: + os_free(packet); + + return ret; +} + + +static int wpa_ft_rrb_rx_seq_resp(struct wpa_authenticator *wpa_auth, + const u8 *src_addr, + const u8 *enc, size_t enc_len, + const u8 *auth, size_t auth_len, + int no_defer) +{ + u8 *key = NULL; + size_t key_len = 0; + struct ft_remote_r0kh *r0kh = NULL, *r0kh_wildcard = NULL; + struct ft_remote_r1kh *r1kh = NULL, *r1kh_wildcard = NULL; + const u8 *f_nonce, *f_seq; + size_t f_nonce_len, f_seq_len; + struct ft_remote_seq *rkh_seq = NULL; + struct ft_remote_item *item; + struct os_reltime now, now_remote; + int seq_ret, found; + const struct ft_rrb_seq *msg_both; + u32 msg_dom, msg_seq; + + wpa_printf(MSG_DEBUG, "FT: Received sequence number response"); + + if (wpa_ft_rrb_rx_seq(wpa_auth, src_addr, FT_PACKET_R0KH_R1KH_SEQ_RESP, + enc, enc_len, auth, auth_len, &rkh_seq, &key, + &key_len, &r0kh, &r1kh, &r0kh_wildcard, + &r1kh_wildcard) < 0) + goto out; + + RRB_GET_AUTH(FT_RRB_NONCE, nonce, "seq response", FT_RRB_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "FT: seq response - nonce", f_nonce, + f_nonce_len); + + found = 0; + dl_list_for_each(item, &rkh_seq->rx.queue, struct ft_remote_item, + list) { + if (os_memcmp_const(f_nonce, item->nonce, + FT_RRB_NONCE_LEN) != 0 || + os_get_reltime(&now) < 0 || + os_reltime_expired(&now, &item->nonce_ts, ftRRBseqTimeout)) + continue; + + found = 1; + break; + } + if (!found) { + wpa_printf(MSG_DEBUG, "FT: seq response - bad nonce"); + goto out; + } + + if (r0kh) { + wpa_ft_rrb_r0kh_replenish(wpa_auth, r0kh, + wpa_auth->conf.rkh_pos_timeout); + if (r0kh_wildcard) + os_memcpy(r0kh->addr, src_addr, ETH_ALEN); + } + + if (r1kh) { + wpa_ft_rrb_r1kh_replenish(wpa_auth, r1kh, + wpa_auth->conf.rkh_pos_timeout); + if (r1kh_wildcard) + os_memcpy(r1kh->addr, src_addr, ETH_ALEN); + } + + seq_ret = wpa_ft_rrb_seq_chk(rkh_seq, src_addr, enc, enc_len, auth, + auth_len, "seq response", 1); + if (seq_ret == FT_RRB_SEQ_OK) { + wpa_printf(MSG_DEBUG, "FT: seq response - valid seq number"); + wpa_ft_rrb_seq_accept(wpa_auth, rkh_seq, src_addr, auth, + auth_len, "seq response"); + } else { + wpa_printf(MSG_DEBUG, "FT: seq response - reset seq number"); + + RRB_GET_AUTH(FT_RRB_SEQ, seq, "seq response", + sizeof(*msg_both)); + msg_both = (const struct ft_rrb_seq *) f_seq; + + msg_dom = le_to_host32(msg_both->dom); + msg_seq = le_to_host32(msg_both->seq); + now_remote.sec = le_to_host32(msg_both->ts); + now_remote.usec = 0; + + rkh_seq->rx.num_last = 2; + rkh_seq->rx.dom = msg_dom; + rkh_seq->rx.offsetidx = 0; + /* Accept some older, possibly cached packets as well */ + rkh_seq->rx.last[0] = msg_seq - FT_REMOTE_SEQ_BACKLOG - + dl_list_len(&rkh_seq->rx.queue); + rkh_seq->rx.last[1] = msg_seq; + + /* local time - offset = remote time + * <=> local time - remote time = offset */ + os_reltime_sub(&now, &now_remote, &rkh_seq->rx.time_offset); + } + + wpa_ft_rrb_seq_flush(wpa_auth, rkh_seq, 1); + + return 0; +out: + return -1; +} + + +int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr, + const u8 *data, size_t data_len) +{ + struct ft_rrb_frame *frame; + u16 alen; + const u8 *pos, *end, *start; + u8 action; + const u8 *sta_addr, *target_ap_addr; + + wpa_printf(MSG_DEBUG, "FT: RRB received frame from remote AP " MACSTR, + MAC2STR(src_addr)); + + if (data_len < sizeof(*frame)) { + wpa_printf(MSG_DEBUG, "FT: Too short RRB frame (data_len=%lu)", + (unsigned long) data_len); + return -1; + } + + pos = data; + frame = (struct ft_rrb_frame *) pos; + pos += sizeof(*frame); + + alen = le_to_host16(frame->action_length); + wpa_printf(MSG_DEBUG, "FT: RRB frame - frame_type=%d packet_type=%d " + "action_length=%d ap_address=" MACSTR, + frame->frame_type, frame->packet_type, alen, + MAC2STR(frame->ap_address)); + + if (frame->frame_type != RSN_REMOTE_FRAME_TYPE_FT_RRB) { + /* Discard frame per IEEE Std 802.11r-2008, 11A.10.3 */ + wpa_printf(MSG_DEBUG, "FT: RRB discarded frame with " + "unrecognized type %d", frame->frame_type); + return -1; + } + + if (alen > data_len - sizeof(*frame)) { + wpa_printf(MSG_DEBUG, "FT: RRB frame too short for action " + "frame"); + return -1; + } + + wpa_hexdump(MSG_MSGDUMP, "FT: RRB - FT Action frame", pos, alen); + + if (alen < 1 + 1 + 2 * ETH_ALEN) { + wpa_printf(MSG_DEBUG, "FT: Too short RRB frame (not enough " + "room for Action Frame body); alen=%lu", + (unsigned long) alen); + return -1; + } + start = pos; + end = pos + alen; + + if (*pos != WLAN_ACTION_FT) { + wpa_printf(MSG_DEBUG, "FT: Unexpected Action frame category " + "%d", *pos); + return -1; + } + + pos++; + action = *pos++; + sta_addr = pos; + pos += ETH_ALEN; + target_ap_addr = pos; + pos += ETH_ALEN; + wpa_printf(MSG_DEBUG, "FT: RRB Action Frame: action=%d sta_addr=" + MACSTR " target_ap_addr=" MACSTR, + action, MAC2STR(sta_addr), MAC2STR(target_ap_addr)); + + if (frame->packet_type == FT_PACKET_REQUEST) { + wpa_printf(MSG_DEBUG, "FT: FT Packet Type - Request"); + + if (action != 1) { + wpa_printf(MSG_DEBUG, "FT: Unexpected Action %d in " + "RRB Request", action); + return -1; + } + + if (!ether_addr_equal(target_ap_addr, wpa_auth->addr)) { + wpa_printf(MSG_DEBUG, "FT: Target AP address in the " + "RRB Request does not match with own " + "address"); + return -1; + } + + if (wpa_ft_rrb_rx_request(wpa_auth, frame->ap_address, + sta_addr, pos, end - pos) < 0) + return -1; + } else if (frame->packet_type == FT_PACKET_RESPONSE) { + u16 status_code; + + if (end - pos < 2) { + wpa_printf(MSG_DEBUG, "FT: Not enough room for status " + "code in RRB Response"); + return -1; + } + status_code = WPA_GET_LE16(pos); + + wpa_printf(MSG_DEBUG, "FT: FT Packet Type - Response " + "(status_code=%d)", status_code); + + if (wpa_ft_action_send(wpa_auth, sta_addr, start, alen) < 0) + return -1; + } else { + wpa_printf(MSG_DEBUG, "FT: RRB discarded frame with unknown " + "packet_type %d", frame->packet_type); + return -1; + } + + return 0; +} + + +void wpa_ft_rrb_oui_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr, + const u8 *dst_addr, u8 oui_suffix, const u8 *data, + size_t data_len) +{ + const u8 *auth, *enc; + size_t alen, elen; + int no_defer = 0; + + wpa_printf(MSG_DEBUG, "FT: RRB-OUI(" MACSTR + ") received frame from remote AP " + MACSTR " oui_suffix=%u dst=" MACSTR, + MAC2STR(wpa_auth->addr), MAC2STR(src_addr), oui_suffix, + MAC2STR(dst_addr)); + wpa_hexdump(MSG_MSGDUMP, "FT: RRB frame payload", data, data_len); + + if (is_multicast_ether_addr(src_addr)) { + wpa_printf(MSG_DEBUG, + "FT: RRB-OUI received frame from multicast address " + MACSTR, MAC2STR(src_addr)); + return; + } + + if (is_multicast_ether_addr(dst_addr)) + no_defer = 1; + + if (data_len < sizeof(u16)) { + wpa_printf(MSG_DEBUG, "FT: RRB-OUI frame too short"); + return; + } + + alen = WPA_GET_LE16(data); + if (data_len < sizeof(u16) + alen) { + wpa_printf(MSG_DEBUG, "FT: RRB-OUI frame too short"); + return; + } + + auth = data + sizeof(u16); + wpa_hexdump(MSG_MSGDUMP, "FT: Authenticated payload", auth, alen); + enc = data + sizeof(u16) + alen; + elen = data_len - sizeof(u16) - alen; + wpa_hexdump(MSG_MSGDUMP, "FT: Encrypted payload", enc, elen); + + switch (oui_suffix) { + case FT_PACKET_R0KH_R1KH_PULL: + wpa_ft_rrb_rx_pull(wpa_auth, src_addr, enc, elen, auth, alen, + no_defer); + break; + case FT_PACKET_R0KH_R1KH_RESP: + wpa_ft_rrb_rx_resp(wpa_auth, src_addr, enc, elen, auth, alen, + no_defer); + break; + case FT_PACKET_R0KH_R1KH_PUSH: + wpa_ft_rrb_rx_push(wpa_auth, src_addr, enc, elen, auth, alen, + no_defer); + break; + case FT_PACKET_R0KH_R1KH_SEQ_REQ: + wpa_ft_rrb_rx_seq_req(wpa_auth, src_addr, enc, elen, auth, alen, + no_defer); + break; + case FT_PACKET_R0KH_R1KH_SEQ_RESP: + wpa_ft_rrb_rx_seq_resp(wpa_auth, src_addr, enc, elen, auth, + alen, no_defer); + break; + } +} + + +static int wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth, + struct wpa_ft_pmk_r0_sa *pmk_r0, + struct ft_remote_r1kh *r1kh, + const u8 *s1kh_id) +{ + u8 *packet; + size_t packet_len; + struct ft_rrb_seq f_seq; + struct tlv_list push[] = { + { .type = FT_RRB_S1KH_ID, .len = ETH_ALEN, + .data = s1kh_id }, + { .type = FT_RRB_PMK_R0_NAME, .len = WPA_PMK_NAME_LEN, + .data = pmk_r0->pmk_r0_name }, + { .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL }, + }; + struct tlv_list push_auth[] = { + { .type = FT_RRB_SEQ, .len = sizeof(f_seq), + .data = (u8 *) &f_seq }, + { .type = FT_RRB_R0KH_ID, + .len = wpa_auth->conf.r0_key_holder_len, + .data = wpa_auth->conf.r0_key_holder }, + { .type = FT_RRB_R1KH_ID, .len = FT_R1KH_ID_LEN, + .data = r1kh->id }, + { .type = FT_RRB_LAST_EMPTY, .len = 0, .data = NULL }, + }; + + if (wpa_ft_new_seq(r1kh->seq, &f_seq) < 0) { + wpa_printf(MSG_DEBUG, "FT: Failed to get seq num"); + return -1; + } + + wpa_printf(MSG_DEBUG, "FT: Send PMK-R1 push from " MACSTR + " to remote R0KH address " MACSTR, + MAC2STR(wpa_auth->addr), MAC2STR(r1kh->addr)); + + if (wpa_ft_rrb_build_r0(r1kh->key, sizeof(r1kh->key), push, pmk_r0, + r1kh->id, s1kh_id, push_auth, wpa_auth->addr, + FT_PACKET_R0KH_R1KH_PUSH, + &packet, &packet_len) < 0) + return -1; + + wpa_ft_rrb_oui_send(wpa_auth, r1kh->addr, FT_PACKET_R0KH_R1KH_PUSH, + packet, packet_len); + + os_free(packet); + return 0; +} + + +void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr) +{ + struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache; + struct wpa_ft_pmk_r0_sa *r0, *r0found = NULL; + struct ft_remote_r1kh *r1kh; + + if (!wpa_auth->conf.pmk_r1_push) + return; + if (!wpa_auth->conf.r1kh_list) + return; + + dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) { + if (ether_addr_equal(r0->spa, addr)) { + r0found = r0; + break; + } + } + + r0 = r0found; + if (r0 == NULL || r0->pmk_r1_pushed) + return; + r0->pmk_r1_pushed = 1; + + wpa_printf(MSG_DEBUG, "FT: Deriving and pushing PMK-R1 keys to R1KHs " + "for STA " MACSTR, MAC2STR(addr)); + + for (r1kh = *wpa_auth->conf.r1kh_list; r1kh; r1kh = r1kh->next) { + if (is_zero_ether_addr(r1kh->addr) || + is_zero_ether_addr(r1kh->id)) + continue; + if (wpa_ft_rrb_init_r1kh_seq(r1kh) < 0) + continue; + wpa_ft_generate_pmk_r1(wpa_auth, r0, r1kh, addr); + } +} + +#endif /* CONFIG_IEEE80211R_AP */ diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c new file mode 100644 index 0000000..e725615 --- /dev/null +++ b/src/ap/wpa_auth_glue.c @@ -0,0 +1,1837 @@ +/* + * hostapd / WPA authenticator glue code + * Copyright (c) 2002-2022, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "utils/list.h" +#include "common/ieee802_11_defs.h" +#include "common/sae.h" +#include "common/wpa_ctrl.h" +#include "common/ptksa_cache.h" +#include "crypto/sha1.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "eapol_auth/eapol_auth_sm_i.h" +#include "eap_server/eap.h" +#include "l2_packet/l2_packet.h" +#include "eth_p_oui.h" +#include "hostapd.h" +#include "ieee802_1x.h" +#include "preauth_auth.h" +#include "sta_info.h" +#include "tkip_countermeasures.h" +#include "ap_drv_ops.h" +#include "ap_config.h" +#include "ieee802_11.h" +#include "ieee802_11_auth.h" +#include "pmksa_cache_auth.h" +#include "wpa_auth.h" +#include "wpa_auth_glue.h" + + +static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf, + struct hostapd_config *iconf, + struct wpa_auth_config *wconf) +{ + int sae_pw_id; + + os_memset(wconf, 0, sizeof(*wconf)); + wconf->wpa = conf->wpa; + wconf->extended_key_id = conf->extended_key_id; + wconf->wpa_key_mgmt = conf->wpa_key_mgmt; + wconf->wpa_pairwise = conf->wpa_pairwise; + wconf->wpa_group = conf->wpa_group; + wconf->wpa_group_rekey = conf->wpa_group_rekey; + wconf->wpa_strict_rekey = conf->wpa_strict_rekey; + wconf->wpa_gmk_rekey = conf->wpa_gmk_rekey; + wconf->wpa_ptk_rekey = conf->wpa_ptk_rekey; + wconf->wpa_group_update_count = conf->wpa_group_update_count; + wconf->wpa_disable_eapol_key_retries = + conf->wpa_disable_eapol_key_retries; + wconf->wpa_pairwise_update_count = conf->wpa_pairwise_update_count; + wconf->rsn_pairwise = conf->rsn_pairwise; + wconf->rsn_preauth = conf->rsn_preauth; + wconf->eapol_version = conf->eapol_version; +#ifdef CONFIG_MACSEC + if (wconf->eapol_version > 2) + wconf->eapol_version = 2; +#endif /* CONFIG_MACSEC */ + wconf->wmm_enabled = conf->wmm_enabled; + wconf->wmm_uapsd = conf->wmm_uapsd; + wconf->disable_pmksa_caching = conf->disable_pmksa_caching; +#ifdef CONFIG_OCV + wconf->ocv = conf->ocv; +#endif /* CONFIG_OCV */ + wconf->okc = conf->okc; + wconf->ieee80211w = conf->ieee80211w; + wconf->beacon_prot = conf->beacon_prot; + wconf->group_mgmt_cipher = conf->group_mgmt_cipher; + wconf->sae_require_mfp = conf->sae_require_mfp; + wconf->ssid_protection = conf->ssid_protection; + wconf->ssid_len = conf->ssid.ssid_len; + if (wconf->ssid_len > SSID_MAX_LEN) + wconf->ssid_len = SSID_MAX_LEN; + os_memcpy(wconf->ssid, conf->ssid.ssid, wconf->ssid_len); +#ifdef CONFIG_IEEE80211R_AP + os_memcpy(wconf->mobility_domain, conf->mobility_domain, + MOBILITY_DOMAIN_ID_LEN); + if (conf->nas_identifier && + os_strlen(conf->nas_identifier) <= FT_R0KH_ID_MAX_LEN) { + wconf->r0_key_holder_len = os_strlen(conf->nas_identifier); + os_memcpy(wconf->r0_key_holder, conf->nas_identifier, + wconf->r0_key_holder_len); + } + os_memcpy(wconf->r1_key_holder, conf->r1_key_holder, FT_R1KH_ID_LEN); + wconf->r0_key_lifetime = conf->r0_key_lifetime; + wconf->r1_max_key_lifetime = conf->r1_max_key_lifetime; + wconf->reassociation_deadline = conf->reassociation_deadline; + wconf->rkh_pos_timeout = conf->rkh_pos_timeout; + wconf->rkh_neg_timeout = conf->rkh_neg_timeout; + wconf->rkh_pull_timeout = conf->rkh_pull_timeout; + wconf->rkh_pull_retries = conf->rkh_pull_retries; + wconf->r0kh_list = &conf->r0kh_list; + wconf->r1kh_list = &conf->r1kh_list; + wconf->pmk_r1_push = conf->pmk_r1_push; + wconf->ft_over_ds = conf->ft_over_ds; + wconf->ft_psk_generate_local = conf->ft_psk_generate_local; +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_HS20 + wconf->disable_gtk = conf->disable_dgaf; + if (conf->osen) { + wconf->disable_gtk = 1; + wconf->wpa = WPA_PROTO_OSEN; + wconf->wpa_key_mgmt = WPA_KEY_MGMT_OSEN; + wconf->wpa_pairwise = 0; + wconf->wpa_group = WPA_CIPHER_CCMP; + wconf->rsn_pairwise = WPA_CIPHER_CCMP; + wconf->rsn_preauth = 0; + wconf->disable_pmksa_caching = 1; + wconf->ieee80211w = 1; + } +#endif /* CONFIG_HS20 */ +#ifdef CONFIG_TESTING_OPTIONS + wconf->corrupt_gtk_rekey_mic_probability = + iconf->corrupt_gtk_rekey_mic_probability; + wconf->delay_eapol_tx = iconf->delay_eapol_tx; + if (conf->own_ie_override && + wpabuf_len(conf->own_ie_override) <= MAX_OWN_IE_OVERRIDE) { + wconf->own_ie_override_len = wpabuf_len(conf->own_ie_override); + os_memcpy(wconf->own_ie_override, + wpabuf_head(conf->own_ie_override), + wconf->own_ie_override_len); + } + if (conf->rsne_override_eapol && + wpabuf_len(conf->rsne_override_eapol) <= MAX_OWN_IE_OVERRIDE) { + wconf->rsne_override_eapol_set = 1; + wconf->rsne_override_eapol_len = + wpabuf_len(conf->rsne_override_eapol); + os_memcpy(wconf->rsne_override_eapol, + wpabuf_head(conf->rsne_override_eapol), + wconf->rsne_override_eapol_len); + } + if (conf->rsnxe_override_eapol && + wpabuf_len(conf->rsnxe_override_eapol) <= MAX_OWN_IE_OVERRIDE) { + wconf->rsnxe_override_eapol_set = 1; + wconf->rsnxe_override_eapol_len = + wpabuf_len(conf->rsnxe_override_eapol); + os_memcpy(wconf->rsnxe_override_eapol, + wpabuf_head(conf->rsnxe_override_eapol), + wconf->rsnxe_override_eapol_len); + } + if (conf->rsne_override_ft && + wpabuf_len(conf->rsne_override_ft) <= MAX_OWN_IE_OVERRIDE) { + wconf->rsne_override_ft_set = 1; + wconf->rsne_override_ft_len = + wpabuf_len(conf->rsne_override_ft); + os_memcpy(wconf->rsne_override_ft, + wpabuf_head(conf->rsne_override_ft), + wconf->rsne_override_ft_len); + } + if (conf->rsnxe_override_ft && + wpabuf_len(conf->rsnxe_override_ft) <= MAX_OWN_IE_OVERRIDE) { + wconf->rsnxe_override_ft_set = 1; + wconf->rsnxe_override_ft_len = + wpabuf_len(conf->rsnxe_override_ft); + os_memcpy(wconf->rsnxe_override_ft, + wpabuf_head(conf->rsnxe_override_ft), + wconf->rsnxe_override_ft_len); + } + if (conf->gtk_rsc_override && + wpabuf_len(conf->gtk_rsc_override) > 0 && + wpabuf_len(conf->gtk_rsc_override) <= WPA_KEY_RSC_LEN) { + os_memcpy(wconf->gtk_rsc_override, + wpabuf_head(conf->gtk_rsc_override), + wpabuf_len(conf->gtk_rsc_override)); + wconf->gtk_rsc_override_set = 1; + } + if (conf->igtk_rsc_override && + wpabuf_len(conf->igtk_rsc_override) > 0 && + wpabuf_len(conf->igtk_rsc_override) <= WPA_KEY_RSC_LEN) { + os_memcpy(wconf->igtk_rsc_override, + wpabuf_head(conf->igtk_rsc_override), + wpabuf_len(conf->igtk_rsc_override)); + wconf->igtk_rsc_override_set = 1; + } + wconf->ft_rsnxe_used = conf->ft_rsnxe_used; + wconf->oci_freq_override_eapol_m3 = conf->oci_freq_override_eapol_m3; + wconf->oci_freq_override_eapol_g1 = conf->oci_freq_override_eapol_g1; + wconf->oci_freq_override_ft_assoc = conf->oci_freq_override_ft_assoc; + wconf->oci_freq_override_fils_assoc = + conf->oci_freq_override_fils_assoc; + + if (conf->eapol_m1_elements) + wconf->eapol_m1_elements = wpabuf_dup(conf->eapol_m1_elements); + if (conf->eapol_m3_elements) + wconf->eapol_m3_elements = wpabuf_dup(conf->eapol_m3_elements); + wconf->eapol_m3_no_encrypt = conf->eapol_m3_no_encrypt; +#endif /* CONFIG_TESTING_OPTIONS */ +#ifdef CONFIG_P2P + os_memcpy(wconf->ip_addr_go, conf->ip_addr_go, 4); + os_memcpy(wconf->ip_addr_mask, conf->ip_addr_mask, 4); + os_memcpy(wconf->ip_addr_start, conf->ip_addr_start, 4); + os_memcpy(wconf->ip_addr_end, conf->ip_addr_end, 4); +#endif /* CONFIG_P2P */ +#ifdef CONFIG_FILS + wconf->fils_cache_id_set = conf->fils_cache_id_set; + os_memcpy(wconf->fils_cache_id, conf->fils_cache_id, + FILS_CACHE_ID_LEN); +#endif /* CONFIG_FILS */ + wconf->sae_pwe = conf->sae_pwe; + sae_pw_id = hostapd_sae_pw_id_in_use(conf); + if (sae_pw_id == 2 && wconf->sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK) + wconf->sae_pwe = SAE_PWE_HASH_TO_ELEMENT; + else if (sae_pw_id == 1 && wconf->sae_pwe == SAE_PWE_HUNT_AND_PECK) + wconf->sae_pwe = SAE_PWE_BOTH; +#ifdef CONFIG_SAE_PK + wconf->sae_pk = hostapd_sae_pk_in_use(conf); +#endif /* CONFIG_SAE_PK */ +#ifdef CONFIG_OWE + wconf->owe_ptk_workaround = conf->owe_ptk_workaround; +#endif /* CONFIG_OWE */ + wconf->transition_disable = conf->transition_disable; +#ifdef CONFIG_DPP2 + wconf->dpp_pfs = conf->dpp_pfs; +#endif /* CONFIG_DPP2 */ +#ifdef CONFIG_PASN +#ifdef CONFIG_TESTING_OPTIONS + wconf->force_kdk_derivation = conf->force_kdk_derivation; +#endif /* CONFIG_TESTING_OPTIONS */ +#endif /* CONFIG_PASN */ + + wconf->radius_psk = conf->wpa_psk_radius == PSK_RADIUS_DURING_4WAY_HS; + wconf->no_disconnect_on_group_keyerror = + conf->bss_max_idle && conf->ap_max_inactivity && + conf->no_disconnect_on_group_keyerror; +} + + +static void hostapd_wpa_auth_logger(void *ctx, const u8 *addr, + logger_level level, const char *txt) +{ +#ifndef CONFIG_NO_HOSTAPD_LOGGER + struct hostapd_data *hapd = ctx; + int hlevel; + + switch (level) { + case LOGGER_WARNING: + hlevel = HOSTAPD_LEVEL_WARNING; + break; + case LOGGER_INFO: + hlevel = HOSTAPD_LEVEL_INFO; + break; + case LOGGER_DEBUG: + default: + hlevel = HOSTAPD_LEVEL_DEBUG; + break; + } + + hostapd_logger(hapd, addr, HOSTAPD_MODULE_WPA, hlevel, "%s", txt); +#endif /* CONFIG_NO_HOSTAPD_LOGGER */ +} + + +static void hostapd_wpa_auth_disconnect(void *ctx, const u8 *addr, + u16 reason) +{ + struct hostapd_data *hapd = ctx; + wpa_printf(MSG_DEBUG, "%s: WPA authenticator requests disconnect: " + "STA " MACSTR " reason %d", + __func__, MAC2STR(addr), reason); + ap_sta_disconnect(hapd, NULL, addr, reason); +} + + +static int hostapd_wpa_auth_mic_failure_report(void *ctx, const u8 *addr) +{ + struct hostapd_data *hapd = ctx; + return michael_mic_failure(hapd, addr, 0); +} + + +static void hostapd_wpa_auth_psk_failure_report(void *ctx, const u8 *addr) +{ + struct hostapd_data *hapd = ctx; + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_POSSIBLE_PSK_MISMATCH MACSTR, + MAC2STR(addr)); +} + + +static void hostapd_wpa_auth_set_eapol(void *ctx, const u8 *addr, + wpa_eapol_variable var, int value) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta = ap_get_sta(hapd, addr); + if (sta == NULL) + return; + switch (var) { + case WPA_EAPOL_portEnabled: + ieee802_1x_notify_port_enabled(sta->eapol_sm, value); + break; + case WPA_EAPOL_portValid: + ieee802_1x_notify_port_valid(sta->eapol_sm, value); + break; + case WPA_EAPOL_authorized: + ieee802_1x_set_sta_authorized(hapd, sta, value); + break; + case WPA_EAPOL_portControl_Auto: + if (sta->eapol_sm) + sta->eapol_sm->portControl = Auto; + break; + case WPA_EAPOL_keyRun: + if (sta->eapol_sm) + sta->eapol_sm->keyRun = value; + break; + case WPA_EAPOL_keyAvailable: + if (sta->eapol_sm) + sta->eapol_sm->eap_if->eapKeyAvailable = value; + break; + case WPA_EAPOL_keyDone: + if (sta->eapol_sm) + sta->eapol_sm->keyDone = value; + break; + case WPA_EAPOL_inc_EapolFramesTx: + if (sta->eapol_sm) + sta->eapol_sm->dot1xAuthEapolFramesTx++; + break; + } +} + + +static int hostapd_wpa_auth_get_eapol(void *ctx, const u8 *addr, + wpa_eapol_variable var) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta = ap_get_sta(hapd, addr); + if (sta == NULL || sta->eapol_sm == NULL) + return -1; + switch (var) { + case WPA_EAPOL_keyRun: + return sta->eapol_sm->keyRun; + case WPA_EAPOL_keyAvailable: + return sta->eapol_sm->eap_if->eapKeyAvailable; + default: + return -1; + } +} + + +static const u8 * hostapd_wpa_auth_get_psk(void *ctx, const u8 *addr, + const u8 *p2p_dev_addr, + const u8 *prev_psk, size_t *psk_len, + int *vlan_id) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta = ap_get_sta(hapd, addr); + const u8 *psk; + + if (vlan_id) + *vlan_id = 0; + if (psk_len) + *psk_len = PMK_LEN; + +#ifdef CONFIG_SAE + if (sta && sta->auth_alg == WLAN_AUTH_SAE) { + if (!sta->sae || prev_psk) + return NULL; + if (psk_len) + *psk_len = sta->sae->pmk_len; + return sta->sae->pmk; + } + if (sta && wpa_auth_uses_sae(sta->wpa_sm)) { + wpa_printf(MSG_DEBUG, + "No PSK for STA trying to use SAE with PMKSA caching"); + return NULL; + } +#endif /* CONFIG_SAE */ + +#ifdef CONFIG_OWE + if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) && + sta && sta->owe_pmk) { + if (psk_len) + *psk_len = sta->owe_pmk_len; + return sta->owe_pmk; + } + if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) && sta) { + struct rsn_pmksa_cache_entry *sa; + + sa = wpa_auth_sta_get_pmksa(sta->wpa_sm); + if (sa && sa->akmp == WPA_KEY_MGMT_OWE) { + if (psk_len) + *psk_len = sa->pmk_len; + return sa->pmk; + } + } +#endif /* CONFIG_OWE */ + + psk = hostapd_get_psk(hapd->conf, addr, p2p_dev_addr, prev_psk, + vlan_id); + /* + * This is about to iterate over all psks, prev_psk gives the last + * returned psk which should not be returned again. + * logic list (all hostapd_get_psk; all sta->psk) + */ + if (sta && sta->psk && !psk) { + struct hostapd_sta_wpa_psk_short *pos; + + if (vlan_id) + *vlan_id = 0; + psk = sta->psk->psk; + for (pos = sta->psk; pos; pos = pos->next) { + if (pos->is_passphrase) { + if (pbkdf2_sha1(pos->passphrase, + hapd->conf->ssid.ssid, + hapd->conf->ssid.ssid_len, 4096, + pos->psk, PMK_LEN) != 0) { + wpa_printf(MSG_WARNING, + "Error in pbkdf2_sha1()"); + continue; + } + pos->is_passphrase = 0; + } + if (pos->psk == prev_psk) { + psk = pos->next ? pos->next->psk : NULL; + break; + } + } + } + return psk; +} + + +static int hostapd_wpa_auth_get_msk(void *ctx, const u8 *addr, u8 *msk, + size_t *len) +{ + struct hostapd_data *hapd = ctx; + const u8 *key; + size_t keylen; + struct sta_info *sta; + + sta = ap_get_sta(hapd, addr); + if (sta == NULL) { + wpa_printf(MSG_DEBUG, "AUTH_GET_MSK: Cannot find STA"); + return -1; + } + + key = ieee802_1x_get_key(sta->eapol_sm, &keylen); + if (key == NULL) { + wpa_printf(MSG_DEBUG, "AUTH_GET_MSK: Key is null, eapol_sm: %p", + sta->eapol_sm); + return -1; + } + + if (keylen > *len) + keylen = *len; + os_memcpy(msk, key, keylen); + *len = keylen; + + return 0; +} + + +static int hostapd_wpa_auth_set_key(void *ctx, int vlan_id, enum wpa_alg alg, + const u8 *addr, int idx, u8 *key, + size_t key_len, enum key_flag key_flag) +{ + struct hostapd_data *hapd = ctx; + const char *ifname = hapd->conf->iface; + + if (vlan_id > 0) { + ifname = hostapd_get_vlan_id_ifname(hapd->conf->vlan, vlan_id); + if (!ifname) { + if (!(hapd->iface->drv_flags & + WPA_DRIVER_FLAGS_VLAN_OFFLOAD)) + return -1; + ifname = hapd->conf->iface; + } + } + +#ifdef CONFIG_TESTING_OPTIONS + if (key_flag & KEY_FLAG_MODIFY) { + /* We are updating an already installed key. Don't overwrite + * the already stored key information with zeros. + */ + } else if (addr && !is_broadcast_ether_addr(addr)) { + struct sta_info *sta; + + sta = ap_get_sta(hapd, addr); + if (sta) { + sta->last_tk_alg = alg; + sta->last_tk_key_idx = idx; + if (key) + os_memcpy(sta->last_tk, key, key_len); + sta->last_tk_len = key_len; + } + } else if (alg == WPA_ALG_BIP_CMAC_128 || + alg == WPA_ALG_BIP_GMAC_128 || + alg == WPA_ALG_BIP_GMAC_256 || + alg == WPA_ALG_BIP_CMAC_256) { + if (idx == 4 || idx == 5) { + hapd->last_igtk_alg = alg; + hapd->last_igtk_key_idx = idx; + if (key) + os_memcpy(hapd->last_igtk, key, key_len); + hapd->last_igtk_len = key_len; + } else if (idx == 6 || idx == 7) { + hapd->last_bigtk_alg = alg; + hapd->last_bigtk_key_idx = idx; + if (key) + os_memcpy(hapd->last_bigtk, key, key_len); + hapd->last_bigtk_len = key_len; + } + } else { + hapd->last_gtk_alg = alg; + hapd->last_gtk_key_idx = idx; + if (key) + os_memcpy(hapd->last_gtk, key, key_len); + hapd->last_gtk_len = key_len; + } +#endif /* CONFIG_TESTING_OPTIONS */ + return hostapd_drv_set_key(ifname, hapd, alg, addr, idx, vlan_id, 1, + NULL, 0, key, key_len, key_flag); +} + + +static int hostapd_wpa_auth_get_seqnum(void *ctx, const u8 *addr, int idx, + u8 *seq) +{ + struct hostapd_data *hapd = ctx; + int link_id = -1; + +#ifdef CONFIG_IEEE80211BE + if (hapd->conf->mld_ap && idx) + link_id = hapd->mld_link_id; +#endif /* CONFIG_IEEE80211BE */ + return hostapd_get_seqnum(hapd->conf->iface, hapd, addr, idx, link_id, + seq); +} + + +int hostapd_wpa_auth_send_eapol(void *ctx, const u8 *addr, + const u8 *data, size_t data_len, + int encrypt) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + u32 flags = 0; + int link_id = -1; + +#ifdef CONFIG_IEEE80211BE + link_id = hapd->conf->mld_ap ? hapd->mld_link_id : -1; +#endif /* CONFIG_IEEE80211BE */ + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->ext_eapol_frame_io) { + size_t hex_len = 2 * data_len + 1; + char *hex = os_malloc(hex_len); + + if (hex == NULL) + return -1; + wpa_snprintf_hex(hex, hex_len, data, data_len); + wpa_msg(hapd->msg_ctx, MSG_INFO, "EAPOL-TX " MACSTR " %s", + MAC2STR(addr), hex); + os_free(hex); + return 0; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + sta = ap_get_sta(hapd, addr); + if (sta) { + flags = hostapd_sta_flags_to_drv(sta->flags); +#ifdef CONFIG_IEEE80211BE + if (ap_sta_is_mld(hapd, sta) && + (sta->flags & WLAN_STA_AUTHORIZED)) + link_id = -1; +#endif /* CONFIG_IEEE80211BE */ + } + + return hostapd_drv_hapd_send_eapol(hapd, addr, data, data_len, + encrypt, flags, link_id); +} + + +static int hostapd_wpa_auth_get_sta_count(void *ctx) +{ + struct hostapd_data *hapd = ctx; + + return hapd->num_sta; +} + + +static int hostapd_wpa_auth_for_each_sta( + void *ctx, int (*cb)(struct wpa_state_machine *sm, void *ctx), + void *cb_ctx) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (sta->wpa_sm && cb(sta->wpa_sm, cb_ctx)) + return 1; + } + return 0; +} + + +struct wpa_auth_iface_iter_data { + int (*cb)(struct wpa_authenticator *sm, void *ctx); + void *cb_ctx; +}; + +static int wpa_auth_iface_iter(struct hostapd_iface *iface, void *ctx) +{ + struct wpa_auth_iface_iter_data *data = ctx; + size_t i; + for (i = 0; i < iface->num_bss; i++) { + if (iface->bss[i]->wpa_auth && + data->cb(iface->bss[i]->wpa_auth, data->cb_ctx)) + return 1; + } + return 0; +} + + +static int hostapd_wpa_auth_for_each_auth( + void *ctx, int (*cb)(struct wpa_authenticator *sm, void *ctx), + void *cb_ctx) +{ + struct hostapd_data *hapd = ctx; + struct wpa_auth_iface_iter_data data; + if (hapd->iface->interfaces == NULL || + hapd->iface->interfaces->for_each_interface == NULL) + return -1; + data.cb = cb; + data.cb_ctx = cb_ctx; + return hapd->iface->interfaces->for_each_interface( + hapd->iface->interfaces, wpa_auth_iface_iter, &data); +} + + +#ifdef CONFIG_IEEE80211R_AP + +struct wpa_ft_rrb_rx_later_data { + struct dl_list list; + u8 addr[ETH_ALEN]; + size_t data_len; + /* followed by data_len octets of data */ +}; + +static void hostapd_wpa_ft_rrb_rx_later(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct wpa_ft_rrb_rx_later_data *data, *n; + + dl_list_for_each_safe(data, n, &hapd->l2_queue, + struct wpa_ft_rrb_rx_later_data, list) { + if (hapd->wpa_auth) { + wpa_ft_rrb_rx(hapd->wpa_auth, data->addr, + (const u8 *) (data + 1), + data->data_len); + } + dl_list_del(&data->list); + os_free(data); + } +} + + +struct wpa_auth_ft_iface_iter_data { + struct hostapd_data *src_hapd; + const u8 *dst; + const u8 *data; + size_t data_len; +}; + + +static int hostapd_wpa_auth_ft_iter(struct hostapd_iface *iface, void *ctx) +{ + struct wpa_auth_ft_iface_iter_data *idata = ctx; + struct wpa_ft_rrb_rx_later_data *data; + struct hostapd_data *hapd; + size_t j; + + for (j = 0; j < iface->num_bss; j++) { + hapd = iface->bss[j]; + if (hapd == idata->src_hapd || + !hapd->wpa_auth || + !ether_addr_equal(hapd->own_addr, idata->dst)) + continue; + + wpa_printf(MSG_DEBUG, + "FT: Send RRB data directly to locally managed BSS " + MACSTR "@%s -> " MACSTR "@%s", + MAC2STR(idata->src_hapd->own_addr), + idata->src_hapd->conf->iface, + MAC2STR(hapd->own_addr), hapd->conf->iface); + + /* Defer wpa_ft_rrb_rx() until next eloop step as this is + * when it would be triggered when reading from a socket. + * This avoids + * hapd0:send -> hapd1:recv -> hapd1:send -> hapd0:recv, + * that is calling hapd0:recv handler from within + * hapd0:send directly. + */ + data = os_zalloc(sizeof(*data) + idata->data_len); + if (!data) + return 1; + + os_memcpy(data->addr, idata->src_hapd->own_addr, ETH_ALEN); + os_memcpy(data + 1, idata->data, idata->data_len); + data->data_len = idata->data_len; + + dl_list_add(&hapd->l2_queue, &data->list); + + if (!eloop_is_timeout_registered(hostapd_wpa_ft_rrb_rx_later, + hapd, NULL)) + eloop_register_timeout(0, 0, + hostapd_wpa_ft_rrb_rx_later, + hapd, NULL); + + return 1; + } + + return 0; +} + +#endif /* CONFIG_IEEE80211R_AP */ + + +static int hostapd_wpa_auth_send_ether(void *ctx, const u8 *dst, u16 proto, + const u8 *data, size_t data_len) +{ + struct hostapd_data *hapd = ctx; + struct l2_ethhdr *buf; + int ret; + +#ifdef CONFIG_TESTING_OPTIONS + if (hapd->ext_eapol_frame_io && proto == ETH_P_EAPOL) { + size_t hex_len = 2 * data_len + 1; + char *hex = os_malloc(hex_len); + + if (hex == NULL) + return -1; + wpa_snprintf_hex(hex, hex_len, data, data_len); + wpa_msg(hapd->msg_ctx, MSG_INFO, "EAPOL-TX " MACSTR " %s", + MAC2STR(dst), hex); + os_free(hex); + return 0; + } +#endif /* CONFIG_TESTING_OPTIONS */ + +#ifdef CONFIG_IEEE80211R_AP + if (proto == ETH_P_RRB && hapd->iface->interfaces && + hapd->iface->interfaces->for_each_interface) { + int res; + struct wpa_auth_ft_iface_iter_data idata; + idata.src_hapd = hapd; + idata.dst = dst; + idata.data = data; + idata.data_len = data_len; + res = hapd->iface->interfaces->for_each_interface( + hapd->iface->interfaces, hostapd_wpa_auth_ft_iter, + &idata); + if (res == 1) + return data_len; + } +#endif /* CONFIG_IEEE80211R_AP */ + + if (hapd->l2 == NULL) + return -1; + + buf = os_malloc(sizeof(*buf) + data_len); + if (buf == NULL) + return -1; + os_memcpy(buf->h_dest, dst, ETH_ALEN); + os_memcpy(buf->h_source, hapd->own_addr, ETH_ALEN); + buf->h_proto = host_to_be16(proto); + os_memcpy(buf + 1, data, data_len); + ret = l2_packet_send(hapd->l2, dst, proto, (u8 *) buf, + sizeof(*buf) + data_len); + os_free(buf); + return ret; +} + + +#ifdef CONFIG_ETH_P_OUI +static struct eth_p_oui_ctx * hostapd_wpa_get_oui(struct hostapd_data *hapd, + u8 oui_suffix) +{ + switch (oui_suffix) { +#ifdef CONFIG_IEEE80211R_AP + case FT_PACKET_R0KH_R1KH_PULL: + return hapd->oui_pull; + case FT_PACKET_R0KH_R1KH_RESP: + return hapd->oui_resp; + case FT_PACKET_R0KH_R1KH_PUSH: + return hapd->oui_push; + case FT_PACKET_R0KH_R1KH_SEQ_REQ: + return hapd->oui_sreq; + case FT_PACKET_R0KH_R1KH_SEQ_RESP: + return hapd->oui_sresp; +#endif /* CONFIG_IEEE80211R_AP */ + default: + return NULL; + } +} +#endif /* CONFIG_ETH_P_OUI */ + + +#ifdef CONFIG_IEEE80211R_AP + +struct oui_deliver_later_data { + struct dl_list list; + u8 src_addr[ETH_ALEN]; + u8 dst_addr[ETH_ALEN]; + size_t data_len; + u8 oui_suffix; + /* followed by data_len octets of data */ +}; + +static void hostapd_oui_deliver_later(void *eloop_ctx, void *timeout_ctx) +{ + struct hostapd_data *hapd = eloop_ctx; + struct oui_deliver_later_data *data, *n; + struct eth_p_oui_ctx *oui_ctx; + + dl_list_for_each_safe(data, n, &hapd->l2_oui_queue, + struct oui_deliver_later_data, list) { + oui_ctx = hostapd_wpa_get_oui(hapd, data->oui_suffix); + wpa_printf(MSG_DEBUG, "RRB(%s): %s src=" MACSTR " dst=" MACSTR + " oui_suffix=%u data_len=%u data=%p", + hapd->conf->iface, __func__, + MAC2STR(data->src_addr), MAC2STR(data->dst_addr), + data->oui_suffix, (unsigned int) data->data_len, + data); + if (hapd->wpa_auth && oui_ctx) { + eth_p_oui_deliver(oui_ctx, data->src_addr, + data->dst_addr, + (const u8 *) (data + 1), + data->data_len); + } + dl_list_del(&data->list); + os_free(data); + } +} + + +struct wpa_auth_oui_iface_iter_data { + struct hostapd_data *src_hapd; + const u8 *dst_addr; + const u8 *data; + size_t data_len; + u8 oui_suffix; +}; + +static int hostapd_wpa_auth_oui_iter(struct hostapd_iface *iface, void *ctx) +{ + struct wpa_auth_oui_iface_iter_data *idata = ctx; + struct oui_deliver_later_data *data; + struct hostapd_data *hapd, *src_hapd = idata->src_hapd; + size_t j; + + for (j = 0; j < iface->num_bss; j++) { + hapd = iface->bss[j]; + if (hapd == src_hapd) + continue; /* don't deliver back to same interface */ + if (!wpa_key_mgmt_ft(hapd->conf->wpa_key_mgmt) || + hapd->conf->ssid.ssid_len != + src_hapd->conf->ssid.ssid_len || + os_memcmp(hapd->conf->ssid.ssid, + src_hapd->conf->ssid.ssid, + hapd->conf->ssid.ssid_len) != 0 || + os_memcmp(hapd->conf->mobility_domain, + src_hapd->conf->mobility_domain, + MOBILITY_DOMAIN_ID_LEN) != 0) + continue; /* no matching FT SSID/mobility domain */ + if (!is_multicast_ether_addr(idata->dst_addr) && + !ether_addr_equal(hapd->own_addr, idata->dst_addr)) + continue; /* destination address does not match */ + + /* defer eth_p_oui_deliver until next eloop step as this is + * when it would be triggerd from reading from sock + * This avoids + * hapd0:send -> hapd1:recv -> hapd1:send -> hapd0:recv, + * that is calling hapd0:recv handler from within + * hapd0:send directly. + */ + data = os_zalloc(sizeof(*data) + idata->data_len); + if (!data) + return 1; + wpa_printf(MSG_DEBUG, + "RRB(%s): local delivery to %s dst=" MACSTR + " oui_suffix=%u data_len=%u data=%p", + src_hapd->conf->iface, hapd->conf->iface, + MAC2STR(idata->dst_addr), idata->oui_suffix, + (unsigned int) idata->data_len, data); + + os_memcpy(data->src_addr, src_hapd->own_addr, ETH_ALEN); + os_memcpy(data->dst_addr, idata->dst_addr, ETH_ALEN); + os_memcpy(data + 1, idata->data, idata->data_len); + data->data_len = idata->data_len; + data->oui_suffix = idata->oui_suffix; + + dl_list_add_tail(&hapd->l2_oui_queue, &data->list); + + if (!eloop_is_timeout_registered(hostapd_oui_deliver_later, + hapd, NULL)) + eloop_register_timeout(0, 0, + hostapd_oui_deliver_later, + hapd, NULL); + + /* If dst_addr is a multicast address, do not return any + * non-zero value here. Otherwise, the iteration of + * for_each_interface() will be stopped. */ + if (!is_multicast_ether_addr(idata->dst_addr)) + return 1; + } + + return 0; +} + +#endif /* CONFIG_IEEE80211R_AP */ + + +static int hostapd_wpa_auth_send_oui(void *ctx, const u8 *dst, u8 oui_suffix, + const u8 *data, size_t data_len) +{ +#ifdef CONFIG_ETH_P_OUI + struct hostapd_data *hapd = ctx; + struct eth_p_oui_ctx *oui_ctx; + + wpa_printf(MSG_DEBUG, "RRB(%s): send to dst=" MACSTR + " oui_suffix=%u data_len=%u", + hapd->conf->iface, MAC2STR(dst), oui_suffix, + (unsigned int) data_len); +#ifdef CONFIG_IEEE80211R_AP + if (hapd->iface->interfaces && + hapd->iface->interfaces->for_each_interface) { + struct wpa_auth_oui_iface_iter_data idata; + int res; + + idata.src_hapd = hapd; + idata.dst_addr = dst; + idata.data = data; + idata.data_len = data_len; + idata.oui_suffix = oui_suffix; + res = hapd->iface->interfaces->for_each_interface( + hapd->iface->interfaces, hostapd_wpa_auth_oui_iter, + &idata); + if (res == 1) + return data_len; + } +#endif /* CONFIG_IEEE80211R_AP */ + + oui_ctx = hostapd_wpa_get_oui(hapd, oui_suffix); + if (!oui_ctx) + return -1; + + return eth_p_oui_send(oui_ctx, hapd->own_addr, dst, data, data_len); +#else /* CONFIG_ETH_P_OUI */ + return -1; +#endif /* CONFIG_ETH_P_OUI */ +} + + +static int hostapd_channel_info(void *ctx, struct wpa_channel_info *ci) +{ + struct hostapd_data *hapd = ctx; + + return hostapd_drv_channel_info(hapd, ci); +} + + +#ifdef CONFIG_PASN + +static void hostapd_store_ptksa(void *ctx, const u8 *addr,int cipher, + u32 life_time, const struct wpa_ptk *ptk) +{ + struct hostapd_data *hapd = ctx; + + ptksa_cache_add(hapd->ptksa, hapd->own_addr, addr, cipher, life_time, + ptk, NULL, NULL, 0); +} + + +static void hostapd_clear_ptksa(void *ctx, const u8 *addr, int cipher) +{ + struct hostapd_data *hapd = ctx; + + ptksa_cache_flush(hapd->ptksa, addr, cipher); +} + +#endif /* CONFIG_PASN */ + + +static int hostapd_wpa_auth_update_vlan(void *ctx, const u8 *addr, int vlan_id) +{ +#ifndef CONFIG_NO_VLAN + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + sta = ap_get_sta(hapd, addr); + if (!sta) + return -1; + + if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD)) { + struct vlan_description vlan_desc; + + os_memset(&vlan_desc, 0, sizeof(vlan_desc)); + vlan_desc.notempty = 1; + vlan_desc.untagged = vlan_id; + if (!hostapd_vlan_valid(hapd->conf->vlan, &vlan_desc)) { + wpa_printf(MSG_INFO, + "Invalid VLAN ID %d in wpa_psk_file", + vlan_id); + return -1; + } + + if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0) { + wpa_printf(MSG_INFO, + "Failed to assign VLAN ID %d from wpa_psk_file to " + MACSTR, vlan_id, MAC2STR(sta->addr)); + return -1; + } + } else { + sta->vlan_id = vlan_id; + } + + wpa_printf(MSG_INFO, + "Assigned VLAN ID %d from wpa_psk_file to " MACSTR, + vlan_id, MAC2STR(sta->addr)); + if ((sta->flags & WLAN_STA_ASSOC) && + ap_sta_bind_vlan(hapd, sta) < 0) + return -1; +#endif /* CONFIG_NO_VLAN */ + + return 0; +} + + +#ifdef CONFIG_OCV +static int hostapd_get_sta_tx_params(void *ctx, const u8 *addr, + int ap_max_chanwidth, int ap_seg1_idx, + int *bandwidth, int *seg1_idx) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + sta = ap_get_sta(hapd, addr); + if (!sta) { + hostapd_wpa_auth_logger(hapd, addr, LOGGER_INFO, + "Failed to get STA info to validate received OCI"); + return -1; + } + + return get_tx_parameters(sta, ap_max_chanwidth, ap_seg1_idx, bandwidth, + seg1_idx); +} +#endif /* CONFIG_OCV */ + + +#ifdef CONFIG_IEEE80211R_AP + +static int hostapd_wpa_auth_send_ft_action(void *ctx, const u8 *dst, + const u8 *data, size_t data_len) +{ + struct hostapd_data *hapd = ctx; + int res; + struct ieee80211_mgmt *m; + size_t mlen; + struct sta_info *sta; + + sta = ap_get_sta(hapd, dst); + if (sta == NULL || sta->wpa_sm == NULL) + return -1; + + m = os_zalloc(sizeof(*m) + data_len); + if (m == NULL) + return -1; + mlen = ((u8 *) &m->u - (u8 *) m) + data_len; + m->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memcpy(m->da, dst, ETH_ALEN); + os_memcpy(m->sa, hapd->own_addr, ETH_ALEN); + os_memcpy(m->bssid, hapd->own_addr, ETH_ALEN); + os_memcpy(&m->u, data, data_len); + + res = hostapd_drv_send_mlme(hapd, (u8 *) m, mlen, 0, NULL, 0, 0); + os_free(m); + return res; +} + + +static struct wpa_state_machine * +hostapd_wpa_auth_add_sta(void *ctx, const u8 *sta_addr) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + int ret; + + wpa_printf(MSG_DEBUG, "Add station entry for " MACSTR + " based on WPA authenticator callback", + MAC2STR(sta_addr)); + ret = hostapd_add_sta_node(hapd, sta_addr, WLAN_AUTH_FT); + + /* + * The expected return values from hostapd_add_sta_node() are + * 0: successfully added STA entry + * -EOPNOTSUPP: driver or driver wrapper does not support/need this + * operations + * any other negative value: error in adding the STA entry */ + if (ret < 0 && ret != -EOPNOTSUPP) + return NULL; + + sta = ap_sta_add(hapd, sta_addr); + if (sta == NULL) + return NULL; + if (ret == 0) + sta->added_unassoc = 1; + + sta->ft_over_ds = 1; + if (sta->wpa_sm) { + sta->auth_alg = WLAN_AUTH_FT; + return sta->wpa_sm; + } + + sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth, sta->addr, NULL); + if (sta->wpa_sm == NULL) { + ap_free_sta(hapd, sta); + return NULL; + } + sta->auth_alg = WLAN_AUTH_FT; + + return sta->wpa_sm; +} + + +static int hostapd_wpa_auth_add_sta_ft(void *ctx, const u8 *sta_addr) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + sta = ap_get_sta(hapd, sta_addr); + if (!sta) + return -1; + + if (FULL_AP_CLIENT_STATE_SUPP(hapd->iface->drv_flags) && + (sta->flags & WLAN_STA_MFP) && ap_sta_is_authorized(sta) && + !(hapd->conf->mesh & MESH_ENABLED) && !(sta->added_unassoc)) { + /* We could not do this in handle_auth() since there was a + * PMF-enabled association for the STA and the new + * authentication attempt was not yet fully processed. Now that + * we are ready to configure the TK to the driver, + * authentication has succeeded and we can clean up the driver + * STA entry to avoid issues with any maintained state from the + * previous association. */ + wpa_printf(MSG_DEBUG, + "FT: Remove and re-add driver STA entry after successful FT authentication"); + return ap_sta_re_add(hapd, sta); + } + + return 0; +} + + +static int hostapd_wpa_auth_set_vlan(void *ctx, const u8 *sta_addr, + struct vlan_description *vlan) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + sta = ap_get_sta(hapd, sta_addr); + if (!sta || !sta->wpa_sm) + return -1; + + if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD)) { + if (vlan->notempty && + !hostapd_vlan_valid(hapd->conf->vlan, vlan)) { + hostapd_logger(hapd, sta->addr, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, + "Invalid VLAN %d%s received from FT", + vlan->untagged, vlan->tagged[0] ? + "+" : ""); + return -1; + } + + if (ap_sta_set_vlan(hapd, sta, vlan) < 0) + return -1; + + } else { + if (vlan->notempty) + sta->vlan_id = vlan->untagged; + } + /* Configure wpa_group for GTK but ignore error due to driver not + * knowing this STA. */ + ap_sta_bind_vlan(hapd, sta); + + if (sta->vlan_id) + hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_INFO, "VLAN ID %d", sta->vlan_id); + + return 0; +} + + +static int hostapd_wpa_auth_get_vlan(void *ctx, const u8 *sta_addr, + struct vlan_description *vlan) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + sta = ap_get_sta(hapd, sta_addr); + if (!sta) + return -1; + + if (sta->vlan_desc) { + *vlan = *sta->vlan_desc; + } else if ((hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD) && + sta->vlan_id) { + vlan->notempty = 1; + vlan->untagged = sta->vlan_id; + } else { + os_memset(vlan, 0, sizeof(*vlan)); + } + + return 0; +} + + +static int +hostapd_wpa_auth_set_identity(void *ctx, const u8 *sta_addr, + const u8 *identity, size_t identity_len) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + sta = ap_get_sta(hapd, sta_addr); + if (!sta) + return -1; + + os_free(sta->identity); + sta->identity = NULL; + + if (sta->eapol_sm) { + os_free(sta->eapol_sm->identity); + sta->eapol_sm->identity = NULL; + sta->eapol_sm->identity_len = 0; + } + + if (!identity_len) + return 0; + + /* sta->identity is NULL terminated */ + sta->identity = os_zalloc(identity_len + 1); + if (!sta->identity) + return -1; + os_memcpy(sta->identity, identity, identity_len); + + if (sta->eapol_sm) { + sta->eapol_sm->identity = os_zalloc(identity_len); + if (!sta->eapol_sm->identity) + return -1; + os_memcpy(sta->eapol_sm->identity, identity, identity_len); + sta->eapol_sm->identity_len = identity_len; + } + + return 0; +} + + +static size_t +hostapd_wpa_auth_get_identity(void *ctx, const u8 *sta_addr, const u8 **buf) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + size_t len; + char *identity; + + sta = ap_get_sta(hapd, sta_addr); + if (!sta) + return 0; + + *buf = ieee802_1x_get_identity(sta->eapol_sm, &len); + if (*buf && len) + return len; + + if (!sta->identity) { + *buf = NULL; + return 0; + } + + identity = sta->identity; + len = os_strlen(identity); + *buf = (u8 *) identity; + + return len; +} + + +static int +hostapd_wpa_auth_set_radius_cui(void *ctx, const u8 *sta_addr, + const u8 *radius_cui, size_t radius_cui_len) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + sta = ap_get_sta(hapd, sta_addr); + if (!sta) + return -1; + + os_free(sta->radius_cui); + sta->radius_cui = NULL; + + if (sta->eapol_sm) { + wpabuf_free(sta->eapol_sm->radius_cui); + sta->eapol_sm->radius_cui = NULL; + } + + if (!radius_cui) + return 0; + + /* sta->radius_cui is NULL terminated */ + sta->radius_cui = os_zalloc(radius_cui_len + 1); + if (!sta->radius_cui) + return -1; + os_memcpy(sta->radius_cui, radius_cui, radius_cui_len); + + if (sta->eapol_sm) { + sta->eapol_sm->radius_cui = wpabuf_alloc_copy(radius_cui, + radius_cui_len); + if (!sta->eapol_sm->radius_cui) + return -1; + } + + return 0; +} + + +static size_t +hostapd_wpa_auth_get_radius_cui(void *ctx, const u8 *sta_addr, const u8 **buf) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + struct wpabuf *b; + size_t len; + char *radius_cui; + + sta = ap_get_sta(hapd, sta_addr); + if (!sta) + return 0; + + b = ieee802_1x_get_radius_cui(sta->eapol_sm); + if (b) { + len = wpabuf_len(b); + *buf = wpabuf_head(b); + return len; + } + + if (!sta->radius_cui) { + *buf = NULL; + return 0; + } + + radius_cui = sta->radius_cui; + len = os_strlen(radius_cui); + *buf = (u8 *) radius_cui; + + return len; +} + + +static void hostapd_wpa_auth_set_session_timeout(void *ctx, const u8 *sta_addr, + int session_timeout) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + + sta = ap_get_sta(hapd, sta_addr); + if (!sta) + return; + + if (session_timeout) { + os_get_reltime(&sta->session_timeout); + sta->session_timeout.sec += session_timeout; + sta->session_timeout_set = 1; + ap_sta_session_timeout(hapd, sta, session_timeout); + } else { + sta->session_timeout_set = 0; + ap_sta_no_session_timeout(hapd, sta); + } +} + + +static int hostapd_wpa_auth_get_session_timeout(void *ctx, const u8 *sta_addr) +{ + struct hostapd_data *hapd = ctx; + struct sta_info *sta; + struct os_reltime now, remaining; + + sta = ap_get_sta(hapd, sta_addr); + if (!sta || !sta->session_timeout_set) + return 0; + + os_get_reltime(&now); + if (os_reltime_before(&sta->session_timeout, &now)) { + /* already expired, return >0 as timeout was set */ + return 1; + } + + os_reltime_sub(&sta->session_timeout, &now, &remaining); + + return (remaining.sec > 0) ? remaining.sec : 1; +} + + +static void hostapd_rrb_receive(void *ctx, const u8 *src_addr, const u8 *buf, + size_t len) +{ + struct hostapd_data *hapd = ctx; + struct l2_ethhdr *ethhdr; + if (len < sizeof(*ethhdr)) + return; + ethhdr = (struct l2_ethhdr *) buf; + wpa_printf(MSG_DEBUG, "FT: RRB received packet " MACSTR " -> " + MACSTR, MAC2STR(ethhdr->h_source), MAC2STR(ethhdr->h_dest)); + if (!is_multicast_ether_addr(ethhdr->h_dest) && + !ether_addr_equal(hapd->own_addr, ethhdr->h_dest)) + return; + wpa_ft_rrb_rx(hapd->wpa_auth, ethhdr->h_source, buf + sizeof(*ethhdr), + len - sizeof(*ethhdr)); +} + + +static void hostapd_rrb_oui_receive(void *ctx, const u8 *src_addr, + const u8 *dst_addr, u8 oui_suffix, + const u8 *buf, size_t len) +{ + struct hostapd_data *hapd = ctx; + + wpa_printf(MSG_DEBUG, "FT: RRB received packet " MACSTR " -> " + MACSTR, MAC2STR(src_addr), MAC2STR(dst_addr)); + if (!is_multicast_ether_addr(dst_addr) && + !ether_addr_equal(hapd->own_addr, dst_addr)) + return; + wpa_ft_rrb_oui_rx(hapd->wpa_auth, src_addr, dst_addr, oui_suffix, buf, + len); +} + + +static int hostapd_wpa_auth_add_tspec(void *ctx, const u8 *sta_addr, + u8 *tspec_ie, size_t tspec_ielen) +{ + struct hostapd_data *hapd = ctx; + return hostapd_add_tspec(hapd, sta_addr, tspec_ie, tspec_ielen); +} + + + +static int hostapd_wpa_register_ft_oui(struct hostapd_data *hapd, + const char *ft_iface) +{ + hapd->oui_pull = eth_p_oui_register(hapd, ft_iface, + FT_PACKET_R0KH_R1KH_PULL, + hostapd_rrb_oui_receive, hapd); + if (!hapd->oui_pull) + return -1; + + hapd->oui_resp = eth_p_oui_register(hapd, ft_iface, + FT_PACKET_R0KH_R1KH_RESP, + hostapd_rrb_oui_receive, hapd); + if (!hapd->oui_resp) + return -1; + + hapd->oui_push = eth_p_oui_register(hapd, ft_iface, + FT_PACKET_R0KH_R1KH_PUSH, + hostapd_rrb_oui_receive, hapd); + if (!hapd->oui_push) + return -1; + + hapd->oui_sreq = eth_p_oui_register(hapd, ft_iface, + FT_PACKET_R0KH_R1KH_SEQ_REQ, + hostapd_rrb_oui_receive, hapd); + if (!hapd->oui_sreq) + return -1; + + hapd->oui_sresp = eth_p_oui_register(hapd, ft_iface, + FT_PACKET_R0KH_R1KH_SEQ_RESP, + hostapd_rrb_oui_receive, hapd); + if (!hapd->oui_sresp) + return -1; + + return 0; +} + + +static void hostapd_wpa_unregister_ft_oui(struct hostapd_data *hapd) +{ + eth_p_oui_unregister(hapd->oui_pull); + hapd->oui_pull = NULL; + eth_p_oui_unregister(hapd->oui_resp); + hapd->oui_resp = NULL; + eth_p_oui_unregister(hapd->oui_push); + hapd->oui_push = NULL; + eth_p_oui_unregister(hapd->oui_sreq); + hapd->oui_sreq = NULL; + eth_p_oui_unregister(hapd->oui_sresp); + hapd->oui_sresp = NULL; +} +#endif /* CONFIG_IEEE80211R_AP */ + + +#ifndef CONFIG_NO_RADIUS +static void hostapd_request_radius_psk(void *ctx, const u8 *addr, int key_mgmt, + const u8 *anonce, + const u8 *eapol, size_t eapol_len) +{ + struct hostapd_data *hapd = ctx; + + wpa_printf(MSG_DEBUG, "RADIUS PSK request for " MACSTR " key_mgmt=0x%x", + MAC2STR(addr), key_mgmt); + wpa_hexdump(MSG_DEBUG, "ANonce", anonce, WPA_NONCE_LEN); + wpa_hexdump(MSG_DEBUG, "EAPOL", eapol, eapol_len); + hostapd_acl_req_radius_psk(hapd, addr, key_mgmt, anonce, eapol, + eapol_len); +} +#endif /* CONFIG_NO_RADIUS */ + + +#ifdef CONFIG_PASN +static int hostapd_set_ltf_keyseed(void *ctx, const u8 *peer_addr, + const u8 *ltf_keyseed, + size_t ltf_keyseed_len) +{ + struct hostapd_data *hapd = ctx; + + return hostapd_drv_set_secure_ranging_ctx(hapd, hapd->own_addr, + peer_addr, 0, 0, NULL, + ltf_keyseed_len, + ltf_keyseed, 0); +} +#endif /* CONFIG_PASN */ + + +#ifdef CONFIG_IEEE80211BE + +static int hostapd_wpa_auth_get_ml_key_info(void *ctx, + struct wpa_auth_ml_key_info *info) +{ + struct hostapd_data *hapd = ctx; + unsigned int i; + + wpa_printf(MSG_DEBUG, "WPA_AUTH: MLD: Get key info CB: n_mld_links=%u", + info->n_mld_links); + + if (!hapd->conf->mld_ap || !hapd->iface || !hapd->iface->interfaces) + return -1; + + for (i = 0; i < info->n_mld_links; i++) { + struct hostapd_data *bss; + u8 link_id = info->links[i].link_id; + bool link_bss_found = false; + + wpa_printf(MSG_DEBUG, + "WPA_AUTH: MLD: Get link info CB: link_id=%u", + link_id); + + if (hapd->mld_link_id == link_id) { + wpa_auth_ml_get_key_info(hapd->wpa_auth, + &info->links[i], + info->mgmt_frame_prot, + info->beacon_prot); + continue; + } + + for_each_mld_link(bss, hapd) { + if (bss == hapd || bss->mld_link_id != link_id) + continue; + + wpa_auth_ml_get_key_info(bss->wpa_auth, + &info->links[i], + info->mgmt_frame_prot, + info->beacon_prot); + link_bss_found = true; + break; + } + + if (!link_bss_found) + wpa_printf(MSG_DEBUG, + "WPA_AUTH: MLD: link=%u not found", link_id); + } + + return 0; +} + +#endif /* CONFIG_IEEE80211BE */ + + +static int hostapd_wpa_auth_get_drv_flags(void *ctx, + u64 *drv_flags, u64 *drv_flags2) +{ + struct hostapd_data *hapd = ctx; + + if (drv_flags) + *drv_flags = hapd->iface->drv_flags; + if (drv_flags2) + *drv_flags2 = hapd->iface->drv_flags2; + + return 0; +} + + +int hostapd_setup_wpa(struct hostapd_data *hapd) +{ + struct wpa_auth_config _conf; + static const struct wpa_auth_callbacks cb = { + .logger = hostapd_wpa_auth_logger, + .disconnect = hostapd_wpa_auth_disconnect, + .mic_failure_report = hostapd_wpa_auth_mic_failure_report, + .psk_failure_report = hostapd_wpa_auth_psk_failure_report, + .set_eapol = hostapd_wpa_auth_set_eapol, + .get_eapol = hostapd_wpa_auth_get_eapol, + .get_psk = hostapd_wpa_auth_get_psk, + .get_msk = hostapd_wpa_auth_get_msk, + .set_key = hostapd_wpa_auth_set_key, + .get_seqnum = hostapd_wpa_auth_get_seqnum, + .send_eapol = hostapd_wpa_auth_send_eapol, + .get_sta_count = hostapd_wpa_auth_get_sta_count, + .for_each_sta = hostapd_wpa_auth_for_each_sta, + .for_each_auth = hostapd_wpa_auth_for_each_auth, + .send_ether = hostapd_wpa_auth_send_ether, + .send_oui = hostapd_wpa_auth_send_oui, + .channel_info = hostapd_channel_info, + .update_vlan = hostapd_wpa_auth_update_vlan, +#ifdef CONFIG_PASN + .store_ptksa = hostapd_store_ptksa, + .clear_ptksa = hostapd_clear_ptksa, +#endif /* CONFIG_PASN */ + +#ifdef CONFIG_OCV + .get_sta_tx_params = hostapd_get_sta_tx_params, +#endif /* CONFIG_OCV */ +#ifdef CONFIG_IEEE80211R_AP + .send_ft_action = hostapd_wpa_auth_send_ft_action, + .add_sta = hostapd_wpa_auth_add_sta, + .add_sta_ft = hostapd_wpa_auth_add_sta_ft, + .add_tspec = hostapd_wpa_auth_add_tspec, + .set_vlan = hostapd_wpa_auth_set_vlan, + .get_vlan = hostapd_wpa_auth_get_vlan, + .set_identity = hostapd_wpa_auth_set_identity, + .get_identity = hostapd_wpa_auth_get_identity, + .set_radius_cui = hostapd_wpa_auth_set_radius_cui, + .get_radius_cui = hostapd_wpa_auth_get_radius_cui, + .set_session_timeout = hostapd_wpa_auth_set_session_timeout, + .get_session_timeout = hostapd_wpa_auth_get_session_timeout, +#endif /* CONFIG_IEEE80211R_AP */ +#ifndef CONFIG_NO_RADIUS + .request_radius_psk = hostapd_request_radius_psk, +#endif /* CONFIG_NO_RADIUS */ +#ifdef CONFIG_PASN + .set_ltf_keyseed = hostapd_set_ltf_keyseed, +#endif /* CONFIG_PASN */ +#ifdef CONFIG_IEEE80211BE + .get_ml_key_info = hostapd_wpa_auth_get_ml_key_info, +#endif /* CONFIG_IEEE80211BE */ + .get_drv_flags = hostapd_wpa_auth_get_drv_flags, + }; + const u8 *wpa_ie; + size_t wpa_ie_len; + struct hostapd_data *tx_bss; + + hostapd_wpa_auth_conf(hapd->conf, hapd->iconf, &_conf); + _conf.msg_ctx = hapd->msg_ctx; + tx_bss = hostapd_mbssid_get_tx_bss(hapd); + if (tx_bss != hapd) + _conf.tx_bss_auth = tx_bss->wpa_auth; + if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_EAPOL_TX_STATUS) + _conf.tx_status = 1; + if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_MLME) + _conf.ap_mlme = 1; + + if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_WIRED) && + (hapd->conf->wpa_deny_ptk0_rekey == PTK0_REKEY_ALLOW_NEVER || + (hapd->conf->wpa_deny_ptk0_rekey == PTK0_REKEY_ALLOW_LOCAL_OK && + !(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_SAFE_PTK0_REKEYS)))) { + wpa_msg(hapd->msg_ctx, MSG_INFO, + "Disable PTK0 rekey support - replaced with disconnect"); + _conf.wpa_deny_ptk0_rekey = 1; + } + + if (_conf.extended_key_id && + (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_EXTENDED_KEY_ID)) + wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Extended Key ID supported"); + else + _conf.extended_key_id = 0; + + if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_BEACON_PROTECTION)) + _conf.beacon_prot = 0; + +#ifdef CONFIG_OCV + if (!(hapd->iface->drv_flags2 & + (WPA_DRIVER_FLAGS2_AP_SME | WPA_DRIVER_FLAGS2_OCV))) + _conf.ocv = 0; +#endif /* CONFIG_OCV */ + + _conf.secure_ltf = + !!(hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_LTF_AP); + _conf.secure_rtt = + !!(hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_RTT_AP); + _conf.prot_range_neg = + !!(hapd->iface->drv_flags2 & + WPA_DRIVER_FLAGS2_PROT_RANGE_NEG_AP); + +#ifdef CONFIG_IEEE80211BE + _conf.mld_addr = NULL; + _conf.link_id = -1; + _conf.first_link_auth = NULL; + + if (hapd->conf->mld_ap) { + struct hostapd_data *lhapd; + + _conf.mld_addr = hapd->mld->mld_addr; + _conf.link_id = hapd->mld_link_id; + + for_each_mld_link(lhapd, hapd) { + if (lhapd == hapd) + continue; + + if (lhapd->wpa_auth) + _conf.first_link_auth = lhapd->wpa_auth; + } + } +#endif /* CONFIG_IEEE80211BE */ + + hapd->wpa_auth = wpa_init(hapd->own_addr, &_conf, &cb, hapd); + if (hapd->wpa_auth == NULL) { + wpa_printf(MSG_ERROR, "WPA initialization failed."); + return -1; + } + + if (hostapd_set_privacy(hapd, 1)) { + wpa_printf(MSG_ERROR, "Could not set PrivacyInvoked " + "for interface %s", hapd->conf->iface); + return -1; + } + + wpa_ie = wpa_auth_get_wpa_ie(hapd->wpa_auth, &wpa_ie_len); + if (hostapd_set_generic_elem(hapd, wpa_ie, wpa_ie_len)) { + wpa_printf(MSG_ERROR, "Failed to configure WPA IE for " + "the kernel driver."); + return -1; + } + + if (rsn_preauth_iface_init(hapd)) { + wpa_printf(MSG_ERROR, "Initialization of RSN " + "pre-authentication failed."); + return -1; + } + + if (!hapd->ptksa) + hapd->ptksa = ptksa_cache_init(); + if (!hapd->ptksa) { + wpa_printf(MSG_ERROR, "Failed to allocate PTKSA cache"); + return -1; + } + +#ifdef CONFIG_IEEE80211R_AP + if (!hostapd_drv_none(hapd) && + wpa_key_mgmt_ft(hapd->conf->wpa_key_mgmt)) { + const char *ft_iface; + + ft_iface = hapd->conf->bridge[0] ? hapd->conf->bridge : + hapd->conf->iface; + hapd->l2 = l2_packet_init(ft_iface, NULL, ETH_P_RRB, + hostapd_rrb_receive, hapd, 1); + if (!hapd->l2) { + wpa_printf(MSG_ERROR, "Failed to open l2_packet " + "interface"); + return -1; + } + + if (hostapd_wpa_register_ft_oui(hapd, ft_iface)) { + wpa_printf(MSG_ERROR, + "Failed to open ETH_P_OUI interface"); + return -1; + } + } +#endif /* CONFIG_IEEE80211R_AP */ + + return 0; + +} + + +void hostapd_reconfig_wpa(struct hostapd_data *hapd) +{ + struct wpa_auth_config wpa_auth_conf; + hostapd_wpa_auth_conf(hapd->conf, hapd->iconf, &wpa_auth_conf); + wpa_reconfig(hapd->wpa_auth, &wpa_auth_conf); +} + + +void hostapd_deinit_wpa(struct hostapd_data *hapd) +{ + ieee80211_tkip_countermeasures_deinit(hapd); + ptksa_cache_deinit(hapd->ptksa); + hapd->ptksa = NULL; + + rsn_preauth_iface_deinit(hapd); + if (hapd->wpa_auth) { + wpa_deinit(hapd->wpa_auth); + hapd->wpa_auth = NULL; + + if (hapd->drv_priv && hostapd_set_privacy(hapd, 0)) { + wpa_printf(MSG_DEBUG, "Could not disable " + "PrivacyInvoked for interface %s", + hapd->conf->iface); + } + + if (hapd->drv_priv && + hostapd_set_generic_elem(hapd, (u8 *) "", 0)) { + wpa_printf(MSG_DEBUG, "Could not remove generic " + "information element from interface %s", + hapd->conf->iface); + } + } + ieee802_1x_deinit(hapd); + +#ifdef CONFIG_IEEE80211R_AP + eloop_cancel_timeout(hostapd_wpa_ft_rrb_rx_later, hapd, ELOOP_ALL_CTX); + hostapd_wpa_ft_rrb_rx_later(hapd, NULL); /* flush without delivering */ + eloop_cancel_timeout(hostapd_oui_deliver_later, hapd, ELOOP_ALL_CTX); + hostapd_oui_deliver_later(hapd, NULL); /* flush without delivering */ + l2_packet_deinit(hapd->l2); + hapd->l2 = NULL; + hostapd_wpa_unregister_ft_oui(hapd); +#endif /* CONFIG_IEEE80211R_AP */ + +#ifdef CONFIG_TESTING_OPTIONS + forced_memzero(hapd->last_gtk, WPA_GTK_MAX_LEN); + forced_memzero(hapd->last_igtk, WPA_IGTK_MAX_LEN); + forced_memzero(hapd->last_bigtk, WPA_BIGTK_MAX_LEN); +#endif /* CONFIG_TESTING_OPTIONS */ +} diff --git a/src/ap/wpa_auth_glue.h b/src/ap/wpa_auth_glue.h new file mode 100644 index 0000000..1b13ae7 --- /dev/null +++ b/src/ap/wpa_auth_glue.h @@ -0,0 +1,16 @@ +/* + * hostapd / WPA authenticator glue code + * Copyright (c) 2002-2009, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPA_AUTH_GLUE_H +#define WPA_AUTH_GLUE_H + +int hostapd_setup_wpa(struct hostapd_data *hapd); +void hostapd_reconfig_wpa(struct hostapd_data *hapd); +void hostapd_deinit_wpa(struct hostapd_data *hapd); + +#endif /* WPA_AUTH_GLUE_H */ diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h new file mode 100644 index 0000000..4e5ba3e --- /dev/null +++ b/src/ap/wpa_auth_i.h @@ -0,0 +1,347 @@ +/* + * hostapd - IEEE 802.11i-2004 / WPA Authenticator: Internal definitions + * Copyright (c) 2004-2015, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPA_AUTH_I_H +#define WPA_AUTH_I_H + +#include "utils/list.h" + +/* max(dot11RSNAConfigGroupUpdateCount,dot11RSNAConfigPairwiseUpdateCount) */ +#define RSNA_MAX_EAPOL_RETRIES 4 + +struct wpa_group; + +struct wpa_state_machine { + struct wpa_authenticator *wpa_auth; + struct wpa_group *group; + + u8 addr[ETH_ALEN]; + u8 p2p_dev_addr[ETH_ALEN]; + u16 auth_alg; + + enum { + WPA_PTK_INITIALIZE, WPA_PTK_DISCONNECT, WPA_PTK_DISCONNECTED, + WPA_PTK_AUTHENTICATION, WPA_PTK_AUTHENTICATION2, + WPA_PTK_INITPMK, WPA_PTK_INITPSK, WPA_PTK_PTKSTART, + WPA_PTK_PTKCALCNEGOTIATING, WPA_PTK_PTKCALCNEGOTIATING2, + WPA_PTK_PTKINITNEGOTIATING, WPA_PTK_PTKINITDONE + } wpa_ptk_state; + + enum { + WPA_PTK_GROUP_IDLE = 0, + WPA_PTK_GROUP_REKEYNEGOTIATING, + WPA_PTK_GROUP_REKEYESTABLISHED, + WPA_PTK_GROUP_KEYERROR + } wpa_ptk_group_state; + + bool Init; + bool DeauthenticationRequest; + bool AuthenticationRequest; + bool ReAuthenticationRequest; + bool Disconnect; + u16 disconnect_reason; /* specific reason code to use with Disconnect */ + u32 TimeoutCtr; + u32 GTimeoutCtr; + bool TimeoutEvt; + bool EAPOLKeyReceived; + bool EAPOLKeyPairwise; + bool EAPOLKeyRequest; + bool MICVerified; + bool GUpdateStationKeys; + u8 ANonce[WPA_NONCE_LEN]; + u8 SNonce[WPA_NONCE_LEN]; + u8 alt_SNonce[WPA_NONCE_LEN]; + u8 alt_replay_counter[WPA_REPLAY_COUNTER_LEN]; + u8 PMK[PMK_LEN_MAX]; + unsigned int pmk_len; + u8 pmkid[PMKID_LEN]; /* valid if pmkid_set == 1 */ + struct wpa_ptk PTK; + u8 keyidx_active; + bool use_ext_key_id; + bool PTK_valid; + bool pairwise_set; + bool tk_already_set; + int keycount; + bool Pair; + struct wpa_key_replay_counter { + u8 counter[WPA_REPLAY_COUNTER_LEN]; + bool valid; + } key_replay[RSNA_MAX_EAPOL_RETRIES], + prev_key_replay[RSNA_MAX_EAPOL_RETRIES]; + bool PInitAKeys; /* WPA only, not in IEEE 802.11i */ + bool PTKRequest; /* not in IEEE 802.11i state machine */ + bool has_GTK; + bool PtkGroupInit; /* init request for PTK Group state machine */ + + u8 *last_rx_eapol_key; /* starting from IEEE 802.1X header */ + size_t last_rx_eapol_key_len; + + unsigned int changed:1; + unsigned int in_step_loop:1; + unsigned int pending_deinit:1; + unsigned int started:1; + unsigned int mgmt_frame_prot:1; + unsigned int mfpr:1; + unsigned int rx_eapol_key_secure:1; + unsigned int update_snonce:1; + unsigned int alt_snonce_valid:1; + unsigned int waiting_radius_psk:1; +#ifdef CONFIG_IEEE80211R_AP + unsigned int ft_completed:1; + unsigned int pmk_r1_name_valid:1; +#endif /* CONFIG_IEEE80211R_AP */ + unsigned int is_wnmsleep:1; + unsigned int pmkid_set:1; + + unsigned int ptkstart_without_success; + +#ifdef CONFIG_OCV + int ocv_enabled; +#endif /* CONFIG_OCV */ + + u8 req_replay_counter[WPA_REPLAY_COUNTER_LEN]; + int req_replay_counter_used; + + u8 *wpa_ie; + size_t wpa_ie_len; + u8 *rsnxe; + size_t rsnxe_len; + + enum { + WPA_VERSION_NO_WPA = 0 /* WPA not used */, + WPA_VERSION_WPA = 1 /* WPA / IEEE 802.11i/D3.0 */, + WPA_VERSION_WPA2 = 2 /* WPA2 / IEEE 802.11i */ + } wpa; + int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */ + int wpa_key_mgmt; /* the selected WPA_KEY_MGMT_* */ + struct rsn_pmksa_cache_entry *pmksa; + + u32 dot11RSNAStatsTKIPLocalMICFailures; + u32 dot11RSNAStatsTKIPRemoteMICFailures; + +#ifdef CONFIG_IEEE80211R_AP + u8 xxkey[PMK_LEN_MAX]; /* PSK or the second 256 bits of MSK, or the + * first 384 bits of MSK */ + size_t xxkey_len; + u8 pmk_r1[PMK_LEN_MAX]; + unsigned int pmk_r1_len; + u8 pmk_r1_name[WPA_PMK_NAME_LEN]; /* PMKR1Name derived from FT Auth + * Request */ + u8 r0kh_id[FT_R0KH_ID_MAX_LEN]; /* R0KH-ID from FT Auth Request */ + size_t r0kh_id_len; + u8 *assoc_resp_ftie; + + void (*ft_pending_cb)(void *ctx, const u8 *dst, + u16 auth_transaction, u16 status, + const u8 *ies, size_t ies_len); + void *ft_pending_cb_ctx; + struct wpabuf *ft_pending_req_ies; + u8 ft_pending_pull_nonce[FT_RRB_NONCE_LEN]; + u8 ft_pending_auth_transaction; + u8 ft_pending_current_ap[ETH_ALEN]; + int ft_pending_pull_left_retries; +#endif /* CONFIG_IEEE80211R_AP */ + + int pending_1_of_4_timeout; + +#ifdef CONFIG_P2P + u8 ip_addr[4]; + unsigned int ip_addr_bit; +#endif /* CONFIG_P2P */ + +#ifdef CONFIG_FILS + u8 fils_key_auth_sta[FILS_MAX_KEY_AUTH_LEN]; + u8 fils_key_auth_ap[FILS_MAX_KEY_AUTH_LEN]; + size_t fils_key_auth_len; + unsigned int fils_completed:1; +#endif /* CONFIG_FILS */ + +#ifdef CONFIG_DPP2 + struct wpabuf *dpp_z; +#endif /* CONFIG_DPP2 */ + +#ifdef CONFIG_TESTING_OPTIONS + void (*eapol_status_cb)(void *ctx1, void *ctx2); + void *eapol_status_cb_ctx1; + void *eapol_status_cb_ctx2; +#endif /* CONFIG_TESTING_OPTIONS */ + +#ifdef CONFIG_IEEE80211BE + u8 peer_mld_addr[ETH_ALEN]; + s8 mld_assoc_link_id; + u8 n_mld_affiliated_links; + + struct mld_link { + bool valid; + u8 peer_addr[ETH_ALEN]; + + struct wpa_authenticator *wpa_auth; + } mld_links[MAX_NUM_MLD_LINKS]; +#endif /* CONFIG_IEEE80211BE */ + + bool ssid_protection; +}; + + +/* per group key state machine data */ +struct wpa_group { + struct wpa_group *next; + int vlan_id; + + bool GInit; + int GKeyDoneStations; + bool GTKReKey; + int GTK_len; + int GN, GM; + bool GTKAuthenticator; + u8 Counter[WPA_NONCE_LEN]; + + enum { + WPA_GROUP_GTK_INIT = 0, + WPA_GROUP_SETKEYS, WPA_GROUP_SETKEYSDONE, + WPA_GROUP_FATAL_FAILURE + } wpa_group_state; + + u8 GMK[WPA_GMK_LEN]; + u8 GTK[2][WPA_GTK_MAX_LEN]; + u8 GNonce[WPA_NONCE_LEN]; + bool changed; + bool first_sta_seen; + bool reject_4way_hs_for_entropy; + u8 IGTK[2][WPA_IGTK_MAX_LEN]; + u8 BIGTK[2][WPA_IGTK_MAX_LEN]; + int GN_igtk, GM_igtk; + int GN_bigtk, GM_bigtk; + bool bigtk_set; + bool bigtk_configured; + /* Number of references except those in struct wpa_group->next */ + unsigned int references; + unsigned int num_setup_iface; +}; + + +struct wpa_ft_pmk_cache; + +/* per authenticator data */ +struct wpa_authenticator { + struct wpa_group *group; + + unsigned int dot11RSNAStatsTKIPRemoteMICFailures; + u32 dot11RSNAAuthenticationSuiteSelected; + u32 dot11RSNAPairwiseCipherSelected; + u32 dot11RSNAGroupCipherSelected; + u8 dot11RSNAPMKIDUsed[PMKID_LEN]; + u32 dot11RSNAAuthenticationSuiteRequested; /* FIX: update */ + u32 dot11RSNAPairwiseCipherRequested; /* FIX: update */ + u32 dot11RSNAGroupCipherRequested; /* FIX: update */ + unsigned int dot11RSNATKIPCounterMeasuresInvoked; + unsigned int dot11RSNA4WayHandshakeFailures; + + struct wpa_auth_config conf; + const struct wpa_auth_callbacks *cb; + void *cb_ctx; + + u8 *wpa_ie; + size_t wpa_ie_len; + + u8 addr[ETH_ALEN]; + + struct rsn_pmksa_cache *pmksa; + struct wpa_ft_pmk_cache *ft_pmk_cache; + + bool non_tx_beacon_prot; + +#ifdef CONFIG_P2P + struct bitfield *ip_pool; +#endif /* CONFIG_P2P */ + +#ifdef CONFIG_IEEE80211BE + bool is_ml; + u8 mld_addr[ETH_ALEN]; + u8 link_id; + bool primary_auth; +#endif /* CONFIG_IEEE80211BE */ +}; + + +#ifdef CONFIG_IEEE80211R_AP + +#define FT_REMOTE_SEQ_BACKLOG 16 +struct ft_remote_seq_rx { + u32 dom; + struct os_reltime time_offset; /* local time - offset = remote time */ + + /* accepted sequence numbers: (offset ... offset + 0x40000000] + * (except those in last) + * dropped sequence numbers: (offset - 0x40000000 ... offset] + * all others trigger SEQ_REQ message (except first message) + */ + u32 last[FT_REMOTE_SEQ_BACKLOG]; + unsigned int num_last; + u32 offsetidx; + + struct dl_list queue; /* send nonces + rrb msgs awaiting seq resp */ +}; + +struct ft_remote_seq_tx { + u32 dom; /* non zero if initialized */ + u32 seq; +}; + +struct ft_remote_seq { + struct ft_remote_seq_rx rx; + struct ft_remote_seq_tx tx; +}; + +#endif /* CONFIG_IEEE80211R_AP */ + + +int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len, + const u8 *pmkid); +int wpa_write_rsnxe(struct wpa_auth_config *conf, u8 *buf, size_t len); +void wpa_auth_logger(struct wpa_authenticator *wpa_auth, const u8 *addr, + logger_level level, const char *txt); +void wpa_auth_vlogger(struct wpa_authenticator *wpa_auth, const u8 *addr, + logger_level level, const char *fmt, ...) + PRINTF_FORMAT(4, 5); +void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int key_info, + const u8 *key_rsc, const u8 *nonce, + const u8 *kde, size_t kde_len, + int keyidx, int encr, int force_version); +int wpa_auth_for_each_sta(struct wpa_authenticator *wpa_auth, + int (*cb)(struct wpa_state_machine *sm, void *ctx), + void *cb_ctx); +int wpa_auth_for_each_auth(struct wpa_authenticator *wpa_auth, + int (*cb)(struct wpa_authenticator *a, void *ctx), + void *cb_ctx); +void wpa_auth_store_ptksa(struct wpa_authenticator *wpa_auth, + const u8 *addr, int cipher, + u32 life_time, const struct wpa_ptk *ptk); + +#ifdef CONFIG_IEEE80211R_AP +int wpa_write_mdie(struct wpa_auth_config *conf, u8 *buf, size_t len); +int wpa_write_ftie(struct wpa_auth_config *conf, int key_mgmt, size_t key_len, + const u8 *r0kh_id, size_t r0kh_id_len, + const u8 *anonce, const u8 *snonce, + u8 *buf, size_t len, const u8 *subelem, + size_t subelem_len, int rsnxe_used); +int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, struct wpa_ptk *ptk, + u8 *pmk_r0, u8 *pmk_r1, u8 *pmk_r0_name, + size_t *key_len, size_t kdk_len); +void wpa_auth_ft_store_keys(struct wpa_state_machine *sm, const u8 *pmk_r0, + const u8 *pmk_r1, const u8 *pmk_r0_name, + size_t key_len); +struct wpa_ft_pmk_cache * wpa_ft_pmk_cache_init(void); +void wpa_ft_pmk_cache_deinit(struct wpa_ft_pmk_cache *cache); +void wpa_ft_install_ptk(struct wpa_state_machine *sm, int retry); +int wpa_ft_store_pmk_fils(struct wpa_state_machine *sm, const u8 *pmk_r0, + const u8 *pmk_r0_name); +#endif /* CONFIG_IEEE80211R_AP */ + +#endif /* WPA_AUTH_I_H */ diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c new file mode 100644 index 0000000..2efadf8 --- /dev/null +++ b/src/ap/wpa_auth_ie.c @@ -0,0 +1,1299 @@ +/* + * hostapd - WPA/RSN IE and KDE definitions + * Copyright (c) 2004-2018, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "drivers/driver.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "ap_config.h" +#include "ieee802_11.h" +#include "wpa_auth.h" +#include "pmksa_cache_auth.h" +#include "wpa_auth_ie.h" +#include "wpa_auth_i.h" + + +#ifdef CONFIG_RSN_TESTING +int rsn_testing = 0; +#endif /* CONFIG_RSN_TESTING */ + + +static int wpa_write_wpa_ie(struct wpa_auth_config *conf, u8 *buf, size_t len) +{ + struct wpa_ie_hdr *hdr; + int num_suites; + u8 *pos, *count; + u32 suite; + + hdr = (struct wpa_ie_hdr *) buf; + hdr->elem_id = WLAN_EID_VENDOR_SPECIFIC; + RSN_SELECTOR_PUT(hdr->oui, WPA_OUI_TYPE); + WPA_PUT_LE16(hdr->version, WPA_VERSION); + pos = (u8 *) (hdr + 1); + + suite = wpa_cipher_to_suite(WPA_PROTO_WPA, conf->wpa_group); + if (suite == 0) { + wpa_printf(MSG_DEBUG, "Invalid group cipher (%d).", + conf->wpa_group); + return -1; + } + RSN_SELECTOR_PUT(pos, suite); + pos += WPA_SELECTOR_LEN; + + count = pos; + pos += 2; + + num_suites = wpa_cipher_put_suites(pos, conf->wpa_pairwise); + if (num_suites == 0) { + wpa_printf(MSG_DEBUG, "Invalid pairwise cipher (%d).", + conf->wpa_pairwise); + return -1; + } + pos += num_suites * WPA_SELECTOR_LEN; + WPA_PUT_LE16(count, num_suites); + + num_suites = 0; + count = pos; + pos += 2; + + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X) { + RSN_SELECTOR_PUT(pos, WPA_AUTH_KEY_MGMT_UNSPEC_802_1X); + pos += WPA_SELECTOR_LEN; + num_suites++; + } + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_PSK) { + RSN_SELECTOR_PUT(pos, WPA_AUTH_KEY_MGMT_PSK_OVER_802_1X); + pos += WPA_SELECTOR_LEN; + num_suites++; + } + + if (num_suites == 0) { + wpa_printf(MSG_DEBUG, "Invalid key management type (%d).", + conf->wpa_key_mgmt); + return -1; + } + WPA_PUT_LE16(count, num_suites); + + /* WPA Capabilities; use defaults, so no need to include it */ + + hdr->len = (pos - buf) - 2; + + return pos - buf; +} + + +static u16 wpa_own_rsn_capab(struct wpa_auth_config *conf) +{ + u16 capab = 0; + + if (conf->rsn_preauth) + capab |= WPA_CAPABILITY_PREAUTH; + if (conf->wmm_enabled) { + /* 4 PTKSA replay counters when using WMM */ + capab |= (RSN_NUM_REPLAY_COUNTERS_16 << 2); + } + if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + capab |= WPA_CAPABILITY_MFPC; + if (conf->ieee80211w == MGMT_FRAME_PROTECTION_REQUIRED) + capab |= WPA_CAPABILITY_MFPR; + } +#ifdef CONFIG_OCV + if (conf->ocv) + capab |= WPA_CAPABILITY_OCVC; +#endif /* CONFIG_OCV */ +#ifdef CONFIG_RSN_TESTING + if (rsn_testing) + capab |= BIT(8) | BIT(15); +#endif /* CONFIG_RSN_TESTING */ + if (conf->extended_key_id) + capab |= WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST; + + return capab; +} + + +int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len, + const u8 *pmkid) +{ + struct rsn_ie_hdr *hdr; + int num_suites, res; + u8 *pos, *count; + u32 suite; + + hdr = (struct rsn_ie_hdr *) buf; + hdr->elem_id = WLAN_EID_RSN; + WPA_PUT_LE16(hdr->version, RSN_VERSION); + pos = (u8 *) (hdr + 1); + + suite = wpa_cipher_to_suite(WPA_PROTO_RSN, conf->wpa_group); + if (suite == 0) { + wpa_printf(MSG_DEBUG, "Invalid group cipher (%d).", + conf->wpa_group); + return -1; + } + RSN_SELECTOR_PUT(pos, suite); + pos += RSN_SELECTOR_LEN; + + num_suites = 0; + count = pos; + pos += 2; + +#ifdef CONFIG_RSN_TESTING + if (rsn_testing) { + RSN_SELECTOR_PUT(pos, RSN_SELECTOR(0x12, 0x34, 0x56, 1)); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_RSN_TESTING */ + + res = rsn_cipher_put_suites(pos, conf->rsn_pairwise); + num_suites += res; + pos += res * RSN_SELECTOR_LEN; + +#ifdef CONFIG_RSN_TESTING + if (rsn_testing) { + RSN_SELECTOR_PUT(pos, RSN_SELECTOR(0x12, 0x34, 0x56, 2)); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_RSN_TESTING */ + + if (num_suites == 0) { + wpa_printf(MSG_DEBUG, "Invalid pairwise cipher (%d).", + conf->rsn_pairwise); + return -1; + } + WPA_PUT_LE16(count, num_suites); + + num_suites = 0; + count = pos; + pos += 2; + +#ifdef CONFIG_RSN_TESTING + if (rsn_testing) { + RSN_SELECTOR_PUT(pos, RSN_SELECTOR(0x12, 0x34, 0x56, 1)); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_RSN_TESTING */ + + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_UNSPEC_802_1X); + pos += RSN_SELECTOR_LEN; + num_suites++; + } + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_PSK) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_PSK_OVER_802_1X); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#ifdef CONFIG_IEEE80211R_AP + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FT_802_1X); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#ifdef CONFIG_SHA384 + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X_SHA384) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FT_802_1X_SHA384); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_SHA384 */ + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_PSK) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FT_PSK); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_SHA384 + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_802_1X_SHA384); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_SHA384 */ + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_802_1X_SHA256); + pos += RSN_SELECTOR_LEN; + num_suites++; + } + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_PSK_SHA256) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_PSK_SHA256); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#ifdef CONFIG_SAE + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_SAE) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_SAE); + pos += RSN_SELECTOR_LEN; + num_suites++; + } + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_SAE_EXT_KEY) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_SAE_EXT_KEY); + pos += RSN_SELECTOR_LEN; + num_suites++; + } + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_SAE) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FT_SAE); + pos += RSN_SELECTOR_LEN; + num_suites++; + } + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_SAE_EXT_KEY) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FT_SAE_EXT_KEY); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_SAE */ + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X_SUITE_B) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_802_1X_SUITE_B); + pos += RSN_SELECTOR_LEN; + num_suites++; + } + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X_SUITE_B_192) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_802_1X_SUITE_B_192); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#ifdef CONFIG_FILS + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FILS_SHA256) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FILS_SHA256); + pos += RSN_SELECTOR_LEN; + num_suites++; + } + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FILS_SHA384) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FILS_SHA384); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#ifdef CONFIG_IEEE80211R_AP + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA256) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FT_FILS_SHA256); + pos += RSN_SELECTOR_LEN; + num_suites++; + } + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA384) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_FT_FILS_SHA384); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_IEEE80211R_AP */ +#endif /* CONFIG_FILS */ +#ifdef CONFIG_OWE + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_OWE); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_OWE */ +#ifdef CONFIG_DPP + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_DPP) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_DPP); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_DPP */ +#ifdef CONFIG_HS20 + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_OSEN) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_OSEN); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_HS20 */ +#ifdef CONFIG_PASN + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_PASN) { + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_PASN); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_PASN */ + +#ifdef CONFIG_RSN_TESTING + if (rsn_testing) { + RSN_SELECTOR_PUT(pos, RSN_SELECTOR(0x12, 0x34, 0x56, 2)); + pos += RSN_SELECTOR_LEN; + num_suites++; + } +#endif /* CONFIG_RSN_TESTING */ + + if (num_suites == 0) { + wpa_printf(MSG_DEBUG, "Invalid key management type (%d).", + conf->wpa_key_mgmt); + return -1; + } + WPA_PUT_LE16(count, num_suites); + + /* RSN Capabilities */ + WPA_PUT_LE16(pos, wpa_own_rsn_capab(conf)); + pos += 2; + + if (pmkid) { + if (2 + PMKID_LEN > buf + len - pos) + return -1; + /* PMKID Count */ + WPA_PUT_LE16(pos, 1); + pos += 2; + os_memcpy(pos, pmkid, PMKID_LEN); + pos += PMKID_LEN; + } + + if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION && + conf->group_mgmt_cipher != WPA_CIPHER_AES_128_CMAC) { + if (2 + 4 > buf + len - pos) + return -1; + if (pmkid == NULL) { + /* PMKID Count */ + WPA_PUT_LE16(pos, 0); + pos += 2; + } + + /* Management Group Cipher Suite */ + switch (conf->group_mgmt_cipher) { + case WPA_CIPHER_AES_128_CMAC: + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_AES_128_CMAC); + break; + case WPA_CIPHER_BIP_GMAC_128: + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_BIP_GMAC_128); + break; + case WPA_CIPHER_BIP_GMAC_256: + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_BIP_GMAC_256); + break; + case WPA_CIPHER_BIP_CMAC_256: + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_BIP_CMAC_256); + break; + default: + wpa_printf(MSG_DEBUG, + "Invalid group management cipher (0x%x)", + conf->group_mgmt_cipher); + return -1; + } + pos += RSN_SELECTOR_LEN; + } + +#ifdef CONFIG_RSN_TESTING + if (rsn_testing) { + /* + * Fill in any defined fields and add extra data to the end of + * the element. + */ + int pmkid_count_set = pmkid != NULL; + if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) + pmkid_count_set = 1; + /* PMKID Count */ + WPA_PUT_LE16(pos, 0); + pos += 2; + if (conf->ieee80211w == NO_MGMT_FRAME_PROTECTION) { + /* Management Group Cipher Suite */ + RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_AES_128_CMAC); + pos += RSN_SELECTOR_LEN; + } + + os_memset(pos, 0x12, 17); + pos += 17; + } +#endif /* CONFIG_RSN_TESTING */ + + hdr->len = (pos - buf) - 2; + + return pos - buf; +} + + +int wpa_write_rsnxe(struct wpa_auth_config *conf, u8 *buf, size_t len) +{ + u8 *pos = buf; + u32 capab = 0, tmp; + size_t flen; + + if (wpa_key_mgmt_sae(conf->wpa_key_mgmt) && + (conf->sae_pwe == SAE_PWE_HASH_TO_ELEMENT || + conf->sae_pwe == SAE_PWE_BOTH || conf->sae_pk || + wpa_key_mgmt_sae_ext_key(conf->wpa_key_mgmt))) { + capab |= BIT(WLAN_RSNX_CAPAB_SAE_H2E); +#ifdef CONFIG_SAE_PK + if (conf->sae_pk) + capab |= BIT(WLAN_RSNX_CAPAB_SAE_PK); +#endif /* CONFIG_SAE_PK */ + } + + if (conf->secure_ltf) + capab |= BIT(WLAN_RSNX_CAPAB_SECURE_LTF); + if (conf->secure_rtt) + capab |= BIT(WLAN_RSNX_CAPAB_SECURE_RTT); + if (conf->prot_range_neg) + capab |= BIT(WLAN_RSNX_CAPAB_URNM_MFPR); + if (conf->ssid_protection) + capab |= BIT(WLAN_RSNX_CAPAB_SSID_PROTECTION); + + if (!capab) + return 0; /* no supported extended RSN capabilities */ + tmp = capab; + flen = 0; + while (tmp) { + flen++; + tmp >>= 8; + } + if (len < 2 + flen) + return -1; + capab |= flen - 1; /* bit 0-3 = Field length (n - 1) */ + + *pos++ = WLAN_EID_RSNX; + *pos++ = flen; + while (capab) { + *pos++ = capab & 0xff; + capab >>= 8; + } + + return pos - buf; +} + + +static u8 * wpa_write_osen(struct wpa_auth_config *conf, u8 *eid) +{ + u8 *len; + u16 capab; + + *eid++ = WLAN_EID_VENDOR_SPECIFIC; + len = eid++; /* to be filled */ + WPA_PUT_BE24(eid, OUI_WFA); + eid += 3; + *eid++ = HS20_OSEN_OUI_TYPE; + + /* Group Data Cipher Suite */ + RSN_SELECTOR_PUT(eid, RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED); + eid += RSN_SELECTOR_LEN; + + /* Pairwise Cipher Suite Count and List */ + WPA_PUT_LE16(eid, 1); + eid += 2; + RSN_SELECTOR_PUT(eid, RSN_CIPHER_SUITE_CCMP); + eid += RSN_SELECTOR_LEN; + + /* AKM Suite Count and List */ + WPA_PUT_LE16(eid, 1); + eid += 2; + RSN_SELECTOR_PUT(eid, RSN_AUTH_KEY_MGMT_OSEN); + eid += RSN_SELECTOR_LEN; + + /* RSN Capabilities */ + capab = 0; + if (conf->wmm_enabled) { + /* 4 PTKSA replay counters when using WMM */ + capab |= (RSN_NUM_REPLAY_COUNTERS_16 << 2); + } + if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + capab |= WPA_CAPABILITY_MFPC; + if (conf->ieee80211w == MGMT_FRAME_PROTECTION_REQUIRED) + capab |= WPA_CAPABILITY_MFPR; + } +#ifdef CONFIG_OCV + if (conf->ocv) + capab |= WPA_CAPABILITY_OCVC; +#endif /* CONFIG_OCV */ + WPA_PUT_LE16(eid, capab); + eid += 2; + + *len = eid - len - 1; + + return eid; +} + + +int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth) +{ + u8 *pos, buf[128]; + int res; + +#ifdef CONFIG_TESTING_OPTIONS + if (wpa_auth->conf.own_ie_override_len) { + wpa_hexdump(MSG_DEBUG, "WPA: Forced own IE(s) for testing", + wpa_auth->conf.own_ie_override, + wpa_auth->conf.own_ie_override_len); + os_free(wpa_auth->wpa_ie); + wpa_auth->wpa_ie = + os_malloc(wpa_auth->conf.own_ie_override_len); + if (wpa_auth->wpa_ie == NULL) + return -1; + os_memcpy(wpa_auth->wpa_ie, wpa_auth->conf.own_ie_override, + wpa_auth->conf.own_ie_override_len); + wpa_auth->wpa_ie_len = wpa_auth->conf.own_ie_override_len; + return 0; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + pos = buf; + + if (wpa_auth->conf.wpa == WPA_PROTO_OSEN) { + pos = wpa_write_osen(&wpa_auth->conf, pos); + } + if (wpa_auth->conf.wpa & WPA_PROTO_RSN) { + res = wpa_write_rsn_ie(&wpa_auth->conf, + pos, buf + sizeof(buf) - pos, NULL); + if (res < 0) + return res; + pos += res; + res = wpa_write_rsnxe(&wpa_auth->conf, pos, + buf + sizeof(buf) - pos); + if (res < 0) + return res; + pos += res; + } +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(wpa_auth->conf.wpa_key_mgmt)) { + res = wpa_write_mdie(&wpa_auth->conf, pos, + buf + sizeof(buf) - pos); + if (res < 0) + return res; + pos += res; + } +#endif /* CONFIG_IEEE80211R_AP */ + if (wpa_auth->conf.wpa & WPA_PROTO_WPA) { + res = wpa_write_wpa_ie(&wpa_auth->conf, + pos, buf + sizeof(buf) - pos); + if (res < 0) + return res; + pos += res; + } + + os_free(wpa_auth->wpa_ie); + wpa_auth->wpa_ie = os_malloc(pos - buf); + if (wpa_auth->wpa_ie == NULL) + return -1; + os_memcpy(wpa_auth->wpa_ie, buf, pos - buf); + wpa_auth->wpa_ie_len = pos - buf; + + return 0; +} + + +u8 * wpa_add_kde(u8 *pos, u32 kde, const u8 *data, size_t data_len, + const u8 *data2, size_t data2_len) +{ + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + *pos++ = RSN_SELECTOR_LEN + data_len + data2_len; + RSN_SELECTOR_PUT(pos, kde); + pos += RSN_SELECTOR_LEN; + os_memcpy(pos, data, data_len); + pos += data_len; + if (data2) { + os_memcpy(pos, data2, data2_len); + pos += data2_len; + } + return pos; +} + + +struct wpa_auth_okc_iter_data { + struct rsn_pmksa_cache_entry *pmksa; + const u8 *aa; + const u8 *spa; + const u8 *pmkid; +}; + + +static int wpa_auth_okc_iter(struct wpa_authenticator *a, void *ctx) +{ + struct wpa_auth_okc_iter_data *data = ctx; + data->pmksa = pmksa_cache_get_okc(a->pmksa, data->aa, data->spa, + data->pmkid); + if (data->pmksa) + return 1; + return 0; +} + + +enum wpa_validate_result +wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, int freq, + const u8 *wpa_ie, size_t wpa_ie_len, + const u8 *rsnxe, size_t rsnxe_len, + const u8 *mdie, size_t mdie_len, + const u8 *owe_dh, size_t owe_dh_len, + struct wpa_state_machine *assoc_sm) +{ + struct wpa_auth_config *conf = &wpa_auth->conf; + struct wpa_ie_data data; + int ciphers, key_mgmt, res, version; + u32 selector; + size_t i; + const u8 *pmkid = NULL; + + if (wpa_auth == NULL || sm == NULL) + return WPA_NOT_ENABLED; + + if (wpa_ie == NULL || wpa_ie_len < 1) + return WPA_INVALID_IE; + + if (wpa_ie[0] == WLAN_EID_RSN) + version = WPA_PROTO_RSN; + else + version = WPA_PROTO_WPA; + + if (!(wpa_auth->conf.wpa & version)) { + wpa_printf(MSG_DEBUG, "Invalid WPA proto (%d) from " MACSTR, + version, MAC2STR(sm->addr)); + return WPA_INVALID_PROTO; + } + + if (version == WPA_PROTO_RSN) { + res = wpa_parse_wpa_ie_rsn(wpa_ie, wpa_ie_len, &data); + if (!data.has_pairwise) + data.pairwise_cipher = wpa_default_rsn_cipher(freq); + if (!data.has_group) + data.group_cipher = wpa_default_rsn_cipher(freq); + + if (wpa_key_mgmt_ft(data.key_mgmt) && !mdie && + !wpa_key_mgmt_only_ft(data.key_mgmt)) { + /* Workaround for some HP and Epson printers that seem + * to incorrectly copy the FT-PSK + WPA-PSK AKMs from AP + * advertised RSNE to Association Request frame. */ + wpa_printf(MSG_DEBUG, + "RSN: FT set in RSNE AKM but MDE is missing from " + MACSTR + " - ignore FT AKM(s) because there's also a non-FT AKM", + MAC2STR(sm->addr)); + data.key_mgmt &= ~WPA_KEY_MGMT_FT; + } + + selector = RSN_AUTH_KEY_MGMT_UNSPEC_802_1X; + if (0) { + } + else if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X_SUITE_B_192) + selector = RSN_AUTH_KEY_MGMT_802_1X_SUITE_B_192; + else if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X_SUITE_B) + selector = RSN_AUTH_KEY_MGMT_802_1X_SUITE_B; +#ifdef CONFIG_FILS +#ifdef CONFIG_IEEE80211R_AP + else if (data.key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA384) + selector = RSN_AUTH_KEY_MGMT_FT_FILS_SHA384; + else if (data.key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA256) + selector = RSN_AUTH_KEY_MGMT_FT_FILS_SHA256; +#endif /* CONFIG_IEEE80211R_AP */ + else if (data.key_mgmt & WPA_KEY_MGMT_FILS_SHA384) + selector = RSN_AUTH_KEY_MGMT_FILS_SHA384; + else if (data.key_mgmt & WPA_KEY_MGMT_FILS_SHA256) + selector = RSN_AUTH_KEY_MGMT_FILS_SHA256; +#endif /* CONFIG_FILS */ +#ifdef CONFIG_IEEE80211R_AP +#ifdef CONFIG_SHA384 + else if (data.key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X_SHA384) + selector = RSN_AUTH_KEY_MGMT_FT_802_1X_SHA384; +#endif /* CONFIG_SHA384 */ + else if (data.key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X) + selector = RSN_AUTH_KEY_MGMT_FT_802_1X; + else if (data.key_mgmt & WPA_KEY_MGMT_FT_PSK) + selector = RSN_AUTH_KEY_MGMT_FT_PSK; +#endif /* CONFIG_IEEE80211R_AP */ + else if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256) + selector = RSN_AUTH_KEY_MGMT_802_1X_SHA256; + else if (data.key_mgmt & WPA_KEY_MGMT_PSK_SHA256) + selector = RSN_AUTH_KEY_MGMT_PSK_SHA256; +#ifdef CONFIG_SAE + else if (data.key_mgmt & WPA_KEY_MGMT_SAE) + selector = RSN_AUTH_KEY_MGMT_SAE; + else if (data.key_mgmt & WPA_KEY_MGMT_SAE_EXT_KEY) + selector = RSN_AUTH_KEY_MGMT_SAE_EXT_KEY; + else if (data.key_mgmt & WPA_KEY_MGMT_FT_SAE) + selector = RSN_AUTH_KEY_MGMT_FT_SAE; + else if (data.key_mgmt & WPA_KEY_MGMT_FT_SAE_EXT_KEY) + selector = RSN_AUTH_KEY_MGMT_FT_SAE_EXT_KEY; +#endif /* CONFIG_SAE */ + else if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X) + selector = RSN_AUTH_KEY_MGMT_UNSPEC_802_1X; + else if (data.key_mgmt & WPA_KEY_MGMT_PSK) + selector = RSN_AUTH_KEY_MGMT_PSK_OVER_802_1X; +#ifdef CONFIG_OWE + else if (data.key_mgmt & WPA_KEY_MGMT_OWE) + selector = RSN_AUTH_KEY_MGMT_OWE; +#endif /* CONFIG_OWE */ +#ifdef CONFIG_DPP + else if (data.key_mgmt & WPA_KEY_MGMT_DPP) + selector = RSN_AUTH_KEY_MGMT_DPP; +#endif /* CONFIG_DPP */ +#ifdef CONFIG_HS20 + else if (data.key_mgmt & WPA_KEY_MGMT_OSEN) + selector = RSN_AUTH_KEY_MGMT_OSEN; +#endif /* CONFIG_HS20 */ +#ifdef CONFIG_SHA384 + else if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384) + selector = RSN_AUTH_KEY_MGMT_802_1X_SHA384; +#endif /* CONFIG_SHA384 */ + wpa_auth->dot11RSNAAuthenticationSuiteSelected = selector; + + selector = wpa_cipher_to_suite(WPA_PROTO_RSN, + data.pairwise_cipher); + if (!selector) + selector = RSN_CIPHER_SUITE_CCMP; + wpa_auth->dot11RSNAPairwiseCipherSelected = selector; + + selector = wpa_cipher_to_suite(WPA_PROTO_RSN, + data.group_cipher); + if (!selector) + selector = RSN_CIPHER_SUITE_CCMP; + wpa_auth->dot11RSNAGroupCipherSelected = selector; + } else { + res = wpa_parse_wpa_ie_wpa(wpa_ie, wpa_ie_len, &data); + + selector = WPA_AUTH_KEY_MGMT_UNSPEC_802_1X; + if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X) + selector = WPA_AUTH_KEY_MGMT_UNSPEC_802_1X; + else if (data.key_mgmt & WPA_KEY_MGMT_PSK) + selector = WPA_AUTH_KEY_MGMT_PSK_OVER_802_1X; + wpa_auth->dot11RSNAAuthenticationSuiteSelected = selector; + + selector = wpa_cipher_to_suite(WPA_PROTO_WPA, + data.pairwise_cipher); + if (!selector) + selector = RSN_CIPHER_SUITE_TKIP; + wpa_auth->dot11RSNAPairwiseCipherSelected = selector; + + selector = wpa_cipher_to_suite(WPA_PROTO_WPA, + data.group_cipher); + if (!selector) + selector = WPA_CIPHER_SUITE_TKIP; + wpa_auth->dot11RSNAGroupCipherSelected = selector; + } + if (res) { + wpa_printf(MSG_DEBUG, "Failed to parse WPA/RSN IE from " + MACSTR " (res=%d)", MAC2STR(sm->addr), res); + wpa_hexdump(MSG_DEBUG, "WPA/RSN IE", wpa_ie, wpa_ie_len); + return WPA_INVALID_IE; + } + + if (data.group_cipher != wpa_auth->conf.wpa_group) { + wpa_printf(MSG_DEBUG, "Invalid WPA group cipher (0x%x) from " + MACSTR, data.group_cipher, MAC2STR(sm->addr)); + return WPA_INVALID_GROUP; + } + + key_mgmt = data.key_mgmt & wpa_auth->conf.wpa_key_mgmt; + if (!key_mgmt) { + wpa_printf(MSG_DEBUG, "Invalid WPA key mgmt (0x%x) from " + MACSTR, data.key_mgmt, MAC2STR(sm->addr)); + return WPA_INVALID_AKMP; + } + if (0) { + } + else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X_SUITE_B_192) + sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SUITE_B_192; + else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X_SUITE_B) + sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SUITE_B; +#ifdef CONFIG_FILS +#ifdef CONFIG_IEEE80211R_AP + else if (key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA384) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_FILS_SHA384; + else if (data.key_mgmt & WPA_KEY_MGMT_FT_FILS_SHA256) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_FILS_SHA256; +#endif /* CONFIG_IEEE80211R_AP */ + else if (key_mgmt & WPA_KEY_MGMT_FILS_SHA384) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FILS_SHA384; + else if (key_mgmt & WPA_KEY_MGMT_FILS_SHA256) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FILS_SHA256; +#endif /* CONFIG_FILS */ +#ifdef CONFIG_IEEE80211R_AP +#ifdef CONFIG_SHA384 + else if (key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X_SHA384) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_IEEE8021X_SHA384; +#endif /* CONFIG_SHA384 */ + else if (key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_IEEE8021X; + else if (key_mgmt & WPA_KEY_MGMT_FT_PSK) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_PSK; +#endif /* CONFIG_IEEE80211R_AP */ +#ifdef CONFIG_SHA384 + else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384) + sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SHA384; +#endif /* CONFIG_SHA384 */ + else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256) + sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SHA256; + else if (key_mgmt & WPA_KEY_MGMT_PSK_SHA256) + sm->wpa_key_mgmt = WPA_KEY_MGMT_PSK_SHA256; +#ifdef CONFIG_SAE + else if (key_mgmt & WPA_KEY_MGMT_SAE) + sm->wpa_key_mgmt = WPA_KEY_MGMT_SAE; + else if (key_mgmt & WPA_KEY_MGMT_SAE_EXT_KEY) + sm->wpa_key_mgmt = WPA_KEY_MGMT_SAE_EXT_KEY; + else if (key_mgmt & WPA_KEY_MGMT_FT_SAE) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_SAE; + else if (key_mgmt & WPA_KEY_MGMT_FT_SAE_EXT_KEY) + sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_SAE_EXT_KEY; +#endif /* CONFIG_SAE */ + else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X) + sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X; +#ifdef CONFIG_OWE + else if (key_mgmt & WPA_KEY_MGMT_OWE) + sm->wpa_key_mgmt = WPA_KEY_MGMT_OWE; +#endif /* CONFIG_OWE */ +#ifdef CONFIG_DPP + else if (key_mgmt & WPA_KEY_MGMT_DPP) + sm->wpa_key_mgmt = WPA_KEY_MGMT_DPP; +#endif /* CONFIG_DPP */ +#ifdef CONFIG_HS20 + else if (key_mgmt & WPA_KEY_MGMT_OSEN) + sm->wpa_key_mgmt = WPA_KEY_MGMT_OSEN; +#endif /* CONFIG_HS20 */ + else + sm->wpa_key_mgmt = WPA_KEY_MGMT_PSK; + + if (version == WPA_PROTO_RSN) + ciphers = data.pairwise_cipher & wpa_auth->conf.rsn_pairwise; + else + ciphers = data.pairwise_cipher & wpa_auth->conf.wpa_pairwise; + if (!ciphers) { + wpa_printf(MSG_DEBUG, "Invalid %s pairwise cipher (0x%x) " + "from " MACSTR, + version == WPA_PROTO_RSN ? "RSN" : "WPA", + data.pairwise_cipher, MAC2STR(sm->addr)); + return WPA_INVALID_PAIRWISE; + } + + if (wpa_auth->conf.ieee80211w == MGMT_FRAME_PROTECTION_REQUIRED) { + if (!(data.capabilities & WPA_CAPABILITY_MFPC)) { + wpa_printf(MSG_DEBUG, "Management frame protection " + "required, but client did not enable it"); + return WPA_MGMT_FRAME_PROTECTION_VIOLATION; + } + + if (data.mgmt_group_cipher != wpa_auth->conf.group_mgmt_cipher) + { + wpa_printf(MSG_DEBUG, "Unsupported management group " + "cipher %d", data.mgmt_group_cipher); + return WPA_INVALID_MGMT_GROUP_CIPHER; + } + } + +#ifdef CONFIG_SAE + if (wpa_auth->conf.ieee80211w == MGMT_FRAME_PROTECTION_OPTIONAL && + wpa_auth->conf.sae_require_mfp && + wpa_key_mgmt_sae(sm->wpa_key_mgmt) && + !(data.capabilities & WPA_CAPABILITY_MFPC)) { + wpa_printf(MSG_DEBUG, + "Management frame protection required with SAE, but client did not enable it"); + return WPA_MGMT_FRAME_PROTECTION_VIOLATION; + } +#endif /* CONFIG_SAE */ + +#ifdef CONFIG_OCV + if (wpa_auth->conf.ocv && (data.capabilities & WPA_CAPABILITY_OCVC) && + !(data.capabilities & WPA_CAPABILITY_MFPC)) { + /* Some legacy MFP incapable STAs wrongly copy OCVC bit from + * AP RSN capabilities. To improve interoperability with such + * legacy STAs allow connection without enabling OCV when the + * workaround mode (ocv=2) is enabled. + */ + if (wpa_auth->conf.ocv == 2) { + wpa_printf(MSG_DEBUG, + "Allow connecting MFP incapable and OCV capable STA without enabling OCV"); + wpa_auth_set_ocv(sm, 0); + } else { + wpa_printf(MSG_DEBUG, + "Management frame protection required with OCV, but client did not enable it"); + return WPA_MGMT_FRAME_PROTECTION_VIOLATION; + } + } else { + wpa_auth_set_ocv(sm, (data.capabilities & WPA_CAPABILITY_OCVC) ? + wpa_auth->conf.ocv : 0); + } +#endif /* CONFIG_OCV */ + + if (wpa_auth->conf.ieee80211w == NO_MGMT_FRAME_PROTECTION || + !(data.capabilities & WPA_CAPABILITY_MFPC)) + sm->mgmt_frame_prot = 0; + else + sm->mgmt_frame_prot = 1; + sm->mfpr = !!(data.capabilities & WPA_CAPABILITY_MFPR); + + if (sm->mgmt_frame_prot && (ciphers & WPA_CIPHER_TKIP)) { + wpa_printf(MSG_DEBUG, + "Management frame protection cannot use TKIP"); + return WPA_MGMT_FRAME_PROTECTION_VIOLATION; + } + +#ifdef CONFIG_IEEE80211R_AP + if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) { + if (mdie == NULL || mdie_len < MOBILITY_DOMAIN_ID_LEN + 1) { + wpa_printf(MSG_DEBUG, "RSN: Trying to use FT, but " + "MDIE not included"); + return WPA_INVALID_MDIE; + } + if (os_memcmp(mdie, wpa_auth->conf.mobility_domain, + MOBILITY_DOMAIN_ID_LEN) != 0) { + wpa_hexdump(MSG_DEBUG, "RSN: Attempted to use unknown " + "MDIE", mdie, MOBILITY_DOMAIN_ID_LEN); + return WPA_INVALID_MDIE; + } + } else if (mdie != NULL) { + wpa_printf(MSG_DEBUG, + "RSN: Trying to use non-FT AKM suite, but MDIE included"); + return WPA_INVALID_AKMP; + } +#endif /* CONFIG_IEEE80211R_AP */ + +#ifdef CONFIG_OWE + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OWE && !owe_dh) { + wpa_printf(MSG_DEBUG, + "OWE: No Diffie-Hellman Parameter element"); + return WPA_INVALID_AKMP; + } +#endif /* CONFIG_OWE */ + +#ifdef CONFIG_DPP2 + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP && + ((conf->dpp_pfs == 1 && !owe_dh) || + (conf->dpp_pfs == 2 && owe_dh))) { + wpa_printf(MSG_DEBUG, "DPP: PFS %s", + conf->dpp_pfs == 1 ? "required" : "not allowed"); + return WPA_DENIED_OTHER_REASON; + } +#endif /* CONFIG_DPP2 */ + + sm->pairwise = wpa_pick_pairwise_cipher(ciphers, 0); + if (sm->pairwise < 0) + return WPA_INVALID_PAIRWISE; + + /* TODO: clear WPA/WPA2 state if STA changes from one to another */ + if (wpa_ie[0] == WLAN_EID_RSN) + sm->wpa = WPA_VERSION_WPA2; + else + sm->wpa = WPA_VERSION_WPA; + + if (assoc_sm) { + /* For ML association link STA cannot choose a different + * AKM or pairwise cipher from association STA */ + if (sm->wpa_key_mgmt != assoc_sm->wpa_key_mgmt) + return WPA_INVALID_AKMP; + if (sm->pairwise != assoc_sm->pairwise) + return WPA_INVALID_PAIRWISE; + } + +#if defined(CONFIG_IEEE80211R_AP) && defined(CONFIG_FILS) + if ((sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_FILS_SHA256 || + sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_FILS_SHA384) && + (sm->auth_alg == WLAN_AUTH_FILS_SK || + sm->auth_alg == WLAN_AUTH_FILS_SK_PFS || + sm->auth_alg == WLAN_AUTH_FILS_PK) && + (data.num_pmkid != 1 || !data.pmkid || !sm->pmk_r1_name_valid || + os_memcmp_const(data.pmkid, sm->pmk_r1_name, + WPA_PMK_NAME_LEN) != 0)) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "No PMKR1Name match for FILS+FT"); + return WPA_INVALID_PMKID; + } +#endif /* CONFIG_IEEE80211R_AP && CONFIG_FILS */ + + sm->pmksa = NULL; + for (i = 0; i < data.num_pmkid; i++) { + wpa_hexdump(MSG_DEBUG, "RSN IE: STA PMKID", + &data.pmkid[i * PMKID_LEN], PMKID_LEN); + sm->pmksa = pmksa_cache_auth_get(wpa_auth->pmksa, sm->addr, + &data.pmkid[i * PMKID_LEN]); + if (sm->pmksa) { + pmkid = sm->pmksa->pmkid; + break; + } + } + for (i = 0; sm->pmksa == NULL && wpa_auth->conf.okc && + i < data.num_pmkid; i++) { + struct wpa_auth_okc_iter_data idata; + idata.pmksa = NULL; + idata.aa = wpa_auth->addr; + idata.spa = sm->addr; + idata.pmkid = &data.pmkid[i * PMKID_LEN]; + wpa_auth_for_each_auth(wpa_auth, wpa_auth_okc_iter, &idata); + if (idata.pmksa) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "OKC match for PMKID"); + sm->pmksa = pmksa_cache_add_okc(wpa_auth->pmksa, + idata.pmksa, + wpa_auth->addr, + idata.pmkid); + pmkid = idata.pmkid; + break; + } + } + if (sm->pmksa && pmkid) { + struct vlan_description *vlan; + + vlan = sm->pmksa->vlan_desc; + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "PMKID found from PMKSA cache eap_type=%d vlan=%d%s", + sm->pmksa->eap_type_authsrv, + vlan ? vlan->untagged : 0, + (vlan && vlan->tagged[0]) ? "+" : ""); + os_memcpy(wpa_auth->dot11RSNAPMKIDUsed, pmkid, PMKID_LEN); + } + +#ifdef CONFIG_SAE + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_SAE || + sm->wpa_key_mgmt == WPA_KEY_MGMT_SAE_EXT_KEY) { + u64 drv_flags = 0; + u64 drv_flags2 = 0; + bool ap_sae_offload = false; + + if (wpa_auth->cb->get_drv_flags && + wpa_auth->cb->get_drv_flags(wpa_auth->cb_ctx, &drv_flags, + &drv_flags2) == 0) + ap_sae_offload = + !!(drv_flags2 & + WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP); + + if (!ap_sae_offload && data.num_pmkid && !sm->pmksa) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "No PMKSA cache entry found for SAE"); + return WPA_INVALID_PMKID; + } + } +#endif /* CONFIG_SAE */ + +#ifdef CONFIG_DPP + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_DPP && !sm->pmksa) { + wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, + "No PMKSA cache entry found for DPP"); + return WPA_INVALID_PMKID; + } +#endif /* CONFIG_DPP */ + + if (conf->extended_key_id && sm->wpa == WPA_VERSION_WPA2 && + sm->pairwise != WPA_CIPHER_TKIP && + (data.capabilities & WPA_CAPABILITY_EXT_KEY_ID_FOR_UNICAST)) { + sm->use_ext_key_id = true; + if (conf->extended_key_id == 2 && + !wpa_key_mgmt_ft(sm->wpa_key_mgmt) && + !wpa_key_mgmt_fils(sm->wpa_key_mgmt)) + sm->keyidx_active = 1; + else + sm->keyidx_active = 0; + wpa_printf(MSG_DEBUG, + "RSN: Extended Key ID supported (start with %d)", + sm->keyidx_active); + } else { + sm->use_ext_key_id = false; + } + + if (sm->wpa_ie == NULL || sm->wpa_ie_len < wpa_ie_len) { + os_free(sm->wpa_ie); + sm->wpa_ie = os_malloc(wpa_ie_len); + if (sm->wpa_ie == NULL) + return WPA_ALLOC_FAIL; + } + os_memcpy(sm->wpa_ie, wpa_ie, wpa_ie_len); + sm->wpa_ie_len = wpa_ie_len; + + if (rsnxe && rsnxe_len) { + if (!sm->rsnxe || sm->rsnxe_len < rsnxe_len) { + os_free(sm->rsnxe); + sm->rsnxe = os_malloc(rsnxe_len); + if (!sm->rsnxe) + return WPA_ALLOC_FAIL; + } + os_memcpy(sm->rsnxe, rsnxe, rsnxe_len); + sm->rsnxe_len = rsnxe_len; + } else { + os_free(sm->rsnxe); + sm->rsnxe = NULL; + sm->rsnxe_len = 0; + } + + return WPA_IE_OK; +} + + +#ifdef CONFIG_HS20 +int wpa_validate_osen(struct wpa_authenticator *wpa_auth, + struct wpa_state_machine *sm, + const u8 *osen_ie, size_t osen_ie_len) +{ + if (wpa_auth == NULL || sm == NULL) + return -1; + + /* TODO: parse OSEN element */ + sm->wpa_key_mgmt = WPA_KEY_MGMT_OSEN; + sm->mgmt_frame_prot = 1; + sm->pairwise = WPA_CIPHER_CCMP; + sm->wpa = WPA_VERSION_WPA2; + + if (sm->wpa_ie == NULL || sm->wpa_ie_len < osen_ie_len) { + os_free(sm->wpa_ie); + sm->wpa_ie = os_malloc(osen_ie_len); + if (sm->wpa_ie == NULL) + return -1; + } + + os_memcpy(sm->wpa_ie, osen_ie, osen_ie_len); + sm->wpa_ie_len = osen_ie_len; + + return 0; +} + +#endif /* CONFIG_HS20 */ + + +int wpa_auth_uses_mfp(struct wpa_state_machine *sm) +{ + return sm ? sm->mgmt_frame_prot : 0; +} + + +#ifdef CONFIG_OCV + +void wpa_auth_set_ocv(struct wpa_state_machine *sm, int ocv) +{ + if (sm) + sm->ocv_enabled = ocv; +} + + +int wpa_auth_uses_ocv(struct wpa_state_machine *sm) +{ + return sm ? sm->ocv_enabled : 0; +} + +#endif /* CONFIG_OCV */ + + +#ifdef CONFIG_OWE +u8 * wpa_auth_write_assoc_resp_owe(struct wpa_state_machine *sm, + u8 *pos, size_t max_len, + const u8 *req_ies, size_t req_ies_len) +{ + int res; + struct wpa_auth_config *conf; + + if (!sm) + return pos; + conf = &sm->wpa_auth->conf; + +#ifdef CONFIG_TESTING_OPTIONS + if (conf->own_ie_override_len) { + if (max_len < conf->own_ie_override_len) + return NULL; + wpa_hexdump(MSG_DEBUG, "WPA: Forced own IE(s) for testing", + conf->own_ie_override, conf->own_ie_override_len); + os_memcpy(pos, conf->own_ie_override, + conf->own_ie_override_len); + return pos + conf->own_ie_override_len; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + res = wpa_write_rsn_ie(conf, pos, max_len, + sm->pmksa ? sm->pmksa->pmkid : NULL); + if (res < 0) + return pos; + return pos + res; +} +#endif /* CONFIG_OWE */ + + +#ifdef CONFIG_FILS + +u8 * wpa_auth_write_assoc_resp_fils(struct wpa_state_machine *sm, + u8 *pos, size_t max_len, + const u8 *req_ies, size_t req_ies_len) +{ + int res; + + if (!sm || + sm->wpa_key_mgmt & (WPA_KEY_MGMT_FT_FILS_SHA256 | + WPA_KEY_MGMT_FT_FILS_SHA384)) + return pos; + + res = wpa_write_rsn_ie(&sm->wpa_auth->conf, pos, max_len, NULL); + if (res < 0) + return pos; + return pos + res; +} + + +bool wpa_auth_write_fd_rsn_info(struct wpa_authenticator *wpa_auth, + u8 *fd_rsn_info) +{ + struct wpa_auth_config *conf; + u32 selectors = 0; + u8 *pos = fd_rsn_info; + int i, res; + u32 cipher, suite, selector, mask; + u8 tmp[10 * RSN_SELECTOR_LEN]; + + if (!wpa_auth) + return false; + conf = &wpa_auth->conf; + + if (!(conf->wpa & WPA_PROTO_RSN)) + return false; + + /* RSN Capability (B0..B15) */ + WPA_PUT_LE16(pos, wpa_own_rsn_capab(conf)); + pos += 2; + + /* Group Data Cipher Suite Selector (B16..B21) */ + suite = wpa_cipher_to_suite(WPA_PROTO_RSN, conf->wpa_group); + if (suite == RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED) + cipher = 63; /* No cipher suite selected */ + else if ((suite >> 8) == 0x000fac && ((suite & 0xff) <= 13)) + cipher = suite & 0xff; + else + cipher = 62; /* vendor specific */ + selectors |= cipher; + + /* Group Management Cipher Suite Selector (B22..B27) */ + cipher = 63; /* Default to no cipher suite selected */ + if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + switch (conf->group_mgmt_cipher) { + case WPA_CIPHER_AES_128_CMAC: + cipher = RSN_CIPHER_SUITE_AES_128_CMAC & 0xff; + break; + case WPA_CIPHER_BIP_GMAC_128: + cipher = RSN_CIPHER_SUITE_BIP_GMAC_128 & 0xff; + break; + case WPA_CIPHER_BIP_GMAC_256: + cipher = RSN_CIPHER_SUITE_BIP_GMAC_256 & 0xff; + break; + case WPA_CIPHER_BIP_CMAC_256: + cipher = RSN_CIPHER_SUITE_BIP_CMAC_256 & 0xff; + break; + } + } + selectors |= cipher << 6; + + /* Pairwise Cipher Suite Selector (B28..B33) */ + cipher = 63; /* Default to no cipher suite selected */ + res = rsn_cipher_put_suites(tmp, conf->rsn_pairwise); + if (res == 1 && tmp[0] == 0x00 && tmp[1] == 0x0f && tmp[2] == 0xac && + tmp[3] <= 13) + cipher = tmp[3]; + selectors |= cipher << 12; + + /* AKM Suite Selector (B34..B39) */ + selector = 0; /* default to AKM from RSNE in Beacon/Probe Response */ + mask = WPA_KEY_MGMT_FILS_SHA256 | WPA_KEY_MGMT_FILS_SHA384 | + WPA_KEY_MGMT_FT_FILS_SHA384; + if ((conf->wpa_key_mgmt & mask) && (conf->wpa_key_mgmt & ~mask) == 0) { + suite = conf->wpa_key_mgmt & mask; + if (suite == WPA_KEY_MGMT_FILS_SHA256) + selector = 1; /* 00-0f-ac:14 */ + else if (suite == WPA_KEY_MGMT_FILS_SHA384) + selector = 2; /* 00-0f-ac:15 */ + else if (suite == (WPA_KEY_MGMT_FILS_SHA256 | + WPA_KEY_MGMT_FILS_SHA384)) + selector = 3; /* 00-0f-ac:14 or 00-0f-ac:15 */ + else if (suite == WPA_KEY_MGMT_FT_FILS_SHA384) + selector = 4; /* 00-0f-ac:17 */ + } + selectors |= selector << 18; + + for (i = 0; i < 3; i++) { + *pos++ = selectors & 0xff; + selectors >>= 8; + } + + return true; +} + +#endif /* CONFIG_FILS */ diff --git a/src/ap/wpa_auth_ie.h b/src/ap/wpa_auth_ie.h new file mode 100644 index 0000000..dd44b9e --- /dev/null +++ b/src/ap/wpa_auth_ie.h @@ -0,0 +1,16 @@ +/* + * hostapd - WPA/RSN IE and KDE definitions + * Copyright (c) 2004-2007, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPA_AUTH_IE_H +#define WPA_AUTH_IE_H + +u8 * wpa_add_kde(u8 *pos, u32 kde, const u8 *data, size_t data_len, + const u8 *data2, size_t data2_len); +int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth); + +#endif /* WPA_AUTH_IE_H */ diff --git a/src/ap/wpa_auth_kay.c b/src/ap/wpa_auth_kay.c new file mode 100644 index 0000000..625f405 --- /dev/null +++ b/src/ap/wpa_auth_kay.c @@ -0,0 +1,496 @@ +/* + * IEEE 802.1X-2010 KaY Interface + * Copyright (c) 2019, The Linux Foundation + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "pae/ieee802_1x_key.h" +#include "pae/ieee802_1x_kay.h" +#include "hostapd.h" +#include "sta_info.h" +#include "wpa_auth_kay.h" +#include "ieee802_1x.h" + + +#define DEFAULT_KEY_LEN 16 +/* secure Connectivity Association Key Name (CKN) */ +#define DEFAULT_CKN_LEN 16 + + +static int hapd_macsec_init(void *priv, struct macsec_init_params *params) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->macsec_init) + return -1; + return hapd->driver->macsec_init(hapd->drv_priv, params); +} + + +static int hapd_macsec_deinit(void *priv) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->macsec_deinit) + return -1; + return hapd->driver->macsec_deinit(hapd->drv_priv); +} + + +static int hapd_macsec_get_capability(void *priv, enum macsec_cap *cap) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->macsec_get_capability) + return -1; + return hapd->driver->macsec_get_capability(hapd->drv_priv, cap); +} + + +static int hapd_enable_protect_frames(void *priv, bool enabled) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->enable_protect_frames) + return -1; + return hapd->driver->enable_protect_frames(hapd->drv_priv, enabled); +} + + +static int hapd_enable_encrypt(void *priv, bool enabled) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->enable_encrypt) + return -1; + return hapd->driver->enable_encrypt(hapd->drv_priv, enabled); +} + + +static int hapd_set_replay_protect(void *priv, bool enabled, u32 window) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->set_replay_protect) + return -1; + return hapd->driver->set_replay_protect(hapd->drv_priv, enabled, + window); +} + + +static int hapd_set_current_cipher_suite(void *priv, u64 cs) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->set_current_cipher_suite) + return -1; + return hapd->driver->set_current_cipher_suite(hapd->drv_priv, cs); +} + + +static int hapd_enable_controlled_port(void *priv, bool enabled) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->enable_controlled_port) + return -1; + return hapd->driver->enable_controlled_port(hapd->drv_priv, enabled); +} + + +static int hapd_get_receive_lowest_pn(void *priv, struct receive_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->get_receive_lowest_pn) + return -1; + return hapd->driver->get_receive_lowest_pn(hapd->drv_priv, sa); +} + + +static int hapd_get_transmit_next_pn(void *priv, struct transmit_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->get_transmit_next_pn) + return -1; + return hapd->driver->get_transmit_next_pn(hapd->drv_priv, sa); +} + + +static int hapd_set_transmit_next_pn(void *priv, struct transmit_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->set_transmit_next_pn) + return -1; + return hapd->driver->set_transmit_next_pn(hapd->drv_priv, sa); +} + + +static unsigned int conf_offset_val(enum confidentiality_offset co) +{ + switch (co) { + case CONFIDENTIALITY_OFFSET_30: + return 30; + case CONFIDENTIALITY_OFFSET_50: + return 50; + default: + return 0; + } +} + + +static int hapd_create_receive_sc(void *priv, struct receive_sc *sc, + enum validate_frames vf, + enum confidentiality_offset co) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->create_receive_sc) + return -1; + return hapd->driver->create_receive_sc(hapd->drv_priv, sc, + conf_offset_val(co), vf); +} + + +static int hapd_delete_receive_sc(void *priv, struct receive_sc *sc) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->delete_receive_sc) + return -1; + return hapd->driver->delete_receive_sc(hapd->drv_priv, sc); +} + + +static int hapd_create_receive_sa(void *priv, struct receive_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->create_receive_sa) + return -1; + return hapd->driver->create_receive_sa(hapd->drv_priv, sa); +} + + +static int hapd_delete_receive_sa(void *priv, struct receive_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->delete_receive_sa) + return -1; + return hapd->driver->delete_receive_sa(hapd->drv_priv, sa); +} + + +static int hapd_enable_receive_sa(void *priv, struct receive_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->enable_receive_sa) + return -1; + return hapd->driver->enable_receive_sa(hapd->drv_priv, sa); +} + + +static int hapd_disable_receive_sa(void *priv, struct receive_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->disable_receive_sa) + return -1; + return hapd->driver->disable_receive_sa(hapd->drv_priv, sa); +} + + +static int +hapd_create_transmit_sc(void *priv, struct transmit_sc *sc, + enum confidentiality_offset co) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->create_transmit_sc) + return -1; + return hapd->driver->create_transmit_sc(hapd->drv_priv, sc, + conf_offset_val(co)); +} + + +static int hapd_delete_transmit_sc(void *priv, struct transmit_sc *sc) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->delete_transmit_sc) + return -1; + return hapd->driver->delete_transmit_sc(hapd->drv_priv, sc); +} + + +static int hapd_create_transmit_sa(void *priv, struct transmit_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->create_transmit_sa) + return -1; + return hapd->driver->create_transmit_sa(hapd->drv_priv, sa); +} + + +static int hapd_delete_transmit_sa(void *priv, struct transmit_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->delete_transmit_sa) + return -1; + return hapd->driver->delete_transmit_sa(hapd->drv_priv, sa); +} + + +static int hapd_enable_transmit_sa(void *priv, struct transmit_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->enable_transmit_sa) + return -1; + return hapd->driver->enable_transmit_sa(hapd->drv_priv, sa); +} + + +static int hapd_disable_transmit_sa(void *priv, struct transmit_sa *sa) +{ + struct hostapd_data *hapd = priv; + + if (!hapd->driver->disable_transmit_sa) + return -1; + return hapd->driver->disable_transmit_sa(hapd->drv_priv, sa); +} + + +int ieee802_1x_alloc_kay_sm_hapd(struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct ieee802_1x_kay_ctx *kay_ctx; + struct ieee802_1x_kay *res = NULL; + enum macsec_policy policy; + + ieee802_1x_dealloc_kay_sm_hapd(hapd); + + if (!hapd->conf || hapd->conf->macsec_policy == 0) + return 0; + + if (hapd->conf->macsec_policy == 1) { + if (hapd->conf->macsec_integ_only == 1) + policy = SHOULD_SECURE; + else + policy = SHOULD_ENCRYPT; + } else { + policy = DO_NOT_SECURE; + } + + wpa_printf(MSG_DEBUG, "%s: if_name=%s", __func__, hapd->conf->iface); + kay_ctx = os_zalloc(sizeof(*kay_ctx)); + if (!kay_ctx) + return -1; + + kay_ctx->ctx = hapd; + + kay_ctx->macsec_init = hapd_macsec_init; + kay_ctx->macsec_deinit = hapd_macsec_deinit; + kay_ctx->macsec_get_capability = hapd_macsec_get_capability; + kay_ctx->enable_protect_frames = hapd_enable_protect_frames; + kay_ctx->enable_encrypt = hapd_enable_encrypt; + kay_ctx->set_replay_protect = hapd_set_replay_protect; + kay_ctx->set_current_cipher_suite = hapd_set_current_cipher_suite; + kay_ctx->enable_controlled_port = hapd_enable_controlled_port; + kay_ctx->get_receive_lowest_pn = hapd_get_receive_lowest_pn; + kay_ctx->get_transmit_next_pn = hapd_get_transmit_next_pn; + kay_ctx->set_transmit_next_pn = hapd_set_transmit_next_pn; + kay_ctx->create_receive_sc = hapd_create_receive_sc; + kay_ctx->delete_receive_sc = hapd_delete_receive_sc; + kay_ctx->create_receive_sa = hapd_create_receive_sa; + kay_ctx->delete_receive_sa = hapd_delete_receive_sa; + kay_ctx->enable_receive_sa = hapd_enable_receive_sa; + kay_ctx->disable_receive_sa = hapd_disable_receive_sa; + kay_ctx->create_transmit_sc = hapd_create_transmit_sc; + kay_ctx->delete_transmit_sc = hapd_delete_transmit_sc; + kay_ctx->create_transmit_sa = hapd_create_transmit_sa; + kay_ctx->delete_transmit_sa = hapd_delete_transmit_sa; + kay_ctx->enable_transmit_sa = hapd_enable_transmit_sa; + kay_ctx->disable_transmit_sa = hapd_disable_transmit_sa; + + res = ieee802_1x_kay_init(kay_ctx, policy, + hapd->conf->macsec_replay_protect, + hapd->conf->macsec_replay_window, + hapd->conf->macsec_offload, + hapd->conf->macsec_port, + hapd->conf->mka_priority, + hapd->conf->macsec_csindex, + hapd->conf->iface, + hapd->own_addr); + /* ieee802_1x_kay_init() frees kay_ctx on failure */ + if (!res) + return -1; + + hapd->kay = res; + + return 0; +} + + +void ieee802_1x_dealloc_kay_sm_hapd(struct hostapd_data *hapd) +{ + if (!hapd->kay) + return; + + ieee802_1x_kay_deinit(hapd->kay); + hapd->kay = NULL; +} + + +static int ieee802_1x_auth_get_msk(struct hostapd_data *hapd, + struct sta_info *sta, u8 *msk, size_t *len) +{ + const u8 *key; + size_t keylen; + + if (!sta->eapol_sm) + return -1; + + key = ieee802_1x_get_key(sta->eapol_sm, &keylen); + if (key == NULL) { + wpa_printf(MSG_DEBUG, + "MACsec: Failed to get MSK from EAPOL state machines"); + return -1; + } + wpa_printf(MSG_DEBUG, "MACsec: Successfully fetched key (len=%lu)", + (unsigned long) keylen); + wpa_hexdump_key(MSG_DEBUG, "MSK: ", key, keylen); + + if (keylen > *len) + keylen = *len; + os_memcpy(msk, key, keylen); + *len = keylen; + + return 0; +} + + +void * ieee802_1x_notify_create_actor_hapd(struct hostapd_data *hapd, + struct sta_info *sta) +{ + const u8 *sid; + size_t sid_len; + struct mka_key_name *ckn; + struct mka_key *cak; + struct mka_key *msk; + void *res = NULL; + + if (!hapd->kay || hapd->kay->policy == DO_NOT_SECURE) + return NULL; + + wpa_printf(MSG_DEBUG, + "IEEE 802.1X: External notification - Create MKA for " + MACSTR, MAC2STR(sta->addr)); + + msk = os_zalloc(sizeof(*msk)); + ckn = os_zalloc(sizeof(*ckn)); + cak = os_zalloc(sizeof(*cak)); + if (!msk || !ckn || !cak) + goto fail; + + msk->len = DEFAULT_KEY_LEN; + if (ieee802_1x_auth_get_msk(hapd, sta, msk->key, &msk->len)) { + wpa_printf(MSG_ERROR, "IEEE 802.1X: Could not get MSK"); + goto fail; + } + + sid = ieee802_1x_get_session_id(sta->eapol_sm, &sid_len); + if (!sid) { + wpa_printf(MSG_ERROR, + "IEEE 802.1X: Could not get EAP Session Id"); + goto fail; + } + + wpa_hexdump(MSG_DEBUG, "own_addr", hapd->own_addr, ETH_ALEN); + wpa_hexdump(MSG_DEBUG, "sta_addr", sta->addr, ETH_ALEN); + + /* Derive CAK from MSK */ + cak->len = DEFAULT_KEY_LEN; + if (ieee802_1x_cak_aes_cmac(msk->key, msk->len, hapd->own_addr, + sta->addr, cak->key, cak->len)) { + wpa_printf(MSG_ERROR, "IEEE 802.1X: Deriving CAK failed"); + goto fail; + } + wpa_hexdump_key(MSG_DEBUG, "Derived CAK", cak->key, cak->len); + + /* Derive CKN from MSK */ + ckn->len = DEFAULT_CKN_LEN; + if (ieee802_1x_ckn_aes_cmac(msk->key, msk->len, hapd->own_addr, + sta->addr, sid, sid_len, ckn->name)) { + wpa_printf(MSG_ERROR, "IEEE 802.1X: Deriving CKN failed"); + goto fail; + } + wpa_hexdump(MSG_DEBUG, "Derived CKN", ckn->name, ckn->len); + + res = ieee802_1x_kay_create_mka(hapd->kay, ckn, cak, 0, EAP_EXCHANGE, + true); + +fail: + bin_clear_free(msk, sizeof(*msk)); + os_free(ckn); + bin_clear_free(cak, sizeof(*cak)); + + return res; +} + + +void * ieee802_1x_create_preshared_mka_hapd(struct hostapd_data *hapd, + struct sta_info *sta) +{ + struct mka_key *cak; + struct mka_key_name *ckn; + void *res = NULL; + + if ((hapd->conf->mka_psk_set & MKA_PSK_SET) != MKA_PSK_SET) + goto end; + + ckn = os_zalloc(sizeof(*ckn)); + if (!ckn) + goto end; + + cak = os_zalloc(sizeof(*cak)); + if (!cak) + goto free_ckn; + + if (ieee802_1x_alloc_kay_sm_hapd(hapd, sta) < 0 || !hapd->kay) + goto free_cak; + + if (hapd->kay->policy == DO_NOT_SECURE) + goto dealloc; + + cak->len = hapd->conf->mka_cak_len; + os_memcpy(cak->key, hapd->conf->mka_cak, cak->len); + + ckn->len = hapd->conf->mka_ckn_len;; + os_memcpy(ckn->name, hapd->conf->mka_ckn, ckn->len); + + res = ieee802_1x_kay_create_mka(hapd->kay, ckn, cak, 0, PSK, true); + if (res) + goto free_cak; + +dealloc: + /* Failed to create MKA */ + ieee802_1x_dealloc_kay_sm_hapd(hapd); +free_cak: + os_free(cak); +free_ckn: + os_free(ckn); +end: + return res; +} diff --git a/src/ap/wpa_auth_kay.h b/src/ap/wpa_auth_kay.h new file mode 100644 index 0000000..0dd7e41 --- /dev/null +++ b/src/ap/wpa_auth_kay.h @@ -0,0 +1,51 @@ +/* + * IEEE 802.1X-2010 KaY Interface + * Copyright (c) 2019, The Linux Foundation + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPA_AUTH_KAY_H +#define WPA_AUTH_KAY_H + +#ifdef CONFIG_MACSEC + +int ieee802_1x_alloc_kay_sm_hapd(struct hostapd_data *hapd, + struct sta_info *sta); +void * ieee802_1x_notify_create_actor_hapd(struct hostapd_data *hapd, + struct sta_info *sta); +void ieee802_1x_dealloc_kay_sm_hapd(struct hostapd_data *hapd); + +void * ieee802_1x_create_preshared_mka_hapd(struct hostapd_data *hapd, + struct sta_info *sta); + +#else /* CONFIG_MACSEC */ + +static inline int ieee802_1x_alloc_kay_sm_hapd(struct hostapd_data *hapd, + struct sta_info *sta) +{ + return 0; +} + +static inline void * +ieee802_1x_notify_create_actor_hapd(struct hostapd_data *hapd, + struct sta_info *sta) +{ + return NULL; +} + +static inline void ieee802_1x_dealloc_kay_sm_hapd(struct hostapd_data *hapd) +{ +} + +static inline void * +ieee802_1x_create_preshared_mka_hapd(struct hostapd_data *hapd, + struct sta_info *sta) +{ + return NULL; +} + +#endif /* CONFIG_MACSEC */ + +#endif /* WPA_AUTH_KAY_H */ diff --git a/src/ap/wps_hostapd.c b/src/ap/wps_hostapd.c new file mode 100644 index 0000000..82d4d5f --- /dev/null +++ b/src/ap/wps_hostapd.c @@ -0,0 +1,2285 @@ +/* + * hostapd / WPS integration + * Copyright (c) 2008-2016, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "utils/uuid.h" +#include "common/wpa_ctrl.h" +#include "common/ieee802_11_defs.h" +#include "common/ieee802_11_common.h" +#include "eapol_auth/eapol_auth_sm.h" +#include "eapol_auth/eapol_auth_sm_i.h" +#include "wps/wps.h" +#include "wps/wps_defs.h" +#include "wps/wps_dev_attr.h" +#include "wps/wps_attr_parse.h" +#include "hostapd.h" +#include "ap_config.h" +#include "ap_drv_ops.h" +#include "beacon.h" +#include "sta_info.h" +#include "wps_hostapd.h" + + +#ifdef CONFIG_WPS_UPNP +#include "wps/wps_upnp.h" +static int hostapd_wps_upnp_init(struct hostapd_data *hapd, + struct wps_context *wps); +static void hostapd_wps_upnp_deinit(struct hostapd_data *hapd); +#endif /* CONFIG_WPS_UPNP */ + +static int hostapd_wps_probe_req_rx(void *ctx, const u8 *addr, const u8 *da, + const u8 *bssid, + const u8 *ie, size_t ie_len, + int ssi_signal); +static void hostapd_wps_ap_pin_timeout(void *eloop_data, void *user_ctx); +static void hostapd_wps_nfc_clear(struct wps_context *wps); + + +struct wps_for_each_data { + int (*func)(struct hostapd_data *h, void *ctx); + void *ctx; + struct hostapd_data *calling_hapd; +}; + + +static int wps_for_each(struct hostapd_iface *iface, void *ctx) +{ + struct wps_for_each_data *data = ctx; + size_t j; + + if (iface == NULL) + return 0; + for (j = 0; j < iface->num_bss; j++) { + struct hostapd_data *hapd = iface->bss[j]; + int ret; + + if (hapd != data->calling_hapd && + (hapd->conf->wps_independent || + data->calling_hapd->conf->wps_independent)) + continue; + + ret = data->func(hapd, data->ctx); + if (ret) + return ret; + } + + return 0; +} + + +static int hostapd_wps_for_each(struct hostapd_data *hapd, + int (*func)(struct hostapd_data *h, void *ctx), + void *ctx) +{ + struct hostapd_iface *iface = hapd->iface; + struct wps_for_each_data data; + data.func = func; + data.ctx = ctx; + data.calling_hapd = hapd; + if (iface->interfaces == NULL || + iface->interfaces->for_each_interface == NULL) + return wps_for_each(iface, &data); + return iface->interfaces->for_each_interface(iface->interfaces, + wps_for_each, &data); +} + + +static int hostapd_wps_new_psk_cb(void *ctx, const u8 *mac_addr, + const u8 *p2p_dev_addr, const u8 *psk, + size_t psk_len) +{ + struct hostapd_data *hapd = ctx; + struct hostapd_wpa_psk *p; + struct hostapd_ssid *ssid = &hapd->conf->ssid; + + if (is_zero_ether_addr(p2p_dev_addr)) { + wpa_printf(MSG_DEBUG, + "Received new WPA/WPA2-PSK from WPS for STA " MACSTR, + MAC2STR(mac_addr)); + } else { + wpa_printf(MSG_DEBUG, + "Received new WPA/WPA2-PSK from WPS for STA " MACSTR + " P2P Device Addr " MACSTR, + MAC2STR(mac_addr), MAC2STR(p2p_dev_addr)); + } + wpa_hexdump_key(MSG_DEBUG, "Per-device PSK", psk, psk_len); + + if (psk_len != PMK_LEN) { + wpa_printf(MSG_DEBUG, "Unexpected PSK length %lu", + (unsigned long) psk_len); + return -1; + } + + /* Add the new PSK to runtime PSK list */ + p = os_zalloc(sizeof(*p)); + if (p == NULL) + return -1; + os_memcpy(p->addr, mac_addr, ETH_ALEN); + os_memcpy(p->p2p_dev_addr, p2p_dev_addr, ETH_ALEN); + os_memcpy(p->psk, psk, PMK_LEN); + p->wps = 1; + + if (hapd->new_psk_cb) { + hapd->new_psk_cb(hapd->new_psk_cb_ctx, mac_addr, p2p_dev_addr, + psk, psk_len); + } + + p->next = ssid->wpa_psk; + ssid->wpa_psk = p; + + if (ssid->wpa_psk_file) { + FILE *f; + char hex[PMK_LEN * 2 + 1]; + + /* Add the new PSK to PSK list file */ + f = fopen(ssid->wpa_psk_file, "a"); + if (!f) { + wpa_printf(MSG_DEBUG, "Failed to add the PSK to '%s'", + ssid->wpa_psk_file); + return -1; + } + + wpa_snprintf_hex(hex, sizeof(hex), psk, psk_len); + fprintf(f, "wps=1 " MACSTR " %s\n", MAC2STR(mac_addr), hex); + fclose(f); + } + + return 0; +} + + +static int hostapd_wps_set_ie_cb(void *ctx, struct wpabuf *beacon_ie, + struct wpabuf *probe_resp_ie) +{ + struct hostapd_data *hapd = ctx; + wpabuf_free(hapd->wps_beacon_ie); + hapd->wps_beacon_ie = beacon_ie; + wpabuf_free(hapd->wps_probe_resp_ie); + hapd->wps_probe_resp_ie = probe_resp_ie; + if (hapd->beacon_set_done) + ieee802_11_set_beacon(hapd); + return hostapd_set_ap_wps_ie(hapd); +} + + +static void hostapd_wps_pin_needed_cb(void *ctx, const u8 *uuid_e, + const struct wps_device_data *dev) +{ + struct hostapd_data *hapd = ctx; + char uuid[40], txt[400]; + int len; + char devtype[WPS_DEV_TYPE_BUFSIZE]; + if (uuid_bin2str(uuid_e, uuid, sizeof(uuid))) + return; + wpa_printf(MSG_DEBUG, "WPS: PIN needed for E-UUID %s", uuid); + len = os_snprintf(txt, sizeof(txt), WPS_EVENT_PIN_NEEDED + "%s " MACSTR " [%s|%s|%s|%s|%s|%s]", + uuid, MAC2STR(dev->mac_addr), dev->device_name, + dev->manufacturer, dev->model_name, + dev->model_number, dev->serial_number, + wps_dev_type_bin2str(dev->pri_dev_type, devtype, + sizeof(devtype))); + if (!os_snprintf_error(sizeof(txt), len)) + wpa_msg(hapd->msg_ctx, MSG_INFO, "%s", txt); + + if (hapd->conf->wps_pin_requests) { + FILE *f; + struct os_time t; + f = fopen(hapd->conf->wps_pin_requests, "a"); + if (f == NULL) + return; + os_get_time(&t); + fprintf(f, "%ld\t%s\t" MACSTR "\t%s\t%s\t%s\t%s\t%s" + "\t%s\n", + t.sec, uuid, MAC2STR(dev->mac_addr), dev->device_name, + dev->manufacturer, dev->model_name, dev->model_number, + dev->serial_number, + wps_dev_type_bin2str(dev->pri_dev_type, devtype, + sizeof(devtype))); + fclose(f); + } +} + + +struct wps_stop_reg_data { + struct hostapd_data *current_hapd; + const u8 *uuid_e; + const u8 *dev_pw; + size_t dev_pw_len; +}; + +static int wps_stop_registrar(struct hostapd_data *hapd, void *ctx) +{ + struct wps_stop_reg_data *data = ctx; + if (hapd != data->current_hapd && hapd->wps != NULL) + wps_registrar_complete(hapd->wps->registrar, data->uuid_e, + data->dev_pw, data->dev_pw_len); + return 0; +} + + +static void hostapd_wps_reg_success_cb(void *ctx, const u8 *mac_addr, + const u8 *uuid_e, const u8 *dev_pw, + size_t dev_pw_len) +{ + struct hostapd_data *hapd = ctx; + char uuid[40]; + struct wps_stop_reg_data data; + if (uuid_bin2str(uuid_e, uuid, sizeof(uuid))) + return; + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_REG_SUCCESS MACSTR " %s", + MAC2STR(mac_addr), uuid); + if (hapd->wps_reg_success_cb) + hapd->wps_reg_success_cb(hapd->wps_reg_success_cb_ctx, + mac_addr, uuid_e); + data.current_hapd = hapd; + data.uuid_e = uuid_e; + data.dev_pw = dev_pw; + data.dev_pw_len = dev_pw_len; + hostapd_wps_for_each(hapd, wps_stop_registrar, &data); +} + + +static void hostapd_wps_enrollee_seen_cb(void *ctx, const u8 *addr, + const u8 *uuid_e, + const u8 *pri_dev_type, + u16 config_methods, + u16 dev_password_id, u8 request_type, + const char *dev_name) +{ + struct hostapd_data *hapd = ctx; + char uuid[40]; + char devtype[WPS_DEV_TYPE_BUFSIZE]; + if (uuid_bin2str(uuid_e, uuid, sizeof(uuid))) + return; + if (dev_name == NULL) + dev_name = ""; + wpa_msg_ctrl(hapd->msg_ctx, MSG_INFO, WPS_EVENT_ENROLLEE_SEEN MACSTR + " %s %s 0x%x %u %u [%s]", + MAC2STR(addr), uuid, + wps_dev_type_bin2str(pri_dev_type, devtype, + sizeof(devtype)), + config_methods, dev_password_id, request_type, dev_name); +} + + +static int hostapd_wps_lookup_pskfile_cb(void *ctx, const u8 *mac_addr, + const u8 **psk) +{ + const struct hostapd_data *hapd = ctx; + const struct hostapd_wpa_psk *wpa_psk; + const u8 *any_psk = NULL; + const u8 *dev_psk = NULL; + + for (wpa_psk = hapd->conf->ssid.wpa_psk; wpa_psk; + wpa_psk = wpa_psk->next) { + if (!wpa_psk->wps) + continue; + + if (!any_psk && is_zero_ether_addr(wpa_psk->addr)) + any_psk = wpa_psk->psk; + + if (mac_addr && !dev_psk && + ether_addr_equal(mac_addr, wpa_psk->addr)) { + dev_psk = wpa_psk->psk; + break; + } + } + + if (dev_psk) { + *psk = dev_psk; + } else if (any_psk) { + *psk = any_psk; + } else { + *psk = NULL; + wpa_printf(MSG_DEBUG, + "WPS: No appropriate PSK in wpa_psk_file"); + return 0; + } + + return 1; +} + + +static void wps_reload_config(void *eloop_data, void *user_ctx) +{ + struct hostapd_iface *iface = eloop_data; + + wpa_printf(MSG_DEBUG, "WPS: Reload configuration data"); + if (iface->interfaces == NULL || + iface->interfaces->reload_config(iface) < 0) { + wpa_printf(MSG_WARNING, "WPS: Failed to reload the updated " + "configuration"); + } +} + + +void hostapd_wps_eap_completed(struct hostapd_data *hapd) +{ + /* + * Reduce race condition of the station trying to reconnect immediately + * after AP reconfiguration through WPS by rescheduling the reload + * timeout to happen after EAP completion rather than the originally + * scheduled 100 ms after new configuration became known. + */ + if (eloop_deplete_timeout(0, 0, wps_reload_config, hapd->iface, NULL) == + 1) + wpa_printf(MSG_DEBUG, "WPS: Reschedule immediate configuration reload"); +} + + +static void hapd_new_ap_event(struct hostapd_data *hapd, const u8 *attr, + size_t attr_len) +{ + size_t blen = attr_len * 2 + 1; + char *buf = os_malloc(blen); + if (buf) { + wpa_snprintf_hex(buf, blen, attr, attr_len); + wpa_msg(hapd->msg_ctx, MSG_INFO, + WPS_EVENT_NEW_AP_SETTINGS "%s", buf); + os_free(buf); + } +} + + +static int hapd_wps_reconfig_in_memory(struct hostapd_data *hapd, + const struct wps_credential *cred) +{ + struct hostapd_bss_config *bss = hapd->conf; + + wpa_printf(MSG_DEBUG, "WPS: Updating in-memory configuration"); + + bss->wps_state = 2; + if (cred->ssid_len <= SSID_MAX_LEN) { + os_memcpy(bss->ssid.ssid, cred->ssid, cred->ssid_len); + bss->ssid.ssid_len = cred->ssid_len; + bss->ssid.ssid_set = 1; + } + +#ifdef CONFIG_NO_TKIP + if (cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA2PSK | + WPS_AUTH_WPA | WPS_AUTH_WPAPSK)) + bss->wpa = 2; + else + bss->wpa = 0; +#else /* CONFIG_NO_TKIP */ + if ((cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA2PSK)) && + (cred->auth_type & (WPS_AUTH_WPA | WPS_AUTH_WPAPSK))) + bss->wpa = 3; + else if (cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA2PSK)) + bss->wpa = 2; + else if (cred->auth_type & (WPS_AUTH_WPA | WPS_AUTH_WPAPSK)) + bss->wpa = 1; + else + bss->wpa = 0; +#endif /* CONFIG_NO_TKIP */ + + if (bss->wpa) { + if (cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA)) + bss->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X; + if (cred->auth_type & (WPS_AUTH_WPA2PSK | WPS_AUTH_WPAPSK)) + bss->wpa_key_mgmt = WPA_KEY_MGMT_PSK; + + bss->wpa_pairwise = 0; + if (cred->encr_type & WPS_ENCR_AES) { + if (hapd->iconf->hw_mode == HOSTAPD_MODE_IEEE80211AD) + bss->wpa_pairwise |= WPA_CIPHER_GCMP; + else + bss->wpa_pairwise |= WPA_CIPHER_CCMP; + } +#ifndef CONFIG_NO_TKIP + if (cred->encr_type & WPS_ENCR_TKIP) + bss->wpa_pairwise |= WPA_CIPHER_TKIP; +#endif /* CONFIG_NO_TKIP */ + bss->rsn_pairwise = bss->wpa_pairwise; + bss->wpa_group = wpa_select_ap_group_cipher(bss->wpa, + bss->wpa_pairwise, + bss->rsn_pairwise); + + if (hapd->conf->wps_cred_add_sae && + (cred->auth_type & WPS_AUTH_WPA2PSK) && + cred->key_len != 2 * PMK_LEN) { + bss->wpa_key_mgmt |= WPA_KEY_MGMT_SAE; + if (bss->ieee80211w == NO_MGMT_FRAME_PROTECTION) + bss->ieee80211w = + MGMT_FRAME_PROTECTION_OPTIONAL; + bss->sae_require_mfp = 1; + } + + if (cred->key_len >= 8 && cred->key_len < 64) { + os_free(bss->ssid.wpa_passphrase); + bss->ssid.wpa_passphrase = os_zalloc(cred->key_len + 1); + if (bss->ssid.wpa_passphrase) + os_memcpy(bss->ssid.wpa_passphrase, cred->key, + cred->key_len); + hostapd_config_clear_wpa_psk(&bss->ssid.wpa_psk); + } else if (cred->key_len == 64) { + hostapd_config_clear_wpa_psk(&bss->ssid.wpa_psk); + bss->ssid.wpa_psk = + os_zalloc(sizeof(struct hostapd_wpa_psk)); + if (bss->ssid.wpa_psk && + hexstr2bin((const char *) cred->key, + bss->ssid.wpa_psk->psk, PMK_LEN) == 0) { + bss->ssid.wpa_psk->group = 1; + os_free(bss->ssid.wpa_passphrase); + bss->ssid.wpa_passphrase = NULL; + } + } + bss->auth_algs = 1; + } else { + /* + * WPS 2.0 does not allow WEP to be configured, so no need to + * process that option here either. + */ + bss->auth_algs = 1; + } + + /* Schedule configuration reload after short period of time to allow + * EAP-WSC to be finished. + */ + eloop_register_timeout(0, 100000, wps_reload_config, hapd->iface, + NULL); + + return 0; +} + + +static int hapd_wps_cred_cb(struct hostapd_data *hapd, void *ctx) +{ + const struct wps_credential *cred = ctx; + FILE *oconf, *nconf; + size_t len, i; + char *tmp_fname; + char buf[1024]; + int multi_bss; + int wpa; + int pmf_changed = 0; + + if (hapd->wps == NULL) + return 0; + + wpa_hexdump_key(MSG_DEBUG, "WPS: Received Credential attribute", + cred->cred_attr, cred->cred_attr_len); + + wpa_printf(MSG_DEBUG, "WPS: Received new AP Settings"); + wpa_hexdump_ascii(MSG_DEBUG, "WPS: SSID", cred->ssid, cred->ssid_len); + wpa_printf(MSG_DEBUG, "WPS: Authentication Type 0x%x", + cred->auth_type); + wpa_printf(MSG_DEBUG, "WPS: Encryption Type 0x%x", cred->encr_type); + wpa_printf(MSG_DEBUG, "WPS: Network Key Index %d", cred->key_idx); + wpa_hexdump_key(MSG_DEBUG, "WPS: Network Key", + cred->key, cred->key_len); + wpa_printf(MSG_DEBUG, "WPS: MAC Address " MACSTR, + MAC2STR(cred->mac_addr)); + + if ((hapd->conf->wps_cred_processing == 1 || + hapd->conf->wps_cred_processing == 2) && cred->cred_attr) { + hapd_new_ap_event(hapd, cred->cred_attr, cred->cred_attr_len); + } else if (hapd->conf->wps_cred_processing == 1 || + hapd->conf->wps_cred_processing == 2) { + struct wpabuf *attr; + attr = wpabuf_alloc(200); + if (attr && wps_build_credential_wrap(attr, cred) == 0) + hapd_new_ap_event(hapd, wpabuf_head_u8(attr), + wpabuf_len(attr)); + wpabuf_free(attr); + } else + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_NEW_AP_SETTINGS); + + if (hapd->conf->wps_cred_processing == 1) + return 0; + + os_memcpy(hapd->wps->ssid, cred->ssid, cred->ssid_len); + hapd->wps->ssid_len = cred->ssid_len; + hapd->wps->encr_types = cred->encr_type; + hapd->wps->encr_types_rsn = cred->encr_type; + hapd->wps->encr_types_wpa = cred->encr_type; + hapd->wps->auth_types = cred->auth_type; + hapd->wps->ap_encr_type = cred->encr_type; + hapd->wps->ap_auth_type = cred->auth_type; + if (cred->key_len == 0) { + os_free(hapd->wps->network_key); + hapd->wps->network_key = NULL; + hapd->wps->network_key_len = 0; + } else if ((cred->auth_type & (WPS_AUTH_WPA2PSK | WPS_AUTH_WPAPSK)) && + (cred->key_len < 8 || cred->key_len > 2 * PMK_LEN)) { + wpa_printf(MSG_INFO, "WPS: Invalid key length %lu for WPA/WPA2", + (unsigned long) cred->key_len); + return -1; + } else { + if (hapd->wps->network_key == NULL || + hapd->wps->network_key_len < cred->key_len) { + hapd->wps->network_key_len = 0; + os_free(hapd->wps->network_key); + hapd->wps->network_key = os_malloc(cred->key_len); + if (hapd->wps->network_key == NULL) + return -1; + } + hapd->wps->network_key_len = cred->key_len; + os_memcpy(hapd->wps->network_key, cred->key, cred->key_len); + } + hapd->wps->wps_state = WPS_STATE_CONFIGURED; + + if (hapd->iface->config_fname == NULL) + return hapd_wps_reconfig_in_memory(hapd, cred); + len = os_strlen(hapd->iface->config_fname) + 5; + tmp_fname = os_malloc(len); + if (tmp_fname == NULL) + return -1; + os_snprintf(tmp_fname, len, "%s-new", hapd->iface->config_fname); + + oconf = fopen(hapd->iface->config_fname, "r"); + if (oconf == NULL) { + wpa_printf(MSG_WARNING, "WPS: Could not open current " + "configuration file"); + os_free(tmp_fname); + return -1; + } + + nconf = fopen(tmp_fname, "w"); + if (nconf == NULL) { + wpa_printf(MSG_WARNING, "WPS: Could not write updated " + "configuration file"); + os_free(tmp_fname); + fclose(oconf); + return -1; + } + + fprintf(nconf, "# WPS configuration - START\n"); + + fprintf(nconf, "wps_state=2\n"); + + if (is_hex(cred->ssid, cred->ssid_len)) { + fprintf(nconf, "ssid2="); + for (i = 0; i < cred->ssid_len; i++) + fprintf(nconf, "%02x", cred->ssid[i]); + fprintf(nconf, "\n"); + } else { + fprintf(nconf, "ssid="); + for (i = 0; i < cred->ssid_len; i++) + fputc(cred->ssid[i], nconf); + fprintf(nconf, "\n"); + } + +#ifdef CONFIG_NO_TKIP + if (cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA2PSK | + WPS_AUTH_WPA | WPS_AUTH_WPAPSK)) + wpa = 2; + else + wpa = 0; +#else /* CONFIG_NO_TKIP */ + if ((cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA2PSK)) && + (cred->auth_type & (WPS_AUTH_WPA | WPS_AUTH_WPAPSK))) + wpa = 3; + else if (cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA2PSK)) + wpa = 2; + else if (cred->auth_type & (WPS_AUTH_WPA | WPS_AUTH_WPAPSK)) + wpa = 1; + else + wpa = 0; +#endif /* CONFIG_NO_TKIP */ + + if (wpa) { + char *prefix; + int sae = 0; + + fprintf(nconf, "wpa=%d\n", wpa); + + fprintf(nconf, "wpa_key_mgmt="); + prefix = ""; + if (cred->auth_type & (WPS_AUTH_WPA2 | WPS_AUTH_WPA)) { + fprintf(nconf, "WPA-EAP"); + prefix = " "; + } + if (cred->auth_type & (WPS_AUTH_WPA2PSK | WPS_AUTH_WPAPSK)) { + fprintf(nconf, "%sWPA-PSK", prefix); + prefix = " "; + } + if (hapd->conf->wps_cred_add_sae && + (cred->auth_type & WPS_AUTH_WPA2PSK) && + cred->key_len != 2 * PMK_LEN) { + fprintf(nconf, "%sSAE", prefix); + sae = 1; + } + fprintf(nconf, "\n"); + + if (sae && hapd->conf->ieee80211w == NO_MGMT_FRAME_PROTECTION) { + fprintf(nconf, "ieee80211w=%d\n", + MGMT_FRAME_PROTECTION_OPTIONAL); + pmf_changed = 1; + } + if (sae) + fprintf(nconf, "sae_require_mfp=1\n"); + + fprintf(nconf, "wpa_pairwise="); + prefix = ""; + if (cred->encr_type & WPS_ENCR_AES) { + if (hapd->iconf->hw_mode == HOSTAPD_MODE_IEEE80211AD) + fprintf(nconf, "GCMP"); + else + fprintf(nconf, "CCMP"); + + prefix = " "; + } +#ifndef CONFIG_NO_TKIP + if (cred->encr_type & WPS_ENCR_TKIP) { + fprintf(nconf, "%sTKIP", prefix); + } +#endif /* CONFIG_NO_TKIP */ + fprintf(nconf, "\n"); + + if (cred->key_len >= 8 && cred->key_len < 64) { + fprintf(nconf, "wpa_passphrase="); + for (i = 0; i < cred->key_len; i++) + fputc(cred->key[i], nconf); + fprintf(nconf, "\n"); + } else if (cred->key_len == 64) { + fprintf(nconf, "wpa_psk="); + for (i = 0; i < cred->key_len; i++) + fputc(cred->key[i], nconf); + fprintf(nconf, "\n"); + } else { + wpa_printf(MSG_WARNING, "WPS: Invalid key length %lu " + "for WPA/WPA2", + (unsigned long) cred->key_len); + } + + fprintf(nconf, "auth_algs=1\n"); + } else { + /* + * WPS 2.0 does not allow WEP to be configured, so no need to + * process that option here either. + */ + fprintf(nconf, "auth_algs=1\n"); + } + + fprintf(nconf, "# WPS configuration - END\n"); + + multi_bss = 0; + while (fgets(buf, sizeof(buf), oconf)) { + if (os_strncmp(buf, "bss=", 4) == 0) + multi_bss = 1; + if (!multi_bss && + (str_starts(buf, "ssid=") || + str_starts(buf, "ssid2=") || + str_starts(buf, "auth_algs=") || +#ifdef CONFIG_WEP + str_starts(buf, "wep_default_key=") || + str_starts(buf, "wep_key") || +#endif /* CONFIG_WEP */ + str_starts(buf, "wps_state=") || + (pmf_changed && str_starts(buf, "ieee80211w=")) || + str_starts(buf, "wpa=") || + str_starts(buf, "wpa_psk=") || + str_starts(buf, "wpa_pairwise=") || + str_starts(buf, "rsn_pairwise=") || + str_starts(buf, "wpa_key_mgmt=") || + str_starts(buf, "wpa_passphrase="))) { + fprintf(nconf, "#WPS# %s", buf); + } else + fprintf(nconf, "%s", buf); + } + + fclose(nconf); + fclose(oconf); + + if (rename(tmp_fname, hapd->iface->config_fname) < 0) { + wpa_printf(MSG_WARNING, "WPS: Failed to rename the updated " + "configuration file: %s", strerror(errno)); + os_free(tmp_fname); + return -1; + } + + os_free(tmp_fname); + + /* Schedule configuration reload after short period of time to allow + * EAP-WSC to be finished. + */ + eloop_register_timeout(0, 100000, wps_reload_config, hapd->iface, + NULL); + + wpa_printf(MSG_DEBUG, "WPS: AP configuration updated"); + + return 0; +} + + +static int hostapd_wps_cred_cb(void *ctx, const struct wps_credential *cred) +{ + struct hostapd_data *hapd = ctx; + return hostapd_wps_for_each(hapd, hapd_wps_cred_cb, (void *) cred); +} + + +static void hostapd_wps_reenable_ap_pin(void *eloop_data, void *user_ctx) +{ + struct hostapd_data *hapd = eloop_data; + + if (hapd->conf->ap_setup_locked) + return; + if (hapd->ap_pin_failures_consecutive >= 10) + return; + + wpa_printf(MSG_DEBUG, "WPS: Re-enable AP PIN"); + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_AP_SETUP_UNLOCKED); + hapd->wps->ap_setup_locked = 0; + wps_registrar_update_ie(hapd->wps->registrar); +} + + +static int wps_pwd_auth_fail(struct hostapd_data *hapd, void *ctx) +{ + struct wps_event_pwd_auth_fail *data = ctx; + + if (!data->enrollee || hapd->conf->ap_pin == NULL || hapd->wps == NULL) + return 0; + + /* + * Registrar failed to prove its knowledge of the AP PIN. Lock AP setup + * for some time if this happens multiple times to slow down brute + * force attacks. + */ + hapd->ap_pin_failures++; + hapd->ap_pin_failures_consecutive++; + wpa_printf(MSG_DEBUG, "WPS: AP PIN authentication failure number %u " + "(%u consecutive)", + hapd->ap_pin_failures, hapd->ap_pin_failures_consecutive); + if (hapd->ap_pin_failures < 3) + return 0; + + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_AP_SETUP_LOCKED); + hapd->wps->ap_setup_locked = 1; + + wps_registrar_update_ie(hapd->wps->registrar); + + if (!hapd->conf->ap_setup_locked && + hapd->ap_pin_failures_consecutive >= 10) { + /* + * In indefinite lockdown - disable automatic AP PIN + * reenablement. + */ + eloop_cancel_timeout(hostapd_wps_reenable_ap_pin, hapd, NULL); + wpa_printf(MSG_DEBUG, "WPS: AP PIN disabled indefinitely"); + } else if (!hapd->conf->ap_setup_locked) { + if (hapd->ap_pin_lockout_time == 0) + hapd->ap_pin_lockout_time = 60; + else if (hapd->ap_pin_lockout_time < 365 * 24 * 60 * 60 && + (hapd->ap_pin_failures % 3) == 0) + hapd->ap_pin_lockout_time *= 2; + + wpa_printf(MSG_DEBUG, "WPS: Disable AP PIN for %u seconds", + hapd->ap_pin_lockout_time); + eloop_cancel_timeout(hostapd_wps_reenable_ap_pin, hapd, NULL); + eloop_register_timeout(hapd->ap_pin_lockout_time, 0, + hostapd_wps_reenable_ap_pin, hapd, + NULL); + } + + return 0; +} + + +static void hostapd_pwd_auth_fail(struct hostapd_data *hapd, + struct wps_event_pwd_auth_fail *data) +{ + /* Update WPS Status - Authentication Failure */ + wpa_printf(MSG_DEBUG, "WPS: Authentication failure update"); + hapd->wps_stats.status = WPS_STATUS_FAILURE; + hapd->wps_stats.failure_reason = WPS_EI_AUTH_FAILURE; + os_memcpy(hapd->wps_stats.peer_addr, data->peer_macaddr, ETH_ALEN); + + hostapd_wps_for_each(hapd, wps_pwd_auth_fail, data); +} + + +static int wps_ap_pin_success(struct hostapd_data *hapd, void *ctx) +{ + if (hapd->conf->ap_pin == NULL || hapd->wps == NULL) + return 0; + + if (hapd->ap_pin_failures_consecutive == 0) + return 0; + + wpa_printf(MSG_DEBUG, "WPS: Clear consecutive AP PIN failure counter " + "- total validation failures %u (%u consecutive)", + hapd->ap_pin_failures, hapd->ap_pin_failures_consecutive); + hapd->ap_pin_failures_consecutive = 0; + + return 0; +} + + +static void hostapd_wps_ap_pin_success(struct hostapd_data *hapd) +{ + hostapd_wps_for_each(hapd, wps_ap_pin_success, NULL); +} + + +static void hostapd_wps_event_pbc_overlap(struct hostapd_data *hapd) +{ + /* Update WPS Status - PBC Overlap */ + hapd->wps_stats.pbc_status = WPS_PBC_STATUS_OVERLAP; +} + + +static void hostapd_wps_event_pbc_timeout(struct hostapd_data *hapd) +{ + /* Update WPS PBC Status:PBC Timeout */ + hapd->wps_stats.pbc_status = WPS_PBC_STATUS_TIMEOUT; +} + + +static void hostapd_wps_event_pbc_active(struct hostapd_data *hapd) +{ + /* Update WPS PBC status - Active */ + hapd->wps_stats.pbc_status = WPS_PBC_STATUS_ACTIVE; +} + + +static void hostapd_wps_event_pbc_disable(struct hostapd_data *hapd) +{ + /* Update WPS PBC status - Active */ + hapd->wps_stats.pbc_status = WPS_PBC_STATUS_DISABLE; +} + + +static void hostapd_wps_event_success(struct hostapd_data *hapd, + struct wps_event_success *success) +{ + /* Update WPS status - Success */ + hapd->wps_stats.pbc_status = WPS_PBC_STATUS_DISABLE; + hapd->wps_stats.status = WPS_STATUS_SUCCESS; + os_memcpy(hapd->wps_stats.peer_addr, success->peer_macaddr, ETH_ALEN); +} + + +static void hostapd_wps_event_fail(struct hostapd_data *hapd, + struct wps_event_fail *fail) +{ + /* Update WPS status - Failure */ + hapd->wps_stats.status = WPS_STATUS_FAILURE; + os_memcpy(hapd->wps_stats.peer_addr, fail->peer_macaddr, ETH_ALEN); + + hapd->wps_stats.failure_reason = fail->error_indication; + + if (fail->error_indication > 0 && + fail->error_indication < NUM_WPS_EI_VALUES) { + wpa_msg(hapd->msg_ctx, MSG_INFO, + WPS_EVENT_FAIL "msg=%d config_error=%d reason=%d (%s)", + fail->msg, fail->config_error, fail->error_indication, + wps_ei_str(fail->error_indication)); + } else { + wpa_msg(hapd->msg_ctx, MSG_INFO, + WPS_EVENT_FAIL "msg=%d config_error=%d", + fail->msg, fail->config_error); + } +} + + +static void hostapd_wps_event_cb(void *ctx, enum wps_event event, + union wps_event_data *data) +{ + struct hostapd_data *hapd = ctx; + + switch (event) { + case WPS_EV_M2D: + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_M2D); + break; + case WPS_EV_FAIL: + hostapd_wps_event_fail(hapd, &data->fail); + break; + case WPS_EV_SUCCESS: + hostapd_wps_event_success(hapd, &data->success); + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_SUCCESS); + break; + case WPS_EV_PWD_AUTH_FAIL: + hostapd_pwd_auth_fail(hapd, &data->pwd_auth_fail); + break; + case WPS_EV_PBC_OVERLAP: + hostapd_wps_event_pbc_overlap(hapd); + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_OVERLAP); + break; + case WPS_EV_PBC_TIMEOUT: + hostapd_wps_event_pbc_timeout(hapd); + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_TIMEOUT); + break; + case WPS_EV_PBC_ACTIVE: + hostapd_wps_event_pbc_active(hapd); + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_ACTIVE); + break; + case WPS_EV_PBC_DISABLE: + hostapd_wps_event_pbc_disable(hapd); + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_DISABLE); + break; + case WPS_EV_ER_AP_ADD: + break; + case WPS_EV_ER_AP_REMOVE: + break; + case WPS_EV_ER_ENROLLEE_ADD: + break; + case WPS_EV_ER_ENROLLEE_REMOVE: + break; + case WPS_EV_ER_AP_SETTINGS: + break; + case WPS_EV_ER_SET_SELECTED_REGISTRAR: + break; + case WPS_EV_AP_PIN_SUCCESS: + hostapd_wps_ap_pin_success(hapd); + break; + } + if (hapd->wps_event_cb) + hapd->wps_event_cb(hapd->wps_event_cb_ctx, event, data); +} + + +static int hostapd_wps_rf_band_cb(void *ctx) +{ + struct hostapd_data *hapd = ctx; + + return hapd->iconf->hw_mode == HOSTAPD_MODE_IEEE80211A ? + WPS_RF_50GHZ : + hapd->iconf->hw_mode == HOSTAPD_MODE_IEEE80211AD ? + WPS_RF_60GHZ : WPS_RF_24GHZ; /* FIX: dualband AP */ +} + + +static void hostapd_wps_clear_ies(struct hostapd_data *hapd, int deinit_only) +{ + wpabuf_free(hapd->wps_beacon_ie); + hapd->wps_beacon_ie = NULL; + + wpabuf_free(hapd->wps_probe_resp_ie); + hapd->wps_probe_resp_ie = NULL; + + if (deinit_only) { + if (hapd->drv_priv) + hostapd_reset_ap_wps_ie(hapd); + return; + } + + hostapd_set_ap_wps_ie(hapd); +} + + +static int get_uuid_cb(struct hostapd_iface *iface, void *ctx) +{ + const u8 **uuid = ctx; + size_t j; + + if (iface == NULL) + return 0; + for (j = 0; j < iface->num_bss; j++) { + struct hostapd_data *hapd = iface->bss[j]; + if (hapd->wps && !hapd->conf->wps_independent && + !is_nil_uuid(hapd->wps->uuid)) { + *uuid = hapd->wps->uuid; + return 1; + } + } + + return 0; +} + + +static const u8 * get_own_uuid(struct hostapd_iface *iface) +{ + const u8 *uuid; + if (iface->interfaces == NULL || + iface->interfaces->for_each_interface == NULL) + return NULL; + uuid = NULL; + iface->interfaces->for_each_interface(iface->interfaces, get_uuid_cb, + &uuid); + return uuid; +} + + +static int count_interface_cb(struct hostapd_iface *iface, void *ctx) +{ + int *count= ctx; + (*count)++; + return 0; +} + + +static int interface_count(struct hostapd_iface *iface) +{ + int count = 0; + if (iface->interfaces == NULL || + iface->interfaces->for_each_interface == NULL) + return 0; + iface->interfaces->for_each_interface(iface->interfaces, + count_interface_cb, &count); + return count; +} + + +static int hostapd_wps_set_vendor_ext(struct hostapd_data *hapd, + struct wps_context *wps) +{ + int i; + + for (i = 0; i < MAX_WPS_VENDOR_EXTENSIONS; i++) { + wpabuf_free(wps->dev.vendor_ext[i]); + wps->dev.vendor_ext[i] = NULL; + + if (hapd->conf->wps_vendor_ext[i] == NULL) + continue; + + wps->dev.vendor_ext[i] = + wpabuf_dup(hapd->conf->wps_vendor_ext[i]); + if (wps->dev.vendor_ext[i] == NULL) { + while (--i >= 0) + wpabuf_free(wps->dev.vendor_ext[i]); + return -1; + } + } + + return 0; +} + + +static int hostapd_wps_set_application_ext(struct hostapd_data *hapd, + struct wps_context *wps) +{ + wpabuf_free(wps->dev.application_ext); + + if (!hapd->conf->wps_application_ext) { + wps->dev.application_ext = NULL; + return 0; + } + + wps->dev.application_ext = wpabuf_dup(hapd->conf->wps_application_ext); + return wps->dev.application_ext ? 0 : -1; +} + + +static void hostapd_free_wps(struct wps_context *wps) +{ + int i; + + for (i = 0; i < MAX_WPS_VENDOR_EXTENSIONS; i++) + wpabuf_free(wps->dev.vendor_ext[i]); + wps_device_data_free(&wps->dev); + bin_clear_free(wps->network_key, wps->network_key_len); + hostapd_wps_nfc_clear(wps); + wpabuf_free(wps->dh_pubkey); + wpabuf_free(wps->dh_privkey); + forced_memzero(wps->psk, sizeof(wps->psk)); + os_free(wps); +} + + +int hostapd_init_wps(struct hostapd_data *hapd, + struct hostapd_bss_config *conf) +{ + struct wps_context *wps; + struct wps_registrar_config cfg; + u8 *multi_ap_netw_key = NULL; + + if (conf->wps_state == 0) { + hostapd_wps_clear_ies(hapd, 0); + return 0; + } + + wps = os_zalloc(sizeof(*wps)); + if (wps == NULL) + return -1; + + wps->cred_cb = hostapd_wps_cred_cb; + wps->event_cb = hostapd_wps_event_cb; + wps->rf_band_cb = hostapd_wps_rf_band_cb; + wps->cb_ctx = hapd; + + os_memset(&cfg, 0, sizeof(cfg)); + wps->wps_state = hapd->conf->wps_state; + wps->ap_setup_locked = hapd->conf->ap_setup_locked; + if (is_nil_uuid(hapd->conf->uuid)) { + const u8 *uuid; + uuid = get_own_uuid(hapd->iface); + if (uuid && !conf->wps_independent) { + os_memcpy(wps->uuid, uuid, UUID_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: Clone UUID from another " + "interface", wps->uuid, UUID_LEN); + } else { + uuid_gen_mac_addr(hapd->own_addr, wps->uuid); + wpa_hexdump(MSG_DEBUG, "WPS: UUID based on MAC " + "address", wps->uuid, UUID_LEN); + } + } else { + os_memcpy(wps->uuid, hapd->conf->uuid, UUID_LEN); + wpa_hexdump(MSG_DEBUG, "WPS: Use configured UUID", + wps->uuid, UUID_LEN); + } + wps->ssid_len = hapd->conf->ssid.ssid_len; + os_memcpy(wps->ssid, hapd->conf->ssid.ssid, wps->ssid_len); + wps->ap = 1; + os_memcpy(wps->dev.mac_addr, hapd->own_addr, ETH_ALEN); + wps->dev.device_name = hapd->conf->device_name ? + os_strdup(hapd->conf->device_name) : NULL; + wps->dev.manufacturer = hapd->conf->manufacturer ? + os_strdup(hapd->conf->manufacturer) : NULL; + wps->dev.model_name = hapd->conf->model_name ? + os_strdup(hapd->conf->model_name) : NULL; + wps->dev.model_number = hapd->conf->model_number ? + os_strdup(hapd->conf->model_number) : NULL; + wps->dev.serial_number = hapd->conf->serial_number ? + os_strdup(hapd->conf->serial_number) : NULL; + wps->config_methods = + wps_config_methods_str2bin(hapd->conf->config_methods); + if ((wps->config_methods & + (WPS_CONFIG_DISPLAY | WPS_CONFIG_VIRT_DISPLAY | + WPS_CONFIG_PHY_DISPLAY)) == WPS_CONFIG_DISPLAY) { + wpa_printf(MSG_INFO, "WPS: Converting display to " + "virtual_display for WPS 2.0 compliance"); + wps->config_methods |= WPS_CONFIG_VIRT_DISPLAY; + } + if ((wps->config_methods & + (WPS_CONFIG_PUSHBUTTON | WPS_CONFIG_VIRT_PUSHBUTTON | + WPS_CONFIG_PHY_PUSHBUTTON)) == WPS_CONFIG_PUSHBUTTON) { + wpa_printf(MSG_INFO, "WPS: Converting push_button to " + "virtual_push_button for WPS 2.0 compliance"); + wps->config_methods |= WPS_CONFIG_VIRT_PUSHBUTTON; + } + os_memcpy(wps->dev.pri_dev_type, hapd->conf->device_type, + WPS_DEV_TYPE_LEN); + + if (hostapd_wps_set_vendor_ext(hapd, wps) < 0 || + hostapd_wps_set_application_ext(hapd, wps) < 0) + goto fail; + + wps->dev.os_version = WPA_GET_BE32(hapd->conf->os_version); + + if (conf->wps_rf_bands) { + wps->dev.rf_bands = conf->wps_rf_bands; + } else { + wps->dev.rf_bands = + hapd->iconf->hw_mode == HOSTAPD_MODE_IEEE80211A ? + WPS_RF_50GHZ : + hapd->iconf->hw_mode == HOSTAPD_MODE_IEEE80211AD ? + WPS_RF_60GHZ : WPS_RF_24GHZ; /* FIX: dualband AP */ + } + + if (conf->wpa & WPA_PROTO_RSN) { + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_PSK) + wps->auth_types |= WPS_AUTH_WPA2PSK; + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X) + wps->auth_types |= WPS_AUTH_WPA2; + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_SAE) + wps->auth_types |= WPS_AUTH_WPA2PSK; + + if (conf->rsn_pairwise & (WPA_CIPHER_CCMP | WPA_CIPHER_GCMP | + WPA_CIPHER_CCMP_256 | + WPA_CIPHER_GCMP_256)) { + wps->encr_types |= WPS_ENCR_AES; + wps->encr_types_rsn |= WPS_ENCR_AES; + } + if (conf->rsn_pairwise & WPA_CIPHER_TKIP) { +#ifdef CONFIG_NO_TKIP + wpa_printf(MSG_INFO, "WPS: TKIP not supported"); + goto fail; +#else /* CONFIG_NO_TKIP */ + wps->encr_types |= WPS_ENCR_TKIP; + wps->encr_types_rsn |= WPS_ENCR_TKIP; +#endif /* CONFIG_NO_TKIP */ + } + } + + if (conf->wpa & WPA_PROTO_WPA) { +#ifdef CONFIG_NO_TKIP + if (!(conf->wpa & WPA_PROTO_RSN)) { + wpa_printf(MSG_INFO, "WPS: WPA(v1) not supported"); + goto fail; + } + conf->wpa &= ~WPA_PROTO_WPA; +#else /* CONFIG_NO_TKIP */ + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_PSK) + wps->auth_types |= WPS_AUTH_WPAPSK; + if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X) + wps->auth_types |= WPS_AUTH_WPA; + + if (conf->wpa_pairwise & WPA_CIPHER_CCMP) { + wps->encr_types |= WPS_ENCR_AES; + wps->encr_types_wpa |= WPS_ENCR_AES; + } + if (conf->wpa_pairwise & WPA_CIPHER_TKIP) { + wps->encr_types |= WPS_ENCR_TKIP; + wps->encr_types_wpa |= WPS_ENCR_TKIP; + } +#endif /* CONFIG_NO_TKIP */ + } + + if (conf->ssid.security_policy == SECURITY_PLAINTEXT) { + wps->encr_types |= WPS_ENCR_NONE; + wps->auth_types |= WPS_AUTH_OPEN; + } + + if (conf->ssid.wpa_psk_file) { + /* Use per-device PSKs */ + } else if (conf->ssid.wpa_passphrase) { + wps->network_key = (u8 *) os_strdup(conf->ssid.wpa_passphrase); + wps->network_key_len = os_strlen(conf->ssid.wpa_passphrase); + } else if (conf->ssid.wpa_psk) { + wps->network_key = os_malloc(2 * PMK_LEN + 1); + if (wps->network_key == NULL) + goto fail; + wpa_snprintf_hex((char *) wps->network_key, 2 * PMK_LEN + 1, + conf->ssid.wpa_psk->psk, PMK_LEN); + wps->network_key_len = 2 * PMK_LEN; +#ifdef CONFIG_WEP + } else if (conf->ssid.wep.keys_set && conf->ssid.wep.key[0]) { + wps->network_key = os_malloc(conf->ssid.wep.len[0]); + if (wps->network_key == NULL) + goto fail; + os_memcpy(wps->network_key, conf->ssid.wep.key[0], + conf->ssid.wep.len[0]); + wps->network_key_len = conf->ssid.wep.len[0]; +#endif /* CONFIG_WEP */ + } + + if (conf->ssid.wpa_psk) { + os_memcpy(wps->psk, conf->ssid.wpa_psk->psk, PMK_LEN); + wps->psk_set = 1; + } + + wps->ap_auth_type = wps->auth_types; + wps->ap_encr_type = wps->encr_types; + if (conf->wps_state == WPS_STATE_NOT_CONFIGURED) { + /* Override parameters to enable security by default */ +#ifdef CONFIG_NO_TKIP + wps->auth_types = WPS_AUTH_WPA2PSK; + wps->encr_types = WPS_ENCR_AES; + wps->encr_types_rsn = WPS_ENCR_AES; + wps->encr_types_wpa = WPS_ENCR_AES; +#else /* CONFIG_NO_TKIP */ + wps->auth_types = WPS_AUTH_WPA2PSK | WPS_AUTH_WPAPSK; + wps->encr_types = WPS_ENCR_AES | WPS_ENCR_TKIP; + wps->encr_types_rsn = WPS_ENCR_AES | WPS_ENCR_TKIP; + wps->encr_types_wpa = WPS_ENCR_AES | WPS_ENCR_TKIP; +#endif /* CONFIG_NO_TKIP */ + } + + if ((hapd->conf->multi_ap & FRONTHAUL_BSS) && + hapd->conf->multi_ap_backhaul_ssid.ssid_len) { + cfg.multi_ap_backhaul_ssid_len = + hapd->conf->multi_ap_backhaul_ssid.ssid_len; + cfg.multi_ap_backhaul_ssid = + hapd->conf->multi_ap_backhaul_ssid.ssid; + + if (conf->multi_ap_backhaul_ssid.wpa_passphrase) { + cfg.multi_ap_backhaul_network_key = (const u8 *) + conf->multi_ap_backhaul_ssid.wpa_passphrase; + cfg.multi_ap_backhaul_network_key_len = + os_strlen(conf->multi_ap_backhaul_ssid.wpa_passphrase); + } else if (conf->multi_ap_backhaul_ssid.wpa_psk) { + multi_ap_netw_key = os_malloc(2 * PMK_LEN + 1); + if (!multi_ap_netw_key) + goto fail; + wpa_snprintf_hex((char *) multi_ap_netw_key, + 2 * PMK_LEN + 1, + conf->multi_ap_backhaul_ssid.wpa_psk->psk, + PMK_LEN); + cfg.multi_ap_backhaul_network_key = multi_ap_netw_key; + cfg.multi_ap_backhaul_network_key_len = 2 * PMK_LEN; + } + } + + wps->ap_settings = conf->ap_settings; + wps->ap_settings_len = conf->ap_settings_len; + + cfg.new_psk_cb = hostapd_wps_new_psk_cb; + cfg.set_ie_cb = hostapd_wps_set_ie_cb; + cfg.pin_needed_cb = hostapd_wps_pin_needed_cb; + cfg.reg_success_cb = hostapd_wps_reg_success_cb; + cfg.enrollee_seen_cb = hostapd_wps_enrollee_seen_cb; + cfg.lookup_pskfile_cb = hostapd_wps_lookup_pskfile_cb; + cfg.cb_ctx = hapd; + cfg.skip_cred_build = conf->skip_cred_build; + cfg.extra_cred = conf->extra_cred; + cfg.extra_cred_len = conf->extra_cred_len; + cfg.disable_auto_conf = (hapd->conf->wps_cred_processing == 1) && + conf->skip_cred_build; + cfg.dualband = interface_count(hapd->iface) > 1; + if ((wps->dev.rf_bands & (WPS_RF_50GHZ | WPS_RF_24GHZ)) == + (WPS_RF_50GHZ | WPS_RF_24GHZ)) + cfg.dualband = 1; + if (cfg.dualband) + wpa_printf(MSG_DEBUG, "WPS: Dualband AP"); + cfg.force_per_enrollee_psk = conf->force_per_enrollee_psk; + + wps->registrar = wps_registrar_init(wps, &cfg); + if (wps->registrar == NULL) { + wpa_printf(MSG_ERROR, "Failed to initialize WPS Registrar"); + goto fail; + } + +#ifdef CONFIG_WPS_UPNP + wps->friendly_name = hapd->conf->friendly_name; + wps->manufacturer_url = hapd->conf->manufacturer_url; + wps->model_description = hapd->conf->model_description; + wps->model_url = hapd->conf->model_url; + wps->upc = hapd->conf->upc; +#endif /* CONFIG_WPS_UPNP */ + + hostapd_register_probereq_cb(hapd, hostapd_wps_probe_req_rx, hapd); + +#ifdef CONFIG_P2P + if ((hapd->conf->p2p & P2P_ENABLED) && + is_6ghz_op_class(hapd->iconf->op_class)) + wps->use_passphrase = true; +#endif /* CONFIG_P2P */ + hapd->wps = wps; + bin_clear_free(multi_ap_netw_key, 2 * PMK_LEN); + + return 0; + +fail: + bin_clear_free(multi_ap_netw_key, 2 * PMK_LEN); + hostapd_free_wps(wps); + return -1; +} + + +int hostapd_init_wps_complete(struct hostapd_data *hapd) +{ + struct wps_context *wps = hapd->wps; + + if (wps == NULL) + return 0; + +#ifdef CONFIG_WPS_UPNP + if (hostapd_wps_upnp_init(hapd, wps) < 0) { + wpa_printf(MSG_ERROR, "Failed to initialize WPS UPnP"); + wps_registrar_deinit(wps->registrar); + hostapd_free_wps(wps); + hapd->wps = NULL; + return -1; + } +#endif /* CONFIG_WPS_UPNP */ + + return 0; +} + + +static void hostapd_wps_nfc_clear(struct wps_context *wps) +{ +#ifdef CONFIG_WPS_NFC + wpa_printf(MSG_DEBUG, "WPS: Clear NFC Tag context %p", wps); + wps->ap_nfc_dev_pw_id = 0; + wpabuf_free(wps->ap_nfc_dh_pubkey); + wps->ap_nfc_dh_pubkey = NULL; + wpabuf_free(wps->ap_nfc_dh_privkey); + wps->ap_nfc_dh_privkey = NULL; + wpabuf_free(wps->ap_nfc_dev_pw); + wps->ap_nfc_dev_pw = NULL; +#endif /* CONFIG_WPS_NFC */ +} + + +static int hostapd_wps_update_multi_ap(struct hostapd_data *hapd, + struct wps_registrar *reg) +{ + struct hostapd_bss_config *conf = hapd->conf; + u8 *multi_ap_backhaul_network_key = NULL; + size_t multi_ap_backhaul_network_key_len = 0; + int ret; + + if (!(conf->multi_ap & FRONTHAUL_BSS) || + !conf->multi_ap_backhaul_ssid.ssid_len) + return 0; + + if (conf->multi_ap_backhaul_ssid.wpa_passphrase) { + multi_ap_backhaul_network_key = + (u8 *) os_strdup( + conf->multi_ap_backhaul_ssid.wpa_passphrase); + if (!multi_ap_backhaul_network_key) + return -1; + multi_ap_backhaul_network_key_len = + os_strlen(conf->multi_ap_backhaul_ssid.wpa_passphrase); + } else if (conf->multi_ap_backhaul_ssid.wpa_psk) { + multi_ap_backhaul_network_key = os_malloc(2 * PMK_LEN + 1); + if (!multi_ap_backhaul_network_key) + return -1; + wpa_snprintf_hex((char *) multi_ap_backhaul_network_key, + 2 * PMK_LEN + 1, + conf->multi_ap_backhaul_ssid.wpa_psk->psk, + PMK_LEN); + multi_ap_backhaul_network_key_len = 2 * PMK_LEN; + } + + ret = wps_registrar_update_multi_ap( + reg, conf->multi_ap_backhaul_ssid.ssid, + conf->multi_ap_backhaul_ssid.ssid_len, + multi_ap_backhaul_network_key, + multi_ap_backhaul_network_key_len); + os_free(multi_ap_backhaul_network_key); + + return ret; +} + + +void hostapd_deinit_wps(struct hostapd_data *hapd) +{ + eloop_cancel_timeout(hostapd_wps_reenable_ap_pin, hapd, NULL); + eloop_cancel_timeout(hostapd_wps_ap_pin_timeout, hapd, NULL); + eloop_cancel_timeout(wps_reload_config, hapd->iface, NULL); + if (hapd->wps == NULL) { + hostapd_wps_clear_ies(hapd, 1); + return; + } +#ifdef CONFIG_WPS_UPNP + hostapd_wps_upnp_deinit(hapd); +#endif /* CONFIG_WPS_UPNP */ + wps_registrar_deinit(hapd->wps->registrar); + wps_free_pending_msgs(hapd->wps->upnp_msgs); + hostapd_free_wps(hapd->wps); + hapd->wps = NULL; + hostapd_wps_clear_ies(hapd, 1); +} + + +void hostapd_update_wps(struct hostapd_data *hapd) +{ + struct wps_context *wps = hapd->wps; + struct hostapd_bss_config *conf = hapd->conf; + + if (!wps) + return; + +#ifdef CONFIG_WPS_UPNP + wps->friendly_name = conf->friendly_name; + wps->manufacturer_url = conf->manufacturer_url; + wps->model_description = conf->model_description; + wps->model_url = conf->model_url; + wps->upc = conf->upc; +#endif /* CONFIG_WPS_UPNP */ + + os_memcpy(wps->ssid, conf->ssid.ssid, conf->ssid.ssid_len); + wps->ssid_len = conf->ssid.ssid_len; + + /* Clear WPS settings, then fill them again */ + os_free(wps->network_key); + wps->network_key = NULL; + wps->network_key_len = 0; + wps->psk_set = 0; + if (conf->ssid.wpa_psk_file) { + /* Use per-device PSKs */ + } else if (conf->ssid.wpa_passphrase) { + wps->network_key = (u8 *) os_strdup(conf->ssid.wpa_passphrase); + if (!wps->network_key) + return; + wps->network_key_len = os_strlen(conf->ssid.wpa_passphrase); + } else if (conf->ssid.wpa_psk) { + wps->network_key = os_malloc(2 * PMK_LEN + 1); + if (!wps->network_key) + return; + wpa_snprintf_hex((char *) wps->network_key, 2 * PMK_LEN + 1, + conf->ssid.wpa_psk->psk, PMK_LEN); + wps->network_key_len = 2 * PMK_LEN; +#ifdef CONFIG_WEP + } else if (conf->ssid.wep.keys_set && conf->ssid.wep.key[0]) { + wps->network_key = os_malloc(conf->ssid.wep.len[0]); + if (!wps->network_key) + return; + os_memcpy(wps->network_key, conf->ssid.wep.key[0], + conf->ssid.wep.len[0]); + wps->network_key_len = conf->ssid.wep.len[0]; +#endif /* CONFIG_WEP */ + } + + if (conf->ssid.wpa_psk) { + os_memcpy(wps->psk, conf->ssid.wpa_psk->psk, PMK_LEN); + wps->psk_set = 1; + } + + hostapd_wps_update_multi_ap(hapd, wps->registrar); + + hostapd_wps_set_vendor_ext(hapd, wps); + hostapd_wps_set_application_ext(hapd, wps); + + if (conf->wps_state) + wps_registrar_update_ie(wps->registrar); + else + hostapd_deinit_wps(hapd); +} + + +struct wps_add_pin_data { + const u8 *addr; + const u8 *uuid; + const u8 *pin; + size_t pin_len; + int timeout; + int added; +}; + + +static int wps_add_pin(struct hostapd_data *hapd, void *ctx) +{ + struct wps_add_pin_data *data = ctx; + int ret; + + if (hapd->wps == NULL) + return 0; + ret = wps_registrar_add_pin(hapd->wps->registrar, data->addr, + data->uuid, data->pin, data->pin_len, + data->timeout); + if (ret == 0) + data->added++; + return ret; +} + + +int hostapd_wps_add_pin(struct hostapd_data *hapd, const u8 *addr, + const char *uuid, const char *pin, int timeout) +{ + u8 u[UUID_LEN]; + struct wps_add_pin_data data; + + data.addr = addr; + data.uuid = u; + data.pin = (const u8 *) pin; + data.pin_len = os_strlen(pin); + data.timeout = timeout; + data.added = 0; + + if (os_strcmp(uuid, "any") == 0) + data.uuid = NULL; + else { + if (uuid_str2bin(uuid, u)) + return -1; + data.uuid = u; + } + if (hostapd_wps_for_each(hapd, wps_add_pin, &data) < 0) + return -1; + return data.added ? 0 : -1; +} + + +struct wps_button_pushed_ctx { + const u8 *p2p_dev_addr; + unsigned int count; +}; + +static int wps_button_pushed(struct hostapd_data *hapd, void *ctx) +{ + struct wps_button_pushed_ctx *data = ctx; + + if (hapd->wps) { + data->count++; + return wps_registrar_button_pushed(hapd->wps->registrar, + data->p2p_dev_addr); + } + + return 0; +} + + +int hostapd_wps_button_pushed(struct hostapd_data *hapd, + const u8 *p2p_dev_addr) +{ + struct wps_button_pushed_ctx ctx; + int ret; + + os_memset(&ctx, 0, sizeof(ctx)); + ctx.p2p_dev_addr = p2p_dev_addr; + ret = hostapd_wps_for_each(hapd, wps_button_pushed, &ctx); + if (ret == 0 && !ctx.count) + ret = -1; + return ret; +} + + +struct wps_cancel_ctx { + unsigned int count; +}; + +static int wps_cancel(struct hostapd_data *hapd, void *ctx) +{ + struct wps_cancel_ctx *data = ctx; + + if (hapd->wps) { + data->count++; + wps_registrar_wps_cancel(hapd->wps->registrar); + ap_for_each_sta(hapd, ap_sta_wps_cancel, NULL); + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_CANCEL); + } + + return 0; +} + + +int hostapd_wps_cancel(struct hostapd_data *hapd) +{ + struct wps_cancel_ctx ctx; + int ret; + + os_memset(&ctx, 0, sizeof(ctx)); + ret = hostapd_wps_for_each(hapd, wps_cancel, &ctx); + if (ret == 0 && !ctx.count) + ret = -1; + return ret; +} + + +static int hostapd_wps_probe_req_rx(void *ctx, const u8 *addr, const u8 *da, + const u8 *bssid, + const u8 *ie, size_t ie_len, + int ssi_signal) +{ + struct hostapd_data *hapd = ctx; + struct wpabuf *wps_ie; + struct ieee802_11_elems elems; + + if (hapd->wps == NULL) + return 0; + + if (ieee802_11_parse_elems(ie, ie_len, &elems, 0) == ParseFailed) { + wpa_printf(MSG_DEBUG, "WPS: Could not parse ProbeReq from " + MACSTR, MAC2STR(addr)); + return 0; + } + + if (elems.ssid && elems.ssid_len > 0 && + (elems.ssid_len != hapd->conf->ssid.ssid_len || + os_memcmp(elems.ssid, hapd->conf->ssid.ssid, elems.ssid_len) != + 0)) + return 0; /* Not for us */ + + wps_ie = ieee802_11_vendor_ie_concat(ie, ie_len, WPS_DEV_OUI_WFA); + if (wps_ie == NULL) + return 0; + if (wps_validate_probe_req(wps_ie, addr) < 0) { + wpabuf_free(wps_ie); + return 0; + } + + if (wpabuf_len(wps_ie) > 0) { + int p2p_wildcard = 0; +#ifdef CONFIG_P2P + if (elems.ssid && elems.ssid_len == P2P_WILDCARD_SSID_LEN && + os_memcmp(elems.ssid, P2P_WILDCARD_SSID, + P2P_WILDCARD_SSID_LEN) == 0) + p2p_wildcard = 1; +#endif /* CONFIG_P2P */ + wps_registrar_probe_req_rx(hapd->wps->registrar, addr, wps_ie, + p2p_wildcard); +#ifdef CONFIG_WPS_UPNP + /* FIX: what exactly should be included in the WLANEvent? + * WPS attributes? Full ProbeReq frame? */ + if (!p2p_wildcard) + upnp_wps_device_send_wlan_event( + hapd->wps_upnp, addr, + UPNP_WPS_WLANEVENT_TYPE_PROBE, wps_ie); +#endif /* CONFIG_WPS_UPNP */ + } + + wpabuf_free(wps_ie); + + return 0; +} + + +#ifdef CONFIG_WPS_UPNP + +static int hostapd_rx_req_put_wlan_response( + void *priv, enum upnp_wps_wlanevent_type ev_type, + const u8 *mac_addr, const struct wpabuf *msg, + enum wps_msg_type msg_type) +{ + struct hostapd_data *hapd = priv; + struct sta_info *sta; + struct upnp_pending_message *p; + + wpa_printf(MSG_DEBUG, "WPS UPnP: PutWLANResponse ev_type=%d mac_addr=" + MACSTR, ev_type, MAC2STR(mac_addr)); + wpa_hexdump(MSG_MSGDUMP, "WPS UPnP: PutWLANResponse NewMessage", + wpabuf_head(msg), wpabuf_len(msg)); + if (ev_type != UPNP_WPS_WLANEVENT_TYPE_EAP) { + wpa_printf(MSG_DEBUG, "WPS UPnP: Ignored unexpected " + "PutWLANResponse WLANEventType %d", ev_type); + return -1; + } + + /* + * EAP response to ongoing to WPS Registration. Send it to EAP-WSC + * server implementation for delivery to the peer. + */ + + sta = ap_get_sta(hapd, mac_addr); +#ifndef CONFIG_WPS_STRICT + if (!sta) { + /* + * Workaround - Intel wsccmd uses bogus NewWLANEventMAC: + * Pick STA that is in an ongoing WPS registration without + * checking the MAC address. + */ + wpa_printf(MSG_DEBUG, "WPS UPnP: No matching STA found based " + "on NewWLANEventMAC; try wildcard match"); + for (sta = hapd->sta_list; sta; sta = sta->next) { + if (sta->eapol_sm && (sta->flags & WLAN_STA_WPS)) + break; + } + } +#endif /* CONFIG_WPS_STRICT */ + + if (!sta || !(sta->flags & WLAN_STA_WPS)) { + wpa_printf(MSG_DEBUG, "WPS UPnP: No matching STA found"); + return 0; + } + + if (!sta->eapol_sm) { + /* + * This can happen, e.g., if an ER sends an extra message after + * the station has disassociated (but not fully + * deauthenticated). + */ + wpa_printf(MSG_DEBUG, "WPS UPnP: Matching STA did not have EAPOL state machine initialized"); + return 0; + } + + p = os_zalloc(sizeof(*p)); + if (p == NULL) + return -1; + os_memcpy(p->addr, sta->addr, ETH_ALEN); + p->msg = wpabuf_dup(msg); + p->type = msg_type; + p->next = hapd->wps->upnp_msgs; + hapd->wps->upnp_msgs = p; + + return eapol_auth_eap_pending_cb(sta->eapol_sm, sta->eapol_sm->eap); +} + + +static int hostapd_wps_upnp_init(struct hostapd_data *hapd, + struct wps_context *wps) +{ + struct upnp_wps_device_ctx *ctx; + + if (!hapd->conf->upnp_iface) + return 0; + ctx = os_zalloc(sizeof(*ctx)); + if (ctx == NULL) + return -1; + + ctx->rx_req_put_wlan_response = hostapd_rx_req_put_wlan_response; + if (hapd->conf->ap_pin) + ctx->ap_pin = os_strdup(hapd->conf->ap_pin); + + hapd->wps_upnp = upnp_wps_device_init(ctx, wps, hapd, + hapd->conf->upnp_iface); + if (hapd->wps_upnp == NULL) + return -1; + wps->wps_upnp = hapd->wps_upnp; + + return 0; +} + + +static void hostapd_wps_upnp_deinit(struct hostapd_data *hapd) +{ + upnp_wps_device_deinit(hapd->wps_upnp, hapd); +} + +#endif /* CONFIG_WPS_UPNP */ + + +int hostapd_wps_get_mib_sta(struct hostapd_data *hapd, const u8 *addr, + char *buf, size_t buflen) +{ + if (hapd->wps == NULL) + return 0; + return wps_registrar_get_info(hapd->wps->registrar, addr, buf, buflen); +} + + +static void hostapd_wps_ap_pin_timeout(void *eloop_data, void *user_ctx) +{ + struct hostapd_data *hapd = eloop_data; + wpa_printf(MSG_DEBUG, "WPS: AP PIN timed out"); + hostapd_wps_ap_pin_disable(hapd); + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_AP_PIN_DISABLED); +} + + +static void hostapd_wps_ap_pin_enable(struct hostapd_data *hapd, int timeout) +{ + wpa_printf(MSG_DEBUG, "WPS: Enabling AP PIN (timeout=%d)", timeout); + hapd->ap_pin_failures = 0; + hapd->ap_pin_failures_consecutive = 0; + hapd->conf->ap_setup_locked = 0; + if (hapd->wps->ap_setup_locked) { + wpa_msg(hapd->msg_ctx, MSG_INFO, WPS_EVENT_AP_SETUP_UNLOCKED); + hapd->wps->ap_setup_locked = 0; + wps_registrar_update_ie(hapd->wps->registrar); + } + eloop_cancel_timeout(hostapd_wps_ap_pin_timeout, hapd, NULL); + if (timeout > 0) + eloop_register_timeout(timeout, 0, + hostapd_wps_ap_pin_timeout, hapd, NULL); +} + + +static int wps_ap_pin_disable(struct hostapd_data *hapd, void *ctx) +{ + os_free(hapd->conf->ap_pin); + hapd->conf->ap_pin = NULL; +#ifdef CONFIG_WPS_UPNP + upnp_wps_set_ap_pin(hapd->wps_upnp, NULL); +#endif /* CONFIG_WPS_UPNP */ + eloop_cancel_timeout(hostapd_wps_ap_pin_timeout, hapd, NULL); + return 0; +} + + +void hostapd_wps_ap_pin_disable(struct hostapd_data *hapd) +{ + wpa_printf(MSG_DEBUG, "WPS: Disabling AP PIN"); + hostapd_wps_for_each(hapd, wps_ap_pin_disable, NULL); +} + + +struct wps_ap_pin_data { + char pin_txt[9]; + int timeout; +}; + + +static int wps_ap_pin_set(struct hostapd_data *hapd, void *ctx) +{ + struct wps_ap_pin_data *data = ctx; + + if (!hapd->wps) + return 0; + + os_free(hapd->conf->ap_pin); + hapd->conf->ap_pin = os_strdup(data->pin_txt); +#ifdef CONFIG_WPS_UPNP + upnp_wps_set_ap_pin(hapd->wps_upnp, data->pin_txt); +#endif /* CONFIG_WPS_UPNP */ + hostapd_wps_ap_pin_enable(hapd, data->timeout); + return 0; +} + + +const char * hostapd_wps_ap_pin_random(struct hostapd_data *hapd, int timeout) +{ + unsigned int pin; + struct wps_ap_pin_data data; + + if (wps_generate_pin(&pin) < 0) + return NULL; + os_snprintf(data.pin_txt, sizeof(data.pin_txt), "%08u", pin); + data.timeout = timeout; + hostapd_wps_for_each(hapd, wps_ap_pin_set, &data); + return hapd->conf->ap_pin; +} + + +const char * hostapd_wps_ap_pin_get(struct hostapd_data *hapd) +{ + return hapd->conf->ap_pin; +} + + +int hostapd_wps_ap_pin_set(struct hostapd_data *hapd, const char *pin, + int timeout) +{ + struct wps_ap_pin_data data; + int ret; + + ret = os_snprintf(data.pin_txt, sizeof(data.pin_txt), "%s", pin); + if (os_snprintf_error(sizeof(data.pin_txt), ret)) + return -1; + data.timeout = timeout; + return hostapd_wps_for_each(hapd, wps_ap_pin_set, &data); +} + + +static int wps_update_ie(struct hostapd_data *hapd, void *ctx) +{ + if (hapd->wps) + wps_registrar_update_ie(hapd->wps->registrar); + return 0; +} + + +void hostapd_wps_update_ie(struct hostapd_data *hapd) +{ + hostapd_wps_for_each(hapd, wps_update_ie, NULL); +} + + +int hostapd_wps_config_ap(struct hostapd_data *hapd, const char *ssid, + const char *auth, const char *encr, const char *key) +{ + struct wps_credential cred; + size_t len; + + os_memset(&cred, 0, sizeof(cred)); + + len = os_strlen(ssid); + if ((len & 1) || len > 2 * sizeof(cred.ssid) || + hexstr2bin(ssid, cred.ssid, len / 2)) + return -1; + cred.ssid_len = len / 2; + + if (os_strncmp(auth, "OPEN", 4) == 0) + cred.auth_type = WPS_AUTH_OPEN; +#ifndef CONFIG_NO_TKIP + else if (os_strncmp(auth, "WPAPSK", 6) == 0) + cred.auth_type = WPS_AUTH_WPAPSK; +#endif /* CONFIG_NO_TKIP */ + else if (os_strncmp(auth, "WPA2PSK", 7) == 0) + cred.auth_type = WPS_AUTH_WPA2PSK; + else + return -1; + + if (encr) { + if (os_strncmp(encr, "NONE", 4) == 0) + cred.encr_type = WPS_ENCR_NONE; +#ifndef CONFIG_NO_TKIP + else if (os_strncmp(encr, "TKIP", 4) == 0) + cred.encr_type = WPS_ENCR_TKIP; +#endif /* CONFIG_NO_TKIP */ + else if (os_strncmp(encr, "CCMP", 4) == 0) + cred.encr_type = WPS_ENCR_AES; + else + return -1; + } else + cred.encr_type = WPS_ENCR_NONE; + + if (key) { + len = os_strlen(key); + if ((len & 1) || len > 2 * sizeof(cred.key) || + hexstr2bin(key, cred.key, len / 2)) + return -1; + cred.key_len = len / 2; + } + + if (!hapd->wps) { + wpa_printf(MSG_ERROR, "WPS: WPS config does not exist"); + return -1; + } + + return wps_registrar_config_ap(hapd->wps->registrar, &cred); +} + + +#ifdef CONFIG_WPS_NFC + +struct wps_nfc_password_token_data { + const u8 *oob_dev_pw; + size_t oob_dev_pw_len; + int added; +}; + + +static int wps_add_nfc_password_token(struct hostapd_data *hapd, void *ctx) +{ + struct wps_nfc_password_token_data *data = ctx; + int ret; + + if (hapd->wps == NULL) + return 0; + ret = wps_registrar_add_nfc_password_token(hapd->wps->registrar, + data->oob_dev_pw, + data->oob_dev_pw_len); + if (ret == 0) + data->added++; + return ret; +} + + +static int hostapd_wps_add_nfc_password_token(struct hostapd_data *hapd, + struct wps_parse_attr *attr) +{ + struct wps_nfc_password_token_data data; + + data.oob_dev_pw = attr->oob_dev_password; + data.oob_dev_pw_len = attr->oob_dev_password_len; + data.added = 0; + if (hostapd_wps_for_each(hapd, wps_add_nfc_password_token, &data) < 0) + return -1; + return data.added ? 0 : -1; +} + + +static int hostapd_wps_nfc_tag_process(struct hostapd_data *hapd, + const struct wpabuf *wps) +{ + struct wps_parse_attr attr; + + wpa_hexdump_buf(MSG_DEBUG, "WPS: Received NFC tag payload", wps); + + if (wps_parse_msg(wps, &attr)) { + wpa_printf(MSG_DEBUG, "WPS: Ignore invalid data from NFC tag"); + return -1; + } + + if (attr.oob_dev_password) + return hostapd_wps_add_nfc_password_token(hapd, &attr); + + wpa_printf(MSG_DEBUG, "WPS: Ignore unrecognized NFC tag"); + return -1; +} + + +int hostapd_wps_nfc_tag_read(struct hostapd_data *hapd, + const struct wpabuf *data) +{ + const struct wpabuf *wps = data; + struct wpabuf *tmp = NULL; + int ret; + + if (wpabuf_len(data) < 4) + return -1; + + if (*wpabuf_head_u8(data) != 0x10) { + /* Assume this contains full NDEF record */ + tmp = ndef_parse_wifi(data); + if (tmp == NULL) { + wpa_printf(MSG_DEBUG, "WPS: Could not parse NDEF"); + return -1; + } + wps = tmp; + } + + ret = hostapd_wps_nfc_tag_process(hapd, wps); + wpabuf_free(tmp); + return ret; +} + + +struct wpabuf * hostapd_wps_nfc_config_token(struct hostapd_data *hapd, + int ndef) +{ + struct wpabuf *ret; + + if (hapd->wps == NULL) + return NULL; + + ret = wps_get_oob_cred(hapd->wps, hostapd_wps_rf_band_cb(hapd), + hapd->iconf->channel); + if (ndef && ret) { + struct wpabuf *tmp; + tmp = ndef_build_wifi(ret); + wpabuf_free(ret); + if (tmp == NULL) + return NULL; + ret = tmp; + } + + return ret; +} + + +struct wpabuf * hostapd_wps_nfc_hs_cr(struct hostapd_data *hapd, int ndef) +{ + struct wpabuf *ret; + + if (hapd->wps == NULL) + return NULL; + + if (hapd->conf->wps_nfc_dh_pubkey == NULL) { + struct wps_context *wps = hapd->wps; + if (wps_nfc_gen_dh(&hapd->conf->wps_nfc_dh_pubkey, + &hapd->conf->wps_nfc_dh_privkey) < 0) + return NULL; + hostapd_wps_nfc_clear(wps); + wps->ap_nfc_dev_pw_id = DEV_PW_NFC_CONNECTION_HANDOVER; + wps->ap_nfc_dh_pubkey = + wpabuf_dup(hapd->conf->wps_nfc_dh_pubkey); + wps->ap_nfc_dh_privkey = + wpabuf_dup(hapd->conf->wps_nfc_dh_privkey); + if (!wps->ap_nfc_dh_pubkey || !wps->ap_nfc_dh_privkey) { + hostapd_wps_nfc_clear(wps); + return NULL; + } + } + + ret = wps_build_nfc_handover_sel(hapd->wps, + hapd->conf->wps_nfc_dh_pubkey, + hapd->own_addr, hapd->iface->freq); + + if (ndef && ret) { + struct wpabuf *tmp; + tmp = ndef_build_wifi(ret); + wpabuf_free(ret); + if (tmp == NULL) + return NULL; + ret = tmp; + } + + return ret; +} + + +int hostapd_wps_nfc_report_handover(struct hostapd_data *hapd, + const struct wpabuf *req, + const struct wpabuf *sel) +{ + struct wpabuf *wps; + int ret = -1; + u16 wsc_len; + const u8 *pos; + struct wpabuf msg; + struct wps_parse_attr attr; + u16 dev_pw_id; + + /* + * Enrollee/station is always initiator of the NFC connection handover, + * so use the request message here to find Enrollee public key hash. + */ + wps = ndef_parse_wifi(req); + if (wps == NULL) + return -1; + wpa_printf(MSG_DEBUG, "WPS: Received application/vnd.wfa.wsc " + "payload from NFC connection handover"); + wpa_hexdump_buf(MSG_DEBUG, "WPS: NFC payload", wps); + if (wpabuf_len(wps) < 2) { + wpa_printf(MSG_DEBUG, "WPS: Too short Wi-Fi Handover Request " + "Message"); + goto out; + } + pos = wpabuf_head(wps); + wsc_len = WPA_GET_BE16(pos); + if (wsc_len > wpabuf_len(wps) - 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid WSC attribute length (%u) " + "in rt Wi-Fi Handover Request Message", wsc_len); + goto out; + } + pos += 2; + + wpa_hexdump(MSG_DEBUG, + "WPS: WSC attributes in Wi-Fi Handover Request Message", + pos, wsc_len); + if (wsc_len < wpabuf_len(wps) - 2) { + wpa_hexdump(MSG_DEBUG, + "WPS: Ignore extra data after WSC attributes", + pos + wsc_len, wpabuf_len(wps) - 2 - wsc_len); + } + + wpabuf_set(&msg, pos, wsc_len); + ret = wps_parse_msg(&msg, &attr); + if (ret < 0) { + wpa_printf(MSG_DEBUG, "WPS: Could not parse WSC attributes in " + "Wi-Fi Handover Request Message"); + goto out; + } + + if (attr.oob_dev_password == NULL || + attr.oob_dev_password_len < WPS_OOB_PUBKEY_HASH_LEN + 2) { + wpa_printf(MSG_DEBUG, "WPS: No Out-of-Band Device Password " + "included in Wi-Fi Handover Request Message"); + ret = -1; + goto out; + } + + if (attr.uuid_e == NULL) { + wpa_printf(MSG_DEBUG, "WPS: No UUID-E included in Wi-Fi " + "Handover Request Message"); + ret = -1; + goto out; + } + + wpa_hexdump(MSG_DEBUG, "WPS: UUID-E", attr.uuid_e, WPS_UUID_LEN); + + wpa_hexdump(MSG_DEBUG, "WPS: Out-of-Band Device Password", + attr.oob_dev_password, attr.oob_dev_password_len); + dev_pw_id = WPA_GET_BE16(attr.oob_dev_password + + WPS_OOB_PUBKEY_HASH_LEN); + if (dev_pw_id != DEV_PW_NFC_CONNECTION_HANDOVER) { + wpa_printf(MSG_DEBUG, "WPS: Unexpected OOB Device Password ID " + "%u in Wi-Fi Handover Request Message", dev_pw_id); + ret = -1; + goto out; + } + wpa_hexdump(MSG_DEBUG, "WPS: Enrollee Public Key hash", + attr.oob_dev_password, WPS_OOB_PUBKEY_HASH_LEN); + + ret = wps_registrar_add_nfc_pw_token(hapd->wps->registrar, + attr.oob_dev_password, + DEV_PW_NFC_CONNECTION_HANDOVER, + NULL, 0, 1); + +out: + wpabuf_free(wps); + return ret; +} + + +struct wpabuf * hostapd_wps_nfc_token_gen(struct hostapd_data *hapd, int ndef) +{ + if (hapd->conf->wps_nfc_pw_from_config) { + return wps_nfc_token_build(ndef, + hapd->conf->wps_nfc_dev_pw_id, + hapd->conf->wps_nfc_dh_pubkey, + hapd->conf->wps_nfc_dev_pw); + } + + return wps_nfc_token_gen(ndef, &hapd->conf->wps_nfc_dev_pw_id, + &hapd->conf->wps_nfc_dh_pubkey, + &hapd->conf->wps_nfc_dh_privkey, + &hapd->conf->wps_nfc_dev_pw); +} + + +int hostapd_wps_nfc_token_enable(struct hostapd_data *hapd) +{ + struct wps_context *wps = hapd->wps; + struct wpabuf *pw; + + if (wps == NULL) + return -1; + + if (!hapd->conf->wps_nfc_dh_pubkey || + !hapd->conf->wps_nfc_dh_privkey || + !hapd->conf->wps_nfc_dev_pw || + !hapd->conf->wps_nfc_dev_pw_id) + return -1; + + hostapd_wps_nfc_clear(wps); + wpa_printf(MSG_DEBUG, + "WPS: Enable NFC Tag (Dev Pw Id %u) for AP interface %s (context %p)", + hapd->conf->wps_nfc_dev_pw_id, hapd->conf->iface, wps); + wps->ap_nfc_dev_pw_id = hapd->conf->wps_nfc_dev_pw_id; + wps->ap_nfc_dh_pubkey = wpabuf_dup(hapd->conf->wps_nfc_dh_pubkey); + wps->ap_nfc_dh_privkey = wpabuf_dup(hapd->conf->wps_nfc_dh_privkey); + pw = hapd->conf->wps_nfc_dev_pw; + wps->ap_nfc_dev_pw = wpabuf_alloc( + wpabuf_len(pw) * 2 + 1); + if (wps->ap_nfc_dev_pw) { + wpa_snprintf_hex_uppercase( + (char *) wpabuf_put(wps->ap_nfc_dev_pw, + wpabuf_len(pw) * 2), + wpabuf_len(pw) * 2 + 1, + wpabuf_head(pw), wpabuf_len(pw)); + } + + if (!wps->ap_nfc_dh_pubkey || !wps->ap_nfc_dh_privkey || + !wps->ap_nfc_dev_pw) { + hostapd_wps_nfc_clear(wps); + return -1; + } + + return 0; +} + + +void hostapd_wps_nfc_token_disable(struct hostapd_data *hapd) +{ + wpa_printf(MSG_DEBUG, "WPS: Disable NFC token for AP interface %s", + hapd->conf->iface); + hostapd_wps_nfc_clear(hapd->wps); +} + +#endif /* CONFIG_WPS_NFC */ diff --git a/src/ap/wps_hostapd.h b/src/ap/wps_hostapd.h new file mode 100644 index 0000000..204bd82 --- /dev/null +++ b/src/ap/wps_hostapd.h @@ -0,0 +1,92 @@ +/* + * hostapd / WPS integration + * Copyright (c) 2008-2012, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPS_HOSTAPD_H +#define WPS_HOSTAPD_H + +#ifdef CONFIG_WPS + +int hostapd_init_wps(struct hostapd_data *hapd, + struct hostapd_bss_config *conf); +int hostapd_init_wps_complete(struct hostapd_data *hapd); +void hostapd_deinit_wps(struct hostapd_data *hapd); +void hostapd_update_wps(struct hostapd_data *hapd); +void hostapd_wps_eap_completed(struct hostapd_data *hapd); +int hostapd_wps_add_pin(struct hostapd_data *hapd, const u8 *addr, + const char *uuid, const char *pin, int timeout); +int hostapd_wps_button_pushed(struct hostapd_data *hapd, + const u8 *p2p_dev_addr); +int hostapd_wps_cancel(struct hostapd_data *hapd); +int hostapd_wps_get_mib_sta(struct hostapd_data *hapd, const u8 *addr, + char *buf, size_t buflen); +void hostapd_wps_ap_pin_disable(struct hostapd_data *hapd); +const char * hostapd_wps_ap_pin_random(struct hostapd_data *hapd, int timeout); +const char * hostapd_wps_ap_pin_get(struct hostapd_data *hapd); +int hostapd_wps_ap_pin_set(struct hostapd_data *hapd, const char *pin, + int timeout); +void hostapd_wps_update_ie(struct hostapd_data *hapd); +int hostapd_wps_config_ap(struct hostapd_data *hapd, const char *ssid, + const char *auth, const char *encr, const char *key); +int hostapd_wps_nfc_tag_read(struct hostapd_data *hapd, + const struct wpabuf *data); +struct wpabuf * hostapd_wps_nfc_config_token(struct hostapd_data *hapd, + int ndef); +struct wpabuf * hostapd_wps_nfc_hs_cr(struct hostapd_data *hapd, int ndef); +int hostapd_wps_nfc_report_handover(struct hostapd_data *hapd, + const struct wpabuf *req, + const struct wpabuf *sel); +struct wpabuf * hostapd_wps_nfc_token_gen(struct hostapd_data *hapd, int ndef); +int hostapd_wps_nfc_token_enable(struct hostapd_data *hapd); +void hostapd_wps_nfc_token_disable(struct hostapd_data *hapd); + +#else /* CONFIG_WPS */ + +static inline int hostapd_init_wps(struct hostapd_data *hapd, + struct hostapd_bss_config *conf) +{ + return 0; +} + +static inline void hostapd_deinit_wps(struct hostapd_data *hapd) +{ +} + +static inline int hostapd_init_wps_complete(struct hostapd_data *hapd) +{ + return 0; +} + +static inline void hostapd_update_wps(struct hostapd_data *hapd) +{ +} + +static inline void hostapd_wps_eap_completed(struct hostapd_data *hapd) +{ +} + +static inline int hostapd_wps_get_mib_sta(struct hostapd_data *hapd, + const u8 *addr, + char *buf, size_t buflen) +{ + return 0; +} + +static inline int hostapd_wps_button_pushed(struct hostapd_data *hapd, + const u8 *p2p_dev_addr) +{ + return 0; +} + +static inline int hostapd_wps_cancel(struct hostapd_data *hapd) +{ + return 0; +} + +#endif /* CONFIG_WPS */ + +#endif /* WPS_HOSTAPD_H */ diff --git a/src/ap/x_snoop.c b/src/ap/x_snoop.c new file mode 100644 index 0000000..029f4de --- /dev/null +++ b/src/ap/x_snoop.c @@ -0,0 +1,136 @@ +/* + * Generic Snooping for Proxy ARP + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "hostapd.h" +#include "sta_info.h" +#include "ap_drv_ops.h" +#include "x_snoop.h" + + +int x_snoop_init(struct hostapd_data *hapd) +{ + struct hostapd_bss_config *conf = hapd->conf; + + if (!conf->isolate) { + wpa_printf(MSG_DEBUG, + "x_snoop: ap_isolate must be enabled for x_snoop"); + return -1; + } + + if (conf->bridge[0] == '\0') { + wpa_printf(MSG_DEBUG, + "x_snoop: Bridge must be configured for x_snoop"); + return -1; + } + + hapd->x_snoop_initialized = true; + + if (hostapd_drv_br_port_set_attr(hapd, DRV_BR_PORT_ATTR_HAIRPIN_MODE, + 1)) { + wpa_printf(MSG_DEBUG, + "x_snoop: Failed to enable hairpin_mode on the bridge port"); + return -1; + } + + if (hostapd_drv_br_port_set_attr(hapd, DRV_BR_PORT_ATTR_PROXYARP, 1)) { + wpa_printf(MSG_DEBUG, + "x_snoop: Failed to enable proxyarp on the bridge port"); + return -1; + } + + if (hostapd_drv_br_set_net_param(hapd, DRV_BR_NET_PARAM_GARP_ACCEPT, + 1)) { + wpa_printf(MSG_DEBUG, + "x_snoop: Failed to enable accepting gratuitous ARP on the bridge"); + return -1; + } + +#ifdef CONFIG_IPV6 + if (hostapd_drv_br_set_net_param(hapd, DRV_BR_MULTICAST_SNOOPING, 1)) { + wpa_printf(MSG_DEBUG, + "x_snoop: Failed to enable multicast snooping on the bridge"); + return -1; + } +#endif /* CONFIG_IPV6 */ + + return 0; +} + + +struct l2_packet_data * +x_snoop_get_l2_packet(struct hostapd_data *hapd, + void (*handler)(void *ctx, const u8 *src_addr, + const u8 *buf, size_t len), + enum l2_packet_filter_type type) +{ + struct hostapd_bss_config *conf = hapd->conf; + struct l2_packet_data *l2; + + l2 = l2_packet_init(conf->bridge, NULL, ETH_P_ALL, handler, hapd, 1); + if (l2 == NULL) { + wpa_printf(MSG_DEBUG, + "x_snoop: Failed to initialize L2 packet processing %s", + strerror(errno)); + return NULL; + } + + if (l2_packet_set_packet_filter(l2, type)) { + wpa_printf(MSG_DEBUG, + "x_snoop: Failed to set L2 packet filter for type: %d", + type); + l2_packet_deinit(l2); + return NULL; + } + + return l2; +} + + +void x_snoop_mcast_to_ucast_convert_send(struct hostapd_data *hapd, + struct sta_info *sta, u8 *buf, + size_t len) +{ + int res; + u8 addr[ETH_ALEN]; + u8 *dst_addr = buf; + + if (!(dst_addr[0] & 0x01)) + return; + + wpa_printf(MSG_EXCESSIVE, "x_snoop: Multicast-to-unicast conversion " + MACSTR " -> " MACSTR " (len %u)", + MAC2STR(dst_addr), MAC2STR(sta->addr), (unsigned int) len); + + /* save the multicast destination address for restoring it later */ + os_memcpy(addr, buf, ETH_ALEN); + + os_memcpy(buf, sta->addr, ETH_ALEN); + res = l2_packet_send(hapd->sock_dhcp, NULL, 0, buf, len); + if (res < 0) { + wpa_printf(MSG_DEBUG, + "x_snoop: Failed to send mcast to ucast converted packet to " + MACSTR, MAC2STR(sta->addr)); + } + + /* restore the multicast destination address */ + os_memcpy(buf, addr, ETH_ALEN); +} + + +void x_snoop_deinit(struct hostapd_data *hapd) +{ + if (!hapd->x_snoop_initialized) + return; + hostapd_drv_br_set_net_param(hapd, DRV_BR_NET_PARAM_GARP_ACCEPT, 0); + hostapd_drv_br_port_set_attr(hapd, DRV_BR_PORT_ATTR_PROXYARP, 0); + hostapd_drv_br_port_set_attr(hapd, DRV_BR_PORT_ATTR_HAIRPIN_MODE, 0); + hapd->x_snoop_initialized = false; +} diff --git a/src/ap/x_snoop.h b/src/ap/x_snoop.h new file mode 100644 index 0000000..e43a78d --- /dev/null +++ b/src/ap/x_snoop.h @@ -0,0 +1,56 @@ +/* + * Generic Snooping for Proxy ARP + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef X_SNOOP_H +#define X_SNOOP_H + +#include "l2_packet/l2_packet.h" + +#ifdef CONFIG_PROXYARP + +int x_snoop_init(struct hostapd_data *hapd); +struct l2_packet_data * +x_snoop_get_l2_packet(struct hostapd_data *hapd, + void (*handler)(void *ctx, const u8 *src_addr, + const u8 *buf, size_t len), + enum l2_packet_filter_type type); +void x_snoop_mcast_to_ucast_convert_send(struct hostapd_data *hapd, + struct sta_info *sta, u8 *buf, + size_t len); +void x_snoop_deinit(struct hostapd_data *hapd); + +#else /* CONFIG_PROXYARP */ + +static inline int x_snoop_init(struct hostapd_data *hapd) +{ + return 0; +} + +static inline struct l2_packet_data * +x_snoop_get_l2_packet(struct hostapd_data *hapd, + void (*handler)(void *ctx, const u8 *src_addr, + const u8 *buf, size_t len), + enum l2_packet_filter_type type) +{ + return NULL; +} + +static inline void +x_snoop_mcast_to_ucast_convert_send(struct hostapd_data *hapd, + struct sta_info *sta, void *buf, + size_t len) +{ +} + +static inline void x_snoop_deinit(struct hostapd_data *hapd) +{ +} + +#endif /* CONFIG_PROXYARP */ + +#endif /* X_SNOOP_H */ diff --git a/src/build.rules b/src/build.rules new file mode 100644 index 0000000..c756ccb --- /dev/null +++ b/src/build.rules @@ -0,0 +1,109 @@ +.PHONY: all +all: _all + +# disable built-in rules +.SUFFIXES: + +# setup some variables +ROOTDIR := $(dir $(lastword $(MAKEFILE_LIST))) +ROOTDIR := $(dir $(ROOTDIR:%../src/=%))../ +BUILDDIR ?= $(abspath $(ROOTDIR)build) +BUILDDIR := $(BUILDDIR:%/=%) +ABSROOT := $(abspath $(ROOTDIR)) +ifeq ($(origin OUT),command line) +_PROJ := $(OUT:%/=%) +_PROJ := $(_PROJ:$(BUILDDIR)/%=%) +else +_PROJ := $(abspath $(dir $(firstword $(MAKEFILE_LIST)))) +_PROJ := $(_PROJ:$(ABSROOT)/%=%) +endif + +ifndef CC +CC=gcc +endif + +ifndef RANLIB +RANLIB=ranlib +endif + +ifndef LDO +LDO=$(CC) +endif + +ifndef CFLAGS +CFLAGS = -MMD -O2 -Wall -g +endif + +ifneq ($(CONFIG_FILE),) +-include $(CONFIG_FILE) + +# export for sub-makefiles +export CONFIG_CODE_COVERAGE + +.PHONY: verify_config +verify_config: + @if [ ! -r $(CONFIG_FILE) ]; then \ + echo 'Building $(firstword $(ALL)) requires a configuration file'; \ + echo '(.config). See README for more instructions. You can'; \ + echo 'run "cp defconfig .config" to create an example'; \ + echo 'configuration.'; \ + exit 1; \ + fi +VERIFY := verify_config +else +VERIFY := +endif + +# default target +.PHONY: _all +_all: $(VERIFY) $(ALL) $(EXTRA_TARGETS) + +# continue setup +COVSUFFIX := $(if $(CONFIG_CODE_COVERAGE),-cov,) +PROJ := $(_PROJ)$(COVSUFFIX) + +Q=@ +E=echo +ifeq ($(V), 1) +Q= +E=true +endif +ifeq ($(QUIET), 1) +Q=@ +E=true +endif + +ifeq ($(Q),@) +MAKEFLAGS += --no-print-directory +endif + +_DIRS := $(BUILDDIR)/$(PROJ) +.PHONY: _make_dirs +_make_dirs: + @mkdir -p $(sort $(_DIRS)) + +$(BUILDDIR)/$(PROJ)/src/%.o: $(ROOTDIR)src/%.c $(CONFIG_FILE) | _make_dirs + $(Q)$(CC) -c -o $@ $(CFLAGS) $< + @$(E) " CC " $< +$(BUILDDIR)/$(PROJ)/%.o: %.c $(CONFIG_FILE) | _make_dirs + $(Q)$(CC) -c -o $@ $(CFLAGS) $< + @$(E) " CC " $< +# for the fuzzing tests +$(BUILDDIR)/$(PROJ)/wpa_supplicant/%.o: $(ROOTDIR)wpa_supplicant/%.c $(CONFIG_FILE) | _make_dirs + $(Q)$(CC) -c -o $@ $(CFLAGS) $< + @$(E) " CC " $< + +# libraries - they know how to build themselves +# (lib_phony so we recurse all the time) +.PHONY: lib_phony +lib_phony: +# nothing + +$(BUILDDIR)/$(PROJ)/%.a: $(CONFIG_FILE) lib_phony + $(Q)$(MAKE) -C $(ROOTDIR)$(dir $(@:$(BUILDDIR)/$(PROJ)/%=%)) OUT=$(abspath $(dir $@))/ + +BUILDOBJ = $(patsubst %,$(BUILDDIR)/$(PROJ)/%,$(patsubst $(ROOTDIR)%,%,$(1))) + +.PHONY: common-clean +common-clean: + $(Q)rm -rf $(ALL) $(BUILDDIR)/$(PROJ) diff --git a/src/common/Makefile b/src/common/Makefile new file mode 100644 index 0000000..e2c5f03 --- /dev/null +++ b/src/common/Makefile @@ -0,0 +1,16 @@ +CFLAGS += -DCONFIG_IEEE80211R +CFLAGS += -DCONFIG_HS20 +CFLAGS += -DCONFIG_SAE +CFLAGS += -DCONFIG_SUITE +CFLAGS += -DCONFIG_SUITEB +CFLAGS += -DCONFIG_PTKSA_CACHE + +LIB_OBJS= \ + gas.o \ + hw_features_common.o \ + ieee802_11_common.o \ + sae.o \ + ptksa_cache.o \ + wpa_common.o + +include ../lib.rules diff --git a/src/common/brcm_vendor.h b/src/common/brcm_vendor.h new file mode 100644 index 0000000..c42ed7e --- /dev/null +++ b/src/common/brcm_vendor.h @@ -0,0 +1,156 @@ +/* + * Broadcom Corporation OUI and vendor specific assignments + * Copyright (c) 2020, Broadcom Corporation. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef BRCM_VENDOR_H +#define BRCM_VENDOR_H + +/* + * This file is a registry of identifier assignments from the Broadcom + * OUI 00:10:18 for purposes other than MAC address assignment. New identifiers + * can be assigned through normal review process for changes to the upstream + * hostap.git repository. + */ + +#define OUI_BRCM 0x001018 + +/** + * enum brcm_nl80211_vendor_subcmds - BRCM nl80211 vendor command identifiers + * + * @BRCM_VENDOR_SCMD_UNSPEC: Reserved value 0 + * + * @BRCM_VENDOR_SCMD_PRIV_STR: Provide vendor private cmds to send to FW. + * + * @BRCM_VENDOR_SCMD_BCM_STR: Provide vendor cmds to BCMDHD driver. + * + * @BRCM_VENDOR_SCMD_BCM_PSK: Used to set SAE password. + * + * @BRCM_VENDOR_SCMD_SET_PMK: Command to check driver support + * for DFS offloading. + * + * @BRCM_VENDOR_SCMD_GET_FEATURES: Command to get the features + * supported by the driver. + * + * @BRCM_VENDOR_SCMD_SET_MAC: Set random mac address for P2P interface. + * + * @BRCM_VENDOR_SCMD_SET_CONNECT_PARAMS: Set some connect parameters. + * Used for the case that FW handle SAE. + * + * @BRCM_VENDOR_SCMD_SET_START_AP_PARAMS: Set SoftAP parameters. + * Used for the case that FW handle SAE. + * + * @BRCM_VENDOR_SCMD_ACS: ACS command/event which is used to + * invoke the ACS function in device and pass selected channels to + * hostapd. Uses enum qca_wlan_vendor_attr_acs_offload attributes. + * + * @BRCM_VENDOR_SCMD_MAX: This acts as a tail of cmds list. + * Make sure it is located at the end of the list. + * + */ +enum brcm_nl80211_vendor_subcmds { + BRCM_VENDOR_SCMD_UNSPEC = 0, + BRCM_VENDOR_SCMD_PRIV_STR = 1, + BRCM_VENDOR_SCMD_BCM_STR = 2, + BRCM_VENDOR_SCMD_BCM_PSK = 3, + BRCM_VENDOR_SCMD_SET_PMK = 4, + BRCM_VENDOR_SCMD_GET_FEATURES = 5, + BRCM_VENDOR_SCMD_SET_MAC = 6, + BRCM_VENDOR_SCMD_SET_CONNECT_PARAMS = 7, + BRCM_VENDOR_SCMD_SET_START_AP_PARAMS = 8, + BRCM_VENDOR_SCMD_ACS = 9, + BRCM_VENDOR_SCMD_MAX = 10 +}; + +/** + * enum brcm_nl80211_vendor_events - BRCM nl80211 asynchronous event identifiers + * + * @BRCM_VENDOR_EVENT_UNSPEC: Reserved value 0 + * + * @BRCM_VENDOR_EVENT_PRIV_STR: String command/event + */ +enum brcm_nl80211_vendor_events { + BRCM_VENDOR_EVENT_UNSPEC = 0, + BRCM_VENDOR_EVENT_PRIV_STR = 1, + GOOGLE_GSCAN_SIGNIFICANT_EVENT = 2, + GOOGLE_GSCAN_GEOFENCE_FOUND_EVENT = 3, + GOOGLE_GSCAN_BATCH_SCAN_EVENT = 4, + GOOGLE_SCAN_FULL_RESULTS_EVENT = 5, + GOOGLE_RTT_COMPLETE_EVENT = 6, + GOOGLE_SCAN_COMPLETE_EVENT = 7, + GOOGLE_GSCAN_GEOFENCE_LOST_EVENT = 8, + GOOGLE_SCAN_EPNO_EVENT = 9, + GOOGLE_DEBUG_RING_EVENT = 10, + GOOGLE_FW_DUMP_EVENT = 11, + GOOGLE_PNO_HOTSPOT_FOUND_EVENT = 12, + GOOGLE_RSSI_MONITOR_EVENT = 13, + GOOGLE_MKEEP_ALIVE_EVENT = 14, + + /* + * BRCM specific events should be placed after + * the Generic events so that enums don't mismatch + * between the DHD and HAL + */ + GOOGLE_NAN_EVENT_ENABLED = 15, + GOOGLE_NAN_EVENT_DISABLED = 16, + GOOGLE_NAN_EVENT_SUBSCRIBE_MATCH = 17, + GOOGLE_NAN_EVENT_REPLIED = 18, + GOOGLE_NAN_EVENT_PUBLISH_TERMINATED = 19, + GOOGLE_NAN_EVENT_SUBSCRIBE_TERMINATED = 20, + GOOGLE_NAN_EVENT_DE_EVENT = 21, + GOOGLE_NAN_EVENT_FOLLOWUP = 22, + GOOGLE_NAN_EVENT_TRANSMIT_FOLLOWUP_IND = 23, + GOOGLE_NAN_EVENT_DATA_REQUEST = 24, + GOOGLE_NAN_EVENT_DATA_CONFIRMATION = 25, + GOOGLE_NAN_EVENT_DATA_END = 26, + GOOGLE_NAN_EVENT_BEACON = 27, + GOOGLE_NAN_EVENT_SDF = 28, + GOOGLE_NAN_EVENT_TCA = 29, + GOOGLE_NAN_EVENT_SUBSCRIBE_UNMATCH = 30, + GOOGLE_NAN_EVENT_UNKNOWN = 31, + GOOGLE_ROAM_EVENT_START = 32, + BRCM_VENDOR_EVENT_HANGED = 33, + BRCM_VENDOR_EVENT_SAE_KEY = 34, + BRCM_VENDOR_EVENT_BEACON_RECV = 35, + BRCM_VENDOR_EVENT_PORT_AUTHORIZED = 36, + GOOGLE_FILE_DUMP_EVENT = 37, + BRCM_VENDOR_EVENT_CU = 38, + BRCM_VENDOR_EVENT_WIPS = 39, + NAN_ASYNC_RESPONSE_DISABLED = 40, + BRCM_VENDOR_EVENT_RCC_INFO = 41, + BRCM_VENDOR_EVENT_ACS = 42, + BRCM_VENDOR_EVENT_LAST + +}; + +#ifdef CONFIG_BRCM_SAE +enum wifi_sae_key_attr { + BRCM_SAE_KEY_ATTR_BSSID, + BRCM_SAE_KEY_ATTR_PMK, + BRCM_SAE_KEY_ATTR_PMKID +}; +#endif /* CONFIG_BRCM_SAE */ + +enum wl_vendor_attr_acs_offload { + BRCM_VENDOR_ATTR_ACS_CHANNEL_INVALID = 0, + BRCM_VENDOR_ATTR_ACS_PRIMARY_FREQ, + BRCM_VENDOR_ATTR_ACS_SECONDARY_FREQ, + BRCM_VENDOR_ATTR_ACS_VHT_SEG0_CENTER_CHANNEL, + BRCM_VENDOR_ATTR_ACS_VHT_SEG1_CENTER_CHANNEL, + + BRCM_VENDOR_ATTR_ACS_HW_MODE, + BRCM_VENDOR_ATTR_ACS_HT_ENABLED, + BRCM_VENDOR_ATTR_ACS_HT40_ENABLED, + BRCM_VENDOR_ATTR_ACS_VHT_ENABLED, + BRCM_VENDOR_ATTR_ACS_CHWIDTH, + BRCM_VENDOR_ATTR_ACS_CH_LIST, + BRCM_VENDOR_ATTR_ACS_FREQ_LIST, + + BRCM_VENDOR_ATTR_ACS_LAST +}; + + +#endif /* BRCM_VENDOR_H */ diff --git a/src/common/cli.c b/src/common/cli.c new file mode 100644 index 0000000..b583d1c --- /dev/null +++ b/src/common/cli.c @@ -0,0 +1,267 @@ +/* + * Common hostapd/wpa_supplicant command line interface functions + * Copyright (c) 2004-2016, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" + +#include "utils/common.h" +#include "common/cli.h" + + +const char *const cli_license = +"This software may be distributed under the terms of the BSD license.\n" +"See README for more details.\n"; + +const char *const cli_full_license = +"This software may be distributed under the terms of the BSD license.\n" +"\n" +"Redistribution and use in source and binary forms, with or without\n" +"modification, are permitted provided that the following conditions are\n" +"met:\n" +"\n" +"1. Redistributions of source code must retain the above copyright\n" +" notice, this list of conditions and the following disclaimer.\n" +"\n" +"2. Redistributions in binary form must reproduce the above copyright\n" +" notice, this list of conditions and the following disclaimer in the\n" +" documentation and/or other materials provided with the distribution.\n" +"\n" +"3. Neither the name(s) of the above-listed copyright holder(s) nor the\n" +" names of its contributors may be used to endorse or promote products\n" +" derived from this software without specific prior written permission.\n" +"\n" +"THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n" +"\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n" +"LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n" +"A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n" +"OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n" +"SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n" +"LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n" +"DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n" +"THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" +"(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n" +"OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" +"\n"; + + +void cli_txt_list_free(struct cli_txt_entry *e) +{ + dl_list_del(&e->list); + os_free(e->txt); + os_free(e); +} + + +void cli_txt_list_flush(struct dl_list *list) +{ + struct cli_txt_entry *e; + + while ((e = dl_list_first(list, struct cli_txt_entry, list))) + cli_txt_list_free(e); +} + + +struct cli_txt_entry * cli_txt_list_get(struct dl_list *txt_list, + const char *txt) +{ + struct cli_txt_entry *e; + + dl_list_for_each(e, txt_list, struct cli_txt_entry, list) { + if (os_strcmp(e->txt, txt) == 0) + return e; + } + return NULL; +} + + +void cli_txt_list_del(struct dl_list *txt_list, const char *txt) +{ + struct cli_txt_entry *e; + + e = cli_txt_list_get(txt_list, txt); + if (e) + cli_txt_list_free(e); +} + + +void cli_txt_list_del_addr(struct dl_list *txt_list, const char *txt) +{ + u8 addr[ETH_ALEN]; + char buf[18]; + + if (hwaddr_aton(txt, addr) < 0) + return; + os_snprintf(buf, sizeof(buf), MACSTR, MAC2STR(addr)); + cli_txt_list_del(txt_list, buf); +} + + +void cli_txt_list_del_word(struct dl_list *txt_list, const char *txt, + int separator) +{ + const char *end; + char *buf; + + end = os_strchr(txt, separator); + if (end == NULL) + end = txt + os_strlen(txt); + buf = dup_binstr(txt, end - txt); + if (buf == NULL) + return; + cli_txt_list_del(txt_list, buf); + os_free(buf); +} + + +int cli_txt_list_add(struct dl_list *txt_list, const char *txt) +{ + struct cli_txt_entry *e; + + e = cli_txt_list_get(txt_list, txt); + if (e) + return 0; + e = os_zalloc(sizeof(*e)); + if (e == NULL) + return -1; + e->txt = os_strdup(txt); + if (e->txt == NULL) { + os_free(e); + return -1; + } + dl_list_add(txt_list, &e->list); + return 0; +} + + +int cli_txt_list_add_addr(struct dl_list *txt_list, const char *txt) +{ + u8 addr[ETH_ALEN]; + char buf[18]; + + if (hwaddr_aton(txt, addr) < 0) + return -1; + os_snprintf(buf, sizeof(buf), MACSTR, MAC2STR(addr)); + return cli_txt_list_add(txt_list, buf); +} + + +int cli_txt_list_add_word(struct dl_list *txt_list, const char *txt, + int separator) +{ + const char *end; + char *buf; + int ret; + + end = os_strchr(txt, separator); + if (end == NULL) + end = txt + os_strlen(txt); + buf = dup_binstr(txt, end - txt); + if (buf == NULL) + return -1; + ret = cli_txt_list_add(txt_list, buf); + os_free(buf); + return ret; +} + + +char ** cli_txt_list_array(struct dl_list *txt_list) +{ + unsigned int i, count = dl_list_len(txt_list); + char **res; + struct cli_txt_entry *e; + + res = os_calloc(count + 1, sizeof(char *)); + if (res == NULL) + return NULL; + + i = 0; + dl_list_for_each(e, txt_list, struct cli_txt_entry, list) { + res[i] = os_strdup(e->txt); + if (res[i] == NULL) + break; + i++; + } + + return res; +} + + +int get_cmd_arg_num(const char *str, int pos) +{ + int arg = 0, i; + + for (i = 0; i <= pos; i++) { + if (str[i] != ' ') { + arg++; + while (i <= pos && str[i] != ' ') + i++; + } + } + + if (arg > 0) + arg--; + return arg; +} + + +int write_cmd(char *buf, size_t buflen, const char *cmd, int argc, char *argv[]) +{ + int i, res; + char *pos, *end; + + pos = buf; + end = buf + buflen; + + res = os_snprintf(pos, end - pos, "%s", cmd); + if (os_snprintf_error(end - pos, res)) + goto fail; + pos += res; + + for (i = 0; i < argc; i++) { + res = os_snprintf(pos, end - pos, " %s", argv[i]); + if (os_snprintf_error(end - pos, res)) + goto fail; + pos += res; + } + + buf[buflen - 1] = '\0'; + return 0; + +fail: + printf("Too long command\n"); + return -1; +} + + +int tokenize_cmd(char *cmd, char *argv[]) +{ + char *pos; + int argc = 0; + + pos = cmd; + for (;;) { + while (*pos == ' ') + pos++; + if (*pos == '\0') + break; + argv[argc] = pos; + argc++; + if (argc == max_args) + break; + if (*pos == '"') { + char *pos2 = os_strrchr(pos, '"'); + if (pos2) + pos = pos2 + 1; + } + while (*pos != '\0' && *pos != ' ') + pos++; + if (*pos == ' ') + *pos++ = '\0'; + } + + return argc; +} diff --git a/src/common/cli.h b/src/common/cli.h new file mode 100644 index 0000000..41ef329 --- /dev/null +++ b/src/common/cli.h @@ -0,0 +1,47 @@ +/* + * Common hostapd/wpa_supplicant command line interface functionality + * Copyright (c) 2004-2016, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef CLI_H +#define CLI_H + +#include "utils/list.h" + +extern const char *const cli_license; +extern const char *const cli_full_license; + +struct cli_txt_entry { + struct dl_list list; + char *txt; +}; + +void cli_txt_list_free(struct cli_txt_entry *e); +void cli_txt_list_flush(struct dl_list *list); + +struct cli_txt_entry * +cli_txt_list_get(struct dl_list *txt_list, const char *txt); + +void cli_txt_list_del(struct dl_list *txt_list, const char *txt); +void cli_txt_list_del_addr(struct dl_list *txt_list, const char *txt); +void cli_txt_list_del_word(struct dl_list *txt_list, const char *txt, + int separator); + +int cli_txt_list_add(struct dl_list *txt_list, const char *txt); +int cli_txt_list_add_addr(struct dl_list *txt_list, const char *txt); +int cli_txt_list_add_word(struct dl_list *txt_list, const char *txt, + int separator); + +char ** cli_txt_list_array(struct dl_list *txt_list); + +int get_cmd_arg_num(const char *str, int pos); +int write_cmd(char *buf, size_t buflen, const char *cmd, int argc, + char *argv[]); + +#define max_args 10 +int tokenize_cmd(char *cmd, char *argv[]); + +#endif /* CLI_H */ diff --git a/src/common/common_module_tests.c b/src/common/common_module_tests.c new file mode 100644 index 0000000..a95ae36 --- /dev/null +++ b/src/common/common_module_tests.c @@ -0,0 +1,794 @@ +/* + * common module tests + * Copyright (c) 2014-2019, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/module_tests.h" +#include "crypto/crypto.h" +#include "crypto/dh_groups.h" +#include "ieee802_11_common.h" +#include "ieee802_11_defs.h" +#include "gas.h" +#include "wpa_common.h" +#include "sae.h" + + +struct ieee802_11_parse_test_data { + u8 *data; + size_t len; + ParseRes result; + int count; +}; + +static const struct ieee802_11_parse_test_data parse_tests[] = { + { (u8 *) "", 0, ParseOK, 0 }, + { (u8 *) " ", 1, ParseFailed, 0 }, + { (u8 *) "\xff\x00", 2, ParseUnknown, 1 }, + { (u8 *) "\xff\x01", 2, ParseFailed, 0 }, + { (u8 *) "\xdd\x03\x01\x02\x03", 5, ParseUnknown, 1 }, + { (u8 *) "\xdd\x04\x01\x02\x03\x04", 6, ParseUnknown, 1 }, + { (u8 *) "\xdd\x04\x00\x50\xf2\x02", 6, ParseUnknown, 1 }, + { (u8 *) "\xdd\x05\x00\x50\xf2\x02\x02", 7, ParseOK, 1 }, + { (u8 *) "\xdd\x05\x00\x50\xf2\x02\xff", 7, ParseUnknown, 1 }, + { (u8 *) "\xdd\x04\x00\x50\xf2\xff", 6, ParseUnknown, 1 }, + { (u8 *) "\xdd\x04\x50\x6f\x9a\xff", 6, ParseUnknown, 1 }, + { (u8 *) "\xdd\x04\x00\x90\x4c\x33", 6, ParseOK, 1 }, + { (u8 *) "\xdd\x04\x00\x90\x4c\xff\xdd\x04\x00\x90\x4c\x33", 12, + ParseUnknown, 2 }, + { (u8 *) "\x10\x01\x00\x21\x00", 5, ParseOK, 2 }, + { (u8 *) "\x24\x00", 2, ParseOK, 1 }, + { (u8 *) "\x38\x00", 2, ParseOK, 1 }, + { (u8 *) "\x54\x00", 2, ParseOK, 1 }, + { (u8 *) "\x5a\x00", 2, ParseOK, 1 }, + { (u8 *) "\x65\x00", 2, ParseOK, 1 }, + { (u8 *) "\x65\x12\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11", + 20, ParseOK, 1 }, + { (u8 *) "\x6e\x00", 2, ParseOK, 1 }, + { (u8 *) "\xc7\x00", 2, ParseOK, 1 }, + { (u8 *) "\xc7\x01\x00", 3, ParseOK, 1 }, + { (u8 *) "\x03\x00\x2a\x00\x36\x00\x37\x00\x38\x00\x2d\x00\x3d\x00\xbf\x00\xc0\x00", + 18, ParseOK, 9 }, + { (u8 *) "\x8b\x00", 2, ParseOK, 1 }, + { (u8 *) "\xdd\x04\x00\x90\x4c\x04", 6, ParseUnknown, 1 }, + { (u8 *) "\xed\x00", 2, ParseOK, 1 }, + { (u8 *) "\xef\x00", 2, ParseOK, 1 }, + { (u8 *) "\xef\x01\x11", 3, ParseOK, 1 }, + { (u8 *) "\xf0\x00", 2, ParseOK, 1 }, + { (u8 *) "\xf1\x00", 2, ParseOK, 1 }, + { (u8 *) "\xf1\x02\x11\x22", 4, ParseOK, 1 }, + { (u8 *) "\xf2\x00", 2, ParseOK, 1 }, + { (u8 *) "\xff\x00", 2, ParseUnknown, 1 }, + { (u8 *) "\xff\x01\x00", 3, ParseUnknown, 1 }, + { (u8 *) "\xff\x01\x01", 3, ParseOK, 1 }, + { (u8 *) "\xff\x02\x01\x00", 4, ParseOK, 1 }, + { (u8 *) "\xff\x01\x02", 3, ParseOK, 1 }, + { (u8 *) "\xff\x04\x02\x11\x22\x33", 6, ParseOK, 1 }, + { (u8 *) "\xff\x01\x04", 3, ParseOK, 1 }, + { (u8 *) "\xff\x01\x05", 3, ParseOK, 1 }, + { (u8 *) "\xff\x0d\x05\x11\x22\x33\x44\x55\x55\x11\x22\x33\x44\x55\x55", + 15, ParseOK, 1 }, + { (u8 *) "\xff\x01\x06", 3, ParseOK, 1 }, + { (u8 *) "\xff\x02\x06\x00", 4, ParseOK, 1 }, + { (u8 *) "\xff\x01\x07", 3, ParseOK, 1 }, + { (u8 *) "\xff\x09\x07\x11\x22\x33\x44\x55\x66\x77\x88", 11, + ParseOK, 1 }, + { (u8 *) "\xff\x01\x0c", 3, ParseOK, 1 }, + { (u8 *) "\xff\x02\x0c\x00", 4, ParseOK, 1 }, + { (u8 *) "\xff\x01\x0d", 3, ParseOK, 1 }, + { NULL, 0, ParseOK, 0 } +}; + +static int ieee802_11_parse_tests(void) +{ + int i, ret = 0; + struct wpabuf *buf; + + wpa_printf(MSG_INFO, "ieee802_11_parse tests"); + + for (i = 0; parse_tests[i].data; i++) { + const struct ieee802_11_parse_test_data *test; + struct ieee802_11_elems elems; + ParseRes res; + + test = &parse_tests[i]; + res = ieee802_11_parse_elems(test->data, test->len, &elems, 1); + if (res != test->result || + ieee802_11_ie_count(test->data, test->len) != test->count) { + wpa_printf(MSG_ERROR, "ieee802_11_parse test %d failed", + i); + ret = -1; + } + } + + if (ieee802_11_vendor_ie_concat((const u8 *) "\x00\x01", 2, 0) != NULL) + { + wpa_printf(MSG_ERROR, + "ieee802_11_vendor_ie_concat test failed"); + ret = -1; + } + + buf = ieee802_11_vendor_ie_concat((const u8 *) "\xdd\x05\x11\x22\x33\x44\x01\xdd\x05\x11\x22\x33\x44\x02\x00\x01", + 16, 0x11223344); + do { + const u8 *pos; + + if (!buf) { + wpa_printf(MSG_ERROR, + "ieee802_11_vendor_ie_concat test 2 failed"); + ret = -1; + break; + } + + if (wpabuf_len(buf) != 2) { + wpa_printf(MSG_ERROR, + "ieee802_11_vendor_ie_concat test 3 failed"); + ret = -1; + break; + } + + pos = wpabuf_head(buf); + if (pos[0] != 0x01 || pos[1] != 0x02) { + wpa_printf(MSG_ERROR, + "ieee802_11_vendor_ie_concat test 3 failed"); + ret = -1; + break; + } + } while (0); + wpabuf_free(buf); + + return ret; +} + + +struct rsn_ie_parse_test_data { + u8 *data; + size_t len; + int result; +}; + +static const struct rsn_ie_parse_test_data rsn_parse_tests[] = { + { (u8 *) "", 0, -1 }, + { (u8 *) "\x30\x00", 2, -1 }, + { (u8 *) "\x30\x02\x01\x00", 4, 0 }, + { (u8 *) "\x30\x02\x00\x00", 4, -2 }, + { (u8 *) "\x30\x02\x02\x00", 4, -2 }, + { (u8 *) "\x30\x02\x00\x01", 4, -2 }, + { (u8 *) "\x30\x02\x00\x00\x00", 5, -2 }, + { (u8 *) "\x30\x03\x01\x00\x00", 5, -3 }, + { (u8 *) "\x30\x06\x01\x00\x00\x00\x00\x00", 8, -1 }, + { (u8 *) "\x30\x06\x01\x00\x00\x0f\xac\x04", 8, 0 }, + { (u8 *) "\x30\x07\x01\x00\x00\x0f\xac\x04\x00", 9, -5 }, + { (u8 *) "\x30\x08\x01\x00\x00\x0f\xac\x04\x00\x00", 10, -4 }, + { (u8 *) "\x30\x08\x01\x00\x00\x0f\xac\x04\x00\x01", 10, -4 }, + { (u8 *) "\x30\x0c\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04", + 14, 0 }, + { (u8 *) "\x30\x0c\x01\x00\x00\x0f\xac\x04\x00\x01\x00\x0f\xac\x04", + 14, -4 }, + { (u8 *) "\x30\x0c\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x06", + 14, -1 }, + { (u8 *) "\x30\x10\x01\x00\x00\x0f\xac\x04\x02\x00\x00\x0f\xac\x04\x00\x0f\xac\x08", + 18, 0 }, + { (u8 *) "\x30\x0d\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x00", + 15, -7 }, + { (u8 *) "\x30\x0e\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x00\x00", + 16, -6 }, + { (u8 *) "\x30\x0e\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x00\x01", + 16, -6 }, + { (u8 *) "\x30\x12\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01", + 20, 0 }, + { (u8 *) "\x30\x16\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x02\x00\x00\x0f\xac\x01\x00\x0f\xac\x02", + 24, 0 }, + { (u8 *) "\x30\x13\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x00", + 21, 0 }, + { (u8 *) "\x30\x14\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x00\x00", + 22, 0 }, + { (u8 *) "\x30\x16\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x00\x00\x00\x00", + 24, 0 }, + { (u8 *) "\x30\x16\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x00\x00\x00\x01", + 24, -9 }, + { (u8 *) "\x30\x1a\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x00\x00\x00\x00\x00\x00\x00\x00", + 28, -10 }, + { (u8 *) "\x30\x1a\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x00\x00\x00\x00\x00\x0f\xac\x06", + 28, 0 }, + { (u8 *) "\x30\x1c\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x04\x01\x00\x00\x0f\xac\x01\x00\x00\x00\x00\x00\x0f\xac\x06\x01\x02", + 30, 0 }, + { NULL, 0, 0 } +}; + +static int rsn_ie_parse_tests(void) +{ + int i, ret = 0; + + wpa_printf(MSG_INFO, "rsn_ie_parse tests"); + + for (i = 0; rsn_parse_tests[i].data; i++) { + const struct rsn_ie_parse_test_data *test; + struct wpa_ie_data data; + + test = &rsn_parse_tests[i]; + if (wpa_parse_wpa_ie_rsn(test->data, test->len, &data) != + test->result) { + wpa_printf(MSG_ERROR, "rsn_ie_parse test %d failed", i); + ret = -1; + } + } + + return ret; +} + + +static int gas_tests(void) +{ + struct wpabuf *buf; + + wpa_printf(MSG_INFO, "gas tests"); + gas_anqp_set_len(NULL); + + buf = wpabuf_alloc(1); + if (buf == NULL) + return -1; + gas_anqp_set_len(buf); + wpabuf_free(buf); + + buf = wpabuf_alloc(20); + if (buf == NULL) + return -1; + wpabuf_put_u8(buf, WLAN_ACTION_PUBLIC); + wpabuf_put_u8(buf, WLAN_PA_GAS_INITIAL_REQ); + wpabuf_put_u8(buf, 0); + wpabuf_put_be32(buf, 0); + wpabuf_put_u8(buf, 0); + gas_anqp_set_len(buf); + wpabuf_free(buf); + + return 0; +} + + +static int sae_tests(void) +{ +#ifdef CONFIG_SAE + struct sae_data sae; + int ret = -1; + /* IEEE Std 802.11-2020, Annex J.10 */ + const u8 addr1[ETH_ALEN] = { 0x4d, 0x3f, 0x2f, 0xff, 0xe3, 0x87 }; + const u8 addr2[ETH_ALEN] = { 0xa5, 0xd8, 0xaa, 0x95, 0x8e, 0x3c }; + const char *ssid = "byteme"; + const char *pw = "mekmitasdigoat"; + const char *pwid = "psk4internet"; + const u8 local_rand[] = { + 0x99, 0x24, 0x65, 0xfd, 0x3d, 0xaa, 0x3c, 0x60, + 0xaa, 0x65, 0x65, 0xb7, 0xf6, 0x2a, 0x2a, 0x7f, + 0x2e, 0x12, 0xdd, 0x12, 0xf1, 0x98, 0xfa, 0xf4, + 0xfb, 0xed, 0x89, 0xd7, 0xff, 0x1a, 0xce, 0x94 + }; + const u8 local_mask[] = { + 0x95, 0x07, 0xa9, 0x0f, 0x77, 0x7a, 0x04, 0x4d, + 0x6a, 0x08, 0x30, 0xb9, 0x1e, 0xa3, 0xd5, 0xdd, + 0x70, 0xbe, 0xce, 0x44, 0xe1, 0xac, 0xff, 0xb8, + 0x69, 0x83, 0xb5, 0xe1, 0xbf, 0x9f, 0xb3, 0x22 + }; + const u8 local_commit[] = { + 0x13, 0x00, 0x2e, 0x2c, 0x0f, 0x0d, 0xb5, 0x24, + 0x40, 0xad, 0x14, 0x6d, 0x96, 0x71, 0x14, 0xce, + 0x00, 0x5c, 0xe1, 0xea, 0xb0, 0xaa, 0x2c, 0x2e, + 0x5c, 0x28, 0x71, 0xb7, 0x74, 0xf6, 0xc2, 0x57, + 0x5c, 0x65, 0xd5, 0xad, 0x9e, 0x00, 0x82, 0x97, + 0x07, 0xaa, 0x36, 0xba, 0x8b, 0x85, 0x97, 0x38, + 0xfc, 0x96, 0x1d, 0x08, 0x24, 0x35, 0x05, 0xf4, + 0x7c, 0x03, 0x53, 0x76, 0xd7, 0xac, 0x4b, 0xc8, + 0xd7, 0xb9, 0x50, 0x83, 0xbf, 0x43, 0x82, 0x7d, + 0x0f, 0xc3, 0x1e, 0xd7, 0x78, 0xdd, 0x36, 0x71, + 0xfd, 0x21, 0xa4, 0x6d, 0x10, 0x91, 0xd6, 0x4b, + 0x6f, 0x9a, 0x1e, 0x12, 0x72, 0x62, 0x13, 0x25, + 0xdb, 0xe1 + }; + const u8 peer_commit[] = { + 0x13, 0x00, 0x59, 0x1b, 0x96, 0xf3, 0x39, 0x7f, + 0xb9, 0x45, 0x10, 0x08, 0x48, 0xe7, 0xb5, 0x50, + 0x54, 0x3b, 0x67, 0x20, 0xd8, 0x83, 0x37, 0xee, + 0x93, 0xfc, 0x49, 0xfd, 0x6d, 0xf7, 0xe0, 0x8b, + 0x52, 0x23, 0xe7, 0x1b, 0x9b, 0xb0, 0x48, 0xd3, + 0x87, 0x3f, 0x20, 0x55, 0x69, 0x53, 0xa9, 0x6c, + 0x91, 0x53, 0x6f, 0xd8, 0xee, 0x6c, 0xa9, 0xb4, + 0xa6, 0x8a, 0x14, 0x8b, 0x05, 0x6a, 0x90, 0x9b, + 0xe0, 0x3e, 0x83, 0xae, 0x20, 0x8f, 0x60, 0xf8, + 0xef, 0x55, 0x37, 0x85, 0x80, 0x74, 0xdb, 0x06, + 0x68, 0x70, 0x32, 0x39, 0x98, 0x62, 0x99, 0x9b, + 0x51, 0x1e, 0x0a, 0x15, 0x52, 0xa5, 0xfe, 0xa3, + 0x17, 0xc2 + }; + const u8 kck[] = { + 0x1e, 0x73, 0x3f, 0x6d, 0x9b, 0xd5, 0x32, 0x56, + 0x28, 0x73, 0x04, 0x33, 0x88, 0x31, 0xb0, 0x9a, + 0x39, 0x40, 0x6d, 0x12, 0x10, 0x17, 0x07, 0x3a, + 0x5c, 0x30, 0xdb, 0x36, 0xf3, 0x6c, 0xb8, 0x1a + }; + const u8 pmk[] = { + 0x4e, 0x4d, 0xfa, 0xb1, 0xa2, 0xdd, 0x8a, 0xc1, + 0xa9, 0x17, 0x90, 0xf9, 0x53, 0xfa, 0xaa, 0x45, + 0x2a, 0xe5, 0xc6, 0x87, 0x3a, 0xb7, 0x5b, 0x63, + 0x60, 0x5b, 0xa6, 0x63, 0xf8, 0xa7, 0xfe, 0x59 + }; + const u8 pmkid[] = { + 0x87, 0x47, 0xa6, 0x00, 0xee, 0xa3, 0xf9, 0xf2, + 0x24, 0x75, 0xdf, 0x58, 0xca, 0x1e, 0x54, 0x98 + }; + struct wpabuf *buf = NULL; + struct crypto_bignum *mask = NULL; + const u8 pwe_19_x[32] = { + 0xc9, 0x30, 0x49, 0xb9, 0xe6, 0x40, 0x00, 0xf8, + 0x48, 0x20, 0x16, 0x49, 0xe9, 0x99, 0xf2, 0xb5, + 0xc2, 0x2d, 0xea, 0x69, 0xb5, 0x63, 0x2c, 0x9d, + 0xf4, 0xd6, 0x33, 0xb8, 0xaa, 0x1f, 0x6c, 0x1e + }; + const u8 pwe_19_y[32] = { + 0x73, 0x63, 0x4e, 0x94, 0xb5, 0x3d, 0x82, 0xe7, + 0x38, 0x3a, 0x8d, 0x25, 0x81, 0x99, 0xd9, 0xdc, + 0x1a, 0x5e, 0xe8, 0x26, 0x9d, 0x06, 0x03, 0x82, + 0xcc, 0xbf, 0x33, 0xe6, 0x14, 0xff, 0x59, 0xa0 + }; + const u8 pwe_15[384] = { + 0x69, 0x68, 0x73, 0x65, 0x8f, 0x65, 0x31, 0x42, + 0x9f, 0x97, 0x39, 0x6f, 0xb8, 0x5f, 0x89, 0xe1, + 0xfc, 0xd2, 0xf6, 0x92, 0x19, 0xa9, 0x0e, 0x82, + 0x2f, 0xf7, 0xf4, 0xbc, 0x0b, 0xd8, 0xa7, 0x9f, + 0xf0, 0x80, 0x35, 0x31, 0x6f, 0xca, 0xe1, 0xa5, + 0x39, 0x77, 0xdc, 0x11, 0x2b, 0x0b, 0xfe, 0x2e, + 0x6f, 0x65, 0x6d, 0xc7, 0xd4, 0xa4, 0x5b, 0x08, + 0x1f, 0xd9, 0xbb, 0xe2, 0x22, 0x85, 0x31, 0x81, + 0x79, 0x70, 0xbe, 0xa1, 0x66, 0x58, 0x4a, 0x09, + 0x3c, 0x57, 0x34, 0x3c, 0x9d, 0x57, 0x8f, 0x42, + 0x58, 0xd0, 0x39, 0x81, 0xdb, 0x8f, 0x79, 0xa2, + 0x1b, 0x01, 0xcd, 0x27, 0xc9, 0xae, 0xcf, 0xcb, + 0x9c, 0xdb, 0x1f, 0x84, 0xb8, 0x88, 0x4e, 0x8f, + 0x50, 0x66, 0xb4, 0x29, 0x83, 0x1e, 0xb9, 0x89, + 0x0c, 0xa5, 0x47, 0x21, 0xba, 0x10, 0xd5, 0xaa, + 0x1a, 0x80, 0xce, 0xf1, 0x4c, 0xad, 0x16, 0xda, + 0x57, 0xb2, 0x41, 0x8a, 0xbe, 0x4b, 0x8c, 0xb0, + 0xb2, 0xeb, 0xf7, 0xa8, 0x0e, 0x3e, 0xcf, 0x22, + 0x8f, 0xd8, 0xb6, 0xdb, 0x79, 0x9c, 0x9b, 0x80, + 0xaf, 0xd7, 0x14, 0xad, 0x51, 0x82, 0xf4, 0x64, + 0xb6, 0x3f, 0x4c, 0x6c, 0xe5, 0x3f, 0xaa, 0x6f, + 0xbf, 0x3d, 0xc2, 0x3f, 0x77, 0xfd, 0xcb, 0xe1, + 0x9c, 0xe3, 0x1e, 0x8a, 0x0e, 0x97, 0xe2, 0x2b, + 0xe2, 0xdd, 0x37, 0x39, 0x88, 0xc2, 0x8e, 0xbe, + 0xfa, 0xac, 0x3d, 0x5b, 0x62, 0x2e, 0x1e, 0x74, + 0xa0, 0x9a, 0xf8, 0xed, 0xfa, 0xe1, 0xce, 0x9c, + 0xab, 0xbb, 0xdc, 0x36, 0xb1, 0x28, 0x46, 0x3c, + 0x7e, 0xa8, 0xbd, 0xb9, 0x36, 0x4c, 0x26, 0x75, + 0xe0, 0x17, 0x73, 0x1f, 0xe0, 0xfe, 0xf6, 0x49, + 0xfa, 0xa0, 0x45, 0xf4, 0x44, 0x05, 0x20, 0x27, + 0x25, 0xc2, 0x99, 0xde, 0x27, 0x8b, 0x70, 0xdc, + 0x54, 0x60, 0x90, 0x02, 0x1e, 0x29, 0x97, 0x9a, + 0xc4, 0xe7, 0xb6, 0xf5, 0x8b, 0xae, 0x7c, 0x34, + 0xaa, 0xef, 0x9b, 0xc6, 0x30, 0xf2, 0x80, 0x8d, + 0x80, 0x78, 0xc2, 0x55, 0x63, 0xa0, 0xa1, 0x38, + 0x70, 0xfb, 0xf4, 0x74, 0x8d, 0xcd, 0x87, 0x90, + 0xb4, 0x54, 0xc3, 0x75, 0xdf, 0x10, 0xc5, 0xb6, + 0xb2, 0x08, 0x59, 0x61, 0xe6, 0x68, 0xa5, 0x82, + 0xf8, 0x8f, 0x47, 0x30, 0x43, 0xb4, 0xdc, 0x31, + 0xfc, 0xbc, 0x69, 0xe7, 0xb4, 0x94, 0xb0, 0x6a, + 0x60, 0x59, 0x80, 0x2e, 0xd3, 0xa4, 0xe8, 0x97, + 0xa2, 0xa3, 0xc9, 0x08, 0x4b, 0x27, 0x6c, 0xc1, + 0x37, 0xe8, 0xfc, 0x5c, 0xe2, 0x54, 0x30, 0x3e, + 0xf8, 0xfe, 0xa2, 0xfc, 0xbb, 0xbd, 0x88, 0x6c, + 0x92, 0xa3, 0x2a, 0x40, 0x7a, 0x2c, 0x22, 0x38, + 0x8c, 0x86, 0x86, 0xfe, 0xb9, 0xd4, 0x6b, 0xd6, + 0x47, 0x88, 0xa7, 0xf6, 0x8e, 0x0f, 0x14, 0xad, + 0x1e, 0xac, 0xcf, 0x33, 0x01, 0x99, 0xc1, 0x62 + }; + int pt_groups[] = { 19, 20, 21, 25, 26, 28, 29, 30, 15, 0 }; + struct sae_pt *pt_info, *pt; + const u8 addr1b[ETH_ALEN] = { 0x00, 0x09, 0x5b, 0x66, 0xec, 0x1e }; + const u8 addr2b[ETH_ALEN] = { 0x00, 0x0b, 0x6b, 0xd9, 0x02, 0x46 }; + + os_memset(&sae, 0, sizeof(sae)); + buf = wpabuf_alloc(1000); + if (!buf || + sae_set_group(&sae, 19) < 0 || + sae_prepare_commit(addr1, addr2, (const u8 *) pw, os_strlen(pw), + &sae) < 0) + goto fail; + + /* Override local values based on SAE test vector */ + crypto_bignum_deinit(sae.tmp->sae_rand, 1); + sae.tmp->sae_rand = crypto_bignum_init_set(local_rand, + sizeof(local_rand)); + mask = crypto_bignum_init_set(local_mask, sizeof(local_mask)); + if (!sae.tmp->sae_rand || !mask) + goto fail; + + if (crypto_bignum_add(sae.tmp->sae_rand, mask, + sae.tmp->own_commit_scalar) < 0 || + crypto_bignum_mod(sae.tmp->own_commit_scalar, sae.tmp->order, + sae.tmp->own_commit_scalar) < 0 || + crypto_ec_point_mul(sae.tmp->ec, sae.tmp->pwe_ecc, mask, + sae.tmp->own_commit_element_ecc) < 0 || + crypto_ec_point_invert(sae.tmp->ec, + sae.tmp->own_commit_element_ecc) < 0) + goto fail; + + /* Check that output matches the test vector */ + if (sae_write_commit(&sae, buf, NULL, NULL) < 0) + goto fail; + wpa_hexdump_buf(MSG_DEBUG, "SAE: Commit message", buf); + + if (wpabuf_len(buf) != sizeof(local_commit) || + os_memcmp(wpabuf_head(buf), local_commit, + sizeof(local_commit)) != 0) { + wpa_printf(MSG_ERROR, "SAE: Mismatch in local commit"); + goto fail; + } + + if (sae_parse_commit(&sae, peer_commit, sizeof(peer_commit), NULL, NULL, + NULL, 0, NULL) != 0 || + sae_process_commit(&sae) < 0) + goto fail; + + if (os_memcmp(kck, sae.tmp->kck, SAE_KCK_LEN) != 0) { + wpa_printf(MSG_ERROR, "SAE: Mismatch in KCK"); + goto fail; + } + + if (os_memcmp(pmk, sae.pmk, SAE_PMK_LEN) != 0) { + wpa_printf(MSG_ERROR, "SAE: Mismatch in PMK"); + goto fail; + } + + if (os_memcmp(pmkid, sae.pmkid, SAE_PMKID_LEN) != 0) { + wpa_printf(MSG_ERROR, "SAE: Mismatch in PMKID"); + goto fail; + } + + pt_info = sae_derive_pt(pt_groups, + (const u8 *) ssid, os_strlen(ssid), + (const u8 *) pw, os_strlen(pw), pwid); + if (!pt_info) + goto fail; + + for (pt = pt_info; pt; pt = pt->next) { + if (pt->group == 19) { + struct crypto_ec_point *pwe; + u8 bin[SAE_MAX_ECC_PRIME_LEN * 2]; + size_t prime_len = sizeof(pwe_19_x); + + pwe = sae_derive_pwe_from_pt_ecc(pt, addr1b, addr2b); + if (!pwe) { + sae_deinit_pt(pt); + goto fail; + } + if (crypto_ec_point_to_bin(pt->ec, pwe, bin, + bin + prime_len) < 0 || + os_memcmp(pwe_19_x, bin, prime_len) != 0 || + os_memcmp(pwe_19_y, bin + prime_len, + prime_len) != 0) { + wpa_printf(MSG_ERROR, + "SAE: PT/PWE test vector mismatch"); + crypto_ec_point_deinit(pwe, 1); + sae_deinit_pt(pt); + goto fail; + } + crypto_ec_point_deinit(pwe, 1); + } + + if (pt->group == 15) { + struct crypto_bignum *pwe; + u8 bin[SAE_MAX_PRIME_LEN]; + size_t prime_len = sizeof(pwe_15); + + pwe = sae_derive_pwe_from_pt_ffc(pt, addr1b, addr2b); + if (!pwe) { + sae_deinit_pt(pt); + goto fail; + } + if (crypto_bignum_to_bin(pwe, bin, sizeof(bin), + prime_len) < 0 || + os_memcmp(pwe_15, bin, prime_len) != 0) { + wpa_printf(MSG_ERROR, + "SAE: PT/PWE test vector mismatch"); + crypto_bignum_deinit(pwe, 1); + sae_deinit_pt(pt); + goto fail; + } + crypto_bignum_deinit(pwe, 1); + } + } + + sae_deinit_pt(pt_info); + + ret = 0; +fail: + sae_clear_data(&sae); + wpabuf_free(buf); + crypto_bignum_deinit(mask, 1); + return ret; +#else /* CONFIG_SAE */ + return 0; +#endif /* CONFIG_SAE */ +} + + +static int sae_pk_tests(void) +{ +#ifdef CONFIG_SAE_PK + const char *invalid[] = { "a2bc-de3f-ghim-", "a2bcde3fghim", "", NULL }; + struct { + const char *pw; + const u8 *val; + } valid[] = { + { "a2bc-de3f-ghim", (u8 *) "\x06\x82\x21\x93\x65\x31\xd0\xc0" }, + { "aaaa-aaaa-aaaj", (u8 *) "\x00\x00\x00\x00\x00\x00\x00\x90" }, + { "7777-7777-777f", (u8 *) "\xff\xff\xff\xff\xff\xff\xfe\x50" }, + { NULL, NULL } + }; + int i; + bool failed; + + for (i = 0; invalid[i]; i++) { + if (sae_pk_valid_password(invalid[i])) { + wpa_printf(MSG_ERROR, + "SAE-PK: Invalid password '%s' not recognized", + invalid[i]); + return -1; + } + } + + failed = false; + for (i = 0; valid[i].pw; i++) { + u8 *res; + size_t res_len; + char *b32; + const char *pw = valid[i].pw; + const u8 *val = valid[i].val; + size_t pw_len = os_strlen(pw); + size_t bits = (pw_len - pw_len / 5) * 5; + size_t bytes = (bits + 7) / 8; + + if (!sae_pk_valid_password(pw)) { + wpa_printf(MSG_ERROR, + "SAE-PK: Valid password '%s' not recognized", + pw); + failed = true; + continue; + } + + res = sae_pk_base32_decode(pw, pw_len, &res_len); + if (!res) { + wpa_printf(MSG_ERROR, + "SAE-PK: Failed to decode password '%s'", + valid[i].pw); + failed = true; + continue; + } + if (res_len != bytes || os_memcmp(val, res, res_len) != 0) { + wpa_printf(MSG_ERROR, + "SAE-PK: Mismatch for decoded password '%s'", + valid[i].pw); + wpa_hexdump(MSG_INFO, "SAE-PK: Decoded value", + res, res_len); + wpa_hexdump(MSG_INFO, "SAE-PK: Expected value", + val, bytes); + failed = true; + } + os_free(res); + + b32 = sae_pk_base32_encode(val, bits - 5); + if (!b32) { + wpa_printf(MSG_ERROR, + "SAE-PK: Failed to encode password '%s'", + pw); + failed = true; + continue; + } + if (os_strcmp(b32, pw) != 0) { + wpa_printf(MSG_ERROR, + "SAE-PK: Mismatch for password '%s'", pw); + wpa_printf(MSG_INFO, "SAE-PK: Encoded value: '%s'", + b32); + failed = true; + } + os_free(b32); + } + + return failed ? -1 : 0; +#else /* CONFIG_SAE_PK */ + return 0; +#endif /* CONFIG_SAE_PK */ +} + + +#ifdef CONFIG_PASN + +static int pasn_test_pasn_auth(void) +{ + /* Test vector taken from IEEE P802.11az/D2.6, J.12 */ + const u8 pmk[] = { + 0xde, 0xf4, 0x3e, 0x55, 0x67, 0xe0, 0x1c, 0xa6, + 0x64, 0x92, 0x65, 0xf1, 0x9a, 0x29, 0x0e, 0xef, + 0xf8, 0xbd, 0x88, 0x8f, 0x6c, 0x1d, 0x9c, 0xc9, + 0xd1, 0x0f, 0x04, 0xbd, 0x37, 0x8f, 0x3c, 0xad + }; + + const u8 spa_addr[] = { + 0x00, 0x90, 0x4c, 0x01, 0xc1, 0x07 + }; + const u8 bssid[] = { + 0xc0, 0xff, 0xd4, 0xa8, 0xdb, 0xc1 + }; + const u8 dhss[] = { + 0xf8, 0x7b, 0x20, 0x8e, 0x7e, 0xd2, 0xb7, 0x37, + 0xaf, 0xdb, 0xc2, 0xe1, 0x3e, 0xae, 0x78, 0xda, + 0x30, 0x01, 0x23, 0xd4, 0xd8, 0x4b, 0xa8, 0xb0, + 0xea, 0xfe, 0x90, 0xc4, 0x8c, 0xdf, 0x1f, 0x93 + }; + const u8 kck[] = { + 0x7b, 0xb8, 0x21, 0xac, 0x0a, 0xa5, 0x90, 0x9d, + 0xd6, 0x54, 0xa5, 0x60, 0x65, 0xad, 0x7c, 0x77, + 0xeb, 0x88, 0x9c, 0xbe, 0x29, 0x05, 0xbb, 0xf0, + 0x5a, 0xbb, 0x1e, 0xea, 0xc8, 0x8b, 0xa3, 0x06 + }; + const u8 tk[] = { + 0x67, 0x3e, 0xab, 0x46, 0xb8, 0x32, 0xd5, 0xa8, + 0x0c, 0xbc, 0x02, 0x43, 0x01, 0x6e, 0x20, 0x7e + }; + const u8 kdk[] = { + 0x2d, 0x0f, 0x0e, 0x82, 0xc7, 0x0d, 0xd2, 0x6b, + 0x79, 0x06, 0x1a, 0x46, 0x81, 0xe8, 0xdb, 0xb2, + 0xea, 0x83, 0xbe, 0xa3, 0x99, 0x84, 0x4b, 0xd5, + 0x89, 0x4e, 0xb3, 0x20, 0xf6, 0x9d, 0x7d, 0xd6 + }; + struct wpa_ptk ptk; + int ret; + + ret = pasn_pmk_to_ptk(pmk, sizeof(pmk), + spa_addr, bssid, + dhss, sizeof(dhss), + &ptk, WPA_KEY_MGMT_PASN, WPA_CIPHER_CCMP, + WPA_KDK_MAX_LEN); + + if (ret) + return ret; + + if (ptk.kck_len != sizeof(kck) || + os_memcmp(kck, ptk.kck, sizeof(kck)) != 0) { + wpa_printf(MSG_ERROR, "PASN: Mismatched KCK"); + return -1; + } + + if (ptk.tk_len != sizeof(tk) || + os_memcmp(tk, ptk.tk, sizeof(tk)) != 0) { + wpa_printf(MSG_ERROR, "PASN: Mismatched TK"); + return -1; + } + + if (ptk.kdk_len != sizeof(kdk) || + os_memcmp(kdk, ptk.kdk, sizeof(kdk)) != 0) { + wpa_printf(MSG_ERROR, "PASN: Mismatched KDK"); + return -1; + } + + return 0; +} + + +static int pasn_test_no_pasn_auth(void) +{ + /* Test vector taken from IEEE P802.11az/D2.6, J.13 */ + const u8 pmk[] = { + 0xde, 0xf4, 0x3e, 0x55, 0x67, 0xe0, 0x1c, 0xa6, + 0x64, 0x92, 0x65, 0xf1, 0x9a, 0x29, 0x0e, 0xef, + 0xf8, 0xbd, 0x88, 0x8f, 0x6c, 0x1d, 0x9c, 0xc9, + 0xd1, 0x0f, 0x04, 0xbd, 0x37, 0x8f, 0x3c, 0xad + }; + const u8 aa[] = { + 0xc0, 0xff, 0xd4, 0xa8, 0xdb, 0xc1 + }; + const u8 spa[] = { + 0x00, 0x90, 0x4c, 0x01, 0xc1, 0x07 + }; + const u8 anonce[] = { + 0xbe, 0x7a, 0x1c, 0xa2, 0x84, 0x34, 0x7b, 0x5b, + 0xd6, 0x7d, 0xbd, 0x2d, 0xfd, 0xb4, 0xd9, 0x9f, + 0x1a, 0xfa, 0xe0, 0xb8, 0x8b, 0xa1, 0x8e, 0x00, + 0x87, 0x18, 0x41, 0x7e, 0x4b, 0x27, 0xef, 0x5f + }; + const u8 snonce[] = { + 0x40, 0x4b, 0x01, 0x2f, 0xfb, 0x43, 0xed, 0x0f, + 0xb4, 0x3e, 0xa1, 0xf2, 0x87, 0xc9, 0x1f, 0x25, + 0x06, 0xd2, 0x1b, 0x4a, 0x92, 0xd7, 0x4b, 0x5e, + 0xa5, 0x0c, 0x94, 0x33, 0x50, 0xce, 0x86, 0x71 + }; + const u8 kck[] = { + 0xcd, 0x7b, 0x9e, 0x75, 0x55, 0x36, 0x2d, 0xf0, + 0xb6, 0x35, 0x68, 0x48, 0x4a, 0x81, 0x12, 0xf5 + }; + const u8 kek[] = { + 0x99, 0xca, 0xd3, 0x58, 0x8d, 0xa0, 0xf1, 0xe6, + 0x3f, 0xd1, 0x90, 0x19, 0x10, 0x39, 0xbb, 0x4b + }; + const u8 tk[] = { + 0x9e, 0x2e, 0x93, 0x77, 0xe7, 0x53, 0x2e, 0x73, + 0x7a, 0x1b, 0xc2, 0x50, 0xfe, 0x19, 0x4a, 0x03 + }; + const u8 kdk[] = { + 0x6c, 0x7f, 0xb9, 0x7c, 0xeb, 0x55, 0xb0, 0x1a, + 0xcf, 0xf0, 0x0f, 0x07, 0x09, 0x42, 0xbd, 0xf5, + 0x29, 0x1f, 0xeb, 0x4b, 0xee, 0x38, 0xe0, 0x36, + 0x5b, 0x25, 0xa2, 0x50, 0xbb, 0x2a, 0xc9, 0xff + }; + struct wpa_ptk ptk; + int ret; + + ret = wpa_pmk_to_ptk(pmk, sizeof(pmk), + "Pairwise key expansion", + spa, aa, snonce, anonce, + &ptk, WPA_KEY_MGMT_SAE, WPA_CIPHER_CCMP, + NULL, 0, WPA_KDK_MAX_LEN); + + if (ret) + return ret; + + if (ptk.kck_len != sizeof(kck) || + os_memcmp(kck, ptk.kck, sizeof(kck)) != 0) { + wpa_printf(MSG_ERROR, "KDK no PASN auth: Mismatched KCK"); + return -1; + } + + if (ptk.kek_len != sizeof(kek) || + os_memcmp(kek, ptk.kek, sizeof(kek)) != 0) { + wpa_printf(MSG_ERROR, "KDK no PASN auth: Mismatched KEK"); + return -1; + } + + if (ptk.tk_len != sizeof(tk) || + os_memcmp(tk, ptk.tk, sizeof(tk)) != 0) { + wpa_printf(MSG_ERROR, "KDK no PASN auth: Mismatched TK"); + return -1; + } + + if (ptk.kdk_len != sizeof(kdk) || + os_memcmp(kdk, ptk.kdk, sizeof(kdk)) != 0) { + wpa_printf(MSG_ERROR, "KDK no PASN auth: Mismatched KDK"); + return -1; + } + + return 0; +} + +#endif /* CONFIG_PASN */ + + +static int pasn_tests(void) +{ +#ifdef CONFIG_PASN + if (pasn_test_pasn_auth() || + pasn_test_no_pasn_auth()) + return -1; +#endif /* CONFIG_PASN */ + return 0; +} + + +int common_module_tests(void) +{ + int ret = 0; + + wpa_printf(MSG_INFO, "common module tests"); + + if (ieee802_11_parse_tests() < 0 || + gas_tests() < 0 || + sae_tests() < 0 || + sae_pk_tests() < 0 || + pasn_tests() < 0 || + rsn_ie_parse_tests() < 0) + ret = -1; + + return ret; +} diff --git a/src/common/ctrl_iface_common.c b/src/common/ctrl_iface_common.c new file mode 100644 index 0000000..e26407d --- /dev/null +++ b/src/common/ctrl_iface_common.c @@ -0,0 +1,209 @@ +/* + * Common hostapd/wpa_supplicant ctrl iface code. + * Copyright (c) 2002-2013, Jouni Malinen + * Copyright (c) 2015, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" +#include +#include + +#include "utils/common.h" +#include "ctrl_iface_common.h" + +static int sockaddr_compare(struct sockaddr_storage *a, socklen_t a_len, + struct sockaddr_storage *b, socklen_t b_len) +{ + if (a->ss_family != b->ss_family) + return 1; + + switch (a->ss_family) { +#ifdef CONFIG_CTRL_IFACE_UDP + case AF_INET: + { + struct sockaddr_in *in_a, *in_b; + + in_a = (struct sockaddr_in *) a; + in_b = (struct sockaddr_in *) b; + + if (in_a->sin_port != in_b->sin_port) + return 1; + if (in_a->sin_addr.s_addr != in_b->sin_addr.s_addr) + return 1; + break; + } + case AF_INET6: + { + struct sockaddr_in6 *in6_a, *in6_b; + + in6_a = (struct sockaddr_in6 *) a; + in6_b = (struct sockaddr_in6 *) b; + + if (in6_a->sin6_port != in6_b->sin6_port) + return 1; + if (os_memcmp(&in6_a->sin6_addr, &in6_b->sin6_addr, + sizeof(in6_a->sin6_addr)) != 0) + return 1; + break; + } +#endif /* CONFIG_CTRL_IFACE_UDP */ +#ifdef CONFIG_CTRL_IFACE_UNIX + case AF_UNIX: + { + struct sockaddr_un *u_a, *u_b; + + u_a = (struct sockaddr_un *) a; + u_b = (struct sockaddr_un *) b; + + if (a_len != b_len || + os_memcmp(u_a->sun_path, u_b->sun_path, + a_len - offsetof(struct sockaddr_un, sun_path)) + != 0) + return 1; + break; + } +#endif /* CONFIG_CTRL_IFACE_UNIX */ + default: + return 1; + } + + return 0; +} + + +void sockaddr_print(int level, const char *msg, struct sockaddr_storage *sock, + socklen_t socklen) +{ + switch (sock->ss_family) { +#ifdef CONFIG_CTRL_IFACE_UDP + case AF_INET: + case AF_INET6: + { + char host[NI_MAXHOST] = { 0 }; + char service[NI_MAXSERV] = { 0 }; + + getnameinfo((struct sockaddr *) sock, socklen, + host, sizeof(host), + service, sizeof(service), + NI_NUMERICHOST); + + wpa_printf(level, "%s %s:%s", msg, host, service); + break; + } +#endif /* CONFIG_CTRL_IFACE_UDP */ +#ifdef CONFIG_CTRL_IFACE_UNIX + case AF_UNIX: + { + char addr_txt[200]; + + printf_encode(addr_txt, sizeof(addr_txt), + (u8 *) ((struct sockaddr_un *) sock)->sun_path, + socklen - offsetof(struct sockaddr_un, sun_path)); + wpa_printf(level, "%s %s", msg, addr_txt); + break; + } +#endif /* CONFIG_CTRL_IFACE_UNIX */ + default: + wpa_printf(level, "%s", msg); + break; + } +} + + +static int ctrl_set_events(struct wpa_ctrl_dst *dst, const char *input) +{ + const char *value; + int val; + + if (!input) + return 0; + + value = os_strchr(input, '='); + if (!value) + return -1; + value++; + val = atoi(value); + if (val < 0 || val > 1) + return -1; + + if (str_starts(input, "probe_rx_events=")) { + if (val) + dst->events |= WPA_EVENT_RX_PROBE_REQUEST; + else + dst->events &= ~WPA_EVENT_RX_PROBE_REQUEST; + } + + return 0; +} + + +int ctrl_iface_attach(struct dl_list *ctrl_dst, struct sockaddr_storage *from, + socklen_t fromlen, const char *input) +{ + struct wpa_ctrl_dst *dst; + + /* Update event registration if already attached */ + dl_list_for_each(dst, ctrl_dst, struct wpa_ctrl_dst, list) { + if (!sockaddr_compare(from, fromlen, + &dst->addr, dst->addrlen)) + return ctrl_set_events(dst, input); + } + + /* New attachment */ + dst = os_zalloc(sizeof(*dst)); + if (dst == NULL) + return -1; + os_memcpy(&dst->addr, from, fromlen); + dst->addrlen = fromlen; + dst->debug_level = MSG_INFO; + ctrl_set_events(dst, input); + dl_list_add(ctrl_dst, &dst->list); + + sockaddr_print(MSG_DEBUG, "CTRL_IFACE monitor attached", from, fromlen); + return 0; +} + + +int ctrl_iface_detach(struct dl_list *ctrl_dst, struct sockaddr_storage *from, + socklen_t fromlen) +{ + struct wpa_ctrl_dst *dst; + + dl_list_for_each(dst, ctrl_dst, struct wpa_ctrl_dst, list) { + if (!sockaddr_compare(from, fromlen, + &dst->addr, dst->addrlen)) { + sockaddr_print(MSG_DEBUG, "CTRL_IFACE monitor detached", + from, fromlen); + dl_list_del(&dst->list); + os_free(dst); + return 0; + } + } + + return -1; +} + + +int ctrl_iface_level(struct dl_list *ctrl_dst, struct sockaddr_storage *from, + socklen_t fromlen, const char *level) +{ + struct wpa_ctrl_dst *dst; + + wpa_printf(MSG_DEBUG, "CTRL_IFACE LEVEL %s", level); + + dl_list_for_each(dst, ctrl_dst, struct wpa_ctrl_dst, list) { + if (!sockaddr_compare(from, fromlen, + &dst->addr, dst->addrlen)) { + sockaddr_print(MSG_DEBUG, + "CTRL_IFACE changed monitor level", + from, fromlen); + dst->debug_level = atoi(level); + return 0; + } + } + + return -1; +} diff --git a/src/common/ctrl_iface_common.h b/src/common/ctrl_iface_common.h new file mode 100644 index 0000000..85e258e --- /dev/null +++ b/src/common/ctrl_iface_common.h @@ -0,0 +1,42 @@ +/* + * Common hostapd/wpa_supplicant ctrl iface code. + * Copyright (c) 2002-2013, Jouni Malinen + * Copyright (c) 2015, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +#ifndef CONTROL_IFACE_COMMON_H +#define CONTROL_IFACE_COMMON_H + +#include "utils/list.h" + +/* Events enable bits (wpa_ctrl_dst::events) */ +#define WPA_EVENT_RX_PROBE_REQUEST BIT(0) + +/** + * struct wpa_ctrl_dst - Data structure of control interface monitors + * + * This structure is used to store information about registered control + * interface monitors into struct wpa_supplicant. + */ +struct wpa_ctrl_dst { + struct dl_list list; + struct sockaddr_storage addr; + socklen_t addrlen; + int debug_level; + int errors; + u32 events; /* WPA_EVENT_* bitmap */ +}; + +void sockaddr_print(int level, const char *msg, struct sockaddr_storage *sock, + socklen_t socklen); + +int ctrl_iface_attach(struct dl_list *ctrl_dst, struct sockaddr_storage *from, + socklen_t fromlen, const char *input); +int ctrl_iface_detach(struct dl_list *ctrl_dst, struct sockaddr_storage *from, + socklen_t fromlen); +int ctrl_iface_level(struct dl_list *ctrl_dst, struct sockaddr_storage *from, + socklen_t fromlen, const char *level); + +#endif /* CONTROL_IFACE_COMMON_H */ diff --git a/src/common/defs.h b/src/common/defs.h new file mode 100644 index 0000000..8cca094 --- /dev/null +++ b/src/common/defs.h @@ -0,0 +1,535 @@ +/* + * WPA Supplicant - Common definitions + * Copyright (c) 2004-2018, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef DEFS_H +#define DEFS_H + +#define WPA_CIPHER_NONE BIT(0) +#define WPA_CIPHER_WEP40 BIT(1) +#define WPA_CIPHER_WEP104 BIT(2) +#define WPA_CIPHER_TKIP BIT(3) +#define WPA_CIPHER_CCMP BIT(4) +#define WPA_CIPHER_AES_128_CMAC BIT(5) +#define WPA_CIPHER_GCMP BIT(6) +#define WPA_CIPHER_SMS4 BIT(7) +#define WPA_CIPHER_GCMP_256 BIT(8) +#define WPA_CIPHER_CCMP_256 BIT(9) +#define WPA_CIPHER_BIP_GMAC_128 BIT(11) +#define WPA_CIPHER_BIP_GMAC_256 BIT(12) +#define WPA_CIPHER_BIP_CMAC_256 BIT(13) +#define WPA_CIPHER_GTK_NOT_USED BIT(14) + +#define WPA_KEY_MGMT_IEEE8021X BIT(0) +#define WPA_KEY_MGMT_PSK BIT(1) +#define WPA_KEY_MGMT_NONE BIT(2) +#define WPA_KEY_MGMT_IEEE8021X_NO_WPA BIT(3) +#define WPA_KEY_MGMT_WPA_NONE BIT(4) +#define WPA_KEY_MGMT_FT_IEEE8021X BIT(5) +#define WPA_KEY_MGMT_FT_PSK BIT(6) +#define WPA_KEY_MGMT_IEEE8021X_SHA256 BIT(7) +#define WPA_KEY_MGMT_PSK_SHA256 BIT(8) +#define WPA_KEY_MGMT_WPS BIT(9) +#define WPA_KEY_MGMT_SAE BIT(10) +#define WPA_KEY_MGMT_FT_SAE BIT(11) +#define WPA_KEY_MGMT_WAPI_PSK BIT(12) +#define WPA_KEY_MGMT_WAPI_CERT BIT(13) +#define WPA_KEY_MGMT_CCKM BIT(14) +#define WPA_KEY_MGMT_OSEN BIT(15) +#define WPA_KEY_MGMT_IEEE8021X_SUITE_B BIT(16) +#define WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 BIT(17) +#define WPA_KEY_MGMT_FILS_SHA256 BIT(18) +#define WPA_KEY_MGMT_FILS_SHA384 BIT(19) +#define WPA_KEY_MGMT_FT_FILS_SHA256 BIT(20) +#define WPA_KEY_MGMT_FT_FILS_SHA384 BIT(21) +#define WPA_KEY_MGMT_OWE BIT(22) +#define WPA_KEY_MGMT_DPP BIT(23) +#define WPA_KEY_MGMT_FT_IEEE8021X_SHA384 BIT(24) +#define WPA_KEY_MGMT_PASN BIT(25) +#define WPA_KEY_MGMT_SAE_EXT_KEY BIT(26) +#define WPA_KEY_MGMT_FT_SAE_EXT_KEY BIT(27) +#define WPA_KEY_MGMT_IEEE8021X_SHA384 BIT(28) + + +#define WPA_KEY_MGMT_FT (WPA_KEY_MGMT_FT_PSK | \ + WPA_KEY_MGMT_FT_IEEE8021X | \ + WPA_KEY_MGMT_FT_IEEE8021X_SHA384 | \ + WPA_KEY_MGMT_FT_SAE | \ + WPA_KEY_MGMT_FT_SAE_EXT_KEY | \ + WPA_KEY_MGMT_FT_FILS_SHA256 | \ + WPA_KEY_MGMT_FT_FILS_SHA384) + +static inline int wpa_key_mgmt_wpa_ieee8021x(int akm) +{ + return !!(akm & (WPA_KEY_MGMT_IEEE8021X | + WPA_KEY_MGMT_FT_IEEE8021X | + WPA_KEY_MGMT_FT_IEEE8021X_SHA384 | + WPA_KEY_MGMT_CCKM | + WPA_KEY_MGMT_OSEN | + WPA_KEY_MGMT_IEEE8021X_SHA256 | + WPA_KEY_MGMT_IEEE8021X_SUITE_B | + WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 | + WPA_KEY_MGMT_FILS_SHA256 | + WPA_KEY_MGMT_FILS_SHA384 | + WPA_KEY_MGMT_FT_FILS_SHA256 | + WPA_KEY_MGMT_FT_FILS_SHA384 | + WPA_KEY_MGMT_IEEE8021X_SHA384)); +} + +static inline int wpa_key_mgmt_wpa_psk_no_sae(int akm) +{ + return !!(akm & (WPA_KEY_MGMT_PSK | + WPA_KEY_MGMT_FT_PSK | + WPA_KEY_MGMT_PSK_SHA256)); +} + +static inline int wpa_key_mgmt_wpa_psk(int akm) +{ + return !!(akm & (WPA_KEY_MGMT_PSK | + WPA_KEY_MGMT_FT_PSK | + WPA_KEY_MGMT_PSK_SHA256 | + WPA_KEY_MGMT_SAE | + WPA_KEY_MGMT_SAE_EXT_KEY | + WPA_KEY_MGMT_FT_SAE | + WPA_KEY_MGMT_FT_SAE_EXT_KEY)); +} + +static inline int wpa_key_mgmt_ft(int akm) +{ + return !!(akm & WPA_KEY_MGMT_FT); +} + +static inline int wpa_key_mgmt_only_ft(int akm) +{ + int ft = wpa_key_mgmt_ft(akm); + akm &= ~WPA_KEY_MGMT_FT; + return ft && !akm; +} + +static inline int wpa_key_mgmt_ft_psk(int akm) +{ + return !!(akm & WPA_KEY_MGMT_FT_PSK); +} + +static inline int wpa_key_mgmt_sae(int akm) +{ + return !!(akm & (WPA_KEY_MGMT_SAE | + WPA_KEY_MGMT_SAE_EXT_KEY | + WPA_KEY_MGMT_FT_SAE | + WPA_KEY_MGMT_FT_SAE_EXT_KEY)); +} + +static inline int wpa_key_mgmt_sae_ext_key(int akm) +{ + return !!(akm & (WPA_KEY_MGMT_SAE_EXT_KEY | + WPA_KEY_MGMT_FT_SAE_EXT_KEY)); +} + +static inline int wpa_key_mgmt_fils(int akm) +{ + return !!(akm & (WPA_KEY_MGMT_FILS_SHA256 | + WPA_KEY_MGMT_FILS_SHA384 | + WPA_KEY_MGMT_FT_FILS_SHA256 | + WPA_KEY_MGMT_FT_FILS_SHA384)); +} + +static inline int wpa_key_mgmt_sha256(int akm) +{ + return !!(akm & (WPA_KEY_MGMT_FT_IEEE8021X | + WPA_KEY_MGMT_PSK_SHA256 | + WPA_KEY_MGMT_IEEE8021X_SHA256 | + WPA_KEY_MGMT_SAE | + WPA_KEY_MGMT_FT_SAE | + WPA_KEY_MGMT_OSEN | + WPA_KEY_MGMT_IEEE8021X_SUITE_B | + WPA_KEY_MGMT_FILS_SHA256 | + WPA_KEY_MGMT_FT_FILS_SHA256)); +} + +static inline int wpa_key_mgmt_sha384(int akm) +{ + return !!(akm & (WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 | + WPA_KEY_MGMT_FT_IEEE8021X_SHA384 | + WPA_KEY_MGMT_FILS_SHA384 | + WPA_KEY_MGMT_FT_FILS_SHA384 | + WPA_KEY_MGMT_IEEE8021X_SHA384)); +} + +static inline int wpa_key_mgmt_suite_b(int akm) +{ + return !!(akm & (WPA_KEY_MGMT_IEEE8021X_SUITE_B | + WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)); +} + +static inline int wpa_key_mgmt_wpa(int akm) +{ + return wpa_key_mgmt_wpa_ieee8021x(akm) || + wpa_key_mgmt_wpa_psk(akm) || + wpa_key_mgmt_fils(akm) || + wpa_key_mgmt_sae(akm) || + akm == WPA_KEY_MGMT_OWE || + akm == WPA_KEY_MGMT_DPP; +} + +static inline int wpa_key_mgmt_wpa_any(int akm) +{ + return wpa_key_mgmt_wpa(akm) || (akm & WPA_KEY_MGMT_WPA_NONE); +} + +static inline int wpa_key_mgmt_cckm(int akm) +{ + return akm == WPA_KEY_MGMT_CCKM; +} + +static inline int wpa_key_mgmt_cross_akm(int akm) +{ + return !!(akm & (WPA_KEY_MGMT_PSK | + WPA_KEY_MGMT_PSK_SHA256 | + WPA_KEY_MGMT_SAE | + WPA_KEY_MGMT_SAE_EXT_KEY)); +} + +#define WPA_PROTO_WPA BIT(0) +#define WPA_PROTO_RSN BIT(1) +#define WPA_PROTO_WAPI BIT(2) +#define WPA_PROTO_OSEN BIT(3) + +#define WPA_AUTH_ALG_OPEN BIT(0) +#define WPA_AUTH_ALG_SHARED BIT(1) +#define WPA_AUTH_ALG_LEAP BIT(2) +#define WPA_AUTH_ALG_FT BIT(3) +#define WPA_AUTH_ALG_SAE BIT(4) +#define WPA_AUTH_ALG_FILS BIT(5) +#define WPA_AUTH_ALG_FILS_SK_PFS BIT(6) + +static inline int wpa_auth_alg_fils(int alg) +{ + return !!(alg & (WPA_AUTH_ALG_FILS | WPA_AUTH_ALG_FILS_SK_PFS)); +} + +enum wpa_alg { + WPA_ALG_NONE, + WPA_ALG_WEP, + WPA_ALG_TKIP, + WPA_ALG_CCMP, + WPA_ALG_BIP_CMAC_128, + WPA_ALG_GCMP, + WPA_ALG_SMS4, + WPA_ALG_KRK, + WPA_ALG_GCMP_256, + WPA_ALG_CCMP_256, + WPA_ALG_BIP_GMAC_128, + WPA_ALG_BIP_GMAC_256, + WPA_ALG_BIP_CMAC_256 +}; + +static inline int wpa_alg_bip(enum wpa_alg alg) +{ + return alg == WPA_ALG_BIP_CMAC_128 || + alg == WPA_ALG_BIP_GMAC_128 || + alg == WPA_ALG_BIP_GMAC_256 || + alg == WPA_ALG_BIP_CMAC_256; +} + +/** + * enum wpa_states - wpa_supplicant state + * + * These enumeration values are used to indicate the current wpa_supplicant + * state (wpa_s->wpa_state). The current state can be retrieved with + * wpa_supplicant_get_state() function and the state can be changed by calling + * wpa_supplicant_set_state(). In WPA state machine (wpa.c and preauth.c), the + * wrapper functions wpa_sm_get_state() and wpa_sm_set_state() should be used + * to access the state variable. + */ +enum wpa_states { + /** + * WPA_DISCONNECTED - Disconnected state + * + * This state indicates that client is not associated, but is likely to + * start looking for an access point. This state is entered when a + * connection is lost. + */ + WPA_DISCONNECTED, + + /** + * WPA_INTERFACE_DISABLED - Interface disabled + * + * This state is entered if the network interface is disabled, e.g., + * due to rfkill. wpa_supplicant refuses any new operations that would + * use the radio until the interface has been enabled. + */ + WPA_INTERFACE_DISABLED, + + /** + * WPA_INACTIVE - Inactive state (wpa_supplicant disabled) + * + * This state is entered if there are no enabled networks in the + * configuration. wpa_supplicant is not trying to associate with a new + * network and external interaction (e.g., ctrl_iface call to add or + * enable a network) is needed to start association. + */ + WPA_INACTIVE, + + /** + * WPA_SCANNING - Scanning for a network + * + * This state is entered when wpa_supplicant starts scanning for a + * network. + */ + WPA_SCANNING, + + /** + * WPA_AUTHENTICATING - Trying to authenticate with a BSS/SSID + * + * This state is entered when wpa_supplicant has found a suitable BSS + * to authenticate with and the driver is configured to try to + * authenticate with this BSS. This state is used only with drivers + * that use wpa_supplicant as the SME. + */ + WPA_AUTHENTICATING, + + /** + * WPA_ASSOCIATING - Trying to associate with a BSS/SSID + * + * This state is entered when wpa_supplicant has found a suitable BSS + * to associate with and the driver is configured to try to associate + * with this BSS in ap_scan=1 mode. When using ap_scan=2 mode, this + * state is entered when the driver is configured to try to associate + * with a network using the configured SSID and security policy. + */ + WPA_ASSOCIATING, + + /** + * WPA_ASSOCIATED - Association completed + * + * This state is entered when the driver reports that association has + * been successfully completed with an AP. If IEEE 802.1X is used + * (with or without WPA/WPA2), wpa_supplicant remains in this state + * until the IEEE 802.1X/EAPOL authentication has been completed. + */ + WPA_ASSOCIATED, + + /** + * WPA_4WAY_HANDSHAKE - WPA 4-Way Key Handshake in progress + * + * This state is entered when WPA/WPA2 4-Way Handshake is started. In + * case of WPA-PSK, this happens when receiving the first EAPOL-Key + * frame after association. In case of WPA-EAP, this state is entered + * when the IEEE 802.1X/EAPOL authentication has been completed. + */ + WPA_4WAY_HANDSHAKE, + + /** + * WPA_GROUP_HANDSHAKE - WPA Group Key Handshake in progress + * + * This state is entered when 4-Way Key Handshake has been completed + * (i.e., when the supplicant sends out message 4/4) and when Group + * Key rekeying is started by the AP (i.e., when supplicant receives + * message 1/2). + */ + WPA_GROUP_HANDSHAKE, + + /** + * WPA_COMPLETED - All authentication completed + * + * This state is entered when the full authentication process is + * completed. In case of WPA2, this happens when the 4-Way Handshake is + * successfully completed. With WPA, this state is entered after the + * Group Key Handshake; with IEEE 802.1X (non-WPA) connection is + * completed after dynamic keys are received (or if not used, after + * the EAP authentication has been completed). With static WEP keys and + * plaintext connections, this state is entered when an association + * has been completed. + * + * This state indicates that the supplicant has completed its + * processing for the association phase and that data connection is + * fully configured. + */ + WPA_COMPLETED +}; + +#define MLME_SETPROTECTION_PROTECT_TYPE_NONE 0 +#define MLME_SETPROTECTION_PROTECT_TYPE_RX 1 +#define MLME_SETPROTECTION_PROTECT_TYPE_TX 2 +#define MLME_SETPROTECTION_PROTECT_TYPE_RX_TX 3 + +#define MLME_SETPROTECTION_KEY_TYPE_GROUP 0 +#define MLME_SETPROTECTION_KEY_TYPE_PAIRWISE 1 + + +/** + * enum mfp_options - Management frame protection (IEEE 802.11w) options + */ +enum mfp_options { + NO_MGMT_FRAME_PROTECTION = 0, + MGMT_FRAME_PROTECTION_OPTIONAL = 1, + MGMT_FRAME_PROTECTION_REQUIRED = 2, +}; +#define MGMT_FRAME_PROTECTION_DEFAULT 3 + +/** + * enum hostapd_hw_mode - Hardware mode + */ +enum hostapd_hw_mode { + HOSTAPD_MODE_IEEE80211B, + HOSTAPD_MODE_IEEE80211G, + HOSTAPD_MODE_IEEE80211A, + HOSTAPD_MODE_IEEE80211AD, + HOSTAPD_MODE_IEEE80211ANY, + NUM_HOSTAPD_MODES +}; + +/** + * enum wpa_ctrl_req_type - Control interface request types + */ +enum wpa_ctrl_req_type { + WPA_CTRL_REQ_UNKNOWN, + WPA_CTRL_REQ_EAP_IDENTITY, + WPA_CTRL_REQ_EAP_PASSWORD, + WPA_CTRL_REQ_EAP_NEW_PASSWORD, + WPA_CTRL_REQ_EAP_PIN, + WPA_CTRL_REQ_EAP_OTP, + WPA_CTRL_REQ_EAP_PASSPHRASE, + WPA_CTRL_REQ_SIM, + WPA_CTRL_REQ_PSK_PASSPHRASE, + WPA_CTRL_REQ_EXT_CERT_CHECK, + NUM_WPA_CTRL_REQS +}; + +/* Maximum number of EAP methods to store for EAP server user information */ +#define EAP_MAX_METHODS 8 + +enum mesh_plink_state { + PLINK_IDLE = 1, + PLINK_OPN_SNT, + PLINK_OPN_RCVD, + PLINK_CNF_RCVD, + PLINK_ESTAB, + PLINK_HOLDING, + PLINK_BLOCKED, /* not defined in the IEEE 802.11 standard */ +}; + +enum set_band { + WPA_SETBAND_AUTO = 0, + WPA_SETBAND_5G = BIT(0), + WPA_SETBAND_2G = BIT(1), + WPA_SETBAND_6G = BIT(2), +}; + +enum wpa_radio_work_band { + BAND_2_4_GHZ = BIT(0), + BAND_5_GHZ = BIT(1), + BAND_60_GHZ = BIT(2), +}; + +enum beacon_rate_type { + BEACON_RATE_LEGACY, + BEACON_RATE_HT, + BEACON_RATE_VHT, + BEACON_RATE_HE +}; + +enum eap_proxy_sim_state { + SIM_STATE_ERROR, +}; + +#define OCE_STA BIT(0) +#define OCE_STA_CFON BIT(1) +#define OCE_AP BIT(2) + +/* enum chan_width - Channel width definitions */ +enum chan_width { + CHAN_WIDTH_20_NOHT, + CHAN_WIDTH_20, + CHAN_WIDTH_40, + CHAN_WIDTH_80, + CHAN_WIDTH_80P80, + CHAN_WIDTH_160, + CHAN_WIDTH_2160, + CHAN_WIDTH_4320, + CHAN_WIDTH_6480, + CHAN_WIDTH_8640, + CHAN_WIDTH_320, + CHAN_WIDTH_UNKNOWN +}; + +/* VHT/EDMG/etc. channel widths + * Note: The first four values are used in hostapd.conf and as such, must + * maintain their defined values. Other values are used internally. */ +enum oper_chan_width { + CONF_OPER_CHWIDTH_USE_HT = 0, + CONF_OPER_CHWIDTH_80MHZ = 1, + CONF_OPER_CHWIDTH_160MHZ = 2, + CONF_OPER_CHWIDTH_80P80MHZ = 3, + CONF_OPER_CHWIDTH_2160MHZ, + CONF_OPER_CHWIDTH_4320MHZ, + CONF_OPER_CHWIDTH_6480MHZ, + CONF_OPER_CHWIDTH_8640MHZ, + CONF_OPER_CHWIDTH_40MHZ_6GHZ, + CONF_OPER_CHWIDTH_320MHZ, +}; + +enum key_flag { + KEY_FLAG_MODIFY = BIT(0), + KEY_FLAG_DEFAULT = BIT(1), + KEY_FLAG_RX = BIT(2), + KEY_FLAG_TX = BIT(3), + KEY_FLAG_GROUP = BIT(4), + KEY_FLAG_PAIRWISE = BIT(5), + KEY_FLAG_PMK = BIT(6), + /* Used flag combinations */ + KEY_FLAG_RX_TX = KEY_FLAG_RX | KEY_FLAG_TX, + KEY_FLAG_GROUP_RX_TX = KEY_FLAG_GROUP | KEY_FLAG_RX_TX, + KEY_FLAG_GROUP_RX_TX_DEFAULT = KEY_FLAG_GROUP_RX_TX | + KEY_FLAG_DEFAULT, + KEY_FLAG_GROUP_RX = KEY_FLAG_GROUP | KEY_FLAG_RX, + KEY_FLAG_GROUP_TX_DEFAULT = KEY_FLAG_GROUP | KEY_FLAG_TX | + KEY_FLAG_DEFAULT, + KEY_FLAG_PAIRWISE_RX_TX = KEY_FLAG_PAIRWISE | KEY_FLAG_RX_TX, + KEY_FLAG_PAIRWISE_RX = KEY_FLAG_PAIRWISE | KEY_FLAG_RX, + KEY_FLAG_PAIRWISE_RX_TX_MODIFY = KEY_FLAG_PAIRWISE_RX_TX | + KEY_FLAG_MODIFY, + /* Max allowed flags for each key type */ + KEY_FLAG_PAIRWISE_MASK = KEY_FLAG_PAIRWISE_RX_TX_MODIFY, + KEY_FLAG_GROUP_MASK = KEY_FLAG_GROUP_RX_TX_DEFAULT, + KEY_FLAG_PMK_MASK = KEY_FLAG_PMK, +}; + +static inline int check_key_flag(enum key_flag key_flag) +{ + return !!(!key_flag || + ((key_flag & (KEY_FLAG_PAIRWISE | KEY_FLAG_MODIFY)) && + (key_flag & ~KEY_FLAG_PAIRWISE_MASK)) || + ((key_flag & KEY_FLAG_GROUP) && + (key_flag & ~KEY_FLAG_GROUP_MASK)) || + ((key_flag & KEY_FLAG_PMK) && + (key_flag & ~KEY_FLAG_PMK_MASK))); +} + +enum ptk0_rekey_handling { + PTK0_REKEY_ALLOW_ALWAYS, + PTK0_REKEY_ALLOW_LOCAL_OK, + PTK0_REKEY_ALLOW_NEVER +}; + +enum frame_encryption { + FRAME_ENCRYPTION_UNKNOWN = -1, + FRAME_NOT_ENCRYPTED = 0, + FRAME_ENCRYPTED = 1 +}; + +#define MAX_NUM_MLD_LINKS 15 + +enum sae_pwe { + SAE_PWE_HUNT_AND_PECK = 0, + SAE_PWE_HASH_TO_ELEMENT = 1, + SAE_PWE_BOTH = 2, + SAE_PWE_FORCE_HUNT_AND_PECK = 3, + SAE_PWE_NOT_SET = 4, +}; + +#endif /* DEFS_H */ diff --git a/src/common/dhcp.h b/src/common/dhcp.h new file mode 100644 index 0000000..7dc67d5 --- /dev/null +++ b/src/common/dhcp.h @@ -0,0 +1,263 @@ +/* + * DHCP definitions + * Copyright (c) 2014-2017, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef DHCP_H +#define DHCP_H + +#include +#if __FAVOR_BSD +#include +#else +#define __FAVOR_BSD 1 +#include +#undef __FAVOR_BSD +#endif + +#define DHCP_SERVER_PORT 67 +#define DHCP_CLIENT_PORT 68 + +struct dhcp_data { + u8 op; + u8 htype; + u8 hlen; + u8 hops; + be32 xid; + be16 secs; + be16 flags; + be32 client_ip; + be32 your_ip; + be32 server_ip; + be32 relay_ip; + u8 hw_addr[16]; + u8 serv_name[64]; + u8 boot_file[128]; +} STRUCT_PACKED; + +struct bootp_pkt { + struct ip iph; + struct udphdr udph; + u8 op; + u8 htype; + u8 hlen; + u8 hops; + be32 xid; + be16 secs; + be16 flags; + be32 client_ip; + be32 your_ip; + be32 server_ip; + be32 relay_ip; + u8 hw_addr[16]; + u8 serv_name[64]; + u8 boot_file[128]; + u8 exten[312]; +} STRUCT_PACKED; + +#define DHCP_MAGIC 0x63825363 + +/* + * IANA DHCP/BOOTP registry + * http://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml +*/ +enum dhcp_options { + DHCP_OPT_PAD = 0, + DHCP_OPT_SUBNET_MASK = 1, + DHCP_OPT_TIME_OFFSET = 2, + DHCP_OPT_ROUTER = 3, + DHCP_OPT_TIME_SERVER = 4, + DHCP_OPT_NAME_SERVER = 5, + DHCP_OPT_DOMAIN_NAME_SERVER = 6, + DHCP_OPT_LOG_SERVER = 7, + DHCP_OPT_QUOTES_SERVER = 8, + DHCP_OPT_LPR_SERVER = 9, + DHCP_OPT_IMPRESS_SERVER = 10, + DHCP_OPT_RLP_SERVER = 11, + DHCP_OPT_HOSTNAME = 12, + DHCP_OPT_BOOT_FILE_SIZE = 13, + DHCP_OPT_MERIT_DUMP_FILE = 14, + DHCP_OPT_DOMAIN_NAME = 15, + DHCP_OPT_SWAP_SERVER = 16, + DHCP_OPT_ROOT_PATH = 17, + DHCP_OPT_EXTENSION_PATH = 18, + DHCP_OPT_FORWARD = 19, + DHCP_OPT_SRC_RTE = 20, + DHCP_OPT_POLICY_FILTER = 21, + DHCP_OPT_MAX_DG_ASSEMBLY = 22, + DHCP_OPT_DEFAULT_IP_TTL = 23, + DHCP_OPT_MTU_TIMEOUT = 24, + DHCP_OPT_MTU_PLATEAU = 25, + DHCP_OPT_MTU_INTERFACE = 26, + DHCP_OPT_ALL_SUBNETS_LOCAL = 27, + DHCP_OPT_BROADCAST_ADDRESS = 28, + DHCP_OPT_MASK_DISCOVERY = 29, + DHCP_OPT_MASK_SUPPLIER = 30, + DHCP_OPT_ROUTER_DISCOVERY = 31, + DHCP_OPT_ROUTER_SOLICITATION_ADDRESS = 32, + DHCP_OPT_STATIC_ROUTE = 33, + DHCP_OPT_TRAILERS = 34, + DHCP_OPT_ARP_TIMEOUT = 35, + DHCP_OPT_ETHERNET = 36, + DHCP_OPT_TCP_DEFAULT_TTL = 37, + DHCP_OPT_TCP_KEEPALIVE_INTERVAL = 38, + DHCP_OPT_TCP_KEEPALIVE_GARBAGE = 39, + DHCP_OPT_NIS_DOMAIN = 40, + DHCP_OPT_NIS_SERVERS = 41, + DHCP_OPT_NTP_SERVERS = 42, + DHCP_OPT_VENDOR_SPECIFIC = 43, + DHCP_OPT_NETBIOS_NAME_SERVER = 44, + DHCP_OPT_NETBIOS_DISTRIBUTION_SERVER = 45, + DHCP_OPT_NETBIOS_NODE_TYPE = 46, + DHCP_OPT_NETBIOS_SCOPE = 47, + DHCP_OPT_FONT_SERVER = 48, + DHCP_OPT_DISPLAY_MANAGER = 49, + DHCP_OPT_REQUESTED_IP_ADDRESS = 50, + DHCP_OPT_IP_ADDRESS_LEASE_TIME = 51, + DHCP_OPT_OVERLOAD = 52, + DHCP_OPT_MSG_TYPE = 53, + DHCP_OPT_SERVER_ID = 54, + DHCP_OPT_PARAMETER_REQ_LIST = 55, + DHCP_OPT_MESSAGE = 56, + DHCP_OPT_MAX_MESSAGE_SIZE = 57, + DHCP_OPT_RENEWAL_TIME = 58, + DHCP_OPT_REBINDING_TIME = 59, + DHCP_OPT_VENDOR_CLASS_ID = 60, + DHCP_OPT_CLIENT_ID = 61, + DHCP_OPT_NETWARE_IP_DOMAIN = 62, + DHCP_OPT_NETWARE_IP_OPTION = 63, + DHCP_OPT_NIS_V3_DOMAIN = 64, + DHCP_OPT_NIS_V3_SERVERS = 65, + DHCP_OPT_TFTP_SERVER_NAME = 66, + DHCP_OPT_BOOT_FILE_NAME = 67, + DHCP_OPT_HOME_AGENT_ADDRESSES = 68, + DHCP_OPT_SMTP_SERVER = 69, + DHCP_OPT_POP3_SERVER = 70, + DHCP_OPT_NNTP_SERVER = 71, + DHCP_OPT_WWW_SERVER = 72, + DHCP_OPT_FINGER_SERVER = 73, + DHCP_OPT_IRC_SERVER = 74, + DHCP_OPT_STREETTALK_SERVER = 75, + DHCP_OPT_STDA_SERVER = 76, + DHCP_OPT_USER_CLASS = 77, + DHCP_OPT_DIRECTORY_AGENT = 78, + DHCP_OPT_SERVICE_SCOPE = 79, + DHCP_OPT_RAPID_COMMIT = 80, + DHCP_OPT_CLIENT_FQDN = 81, + DHCP_OPT_RELAY_AGENT_INFO = 82, + DHCP_OPT_ISNS = 83, + DHCP_OPT_NDS_SERVERS = 85, + DHCP_OPT_NDS_TREE_NAME = 86, + DHCP_OPT_NDS_CONTEXT = 87, + DHCP_OPT_BCMCS_CONTROLLER_DOMAIN_NAME_LIST = 88, + DHCP_OPT_BCMCS_CONTROLLER_IPV4_ADDRESS = 89, + DHCP_OPT_AUTHENTICATION = 90, + DHCP_OPT_CLIENT_LAST_TRANSACTION_TIME = 91, + DHCP_OPT_ASSOCIATED_IP = 92, + DHCP_OPT_CLIENT_SYSYEM = 93, + DHCP_OPT_CLIENT_NDI = 94, + DHCP_OPT_LDAP = 95, + DHCP_OPT_UUID_GUID = 97, + DHCP_OPT_USER_AUTH = 98, + DHCP_OPT_GEOCONF_CIVIC = 99, + DHCP_OPT_PCODE = 100, + DHCP_OPT_TCODE = 101, + DHCP_OPT_NETINFO_ADDRESS = 112, + DHCP_OPT_NETINFO_TAG = 113, + DHCP_OPT_URL = 114, + DHCP_OPT_AUTO_CONFIG = 116, + DHCP_OPT_NAME_SERVICE_SEARCH = 117, + DHCP_OPT_SUBNET_SELECTION = 118, + DHCP_OPT_DOMAIN_SEARCH = 119, + DHCP_OPT_SIP_SERVERS_DCP = 120, + DHCP_OPT_CLASSLESS_STATIC_ROUTE = 121, + DHCP_OPT_CCC = 122, + DHCP_OPT_GEOCONF = 123, + DHCP_OPT_V_I_VENDOR_CLASS = 124, + DHCP_OPT_V_I_VENDOR_SPECIFIC_INFO = 125, + DHCP_OPT_PANA_AGENT = 136, + DHCP_OPT_V4_LOST = 137, + DHCP_OPT_CAPWAP_AC_V4 = 138, + DHCP_OPT_IPV4_ADDRESS_MOS = 139, + DHCP_OPT_IPV4_FQDN_MOS = 140, + DHCP_OPT_SIP_UA_CONF = 141, + DHCP_OPT_IPV4_ADDRESS_ANDSF = 142, + DHCP_OPT_GEOLOC = 144, + DHCP_OPT_FORCERENEW_NONCE_CAPABLE = 145, + DHCP_OPT_RDNSS_SELECTION = 146, + DHCP_OPT_TFTP_SERVER_ADDRESS = 150, + DHCP_OPT_STATUS_CODE = 151, + DHCP_OPT_BASE_TIME = 152, + DHCP_OPT_START_TIME_OF_STATE = 153, + DHCP_OPT_QUERY_START_TIME = 154, + DHCP_OPT_QUERY_END_TIME = 155, + DHCP_OPT_STATE = 156, + DHCP_OPT_DATA_SOURCE = 157, + DHCP_OPT_V4_PCP_SERVER = 158, + DHCP_OPT_V4_PORTPARAMS = 159, + DHCP_OPT_CAPTIVE_PORTAL = 160, + DHCP_OPT_CONF_FILE = 209, + DHCP_OPT_PATH_PREFIX = 210, + DHCP_OPT_REBOOT_TIME = 211, + DHCP_OPT_6RD = 212, + DHCP_OPT_V4_ACCESS_DOMAIN = 213, + DHCP_OPT_SUBNET_ALLOCATION = 220, + DHCP_OPT_VSS = 221, + DHCP_OPT_END = 255 +}; + +enum dhcp_message_types { + DHCPDISCOVER = 1, + DHCPOFFER = 2, + DHCPREQUEST = 3, + DHCPDECLINE = 4, + DHCPACK = 5, + DHCPNAK = 6, + DHCPRELEASE = 7, + DHCPINFORM = 8, + DHCPFORCERENEW = 9, + DHCPLEASEQUERY = 10, + DHCPLEASEUNASSIGNED = 11, + DHCPLEASEUNKNOWN = 12, + DHCPLEASEACTIVE = 13, + DHCPBULKLEASEQUERY = 14, + DHCPLEASEQUERYDONE = 15, + DHCPACTIVELEASEQUERY = 16, + DHCPLEASEQUERYSTATUS = 17, + DHCPTLS = 18, +}; + +enum dhcp_relay_agent_suboptions { + DHCP_RELAY_OPT_AGENT_CIRCUIT_ID = 1, + DHCP_RELAY_OPT_AGENT_REMOTE_ID = 2, + DHCP_RELAY_OPT_DOCSIS_DEVICE_CLASS = 4, + DHCP_RELAY_OPT_LINK_SELECTION = 5, + DHCP_RELAY_OPT_SUBSCRIBE_ID = 6, + DHCP_RELAY_OPT_RADIUS_ATTRIBUTES = 7, + DHCP_RELAY_OPT_AUTHENTICATION = 8, + DHCP_RELAY_OPT_VEDOR_SPECIFIC = 9, + DHCP_RELAY_OPT_RELAY_AGENT_FLAGS = 10, + DHCP_RELAY_OPT_SERVER_ID_OVERRIDE = 11, + DHCP_RELAY_OPT_RELAY_AGENT_ID = 12, + DHCP_RELAY_OPT_ACCESS_TECHNOLOGY_TYPE = 13, + DHCP_RELAY_OPT_ACCESS_NETWORK_NAME = 14, + DHCP_RELAY_OPT_ACCESS_POINT_NAME = 15, + DHCP_RELAY_OPT_ACCESS_POINT_BSSID = 16, + DHCP_RELAY_OPT_OPERATOR_ID = 17, + DHCP_RELAY_OPT_OPERATOR_REALM = 18, + DHCP_RELAY_OPT_DHCPV4_VIRTUAL_SUBNET_SELECTION = 151, + DHCP_RELAY_OPT_DHCPV4_VIRTUAL_SUBNET_SELECTION_CONTROL = 152, +}; + +enum access_technology_types { + ACCESS_TECHNOLOGY_VIRTUAL = 1, + ACCESS_TECHNOLOGY_PPP = 2, + ACCESS_TECHNOLOGY_ETHERNET = 3, + ACCESS_TECHNOLOGY_WLAN = 4, + ACCESS_TECHNOLOGY_WIMAX = 5, +}; + +#endif /* DHCP_H */ diff --git a/src/common/dpp.c b/src/common/dpp.c new file mode 100644 index 0000000..3b9f35e --- /dev/null +++ b/src/common/dpp.c @@ -0,0 +1,5190 @@ +/* + * DPP functionality shared between hostapd and wpa_supplicant + * Copyright (c) 2017, Qualcomm Atheros, Inc. + * Copyright (c) 2018-2020, The Linux Foundation + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "utils/base64.h" +#include "utils/json.h" +#include "utils/ip_addr.h" +#include "common/ieee802_11_common.h" +#include "common/wpa_ctrl.h" +#include "common/gas.h" +#include "eap_common/eap_defs.h" +#include "crypto/crypto.h" +#include "crypto/random.h" +#include "crypto/aes.h" +#include "crypto/aes_siv.h" +#include "drivers/driver.h" +#include "dpp.h" +#include "dpp_i.h" + + +#ifdef CONFIG_TESTING_OPTIONS +#ifdef CONFIG_DPP3 +int dpp_version_override = 3; +#elif defined(CONFIG_DPP2) +int dpp_version_override = 2; +#else +int dpp_version_override = 1; +#endif +enum dpp_test_behavior dpp_test = DPP_TEST_DISABLED; +#endif /* CONFIG_TESTING_OPTIONS */ + + +void dpp_auth_fail(struct dpp_authentication *auth, const char *txt) +{ + wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_FAIL "%s", txt); +} + + +struct wpabuf * dpp_alloc_msg(enum dpp_public_action_frame_type type, + size_t len) +{ + struct wpabuf *msg; + + msg = wpabuf_alloc(8 + len); + if (!msg) + return NULL; + wpabuf_put_u8(msg, WLAN_ACTION_PUBLIC); + wpabuf_put_u8(msg, WLAN_PA_VENDOR_SPECIFIC); + wpabuf_put_be24(msg, OUI_WFA); + wpabuf_put_u8(msg, DPP_OUI_TYPE); + wpabuf_put_u8(msg, 1); /* Crypto Suite */ + wpabuf_put_u8(msg, type); + return msg; +} + + +const u8 * dpp_get_attr(const u8 *buf, size_t len, u16 req_id, u16 *ret_len) +{ + u16 id, alen; + const u8 *pos = buf, *end = buf + len; + + while (end - pos >= 4) { + id = WPA_GET_LE16(pos); + pos += 2; + alen = WPA_GET_LE16(pos); + pos += 2; + if (alen > end - pos) + return NULL; + if (id == req_id) { + *ret_len = alen; + return pos; + } + pos += alen; + } + + return NULL; +} + + +static const u8 * dpp_get_attr_next(const u8 *prev, const u8 *buf, size_t len, + u16 req_id, u16 *ret_len) +{ + u16 id, alen; + const u8 *pos, *end = buf + len; + + if (!prev) + pos = buf; + else + pos = prev + WPA_GET_LE16(prev - 2); + while (end - pos >= 4) { + id = WPA_GET_LE16(pos); + pos += 2; + alen = WPA_GET_LE16(pos); + pos += 2; + if (alen > end - pos) + return NULL; + if (id == req_id) { + *ret_len = alen; + return pos; + } + pos += alen; + } + + return NULL; +} + + +int dpp_check_attrs(const u8 *buf, size_t len) +{ + const u8 *pos, *end; + int wrapped_data = 0; + + pos = buf; + end = buf + len; + while (end - pos >= 4) { + u16 id, alen; + + id = WPA_GET_LE16(pos); + pos += 2; + alen = WPA_GET_LE16(pos); + pos += 2; + wpa_printf(MSG_MSGDUMP, "DPP: Attribute ID %04x len %u", + id, alen); + if (alen > end - pos) { + wpa_printf(MSG_DEBUG, + "DPP: Truncated message - not enough room for the attribute - dropped"); + return -1; + } + if (wrapped_data) { + wpa_printf(MSG_DEBUG, + "DPP: An unexpected attribute included after the Wrapped Data attribute"); + return -1; + } + if (id == DPP_ATTR_WRAPPED_DATA) + wrapped_data = 1; + pos += alen; + } + + if (end != pos) { + wpa_printf(MSG_DEBUG, + "DPP: Unexpected octets (%d) after the last attribute", + (int) (end - pos)); + return -1; + } + + return 0; +} + + +void dpp_bootstrap_info_free(struct dpp_bootstrap_info *info) +{ + if (!info) + return; + os_free(info->uri); + os_free(info->info); + os_free(info->chan); + os_free(info->host); + os_free(info->pk); + crypto_ec_key_deinit(info->pubkey); + str_clear_free(info->configurator_params); + os_free(info); +} + + +const char * dpp_bootstrap_type_txt(enum dpp_bootstrap_type type) +{ + switch (type) { + case DPP_BOOTSTRAP_QR_CODE: + return "QRCODE"; + case DPP_BOOTSTRAP_PKEX: + return "PKEX"; + case DPP_BOOTSTRAP_NFC_URI: + return "NFC-URI"; + } + return "??"; +} + + +static int dpp_uri_valid_info(const char *info) +{ + while (*info) { + unsigned char val = *info++; + + if (val < 0x20 || val > 0x7e || val == 0x3b) + return 0; + } + + return 1; +} + + +static int dpp_clone_uri(struct dpp_bootstrap_info *bi, const char *uri) +{ + bi->uri = os_strdup(uri); + return bi->uri ? 0 : -1; +} + + +int dpp_parse_uri_chan_list(struct dpp_bootstrap_info *bi, + const char *chan_list) +{ + const char *pos = chan_list, *pos2; + int opclass = -1, channel, freq; + + while (pos && *pos && *pos != ';') { + pos2 = pos; + while (*pos2 >= '0' && *pos2 <= '9') + pos2++; + if (*pos2 == '/') { + opclass = atoi(pos); + pos = pos2 + 1; + } + if (opclass <= 0) + goto fail; + channel = atoi(pos); + if (channel <= 0) + goto fail; + while (*pos >= '0' && *pos <= '9') + pos++; + freq = ieee80211_chan_to_freq(NULL, opclass, channel); + wpa_printf(MSG_DEBUG, + "DPP: URI channel-list: opclass=%d channel=%d ==> freq=%d", + opclass, channel, freq); + bi->channels_listed = true; + if (freq < 0) { + wpa_printf(MSG_DEBUG, + "DPP: Ignore unknown URI channel-list channel (opclass=%d channel=%d)", + opclass, channel); + } else if (bi->num_freq == DPP_BOOTSTRAP_MAX_FREQ) { + wpa_printf(MSG_DEBUG, + "DPP: Too many channels in URI channel-list - ignore list"); + bi->num_freq = 0; + break; + } else { + bi->freq[bi->num_freq++] = freq; + } + + if (*pos == ';' || *pos == '\0') + break; + if (*pos != ',') + goto fail; + pos++; + } + + return 0; +fail: + wpa_printf(MSG_DEBUG, "DPP: Invalid URI channel-list"); + return -1; +} + + +int dpp_parse_uri_mac(struct dpp_bootstrap_info *bi, const char *mac) +{ + if (!mac) + return 0; + + if (hwaddr_aton2(mac, bi->mac_addr) < 0) { + wpa_printf(MSG_DEBUG, "DPP: Invalid URI mac"); + return -1; + } + + wpa_printf(MSG_DEBUG, "DPP: URI mac: " MACSTR, MAC2STR(bi->mac_addr)); + + return 0; +} + + +int dpp_parse_uri_info(struct dpp_bootstrap_info *bi, const char *info) +{ + const char *end; + + if (!info) + return 0; + + end = os_strchr(info, ';'); + if (!end) + end = info + os_strlen(info); + bi->info = os_malloc(end - info + 1); + if (!bi->info) + return -1; + os_memcpy(bi->info, info, end - info); + bi->info[end - info] = '\0'; + wpa_printf(MSG_DEBUG, "DPP: URI(information): %s", bi->info); + if (!dpp_uri_valid_info(bi->info)) { + wpa_printf(MSG_DEBUG, "DPP: Invalid URI information payload"); + return -1; + } + + return 0; +} + + +int dpp_parse_uri_version(struct dpp_bootstrap_info *bi, const char *version) +{ +#ifdef CONFIG_DPP2 + if (!version || DPP_VERSION < 2) + return 0; + + if (*version == '1') + bi->version = 1; + else if (*version == '2') + bi->version = 2; + else if (*version == '3') + bi->version = 3; + else + wpa_printf(MSG_DEBUG, "DPP: Unknown URI version"); + + wpa_printf(MSG_DEBUG, "DPP: URI version: %d", bi->version); +#endif /* CONFIG_DPP2 */ + + return 0; +} + + +static int dpp_parse_uri_pk(struct dpp_bootstrap_info *bi, const char *info) +{ + u8 *data; + size_t data_len; + int res; + const char *end; + + end = os_strchr(info, ';'); + if (!end) + return -1; + + data = base64_decode(info, end - info, &data_len); + if (!data) { + wpa_printf(MSG_DEBUG, + "DPP: Invalid base64 encoding on URI public-key"); + return -1; + } + wpa_hexdump(MSG_DEBUG, "DPP: Base64 decoded URI public-key", + data, data_len); + + res = dpp_get_subject_public_key(bi, data, data_len); + os_free(data); + return res; +} + + +static int dpp_parse_uri_supported_curves(struct dpp_bootstrap_info *bi, + const char *txt) +{ + int val; + + if (!txt) + return 0; + + val = hex2num(txt[0]); + if (val < 0) + return -1; + bi->supported_curves = val; + + val = hex2num(txt[1]); + if (val > 0) + bi->supported_curves |= val << 4; + + wpa_printf(MSG_DEBUG, "DPP: URI supported curves: 0x%x", + bi->supported_curves); + + return 0; +} + + +static int dpp_parse_uri_host(struct dpp_bootstrap_info *bi, const char *txt) +{ + const char *end; + char *port; + struct hostapd_ip_addr addr; + char buf[100], *pos; + + if (!txt) + return 0; + + end = os_strchr(txt, ';'); + if (!end) + end = txt + os_strlen(txt); + if (end - txt > (int) sizeof(buf) - 1) + return -1; + os_memcpy(buf, txt, end - txt); + buf[end - txt] = '\0'; + + bi->port = DPP_TCP_PORT; + + pos = buf; + if (*pos == '[') { + pos = &buf[1]; + port = os_strchr(pos, ']'); + if (!port) + return -1; + *port++ = '\0'; + if (*port == ':') + bi->port = atoi(port + 1); + } + + if (hostapd_parse_ip_addr(pos, &addr) < 0) { + if (buf[0] != '[') { + port = os_strrchr(pos, ':'); + if (port) { + *port++ = '\0'; + bi->port = atoi(port); + } + } + if (hostapd_parse_ip_addr(pos, &addr) < 0) { + wpa_printf(MSG_INFO, + "DPP: Invalid IP address in URI host entry: %s", + pos); + return -1; + } + } + os_free(bi->host); + bi->host = os_memdup(&addr, sizeof(addr)); + if (!bi->host) + return -1; + + wpa_printf(MSG_DEBUG, "DPP: host: %s port: %u", + hostapd_ip_txt(bi->host, buf, sizeof(buf)), bi->port); + + return 0; +} + + +static struct dpp_bootstrap_info * dpp_parse_uri(const char *uri) +{ + const char *pos = uri; + const char *end; + const char *chan_list = NULL, *mac = NULL, *info = NULL, *pk = NULL; + const char *version = NULL, *supported_curves = NULL, *host = NULL; + struct dpp_bootstrap_info *bi; + + wpa_hexdump_ascii(MSG_DEBUG, "DPP: URI", uri, os_strlen(uri)); + + if (os_strncmp(pos, "DPP:", 4) != 0) { + wpa_printf(MSG_INFO, "DPP: Not a DPP URI"); + return NULL; + } + pos += 4; + + for (;;) { + end = os_strchr(pos, ';'); + if (!end) + break; + + if (end == pos) { + /* Handle terminating ";;" and ignore unexpected ";" + * for parsing robustness. */ + pos++; + continue; + } + + if (pos[0] == 'C' && pos[1] == ':' && !chan_list) + chan_list = pos + 2; + else if (pos[0] == 'M' && pos[1] == ':' && !mac) + mac = pos + 2; + else if (pos[0] == 'I' && pos[1] == ':' && !info) + info = pos + 2; + else if (pos[0] == 'K' && pos[1] == ':' && !pk) + pk = pos + 2; + else if (pos[0] == 'V' && pos[1] == ':' && !version) + version = pos + 2; + else if (pos[0] == 'B' && pos[1] == ':' && !supported_curves) + supported_curves = pos + 2; + else if (pos[0] == 'H' && pos[1] == ':' && !host) + host = pos + 2; + else + wpa_hexdump_ascii(MSG_DEBUG, + "DPP: Ignore unrecognized URI parameter", + pos, end - pos); + pos = end + 1; + } + + if (!pk) { + wpa_printf(MSG_INFO, "DPP: URI missing public-key"); + return NULL; + } + + bi = os_zalloc(sizeof(*bi)); + if (!bi) + return NULL; + + if (dpp_clone_uri(bi, uri) < 0 || + dpp_parse_uri_chan_list(bi, chan_list) < 0 || + dpp_parse_uri_mac(bi, mac) < 0 || + dpp_parse_uri_info(bi, info) < 0 || + dpp_parse_uri_version(bi, version) < 0 || + dpp_parse_uri_supported_curves(bi, supported_curves) < 0 || + dpp_parse_uri_host(bi, host) < 0 || + dpp_parse_uri_pk(bi, pk) < 0) { + dpp_bootstrap_info_free(bi); + bi = NULL; + } + + return bi; +} + + +void dpp_build_attr_status(struct wpabuf *msg, enum dpp_status_error status) +{ + wpa_printf(MSG_DEBUG, "DPP: Status %d", status); + wpabuf_put_le16(msg, DPP_ATTR_STATUS); + wpabuf_put_le16(msg, 1); + wpabuf_put_u8(msg, status); +} + + +void dpp_build_attr_r_bootstrap_key_hash(struct wpabuf *msg, const u8 *hash) +{ + if (hash) { + wpa_printf(MSG_DEBUG, "DPP: R-Bootstrap Key Hash"); + wpabuf_put_le16(msg, DPP_ATTR_R_BOOTSTRAP_KEY_HASH); + wpabuf_put_le16(msg, SHA256_MAC_LEN); + wpabuf_put_data(msg, hash, SHA256_MAC_LEN); + } +} + + +static int dpp_channel_ok_init(struct hostapd_hw_modes *own_modes, + u16 num_modes, unsigned int freq) +{ + u16 m; + int c, flag; + + if (!own_modes || !num_modes) + return 1; + + for (m = 0; m < num_modes; m++) { + for (c = 0; c < own_modes[m].num_channels; c++) { + if ((unsigned int) own_modes[m].channels[c].freq != + freq) + continue; + flag = own_modes[m].channels[c].flag; + if (!(flag & (HOSTAPD_CHAN_DISABLED | + HOSTAPD_CHAN_NO_IR | + HOSTAPD_CHAN_RADAR))) + return 1; + } + } + + wpa_printf(MSG_DEBUG, "DPP: Peer channel %u MHz not supported", freq); + return 0; +} + + +static int freq_included(const unsigned int freqs[], unsigned int num, + unsigned int freq) +{ + while (num > 0) { + if (freqs[--num] == freq) + return 1; + } + return 0; +} + + +static void freq_to_start(unsigned int freqs[], unsigned int num, + unsigned int freq) +{ + unsigned int i; + + for (i = 0; i < num; i++) { + if (freqs[i] == freq) + break; + } + if (i == 0 || i >= num) + return; + os_memmove(&freqs[1], &freqs[0], i * sizeof(freqs[0])); + freqs[0] = freq; +} + + +static int dpp_channel_intersect(struct dpp_authentication *auth, + struct hostapd_hw_modes *own_modes, + u16 num_modes) +{ + struct dpp_bootstrap_info *peer_bi = auth->peer_bi; + unsigned int i, freq; + + for (i = 0; i < peer_bi->num_freq; i++) { + freq = peer_bi->freq[i]; + if (freq_included(auth->freq, auth->num_freq, freq)) + continue; + if (dpp_channel_ok_init(own_modes, num_modes, freq)) + auth->freq[auth->num_freq++] = freq; + } + if (!auth->num_freq) { + wpa_printf(MSG_INFO, + "DPP: No available channels for initiating DPP Authentication"); + return -1; + } + auth->curr_freq = auth->freq[0]; + return 0; +} + + +static int dpp_channel_local_list(struct dpp_authentication *auth, + struct hostapd_hw_modes *own_modes, + u16 num_modes) +{ + u16 m; + int c, flag; + unsigned int freq; + + auth->num_freq = 0; + + if (!own_modes || !num_modes) { + auth->freq[0] = 2412; + auth->freq[1] = 2437; + auth->freq[2] = 2462; + auth->num_freq = 3; + return 0; + } + + for (m = 0; m < num_modes; m++) { + for (c = 0; c < own_modes[m].num_channels; c++) { + freq = own_modes[m].channels[c].freq; + flag = own_modes[m].channels[c].flag; + if (flag & (HOSTAPD_CHAN_DISABLED | + HOSTAPD_CHAN_NO_IR | + HOSTAPD_CHAN_RADAR)) + continue; + if (freq_included(auth->freq, auth->num_freq, freq)) + continue; + auth->freq[auth->num_freq++] = freq; + if (auth->num_freq == DPP_BOOTSTRAP_MAX_FREQ) { + m = num_modes; + break; + } + } + } + + return auth->num_freq == 0 ? -1 : 0; +} + + +int dpp_prepare_channel_list(struct dpp_authentication *auth, + unsigned int neg_freq, + struct hostapd_hw_modes *own_modes, u16 num_modes) +{ + int res; + char freqs[DPP_BOOTSTRAP_MAX_FREQ * 6 + 10], *pos, *end; + unsigned int i; + + if (!own_modes) { + if (!neg_freq) + return -1; + auth->num_freq = 1; + auth->freq[0] = neg_freq; + auth->curr_freq = neg_freq; + return 0; + } + + if (auth->peer_bi->num_freq > 0) + res = dpp_channel_intersect(auth, own_modes, num_modes); + else + res = dpp_channel_local_list(auth, own_modes, num_modes); + if (res < 0) + return res; + + /* Prioritize 2.4 GHz channels 6, 1, 11 (in this order) to hit the most + * likely channels first. */ + freq_to_start(auth->freq, auth->num_freq, 2462); + freq_to_start(auth->freq, auth->num_freq, 2412); + freq_to_start(auth->freq, auth->num_freq, 2437); + + auth->freq_idx = 0; + auth->curr_freq = auth->freq[0]; + + pos = freqs; + end = pos + sizeof(freqs); + for (i = 0; i < auth->num_freq; i++) { + res = os_snprintf(pos, end - pos, " %u", auth->freq[i]); + if (os_snprintf_error(end - pos, res)) + break; + pos += res; + } + *pos = '\0'; + wpa_printf(MSG_DEBUG, "DPP: Possible frequencies for initiating:%s", + freqs); + + return 0; +} + + +int dpp_gen_uri(struct dpp_bootstrap_info *bi) +{ + char macstr[ETH_ALEN * 2 + 10]; + size_t len; + char supp_curves[10]; + char host[100]; + + len = 4; /* "DPP:" */ + if (bi->chan) + len += 3 + os_strlen(bi->chan); /* C:...; */ + if (is_zero_ether_addr(bi->mac_addr)) + macstr[0] = '\0'; + else + os_snprintf(macstr, sizeof(macstr), "M:" COMPACT_MACSTR ";", + MAC2STR(bi->mac_addr)); + len += os_strlen(macstr); /* M:...; */ + if (bi->info) + len += 3 + os_strlen(bi->info); /* I:...; */ +#ifdef CONFIG_DPP2 + len += 4; /* V:2; */ +#endif /* CONFIG_DPP2 */ + len += 4 + os_strlen(bi->pk); /* K:...;; */ + + if (bi->supported_curves) { + u8 val = bi->supported_curves; + + if (val & 0xf0) { + val = ((val & 0xf0) >> 4) | ((val & 0x0f) << 4); + len += os_snprintf(supp_curves, sizeof(supp_curves), + "B:%02x;", val); + } else { + len += os_snprintf(supp_curves, sizeof(supp_curves), + "B:%x;", val); + } + } else { + supp_curves[0] = '\0'; + } + + host[0] = '\0'; + if (bi->host) { + char buf[100]; + const char *addr; + + addr = hostapd_ip_txt(bi->host, buf, sizeof(buf)); + if (!addr) + return -1; + if (bi->port == DPP_TCP_PORT) + len += os_snprintf(host, sizeof(host), "H:%s;", addr); + else if (bi->host->af == AF_INET) + len += os_snprintf(host, sizeof(host), "H:%s:%u;", + addr, bi->port); + else + len += os_snprintf(host, sizeof(host), "H:[%s]:%u;", + addr, bi->port); + } + + os_free(bi->uri); + bi->uri = os_malloc(len + 1); + if (!bi->uri) + return -1; + os_snprintf(bi->uri, len + 1, "DPP:%s%s%s%s%s%s%s%s%s%sK:%s;;", + bi->chan ? "C:" : "", bi->chan ? bi->chan : "", + bi->chan ? ";" : "", + macstr, + bi->info ? "I:" : "", bi->info ? bi->info : "", + bi->info ? ";" : "", + DPP_VERSION == 3 ? "V:3;" : + (DPP_VERSION == 2 ? "V:2;" : ""), + supp_curves, + host, + bi->pk); + return 0; +} + + +struct dpp_authentication * +dpp_alloc_auth(struct dpp_global *dpp, void *msg_ctx) +{ + struct dpp_authentication *auth; + + auth = os_zalloc(sizeof(*auth)); + if (!auth) + return NULL; + auth->global = dpp; + auth->msg_ctx = msg_ctx; + auth->conf_resp_status = 255; + return auth; +} + + +static struct wpabuf * dpp_build_conf_req_attr(struct dpp_authentication *auth, + const char *json) +{ + size_t nonce_len; + size_t json_len, clear_len; + struct wpabuf *clear = NULL, *msg = NULL, *pe = NULL; + u8 *wrapped; + size_t attr_len; +#ifdef CONFIG_DPP3 + u8 auth_i[DPP_MAX_HASH_LEN]; +#endif /* CONFIG_DPP3 */ + + wpa_printf(MSG_DEBUG, "DPP: Build configuration request"); + + nonce_len = auth->curve->nonce_len; + if (random_get_bytes(auth->e_nonce, nonce_len)) { + wpa_printf(MSG_ERROR, "DPP: Failed to generate E-nonce"); + goto fail; + } + wpa_hexdump(MSG_DEBUG, "DPP: E-nonce", auth->e_nonce, nonce_len); + json_len = os_strlen(json); + wpa_hexdump_ascii(MSG_DEBUG, "DPP: configRequest JSON", json, json_len); + + /* { E-nonce, configAttrib }ke */ + clear_len = 4 + nonce_len + 4 + json_len; +#ifdef CONFIG_DPP3 + if (auth->waiting_new_key) { + pe = crypto_ec_key_get_pubkey_point(auth->own_protocol_key, 0); + if (!pe) + goto fail; + clear_len += 4 + wpabuf_len(pe); + + if (dpp_derive_auth_i(auth, auth_i) < 0) + goto fail; + clear_len += 4 + auth->curve->hash_len; + } +#endif /* CONFIG_DPP3 */ + clear = wpabuf_alloc(clear_len); + attr_len = 4 + clear_len + AES_BLOCK_SIZE; +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_CONF_REQ) + attr_len += 5; +#endif /* CONFIG_TESTING_OPTIONS */ + msg = wpabuf_alloc(attr_len); + if (!clear || !msg) + goto fail; + +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_NO_E_NONCE_CONF_REQ) { + wpa_printf(MSG_INFO, "DPP: TESTING - no E-nonce"); + goto skip_e_nonce; + } + if (dpp_test == DPP_TEST_INVALID_E_NONCE_CONF_REQ) { + wpa_printf(MSG_INFO, "DPP: TESTING - invalid E-nonce"); + wpabuf_put_le16(clear, DPP_ATTR_ENROLLEE_NONCE); + wpabuf_put_le16(clear, nonce_len - 1); + wpabuf_put_data(clear, auth->e_nonce, nonce_len - 1); + goto skip_e_nonce; + } + if (dpp_test == DPP_TEST_NO_WRAPPED_DATA_CONF_REQ) { + wpa_printf(MSG_INFO, "DPP: TESTING - no Wrapped Data"); + goto skip_wrapped_data; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + /* E-nonce */ + wpabuf_put_le16(clear, DPP_ATTR_ENROLLEE_NONCE); + wpabuf_put_le16(clear, nonce_len); + wpabuf_put_data(clear, auth->e_nonce, nonce_len); + +#ifdef CONFIG_TESTING_OPTIONS +skip_e_nonce: + if (dpp_test == DPP_TEST_NO_CONFIG_ATTR_OBJ_CONF_REQ) { + wpa_printf(MSG_INFO, "DPP: TESTING - no configAttrib"); + goto skip_conf_attr_obj; + } +#endif /* CONFIG_TESTING_OPTIONS */ + +#ifdef CONFIG_DPP3 + if (pe) { + wpa_printf(MSG_DEBUG, "DPP: Pe"); + wpabuf_put_le16(clear, DPP_ATTR_I_PROTOCOL_KEY); + wpabuf_put_le16(clear, wpabuf_len(pe)); + wpabuf_put_buf(clear, pe); + } + if (auth->waiting_new_key) { + wpa_printf(MSG_DEBUG, "DPP: Initiator Authentication Tag"); + wpabuf_put_le16(clear, DPP_ATTR_I_AUTH_TAG); + wpabuf_put_le16(clear, auth->curve->hash_len); + wpabuf_put_data(clear, auth_i, auth->curve->hash_len); + } +#endif /* CONFIG_DPP3 */ + + /* configAttrib */ + wpabuf_put_le16(clear, DPP_ATTR_CONFIG_ATTR_OBJ); + wpabuf_put_le16(clear, json_len); + wpabuf_put_data(clear, json, json_len); + +#ifdef CONFIG_TESTING_OPTIONS +skip_conf_attr_obj: +#endif /* CONFIG_TESTING_OPTIONS */ + + wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA); + wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE); + wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE); + + /* No AES-SIV AD */ + wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear); + if (aes_siv_encrypt(auth->ke, auth->curve->hash_len, + wpabuf_head(clear), wpabuf_len(clear), + 0, NULL, NULL, wrapped) < 0) + goto fail; + wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext", + wrapped, wpabuf_len(clear) + AES_BLOCK_SIZE); + +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_CONF_REQ) { + wpa_printf(MSG_INFO, "DPP: TESTING - attr after Wrapped Data"); + dpp_build_attr_status(msg, DPP_STATUS_OK); + } +skip_wrapped_data: +#endif /* CONFIG_TESTING_OPTIONS */ + + wpa_hexdump_buf(MSG_DEBUG, + "DPP: Configuration Request frame attributes", msg); +out: + wpabuf_free(clear); + wpabuf_free(pe); + return msg; + +fail: + wpabuf_free(msg); + msg = NULL; + goto out; +} + + +void dpp_write_adv_proto(struct wpabuf *buf) +{ + /* Advertisement Protocol IE */ + wpabuf_put_u8(buf, WLAN_EID_ADV_PROTO); + wpabuf_put_u8(buf, 8); /* Length */ + wpabuf_put_u8(buf, 0x7f); + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); + wpabuf_put_u8(buf, 5); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, DPP_OUI_TYPE); + wpabuf_put_u8(buf, 0x01); +} + + +void dpp_write_gas_query(struct wpabuf *buf, struct wpabuf *query) +{ + /* GAS Query */ + wpabuf_put_le16(buf, wpabuf_len(query)); + wpabuf_put_buf(buf, query); +} + + +struct wpabuf * dpp_build_conf_req(struct dpp_authentication *auth, + const char *json) +{ + struct wpabuf *buf, *conf_req; + + conf_req = dpp_build_conf_req_attr(auth, json); + if (!conf_req) { + wpa_printf(MSG_DEBUG, + "DPP: No configuration request data available"); + return NULL; + } + + buf = gas_build_initial_req(0, 10 + 2 + wpabuf_len(conf_req)); + if (!buf) { + wpabuf_free(conf_req); + return NULL; + } + + dpp_write_adv_proto(buf); + dpp_write_gas_query(buf, conf_req); + wpabuf_free(conf_req); + wpa_hexdump_buf(MSG_MSGDUMP, "DPP: GAS Config Request", buf); + + return buf; +} + + +struct wpabuf * dpp_build_conf_req_helper(struct dpp_authentication *auth, + const char *name, + enum dpp_netrole netrole, + const char *mud_url, int *opclasses, + const char *extra_name, + const char *extra_value) +{ + size_t len, name_len; + const char *tech = "infra"; + const char *dpp_name; + struct wpabuf *buf = NULL, *json = NULL; + char *csr = NULL; + +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_INVALID_CONFIG_ATTR_OBJ_CONF_REQ) { + static const char *bogus_tech = "knfra"; + + wpa_printf(MSG_INFO, "DPP: TESTING - invalid Config Attr"); + tech = bogus_tech; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + dpp_name = name ? name : "Test"; + name_len = os_strlen(dpp_name); + + len = 100 + name_len * 6 + 1 + int_array_len(opclasses) * 4; + if (mud_url && mud_url[0]) + len += 10 + os_strlen(mud_url); + if (extra_name && extra_value && extra_name[0] && extra_value[0]) + len += 10 + os_strlen(extra_name) + os_strlen(extra_value); +#ifdef CONFIG_DPP2 + if (auth->csr) { + size_t csr_len; + + csr = base64_encode_no_lf(wpabuf_head(auth->csr), + wpabuf_len(auth->csr), &csr_len); + if (!csr) + goto fail; + len += 30 + csr_len; + } +#endif /* CONFIG_DPP2 */ + json = wpabuf_alloc(len); + if (!json) + goto fail; + + json_start_object(json, NULL); + if (json_add_string_escape(json, "name", dpp_name, name_len) < 0) + goto fail; + json_value_sep(json); + json_add_string(json, "wi-fi_tech", tech); + json_value_sep(json); + json_add_string(json, "netRole", dpp_netrole_str(netrole)); + if (mud_url && mud_url[0]) { + json_value_sep(json); + json_add_string(json, "mudurl", mud_url); + } + if (opclasses) { + int i; + + json_value_sep(json); + json_start_array(json, "bandSupport"); + for (i = 0; opclasses[i]; i++) + wpabuf_printf(json, "%s%u", i ? "," : "", opclasses[i]); + json_end_array(json); + } + if (csr) { + json_value_sep(json); + json_add_string(json, "pkcs10", csr); + } + if (extra_name && extra_value && extra_name[0] && extra_value[0]) { + json_value_sep(json); + wpabuf_printf(json, "\"%s\":%s", extra_name, extra_value); + } + json_end_object(json); + + buf = dpp_build_conf_req(auth, wpabuf_head(json)); +fail: + wpabuf_free(json); + os_free(csr); + + return buf; +} + + +static int bin_str_eq(const char *val, size_t len, const char *cmp) +{ + return os_strlen(cmp) == len && os_memcmp(val, cmp, len) == 0; +} + + +struct dpp_configuration * dpp_configuration_alloc(const char *type) +{ + struct dpp_configuration *conf; + const char *end; + size_t len; + + conf = os_zalloc(sizeof(*conf)); + if (!conf) + goto fail; + + end = os_strchr(type, ' '); + if (end) + len = end - type; + else + len = os_strlen(type); + + if (bin_str_eq(type, len, "psk")) + conf->akm = DPP_AKM_PSK; + else if (bin_str_eq(type, len, "sae")) + conf->akm = DPP_AKM_SAE; + else if (bin_str_eq(type, len, "psk-sae") || + bin_str_eq(type, len, "psk+sae")) + conf->akm = DPP_AKM_PSK_SAE; + else if (bin_str_eq(type, len, "sae-dpp") || + bin_str_eq(type, len, "dpp+sae")) + conf->akm = DPP_AKM_SAE_DPP; + else if (bin_str_eq(type, len, "psk-sae-dpp") || + bin_str_eq(type, len, "dpp+psk+sae")) + conf->akm = DPP_AKM_PSK_SAE_DPP; + else if (bin_str_eq(type, len, "dpp")) + conf->akm = DPP_AKM_DPP; + else if (bin_str_eq(type, len, "dot1x")) + conf->akm = DPP_AKM_DOT1X; + else + goto fail; + + return conf; +fail: + dpp_configuration_free(conf); + return NULL; +} + + +int dpp_akm_psk(enum dpp_akm akm) +{ + return akm == DPP_AKM_PSK || akm == DPP_AKM_PSK_SAE || + akm == DPP_AKM_PSK_SAE_DPP; +} + + +int dpp_akm_sae(enum dpp_akm akm) +{ + return akm == DPP_AKM_SAE || akm == DPP_AKM_PSK_SAE || + akm == DPP_AKM_SAE_DPP || akm == DPP_AKM_PSK_SAE_DPP; +} + + +int dpp_akm_legacy(enum dpp_akm akm) +{ + return akm == DPP_AKM_PSK || akm == DPP_AKM_PSK_SAE || + akm == DPP_AKM_SAE; +} + + +int dpp_akm_dpp(enum dpp_akm akm) +{ + return akm == DPP_AKM_DPP || akm == DPP_AKM_SAE_DPP || + akm == DPP_AKM_PSK_SAE_DPP; +} + + +int dpp_akm_ver2(enum dpp_akm akm) +{ + return akm == DPP_AKM_SAE_DPP || akm == DPP_AKM_PSK_SAE_DPP; +} + + +int dpp_configuration_valid(const struct dpp_configuration *conf) +{ + if (conf->ssid_len == 0) + return 0; + if (dpp_akm_psk(conf->akm) && !conf->passphrase && !conf->psk_set) + return 0; + if (dpp_akm_sae(conf->akm) && !conf->passphrase) + return 0; + return 1; +} + + +void dpp_configuration_free(struct dpp_configuration *conf) +{ + if (!conf) + return; + str_clear_free(conf->passphrase); + os_free(conf->group_id); + os_free(conf->csrattrs); + os_free(conf->extra_name); + os_free(conf->extra_value); + bin_clear_free(conf, sizeof(*conf)); +} + + +static int dpp_configuration_parse_helper(struct dpp_authentication *auth, + const char *cmd, int idx) +{ + const char *pos, *end; + struct dpp_configuration *conf_sta = NULL, *conf_ap = NULL; + struct dpp_configuration *conf = NULL; + size_t len; + + pos = os_strstr(cmd, " conf=sta-"); + if (pos) { + conf_sta = dpp_configuration_alloc(pos + 10); + if (!conf_sta) + goto fail; + conf_sta->netrole = DPP_NETROLE_STA; + conf = conf_sta; + } + + pos = os_strstr(cmd, " conf=ap-"); + if (pos) { + conf_ap = dpp_configuration_alloc(pos + 9); + if (!conf_ap) + goto fail; + conf_ap->netrole = DPP_NETROLE_AP; + conf = conf_ap; + } + + pos = os_strstr(cmd, " conf=configurator"); + if (pos) + auth->provision_configurator = 1; + + if (!conf) + return 0; + + pos = os_strstr(cmd, " ssid="); + if (pos) { + pos += 6; + end = os_strchr(pos, ' '); + conf->ssid_len = end ? (size_t) (end - pos) : os_strlen(pos); + conf->ssid_len /= 2; + if (conf->ssid_len > sizeof(conf->ssid) || + hexstr2bin(pos, conf->ssid, conf->ssid_len) < 0) + goto fail; + } else { +#ifdef CONFIG_TESTING_OPTIONS + /* use a default SSID for legacy testing reasons */ + os_memcpy(conf->ssid, "test", 4); + conf->ssid_len = 4; +#else /* CONFIG_TESTING_OPTIONS */ + goto fail; +#endif /* CONFIG_TESTING_OPTIONS */ + } + + pos = os_strstr(cmd, " ssid_charset="); + if (pos) { + if (conf_ap) { + wpa_printf(MSG_INFO, + "DPP: ssid64 option (ssid_charset param) not allowed for AP enrollee"); + goto fail; + } + conf->ssid_charset = atoi(pos + 14); + } + + pos = os_strstr(cmd, " pass="); + if (pos) { + size_t pass_len; + + pos += 6; + end = os_strchr(pos, ' '); + pass_len = end ? (size_t) (end - pos) : os_strlen(pos); + pass_len /= 2; + if (pass_len > 63 || pass_len < 8) + goto fail; + conf->passphrase = os_zalloc(pass_len + 1); + if (!conf->passphrase || + hexstr2bin(pos, (u8 *) conf->passphrase, pass_len) < 0) + goto fail; + } + + pos = os_strstr(cmd, " psk="); + if (pos) { + pos += 5; + if (hexstr2bin(pos, conf->psk, PMK_LEN) < 0) + goto fail; + conf->psk_set = 1; + } + + pos = os_strstr(cmd, " group_id="); + if (pos) { + size_t group_id_len; + + pos += 10; + end = os_strchr(pos, ' '); + group_id_len = end ? (size_t) (end - pos) : os_strlen(pos); + conf->group_id = os_malloc(group_id_len + 1); + if (!conf->group_id) + goto fail; + os_memcpy(conf->group_id, pos, group_id_len); + conf->group_id[group_id_len] = '\0'; + } + + pos = os_strstr(cmd, " expiry="); + if (pos) { + long int val; + + pos += 8; + val = strtol(pos, NULL, 0); + if (val <= 0) + goto fail; + conf->netaccesskey_expiry = val; + } + + pos = os_strstr(cmd, " csrattrs="); + if (pos) { + pos += 10; + end = os_strchr(pos, ' '); + len = end ? (size_t) (end - pos) : os_strlen(pos); + conf->csrattrs = os_zalloc(len + 1); + if (!conf->csrattrs) + goto fail; + os_memcpy(conf->csrattrs, pos, len); + } + + pos = os_strstr(cmd, " conf_extra_name="); + if (pos) { + pos += 17; + end = os_strchr(pos, ' '); + len = end ? (size_t) (end - pos) : os_strlen(pos); + conf->extra_name = os_zalloc(len + 1); + if (!conf->extra_name) + goto fail; + os_memcpy(conf->extra_name, pos, len); + } + + pos = os_strstr(cmd, " conf_extra_value="); + if (pos) { + pos += 18; + end = os_strchr(pos, ' '); + len = end ? (size_t) (end - pos) : os_strlen(pos); + len /= 2; + conf->extra_value = os_zalloc(len + 1); + if (!conf->extra_value || + hexstr2bin(pos, (u8 *) conf->extra_value, len) < 0) + goto fail; + } + + if (!dpp_configuration_valid(conf)) + goto fail; + + if (idx == 0) { + auth->conf_sta = conf_sta; + auth->conf_ap = conf_ap; + } else if (idx == 1) { + if (!auth->conf_sta) + auth->conf_sta = conf_sta; + else + auth->conf2_sta = conf_sta; + if (!auth->conf_ap) + auth->conf_ap = conf_ap; + else + auth->conf2_ap = conf_ap; + } else { + goto fail; + } + return 0; + +fail: + dpp_configuration_free(conf_sta); + dpp_configuration_free(conf_ap); + return -1; +} + + +static int dpp_configuration_parse(struct dpp_authentication *auth, + const char *cmd) +{ + const char *pos; + char *tmp; + size_t len; + int res; + + pos = os_strstr(cmd, " @CONF-OBJ-SEP@ "); + if (!pos) + return dpp_configuration_parse_helper(auth, cmd, 0); + + len = pos - cmd; + tmp = os_malloc(len + 1); + if (!tmp) + goto fail; + os_memcpy(tmp, cmd, len); + tmp[len] = '\0'; + res = dpp_configuration_parse_helper(auth, tmp, 0); + str_clear_free(tmp); + if (res) + goto fail; + res = dpp_configuration_parse_helper(auth, cmd + len, 1); + if (res) + goto fail; + return 0; +fail: + dpp_configuration_free(auth->conf_sta); + dpp_configuration_free(auth->conf2_sta); + dpp_configuration_free(auth->conf_ap); + dpp_configuration_free(auth->conf2_ap); + return -1; +} + + +static struct dpp_configurator * +dpp_configurator_get_id(struct dpp_global *dpp, unsigned int id) +{ + struct dpp_configurator *conf; + + if (!dpp) + return NULL; + + dl_list_for_each(conf, &dpp->configurator, + struct dpp_configurator, list) { + if (conf->id == id) + return conf; + } + return NULL; +} + + +int dpp_set_configurator(struct dpp_authentication *auth, const char *cmd) +{ + const char *pos; + char *tmp = NULL; + int ret = -1; + + if (!cmd || auth->configurator_set) + return 0; + auth->configurator_set = 1; + + if (cmd[0] != ' ') { + size_t len; + + len = os_strlen(cmd); + tmp = os_malloc(len + 2); + if (!tmp) + goto fail; + tmp[0] = ' '; + os_memcpy(tmp + 1, cmd, len + 1); + cmd = tmp; + } + + wpa_printf(MSG_DEBUG, "DPP: Set configurator parameters: %s", cmd); + + if (os_strstr(cmd, " conf=query")) { + auth->configurator_set = 0; + auth->use_config_query = true; + ret = 0; + goto fail; + } + + pos = os_strstr(cmd, " configurator="); + if (!auth->conf && pos) { + pos += 14; + auth->conf = dpp_configurator_get_id(auth->global, atoi(pos)); + if (!auth->conf) { + wpa_printf(MSG_INFO, + "DPP: Could not find the specified configurator"); + goto fail; + } + } + + pos = os_strstr(cmd, " conn_status="); + if (pos) { + pos += 13; + auth->send_conn_status = atoi(pos); + } + + pos = os_strstr(cmd, " akm_use_selector="); + if (pos) { + pos += 18; + auth->akm_use_selector = atoi(pos); + } + + if (dpp_configuration_parse(auth, cmd) < 0) { + wpa_msg(auth->msg_ctx, MSG_INFO, + "DPP: Failed to set configurator parameters"); + goto fail; + } + ret = 0; +fail: + os_free(tmp); + return ret; +} + + +void dpp_auth_deinit(struct dpp_authentication *auth) +{ + unsigned int i; + + if (!auth) + return; + dpp_configuration_free(auth->conf_ap); + dpp_configuration_free(auth->conf2_ap); + dpp_configuration_free(auth->conf_sta); + dpp_configuration_free(auth->conf2_sta); + crypto_ec_key_deinit(auth->own_protocol_key); + crypto_ec_key_deinit(auth->peer_protocol_key); + crypto_ec_key_deinit(auth->reconfig_old_protocol_key); + wpabuf_free(auth->req_msg); + wpabuf_free(auth->resp_msg); + wpabuf_free(auth->conf_req); + wpabuf_free(auth->reconfig_req_msg); + wpabuf_free(auth->reconfig_resp_msg); + for (i = 0; i < auth->num_conf_obj; i++) { + struct dpp_config_obj *conf = &auth->conf_obj[i]; + + os_free(conf->connector); + wpabuf_free(conf->c_sign_key); + wpabuf_free(conf->certbag); + wpabuf_free(conf->certs); + wpabuf_free(conf->cacert); + os_free(conf->server_name); + wpabuf_free(conf->pp_key); + } +#ifdef CONFIG_DPP2 + dpp_free_asymmetric_key(auth->conf_key_pkg); + os_free(auth->csrattrs); + wpabuf_free(auth->csr); + wpabuf_free(auth->priv_key); + wpabuf_free(auth->cacert); + wpabuf_free(auth->certbag); + os_free(auth->trusted_eap_server_name); + wpabuf_free(auth->conf_resp_tcp); +#endif /* CONFIG_DPP2 */ + wpabuf_free(auth->net_access_key); + dpp_bootstrap_info_free(auth->tmp_own_bi); + if (auth->tmp_peer_bi) { + dl_list_del(&auth->tmp_peer_bi->list); + dpp_bootstrap_info_free(auth->tmp_peer_bi); + } + os_free(auth->e_name); + os_free(auth->e_mud_url); + os_free(auth->e_band_support); +#ifdef CONFIG_TESTING_OPTIONS + os_free(auth->config_obj_override); + os_free(auth->discovery_override); + os_free(auth->groups_override); +#endif /* CONFIG_TESTING_OPTIONS */ + bin_clear_free(auth, sizeof(*auth)); +} + + +static struct wpabuf * +dpp_build_conf_start(struct dpp_authentication *auth, + struct dpp_configuration *conf, size_t tailroom) +{ + struct wpabuf *buf; + +#ifdef CONFIG_TESTING_OPTIONS + if (auth->discovery_override) + tailroom += os_strlen(auth->discovery_override); +#endif /* CONFIG_TESTING_OPTIONS */ + + buf = wpabuf_alloc(200 + tailroom); + if (!buf) + return NULL; + json_start_object(buf, NULL); + json_add_string(buf, "wi-fi_tech", "infra"); + json_value_sep(buf); +#ifdef CONFIG_TESTING_OPTIONS + if (auth->discovery_override) { + wpa_printf(MSG_DEBUG, "DPP: TESTING - discovery override: '%s'", + auth->discovery_override); + wpabuf_put_str(buf, "\"discovery\":"); + wpabuf_put_str(buf, auth->discovery_override); + json_value_sep(buf); + return buf; + } +#endif /* CONFIG_TESTING_OPTIONS */ + json_start_object(buf, "discovery"); + if (((!conf->ssid_charset || auth->peer_version < 2) && + json_add_string_escape(buf, "ssid", conf->ssid, + conf->ssid_len) < 0) || + ((conf->ssid_charset && auth->peer_version >= 2) && + json_add_base64url(buf, "ssid64", conf->ssid, + conf->ssid_len) < 0)) { + wpabuf_free(buf); + return NULL; + } + if (conf->ssid_charset > 0) { + json_value_sep(buf); + json_add_int(buf, "ssid_charset", conf->ssid_charset); + } + json_end_object(buf); + json_value_sep(buf); + + return buf; +} + + +int dpp_build_jwk(struct wpabuf *buf, const char *name, + struct crypto_ec_key *key, const char *kid, + const struct dpp_curve_params *curve) +{ + struct wpabuf *pub; + const u8 *pos; + int ret = -1; + + pub = crypto_ec_key_get_pubkey_point(key, 0); + if (!pub) + goto fail; + + json_start_object(buf, name); + json_add_string(buf, "kty", "EC"); + json_value_sep(buf); + json_add_string(buf, "crv", curve->jwk_crv); + json_value_sep(buf); + pos = wpabuf_head(pub); + if (json_add_base64url(buf, "x", pos, curve->prime_len) < 0) + goto fail; + json_value_sep(buf); + pos += curve->prime_len; + if (json_add_base64url(buf, "y", pos, curve->prime_len) < 0) + goto fail; + if (kid) { + json_value_sep(buf); + json_add_string(buf, "kid", kid); + } + json_end_object(buf); + ret = 0; +fail: + wpabuf_free(pub); + return ret; +} + + +static void dpp_build_legacy_cred_params(struct wpabuf *buf, + struct dpp_configuration *conf) +{ + if (conf->passphrase && os_strlen(conf->passphrase) < 64) { + json_add_string_escape(buf, "pass", conf->passphrase, + os_strlen(conf->passphrase)); + } else if (conf->psk_set) { + char psk[2 * sizeof(conf->psk) + 1]; + + wpa_snprintf_hex(psk, sizeof(psk), + conf->psk, sizeof(conf->psk)); + json_add_string(buf, "psk_hex", psk); + forced_memzero(psk, sizeof(psk)); + } +} + + +const char * dpp_netrole_str(enum dpp_netrole netrole) +{ + switch (netrole) { + case DPP_NETROLE_STA: + return "sta"; + case DPP_NETROLE_AP: + return "ap"; + case DPP_NETROLE_CONFIGURATOR: + return "configurator"; + default: + return "??"; + } +} + + +static bool dpp_supports_curve(const char *curve, struct dpp_bootstrap_info *bi) +{ + enum dpp_bootstrap_supported_curves idx; + + if (!bi || !bi->supported_curves) + return true; /* no support indication available */ + + if (os_strcmp(curve, "prime256v1") == 0) + idx = DPP_BOOTSTRAP_CURVE_P_256; + else if (os_strcmp(curve, "secp384r1") == 0) + idx = DPP_BOOTSTRAP_CURVE_P_384; + else if (os_strcmp(curve, "secp521r1") == 0) + idx = DPP_BOOTSTRAP_CURVE_P_521; + else if (os_strcmp(curve, "brainpoolP256r1") == 0) + idx = DPP_BOOTSTRAP_CURVE_BP_256; + else if (os_strcmp(curve, "brainpoolP384r1") == 0) + idx = DPP_BOOTSTRAP_CURVE_BP_384; + else if (os_strcmp(curve, "brainpoolP512r1") == 0) + idx = DPP_BOOTSTRAP_CURVE_BP_512; + else + return true; + + return bi->supported_curves & BIT(idx); +} + + +static struct wpabuf * +dpp_build_conf_obj_dpp(struct dpp_authentication *auth, + struct dpp_configuration *conf) +{ + struct wpabuf *buf = NULL; + char *signed_conn = NULL; + size_t tailroom; + const struct dpp_curve_params *curve; /* C-sign-key curve */ + const struct dpp_curve_params *nak_curve; /* netAccessKey curve */ + struct wpabuf *dppcon = NULL; + size_t extra_len = 1000; + int incl_legacy; + enum dpp_akm akm; + const char *akm_str; + + if (!auth->conf) { + wpa_printf(MSG_INFO, + "DPP: No configurator specified - cannot generate DPP config object"); + goto fail; + } + curve = auth->conf->curve; + if (dpp_akm_dpp(conf->akm) && + !dpp_supports_curve(curve->name, auth->peer_bi)) { + wpa_printf(MSG_DEBUG, + "DPP: Enrollee does not support C-sign-key curve (%s) - cannot generate config object", + curve->name); + goto fail; + } + if (auth->new_curve && auth->new_key_received) + nak_curve = auth->new_curve; + else + nak_curve = auth->curve; + if (!dpp_supports_curve(nak_curve->name, auth->peer_bi)) { + wpa_printf(MSG_DEBUG, + "DPP: Enrollee does not support netAccessKey curve (%s) - cannot generate config object", + nak_curve->name); + goto fail; + } + + akm = conf->akm; + if (dpp_akm_ver2(akm) && auth->peer_version < 2) { + wpa_printf(MSG_DEBUG, + "DPP: Convert DPP+legacy credential to DPP-only for peer that does not support version 2"); + akm = DPP_AKM_DPP; + } + +#ifdef CONFIG_TESTING_OPTIONS + if (auth->groups_override) + extra_len += os_strlen(auth->groups_override); +#endif /* CONFIG_TESTING_OPTIONS */ + + if (conf->group_id) + extra_len += os_strlen(conf->group_id); + + /* Connector (JSON dppCon object) */ + dppcon = wpabuf_alloc(extra_len + 2 * nak_curve->prime_len * 4 / 3); + if (!dppcon) + goto fail; +#ifdef CONFIG_TESTING_OPTIONS + if (auth->groups_override) { + wpabuf_put_u8(dppcon, '{'); + if (auth->groups_override) { + wpa_printf(MSG_DEBUG, + "DPP: TESTING - groups override: '%s'", + auth->groups_override); + wpabuf_put_str(dppcon, "\"groups\":"); + wpabuf_put_str(dppcon, auth->groups_override); + json_value_sep(dppcon); + } + goto skip_groups; + } +#endif /* CONFIG_TESTING_OPTIONS */ + json_start_object(dppcon, NULL); + json_start_array(dppcon, "groups"); + json_start_object(dppcon, NULL); + json_add_string(dppcon, "groupId", + conf->group_id ? conf->group_id : "*"); + json_value_sep(dppcon); + json_add_string(dppcon, "netRole", dpp_netrole_str(conf->netrole)); + json_end_object(dppcon); + json_end_array(dppcon); + json_value_sep(dppcon); +#ifdef CONFIG_TESTING_OPTIONS +skip_groups: +#endif /* CONFIG_TESTING_OPTIONS */ + if (!auth->peer_protocol_key) { + wpa_printf(MSG_DEBUG, + "DPP: No peer protocol key available to build netAccessKey JWK"); + goto fail; + } +#ifdef CONFIG_DPP3 + if (auth->conf->net_access_key_curve && + auth->curve != auth->conf->net_access_key_curve && + !auth->new_key_received) { + if (!dpp_supports_curve(auth->conf->net_access_key_curve->name, + auth->peer_bi)) { + wpa_printf(MSG_DEBUG, + "DPP: Enrollee does not support the required netAccessKey curve (%s) - cannot generate config object", + auth->conf->net_access_key_curve->name); + goto fail; + } + wpa_printf(MSG_DEBUG, + "DPP: Peer protocol key curve (%s) does not match the required netAccessKey curve (%s) - %s", + auth->curve->name, + auth->conf->net_access_key_curve->name, + auth->waiting_new_key ? + "the required key not received" : + "request a new key"); + if (auth->waiting_new_key) + auth->waiting_new_key = false; /* failed */ + else + auth->waiting_new_key = true; + goto fail; + } +#endif /* CONFIG_DPP3 */ + if (dpp_build_jwk(dppcon, "netAccessKey", auth->peer_protocol_key, NULL, + nak_curve) < 0) { + wpa_printf(MSG_DEBUG, "DPP: Failed to build netAccessKey JWK"); + goto fail; + } + if (conf->netaccesskey_expiry) { + struct os_tm tm; + char expiry[30]; + + if (os_gmtime(conf->netaccesskey_expiry, &tm) < 0) { + wpa_printf(MSG_DEBUG, + "DPP: Failed to generate expiry string"); + goto fail; + } + os_snprintf(expiry, sizeof(expiry), + "%04u-%02u-%02uT%02u:%02u:%02uZ", + tm.year, tm.month, tm.day, + tm.hour, tm.min, tm.sec); + json_value_sep(dppcon); + json_add_string(dppcon, "expiry", expiry); + } +#ifdef CONFIG_DPP3 + json_value_sep(dppcon); + json_add_int(dppcon, "version", auth->peer_version); +#endif /* CONFIG_DPP3 */ + json_end_object(dppcon); + wpa_printf(MSG_DEBUG, "DPP: dppCon: %s", + (const char *) wpabuf_head(dppcon)); + + signed_conn = dpp_sign_connector(auth->conf, dppcon); + if (!signed_conn) + goto fail; + + incl_legacy = dpp_akm_psk(akm) || dpp_akm_sae(akm); + tailroom = 1000; + tailroom += 2 * curve->prime_len * 4 / 3 + os_strlen(auth->conf->kid); + tailroom += os_strlen(signed_conn); + if (incl_legacy) + tailroom += 1000; + if (akm == DPP_AKM_DOT1X) { + if (auth->certbag) + tailroom += 2 * wpabuf_len(auth->certbag); + if (auth->cacert) + tailroom += 2 * wpabuf_len(auth->cacert); + if (auth->trusted_eap_server_name) + tailroom += os_strlen(auth->trusted_eap_server_name); + tailroom += 1000; + } + if (conf->extra_name && conf->extra_value) + tailroom += 10 + os_strlen(conf->extra_name) + + os_strlen(conf->extra_value); + buf = dpp_build_conf_start(auth, conf, tailroom); + if (!buf) + goto fail; + + if (auth->akm_use_selector && dpp_akm_ver2(akm)) + akm_str = dpp_akm_selector_str(akm); + else + akm_str = dpp_akm_str(akm); + json_start_object(buf, "cred"); + json_add_string(buf, "akm", akm_str); + json_value_sep(buf); + if (incl_legacy) { + dpp_build_legacy_cred_params(buf, conf); + json_value_sep(buf); + } + if (akm == DPP_AKM_DOT1X) { + json_start_object(buf, "entCreds"); + if (!auth->certbag) + goto fail; + json_add_base64(buf, "certBag", wpabuf_head(auth->certbag), + wpabuf_len(auth->certbag)); + if (auth->cacert) { + json_value_sep(buf); + json_add_base64(buf, "caCert", + wpabuf_head(auth->cacert), + wpabuf_len(auth->cacert)); + } + if (auth->trusted_eap_server_name) { + json_value_sep(buf); + json_add_string(buf, "trustedEapServerName", + auth->trusted_eap_server_name); + } + json_value_sep(buf); + json_start_array(buf, "eapMethods"); + wpabuf_printf(buf, "%d", EAP_TYPE_TLS); + json_end_array(buf); + json_end_object(buf); + json_value_sep(buf); + } + wpabuf_put_str(buf, "\"signedConnector\":\""); + wpabuf_put_str(buf, signed_conn); + wpabuf_put_str(buf, "\""); + json_value_sep(buf); + if (dpp_build_jwk(buf, "csign", auth->conf->csign, auth->conf->kid, + curve) < 0) { + wpa_printf(MSG_DEBUG, "DPP: Failed to build csign JWK"); + goto fail; + } +#ifdef CONFIG_DPP2 + if (auth->peer_version >= 2 && auth->conf->pp_key) { + json_value_sep(buf); + if (dpp_build_jwk(buf, "ppKey", auth->conf->pp_key, NULL, + curve) < 0) { + wpa_printf(MSG_DEBUG, "DPP: Failed to build ppKey JWK"); + goto fail; + } + } +#endif /* CONFIG_DPP2 */ + + json_end_object(buf); + if (conf->extra_name && conf->extra_value) { + json_value_sep(buf); + wpabuf_printf(buf, "\"%s\":%s", conf->extra_name, + conf->extra_value); + } + json_end_object(buf); + + wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: Configuration Object", + wpabuf_head(buf), wpabuf_len(buf)); + +#ifdef CONFIG_DPP3 + if (!auth->conf->net_access_key_curve) { + /* All netAccessKey values used in the network will have to be + * from the same curve for network introduction to work, so + * hardcode the first used netAccessKey curve for consecutive + * operations if there was no explicit configuration of which + * curve to use. */ + wpa_printf(MSG_DEBUG, + "DPP: Update Configurator to require netAccessKey curve %s based on first provisioning", + nak_curve->name); + auth->conf->net_access_key_curve = nak_curve; + } +#endif /* CONFIG_DPP3 */ + +out: + os_free(signed_conn); + wpabuf_free(dppcon); + return buf; +fail: + wpa_printf(MSG_DEBUG, "DPP: Failed to build configuration object"); + wpabuf_free(buf); + buf = NULL; + goto out; +} + + +static struct wpabuf * +dpp_build_conf_obj_legacy(struct dpp_authentication *auth, + struct dpp_configuration *conf) +{ + struct wpabuf *buf; + const char *akm_str; + size_t len = 1000; + + if (conf->extra_name && conf->extra_value) + len += 10 + os_strlen(conf->extra_name) + + os_strlen(conf->extra_value); + buf = dpp_build_conf_start(auth, conf, len); + if (!buf) + return NULL; + + if (auth->akm_use_selector && dpp_akm_ver2(conf->akm)) + akm_str = dpp_akm_selector_str(conf->akm); + else + akm_str = dpp_akm_str(conf->akm); + json_start_object(buf, "cred"); + json_add_string(buf, "akm", akm_str); + json_value_sep(buf); + dpp_build_legacy_cred_params(buf, conf); + json_end_object(buf); + if (conf->extra_name && conf->extra_value) { + json_value_sep(buf); + wpabuf_printf(buf, "\"%s\":%s", conf->extra_name, + conf->extra_value); + } + json_end_object(buf); + + wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: Configuration Object (legacy)", + wpabuf_head(buf), wpabuf_len(buf)); + + return buf; +} + + +static int dpp_get_peer_bi_id(struct dpp_authentication *auth) +{ + struct dpp_bootstrap_info *bi; + + if (auth->peer_bi) + return auth->peer_bi->id; + if (auth->tmp_peer_bi) + return auth->tmp_peer_bi->id; + + bi = os_zalloc(sizeof(*bi)); + if (!bi) + return -1; + bi->id = dpp_next_id(auth->global); + dl_list_add(&auth->global->bootstrap, &bi->list); + auth->tmp_peer_bi = bi; + return bi->id; +} + + +static struct wpabuf * +dpp_build_conf_obj(struct dpp_authentication *auth, enum dpp_netrole netrole, + int idx, bool cert_req) +{ + struct dpp_configuration *conf = NULL; + +#ifdef CONFIG_TESTING_OPTIONS + if (auth->config_obj_override) { + if (idx != 0) + return NULL; + wpa_printf(MSG_DEBUG, "DPP: Testing - Config Object override"); + return wpabuf_alloc_copy(auth->config_obj_override, + os_strlen(auth->config_obj_override)); + } +#endif /* CONFIG_TESTING_OPTIONS */ + + if (idx == 0) { + if (netrole == DPP_NETROLE_STA) + conf = auth->conf_sta; + else if (netrole == DPP_NETROLE_AP) + conf = auth->conf_ap; + } else if (idx == 1) { + if (netrole == DPP_NETROLE_STA) + conf = auth->conf2_sta; + else if (netrole == DPP_NETROLE_AP) + conf = auth->conf2_ap; + } + if (!conf) { + if (idx == 0) { + if (auth->use_config_query) { + wpa_printf(MSG_DEBUG, + "DPP: No configuration available for Enrollee(%s) - waiting for configuration", + dpp_netrole_str(netrole)); + auth->waiting_config = true; + dpp_get_peer_bi_id(auth); + return NULL; + } + wpa_printf(MSG_DEBUG, + "DPP: No configuration available for Enrollee(%s) - reject configuration request", + dpp_netrole_str(netrole)); + } + return NULL; + } + + if (conf->akm == DPP_AKM_DOT1X) { + if (!auth->conf) { + wpa_printf(MSG_DEBUG, + "DPP: No Configurator data available"); + return NULL; + } + if (!cert_req && !auth->certbag) { + wpa_printf(MSG_DEBUG, + "DPP: No certificate data available for dot1x configuration"); + return NULL; + } + return dpp_build_conf_obj_dpp(auth, conf); + } + if (dpp_akm_dpp(conf->akm) || (auth->peer_version >= 2 && auth->conf)) + return dpp_build_conf_obj_dpp(auth, conf); + return dpp_build_conf_obj_legacy(auth, conf); +} + + +struct wpabuf * +dpp_build_conf_resp(struct dpp_authentication *auth, const u8 *e_nonce, + u16 e_nonce_len, enum dpp_netrole netrole, bool cert_req) +{ + struct wpabuf *conf = NULL, *conf2 = NULL, *env_data = NULL, *pc = NULL; + size_t clear_len, attr_len; + struct wpabuf *clear = NULL, *msg = NULL; + u8 *wrapped; + const u8 *addr[1]; + size_t len[1]; + enum dpp_status_error status; + + if (auth->force_conf_resp_status != DPP_STATUS_OK) { + status = auth->force_conf_resp_status; + goto forced_status; + } + + if (netrole == DPP_NETROLE_CONFIGURATOR) { +#ifdef CONFIG_DPP2 + env_data = dpp_build_enveloped_data(auth); +#endif /* CONFIG_DPP2 */ + } else { + conf = dpp_build_conf_obj(auth, netrole, 0, cert_req); + if (conf) { + wpa_hexdump_ascii(MSG_DEBUG, + "DPP: configurationObject JSON", + wpabuf_head(conf), wpabuf_len(conf)); + conf2 = dpp_build_conf_obj(auth, netrole, 1, cert_req); + } + } + + if (!conf && auth->waiting_config) + return NULL; + if (conf || env_data) + status = DPP_STATUS_OK; + else if (!cert_req && netrole == DPP_NETROLE_STA && auth->conf_sta && + auth->conf_sta->akm == DPP_AKM_DOT1X && !auth->waiting_csr) + status = DPP_STATUS_CSR_NEEDED; +#ifdef CONFIG_DPP3 + else if (auth->waiting_new_key) + status = DPP_STATUS_NEW_KEY_NEEDED; +#endif /* CONFIG_DPP3 */ + else + status = DPP_STATUS_CONFIGURE_FAILURE; +forced_status: + auth->conf_resp_status = status; + + /* { E-nonce, configurationObject[, sendConnStatus]}ke */ + clear_len = 4 + e_nonce_len; + if (conf) + clear_len += 4 + wpabuf_len(conf); + if (conf2) + clear_len += 4 + wpabuf_len(conf2); + if (env_data) + clear_len += 4 + wpabuf_len(env_data); + if (auth->peer_version >= 2 && auth->send_conn_status && + netrole == DPP_NETROLE_STA) + clear_len += 4; + if (status == DPP_STATUS_CSR_NEEDED && auth->conf_sta && + auth->conf_sta->csrattrs) + clear_len += 4 + os_strlen(auth->conf_sta->csrattrs); +#ifdef CONFIG_DPP3 + if (status == DPP_STATUS_NEW_KEY_NEEDED) { + struct crypto_ec_key *new_pc; + + clear_len += 6; /* Finite Cyclic Group attribute */ + + wpa_printf(MSG_DEBUG, + "DPP: Generate a new own protocol key for the curve %s", + auth->conf->net_access_key_curve->name); + new_pc = dpp_gen_keypair(auth->conf->net_access_key_curve); + if (!new_pc) { + wpa_printf(MSG_DEBUG, "DPP: Failed to generate new Pc"); + return NULL; + } + pc = crypto_ec_key_get_pubkey_point(new_pc, 0); + if (!pc) { + crypto_ec_key_deinit(new_pc); + return NULL; + } + crypto_ec_key_deinit(auth->own_protocol_key); + auth->own_protocol_key = new_pc; + auth->new_curve = auth->conf->net_access_key_curve; + clear_len += 4 + wpabuf_len(pc); + } +#endif /* CONFIG_DPP3 */ + clear = wpabuf_alloc(clear_len); + attr_len = 4 + 1 + 4 + clear_len + AES_BLOCK_SIZE; +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_CONF_RESP) + attr_len += 5; +#endif /* CONFIG_TESTING_OPTIONS */ + msg = wpabuf_alloc(attr_len); + if (!clear || !msg) + goto fail; + +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_NO_E_NONCE_CONF_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - no E-nonce"); + goto skip_e_nonce; + } + if (dpp_test == DPP_TEST_E_NONCE_MISMATCH_CONF_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - E-nonce mismatch"); + wpabuf_put_le16(clear, DPP_ATTR_ENROLLEE_NONCE); + wpabuf_put_le16(clear, e_nonce_len); + wpabuf_put_data(clear, e_nonce, e_nonce_len - 1); + wpabuf_put_u8(clear, e_nonce[e_nonce_len - 1] ^ 0x01); + goto skip_e_nonce; + } + if (dpp_test == DPP_TEST_NO_WRAPPED_DATA_CONF_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - no Wrapped Data"); + goto skip_wrapped_data; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + /* E-nonce */ + wpabuf_put_le16(clear, DPP_ATTR_ENROLLEE_NONCE); + wpabuf_put_le16(clear, e_nonce_len); + wpabuf_put_data(clear, e_nonce, e_nonce_len); + +#ifdef CONFIG_TESTING_OPTIONS +skip_e_nonce: + if (dpp_test == DPP_TEST_NO_CONFIG_OBJ_CONF_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - Config Object"); + goto skip_config_obj; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + if (conf) { + wpabuf_put_le16(clear, DPP_ATTR_CONFIG_OBJ); + wpabuf_put_le16(clear, wpabuf_len(conf)); + wpabuf_put_buf(clear, conf); + } + if (auth->peer_version >= 2 && conf2) { + wpabuf_put_le16(clear, DPP_ATTR_CONFIG_OBJ); + wpabuf_put_le16(clear, wpabuf_len(conf2)); + wpabuf_put_buf(clear, conf2); + } else if (conf2) { + wpa_printf(MSG_DEBUG, + "DPP: Second Config Object available, but peer does not support more than one"); + } + if (env_data) { + wpabuf_put_le16(clear, DPP_ATTR_ENVELOPED_DATA); + wpabuf_put_le16(clear, wpabuf_len(env_data)); + wpabuf_put_buf(clear, env_data); + } + + if (auth->peer_version >= 2 && auth->send_conn_status && + netrole == DPP_NETROLE_STA && status == DPP_STATUS_OK) { + wpa_printf(MSG_DEBUG, "DPP: sendConnStatus"); + wpabuf_put_le16(clear, DPP_ATTR_SEND_CONN_STATUS); + wpabuf_put_le16(clear, 0); + } + + if (status == DPP_STATUS_CSR_NEEDED && auth->conf_sta && + auth->conf_sta->csrattrs) { + auth->waiting_csr = true; + wpa_printf(MSG_DEBUG, "DPP: CSR Attributes Request"); + wpabuf_put_le16(clear, DPP_ATTR_CSR_ATTR_REQ); + wpabuf_put_le16(clear, os_strlen(auth->conf_sta->csrattrs)); + wpabuf_put_str(clear, auth->conf_sta->csrattrs); + } + +#ifdef CONFIG_DPP3 + if (status == DPP_STATUS_NEW_KEY_NEEDED && auth->conf && + auth->conf->net_access_key_curve) { + u16 ike_group = auth->conf->net_access_key_curve->ike_group; + + /* Finite Cyclic Group attribute */ + wpa_printf(MSG_DEBUG, "DPP: Finite Cyclic Group: %u", + ike_group); + wpabuf_put_le16(clear, DPP_ATTR_FINITE_CYCLIC_GROUP); + wpabuf_put_le16(clear, 2); + wpabuf_put_le16(clear, ike_group); + + if (pc) { + wpa_printf(MSG_DEBUG, "DPP: Pc"); + wpabuf_put_le16(clear, DPP_ATTR_R_PROTOCOL_KEY); + wpabuf_put_le16(clear, wpabuf_len(pc)); + wpabuf_put_buf(clear, pc); + } + } +#endif /* CONFIG_DPP3 */ + +#ifdef CONFIG_TESTING_OPTIONS +skip_config_obj: + if (dpp_test == DPP_TEST_NO_STATUS_CONF_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - Status"); + goto skip_status; + } + if (dpp_test == DPP_TEST_INVALID_STATUS_CONF_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - invalid Status"); + status = 255; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + /* DPP Status */ + dpp_build_attr_status(msg, status); + +#ifdef CONFIG_TESTING_OPTIONS +skip_status: +#endif /* CONFIG_TESTING_OPTIONS */ + + addr[0] = wpabuf_head(msg); + len[0] = wpabuf_len(msg); + wpa_hexdump(MSG_DEBUG, "DDP: AES-SIV AD", addr[0], len[0]); + + wpabuf_put_le16(msg, DPP_ATTR_WRAPPED_DATA); + wpabuf_put_le16(msg, wpabuf_len(clear) + AES_BLOCK_SIZE); + wrapped = wpabuf_put(msg, wpabuf_len(clear) + AES_BLOCK_SIZE); + + wpa_hexdump_buf(MSG_DEBUG, "DPP: AES-SIV cleartext", clear); + if (aes_siv_encrypt(auth->ke, auth->curve->hash_len, + wpabuf_head(clear), wpabuf_len(clear), + 1, addr, len, wrapped) < 0) + goto fail; + wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext", + wrapped, wpabuf_len(clear) + AES_BLOCK_SIZE); + +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_AFTER_WRAPPED_DATA_CONF_RESP) { + wpa_printf(MSG_INFO, "DPP: TESTING - attr after Wrapped Data"); + dpp_build_attr_status(msg, DPP_STATUS_OK); + } +skip_wrapped_data: +#endif /* CONFIG_TESTING_OPTIONS */ + + wpa_hexdump_buf(MSG_DEBUG, + "DPP: Configuration Response attributes", msg); +out: + wpabuf_clear_free(conf); + wpabuf_clear_free(conf2); + wpabuf_clear_free(env_data); + wpabuf_clear_free(clear); + wpabuf_free(pc); + + return msg; +fail: + wpabuf_free(msg); + msg = NULL; + goto out; +} + + +struct wpabuf * +dpp_conf_req_rx(struct dpp_authentication *auth, const u8 *attr_start, + size_t attr_len) +{ + const u8 *wrapped_data, *e_nonce, *config_attr; + u16 wrapped_data_len, e_nonce_len, config_attr_len; + u8 *unwrapped = NULL; + size_t unwrapped_len = 0; + struct wpabuf *resp = NULL; + struct json_token *root = NULL, *token; + enum dpp_netrole netrole; + struct wpabuf *cert_req = NULL; +#ifdef CONFIG_DPP3 + const u8 *i_proto; + u16 i_proto_len; +#endif /* CONFIG_DPP3 */ + +#ifdef CONFIG_TESTING_OPTIONS + if (dpp_test == DPP_TEST_STOP_AT_CONF_REQ) { + wpa_printf(MSG_INFO, + "DPP: TESTING - stop at Config Request"); + return NULL; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + if (dpp_check_attrs(attr_start, attr_len) < 0) { + dpp_auth_fail(auth, "Invalid attribute in config request"); + return NULL; + } + + wrapped_data = dpp_get_attr(attr_start, attr_len, DPP_ATTR_WRAPPED_DATA, + &wrapped_data_len); + if (!wrapped_data || wrapped_data_len < AES_BLOCK_SIZE) { + dpp_auth_fail(auth, + "Missing or invalid required Wrapped Data attribute"); + return NULL; + } + + wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV ciphertext", + wrapped_data, wrapped_data_len); + unwrapped_len = wrapped_data_len - AES_BLOCK_SIZE; + unwrapped = os_malloc(unwrapped_len); + if (!unwrapped) + return NULL; + if (aes_siv_decrypt(auth->ke, auth->curve->hash_len, + wrapped_data, wrapped_data_len, + 0, NULL, NULL, unwrapped) < 0) { + dpp_auth_fail(auth, "AES-SIV decryption failed"); + goto fail; + } + wpa_hexdump(MSG_DEBUG, "DPP: AES-SIV cleartext", + unwrapped, unwrapped_len); + + if (dpp_check_attrs(unwrapped, unwrapped_len) < 0) { + dpp_auth_fail(auth, "Invalid attribute in unwrapped data"); + goto fail; + } + + e_nonce = dpp_get_attr(unwrapped, unwrapped_len, + DPP_ATTR_ENROLLEE_NONCE, + &e_nonce_len); + if (!e_nonce || e_nonce_len != auth->curve->nonce_len) { + dpp_auth_fail(auth, + "Missing or invalid Enrollee Nonce attribute"); + goto fail; + } + wpa_hexdump(MSG_DEBUG, "DPP: Enrollee Nonce", e_nonce, e_nonce_len); + os_memcpy(auth->e_nonce, e_nonce, e_nonce_len); + +#ifdef CONFIG_DPP3 + i_proto = dpp_get_attr(unwrapped, unwrapped_len, + DPP_ATTR_I_PROTOCOL_KEY, &i_proto_len); + if (i_proto && !auth->waiting_new_key) { + dpp_auth_fail(auth, + "Enrollee included a new protocol key even though one was not expected"); + goto fail; + } + if (i_proto) { + struct crypto_ec_key *pe; + u8 auth_i[DPP_MAX_HASH_LEN]; + const u8 *rx_auth_i; + u16 rx_auth_i_len; + + wpa_hexdump(MSG_MSGDUMP, "DPP: Initiator Protocol Key (new Pe)", + i_proto, i_proto_len); + + pe = dpp_set_pubkey_point(auth->own_protocol_key, + i_proto, i_proto_len); + if (!pe) { + dpp_auth_fail(auth, + "Invalid Initiator Protocol Key (Pe)"); + goto fail; + } + dpp_debug_print_key("New Peer Protocol Key (Pe)", pe); + crypto_ec_key_deinit(auth->peer_protocol_key); + auth->peer_protocol_key = pe; + auth->new_key_received = true; + auth->waiting_new_key = false; + + if (dpp_derive_auth_i(auth, auth_i) < 0) + goto fail; + + rx_auth_i = dpp_get_attr(unwrapped, unwrapped_len, + DPP_ATTR_I_AUTH_TAG, &rx_auth_i_len); + if (!rx_auth_i) { + dpp_auth_fail(auth, + "Missing Initiator Authentication Tag"); + goto fail; + } + if (rx_auth_i_len != auth->curve->hash_len || + os_memcmp(rx_auth_i, auth_i, auth->curve->hash_len) != 0) { + dpp_auth_fail(auth, + "Mismatch in Initiator Authenticating Tag"); + wpa_hexdump(MSG_DEBUG, "DPP: Received Auth-I", + rx_auth_i, rx_auth_i_len); + wpa_hexdump(MSG_DEBUG, "DPP: Derived Auth-I'", + auth_i, auth->curve->hash_len); + goto fail; + } + } +#endif /* CONFIG_DPP3 */ + + config_attr = dpp_get_attr(unwrapped, unwrapped_len, + DPP_ATTR_CONFIG_ATTR_OBJ, + &config_attr_len); + if (!config_attr) { + dpp_auth_fail(auth, + "Missing or invalid Config Attributes attribute"); + goto fail; + } + wpa_hexdump_ascii(MSG_DEBUG, "DPP: Config Attributes", + config_attr, config_attr_len); + + root = json_parse((const char *) config_attr, config_attr_len); + if (!root) { + dpp_auth_fail(auth, "Could not parse Config Attributes"); + goto fail; + } + + token = json_get_member(root, "name"); + if (!token || token->type != JSON_STRING) { + dpp_auth_fail(auth, "No Config Attributes - name"); + goto fail; + } + wpa_printf(MSG_DEBUG, "DPP: Enrollee name = '%s'", token->string); + os_free(auth->e_name); + auth->e_name = os_strdup(token->string); + + token = json_get_member(root, "wi-fi_tech"); + if (!token || token->type != JSON_STRING) { + dpp_auth_fail(auth, "No Config Attributes - wi-fi_tech"); + goto fail; + } + wpa_printf(MSG_DEBUG, "DPP: wi-fi_tech = '%s'", token->string); + if (os_strcmp(token->string, "infra") != 0) { + wpa_printf(MSG_DEBUG, "DPP: Unsupported wi-fi_tech '%s'", + token->string); + dpp_auth_fail(auth, "Unsupported wi-fi_tech"); + goto fail; + } + + token = json_get_member(root, "netRole"); + if (!token || token->type != JSON_STRING) { + dpp_auth_fail(auth, "No Config Attributes - netRole"); + goto fail; + } + wpa_printf(MSG_DEBUG, "DPP: netRole = '%s'", token->string); + if (os_strcmp(token->string, "sta") == 0) { + netrole = DPP_NETROLE_STA; + } else if (os_strcmp(token->string, "ap") == 0) { + netrole = DPP_NETROLE_AP; + } else if (os_strcmp(token->string, "configurator") == 0) { + netrole = DPP_NETROLE_CONFIGURATOR; + } else { + wpa_printf(MSG_DEBUG, "DPP: Unsupported netRole '%s'", + token->string); + dpp_auth_fail(auth, "Unsupported netRole"); + goto fail; + } + auth->e_netrole = netrole; + + token = json_get_member(root, "mudurl"); + if (token && token->type == JSON_STRING) { + wpa_printf(MSG_DEBUG, "DPP: mudurl = '%s'", token->string); + wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_MUD_URL "%s", + token->string); + os_free(auth->e_mud_url); + auth->e_mud_url = os_strdup(token->string); + } + + token = json_get_member(root, "bandSupport"); + if (token && token->type == JSON_ARRAY) { + int *opclass = NULL; + char txt[200], *pos, *end; + int i, res; + + wpa_printf(MSG_DEBUG, "DPP: bandSupport"); + token = token->child; + while (token) { + if (token->type != JSON_NUMBER) { + wpa_printf(MSG_DEBUG, + "DPP: Invalid bandSupport array member type"); + } else { + wpa_printf(MSG_DEBUG, + "DPP: Supported global operating class: %d", + token->number); + int_array_add_unique(&opclass, token->number); + } + token = token->sibling; + } + + txt[0] = '\0'; + pos = txt; + end = txt + sizeof(txt); + for (i = 0; opclass && opclass[i]; i++) { + res = os_snprintf(pos, end - pos, "%s%d", + pos == txt ? "" : ",", opclass[i]); + if (os_snprintf_error(end - pos, res)) { + *pos = '\0'; + break; + } + pos += res; + } + os_free(auth->e_band_support); + auth->e_band_support = opclass; + wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_BAND_SUPPORT "%s", + txt); + } + +#ifdef CONFIG_DPP2 + cert_req = json_get_member_base64(root, "pkcs10"); + if (cert_req) { + char *txt; + int id; + + wpa_hexdump_buf(MSG_DEBUG, "DPP: CertificateRequest", cert_req); + if (dpp_validate_csr(auth, cert_req) < 0) { + wpa_printf(MSG_DEBUG, "DPP: CSR is not valid"); + auth->force_conf_resp_status = DPP_STATUS_CSR_BAD; + goto cont; + } + + id = dpp_get_peer_bi_id(auth); + if (id < 0) + goto fail; + + wpa_printf(MSG_DEBUG, "DPP: CSR is valid - forward to CA/RA"); + txt = base64_encode_no_lf(wpabuf_head(cert_req), + wpabuf_len(cert_req), NULL); + if (!txt) + goto fail; + + wpa_msg(auth->msg_ctx, MSG_INFO, DPP_EVENT_CSR "peer=%d csr=%s", + id, txt); + os_free(txt); + auth->waiting_csr = false; + auth->waiting_cert = true; + goto fail; + } +cont: +#endif /* CONFIG_DPP2 */ + + resp = dpp_build_conf_resp(auth, e_nonce, e_nonce_len, netrole, + cert_req); + +fail: + wpabuf_free(cert_req); + json_free(root); + os_free(unwrapped); + return resp; +} + + +static int dpp_parse_cred_legacy(struct dpp_config_obj *conf, + struct json_token *cred) +{ + struct json_token *pass, *psk_hex; + + wpa_printf(MSG_DEBUG, "DPP: Legacy akm=psk credential"); + + pass = json_get_member(cred, "pass"); + psk_hex = json_get_member(cred, "psk_hex"); + + if (pass && pass->type == JSON_STRING) { + size_t len = os_strlen(pass->string); + + wpa_hexdump_ascii_key(MSG_DEBUG, "DPP: Legacy passphrase", + pass->string, len); + if (len < 8 || len > 63) + return -1; + os_strlcpy(conf->passphrase, pass->string, + sizeof(conf->passphrase)); + } else if (psk_hex && psk_hex->type == JSON_STRING) { + if (dpp_akm_sae(conf->akm) && !dpp_akm_psk(conf->akm)) { + wpa_printf(MSG_DEBUG, + "DPP: Unexpected psk_hex with akm=sae"); + return -1; + } + if (os_strlen(psk_hex->string) != PMK_LEN * 2 || + hexstr2bin(psk_hex->string, conf->psk, PMK_LEN) < 0) { + wpa_printf(MSG_DEBUG, "DPP: Invalid psk_hex encoding"); + return -1; + } + wpa_hexdump_key(MSG_DEBUG, "DPP: Legacy PSK", + conf->psk, PMK_LEN); + conf->psk_set = 1; + } else { + wpa_printf(MSG_DEBUG, "DPP: No pass or psk_hex strings found"); + return -1; + } + + if (dpp_akm_sae(conf->akm) && !conf->passphrase[0]) { + wpa_printf(MSG_DEBUG, "DPP: No pass for sae found"); + return -1; + } + + return 0; +} + + +struct crypto_ec_key * dpp_parse_jwk(struct json_token *jwk, + const struct dpp_curve_params **key_curve) +{ + struct json_token *token; + const struct dpp_curve_params *curve; + struct wpabuf *x = NULL, *y = NULL; + struct crypto_ec_key *key = NULL; + + token = json_get_member(jwk, "kty"); + if (!token || token->type != JSON_STRING) { + wpa_printf(MSG_DEBUG, "DPP: No kty in JWK"); + goto fail; + } + if (os_strcmp(token->string, "EC") != 0) { + wpa_printf(MSG_DEBUG, "DPP: Unexpected JWK kty '%s'", + token->string); + goto fail; + } + + token = json_get_member(jwk, "crv"); + if (!token || token->type != JSON_STRING) { + wpa_printf(MSG_DEBUG, "DPP: No crv in JWK"); + goto fail; + } + curve = dpp_get_curve_jwk_crv(token->string); + if (!curve) { + wpa_printf(MSG_DEBUG, "DPP: Unsupported JWK crv '%s'", + token->string); + goto fail; + } + + x = json_get_member_base64url(jwk, "x"); + if (!x) { + wpa_printf(MSG_DEBUG, "DPP: No x in JWK"); + goto fail; + } + wpa_hexdump_buf(MSG_DEBUG, "DPP: JWK x", x); + if (wpabuf_len(x) != curve->prime_len) { + wpa_printf(MSG_DEBUG, + "DPP: Unexpected JWK x length %u (expected %u for curve %s)", + (unsigned int) wpabuf_len(x), + (unsigned int) curve->prime_len, curve->name); + goto fail; + } + + y = json_get_member_base64url(jwk, "y"); + if (!y) { + wpa_printf(MSG_DEBUG, "DPP: No y in JWK"); + goto fail; + } + wpa_hexdump_buf(MSG_DEBUG, "DPP: JWK y", y); + if (wpabuf_len(y) != curve->prime_len) { + wpa_printf(MSG_DEBUG, + "DPP: Unexpected JWK y length %u (expected %u for curve %s)", + (unsigned int) wpabuf_len(y), + (unsigned int) curve->prime_len, curve->name); + goto fail; + } + + key = crypto_ec_key_set_pub(curve->ike_group, wpabuf_head(x), + wpabuf_head(y), wpabuf_len(x)); + if (!key) + goto fail; + + *key_curve = curve; + +fail: + wpabuf_free(x); + wpabuf_free(y); + + return key; +} + + +int dpp_key_expired(const char *timestamp, os_time_t *expiry) +{ + struct os_time now; + unsigned int year, month, day, hour, min, sec; + os_time_t utime; + const char *pos; + + /* ISO 8601 date and time: + * T