diff options
author | Stefan Lippers-Hollmann <s.l-h@gmx.de> | 2014-06-29 23:35:18 +0000 |
---|---|---|
committer | Andrew Shadura <andrewsh@debian.org> | 2016-07-20 23:00:56 +0200 |
commit | 7ea3403faef12dfe4f6ee746c3e21306b4dc4460 (patch) | |
tree | 86895ee3abcf12175f860392e51f145fdb51c0d9 | |
parent | 3a03694454bbec18114240b108a0af232133766b (diff) |
Imported Upstream version 2.2
334 files changed, 38260 insertions, 4755 deletions
diff --git a/CONTRIBUTIONS b/CONTRIBUTIONS new file mode 100644 index 0000000..d20a556 --- /dev/null +++ b/CONTRIBUTIONS @@ -0,0 +1,143 @@ +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 <your@email.example.org> + +using your real name. Pseudonyms or anonymous contributions cannot +unfortunately be accepted. + + +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-2014, Jouni Malinen <j@w1.fi> 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/hostapd/Android.mk b/hostapd/Android.mk index 6e37beb..edaf6fc 100644 --- a/hostapd/Android.mk +++ b/hostapd/Android.mk @@ -24,16 +24,13 @@ L_CFLAGS += -DVERSION_STR_POSTFIX=\"-$(PLATFORM_VERSION)\" # Set Android log name L_CFLAGS += -DANDROID_LOG_NAME=\"hostapd\" -ifeq ($(BOARD_WLAN_DEVICE), bcmdhd) -L_CFLAGS += -DANDROID_P2P -endif - -ifeq ($(BOARD_WLAN_DEVICE), qcwcn) -L_CFLAGS += -DANDROID_P2P -endif +# Disable unused parameter warnings +L_CFLAGS += -Wno-unused-parameter -ifeq ($(BOARD_WLAN_DEVICE), mrvl) +# Set Android extended P2P functionality L_CFLAGS += -DANDROID_P2P +ifeq ($(BOARD_HOSTAPD_PRIVATE_LIB),) +L_CFLAGS += -DANDROID_P2P_STUB endif # Use Android specific directory for control interface sockets @@ -51,8 +48,12 @@ INCLUDES += $(LOCAL_PATH)/src/utils INCLUDES += external/openssl/include INCLUDES += system/security/keystore/include ifdef CONFIG_DRIVER_NL80211 +ifneq ($(wildcard external/libnl),) +INCLUDES += external/libnl/include +else INCLUDES += external/libnl-headers endif +endif ifndef CONFIG_OS @@ -197,6 +198,10 @@ L_CFLAGS += -DCONFIG_PEERKEY OBJS += src/ap/peerkey_auth.c endif +ifdef CONFIG_HS20 +NEED_AES_OMAC1=y +endif + ifdef CONFIG_IEEE80211W L_CFLAGS += -DCONFIG_IEEE80211W NEED_SHA256=y @@ -385,10 +390,6 @@ NEED_AES_UNWRAP=y endif ifdef CONFIG_WPS -ifdef CONFIG_WPS2 -L_CFLAGS += -DCONFIG_WPS2 -endif - L_CFLAGS += -DCONFIG_WPS -DEAP_SERVER_WSC OBJS += src/utils/uuid.c OBJS += src/ap/wps_hostapd.c @@ -531,7 +532,8 @@ endif OBJS += src/crypto/crypto_gnutls.c HOBJS += src/crypto/crypto_gnutls.c ifdef NEED_FIPS186_2_PRF -OBJS += src/crypto/fips_prf_gnutls.c +OBJS += src/crypto/fips_prf_internal.c +OBJS += src/crypto/sha1-internal.c endif LIBS += -lgcrypt LIBS_h += -lgcrypt @@ -558,7 +560,8 @@ LIBS += -lssl3 endif OBJS += src/crypto/crypto_nss.c ifdef NEED_FIPS186_2_PRF -OBJS += src/crypto/fips_prf_nss.c +OBJS += src/crypto/fips_prf_internal.c +OBJS += src/crypto/sha1-internal.c endif LIBS += -lnss3 LIBS_h += -lnss3 @@ -910,8 +913,12 @@ LOCAL_STATIC_LIBRARIES += $(BOARD_HOSTAPD_PRIVATE_LIB) endif LOCAL_SHARED_LIBRARIES := libc libcutils liblog libcrypto libssl ifdef CONFIG_DRIVER_NL80211 +ifneq ($(wildcard external/libnl),) +LOCAL_SHARED_LIBRARIES += libnl +else LOCAL_STATIC_LIBRARIES += libnl_2 endif +endif LOCAL_CFLAGS := $(L_CFLAGS) LOCAL_SRC_FILES := $(OBJS) LOCAL_C_INCLUDES := $(INCLUDES) diff --git a/hostapd/ChangeLog b/hostapd/ChangeLog index 5ef9676..9de9438 100644 --- a/hostapd/ChangeLog +++ b/hostapd/ChangeLog @@ -1,5 +1,92 @@ ChangeLog for hostapd +2014-06-04 - v2.2 + * fixed SAE confirm-before-commit validation to avoid a potential + segmentation fault in an unexpected message sequence that could be + triggered remotely + * extended VHT support + - Operating Mode Notification + - Power Constraint element (local_pwr_constraint) + - Spectrum management capability (spectrum_mgmt_required=1) + - fix VHT80 segment picking in ACS + - fix vht_capab 'Maximum A-MPDU Length Exponent' handling + - fix VHT20 + * fixed HT40 co-ex scan for some pri/sec channel switches + * extended HT40 co-ex support to allow dynamic channel width changes + during the lifetime of the BSS + * fixed HT40 co-ex support to check for overlapping 20 MHz BSS + * fixed MSCHAP UTF-8 to UCS-2 conversion for three-byte encoding; + this fixes password with include UTF-8 characters that use + three-byte encoding EAP methods that use NtPasswordHash + * reverted TLS certificate validation step change in v2.1 that rejected + any AAA server certificate with id-kp-clientAuth even if + id-kp-serverAuth EKU was included + * fixed STA validation step for WPS ER commands to prevent a potential + crash if an ER sends an unexpected PutWLANResponse to a station that + is disassociated, but not fully removed + * enforce full EAP authentication after RADIUS Disconnect-Request by + removing the PMKSA cache entry + * added support for NAS-IP-Address, NAS-identifier, and NAS-IPv6-Address + in RADIUS Disconnect-Request + * added mechanism for removing addresses for MAC ACLs by prefixing an + entry with "-" + * Interworking/Hotspot 2.0 enhancements + - support Hotspot 2.0 Release 2 + * OSEN network for online signup connection + * subscription remediation (based on RADIUS server request or + control interface HS20_WNM_NOTIF for testing purposes) + * Hotspot 2.0 release number indication in WFA RADIUS VSA + * deauthentication request (based on RADIUS server request or + control interface WNM_DEAUTH_REQ for testing purposes) + * Session Info URL RADIUS AVP to trigger ESS Disassociation Imminent + * hs20_icon config parameter to configure icon files for OSU + * osu_* config parameters for OSU Providers list + - do not use Interworking filtering rules on Probe Request if + Interworking is disabled to avoid interop issues + * added/fixed nl80211 functionality + - AP interface teardown optimization + - support vendor specific driver command + (VENDOR <vendor id> <sub command id> [<hex formatted data>]) + * fixed PMF protection of Deauthentication frame when this is triggered + by session timeout + * internal TLS implementation enhancements/fixes + - add SHA256-based cipher suites + - add DHE-RSA cipher suites + - fix X.509 validation of PKCS#1 signature to check for extra data + * RADIUS server functionality + - add minimal RADIUS accounting server support (hostapd-as-server); + this is mainly to enable testing coverage with hwsim scripts + - allow authentication log to be written into SQLite databse + - added option for TLS protocol testing of an EAP peer by simulating + various misbehaviors/known attacks + - MAC ACL support for testing purposes + * fixed PTK derivation for CCMP-256 and GCMP-256 + * extended WPS per-station PSK to support ER case + * added option to configure the management group cipher + (group_mgmt_cipher=AES-128-CMAC (default), BIP-GMAC-128, BIP-GMAC-256, + BIP-CMAC-256) + * fixed AP mode default TXOP Limit values for AC_VI and AC_VO (these + were rounded incorrectly) + * added support for postponing FT response in case PMK-R1 needs to be + pulled from R0KH + * added option to advertise 40 MHz intolerant HT capability with + ht_capab=[40-INTOLERANT] + * remove WPS 1.0 only support, i.e., WSC 2.0 support is now enabled + whenever CONFIG_WPS=y is set + * EAP-pwd fixes + - fix possible segmentation fault on EAP method deinit if an invalid + group is negotiated + * fixed RADIUS client retransmit/failover behavior + - there was a potential ctash due to freed memory being accessed + - failover to a backup server mechanism did not work properly + * fixed a possible crash on double DISABLE command when multiple BSSes + are enabled + * fixed a memory leak in SAE random number generation + * fixed GTK rekeying when the station uses FT protocol + * fixed off-by-one bounds checking in printf_encode() + - this could result in deinial of service in some EAP server cases + * various bug fixes + 2014-02-04 - v2.1 * added support for simultaneous authentication of equals (SAE) for stronger password-based authentication with WPA2-Personal diff --git a/hostapd/Makefile b/hostapd/Makefile index 5fd6481..ac6373e 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -70,6 +70,11 @@ NEED_SHA1=y OBJS += ../src/drivers/drivers.o CFLAGS += -DHOSTAPD +ifdef CONFIG_MODULE_TESTS +CFLAGS += -DCONFIG_MODULE_TESTS +OBJS += hapd_module_tests.o +endif + ifdef CONFIG_WPA_TRACE CFLAGS += -DWPA_TRACE OBJS += ../src/utils/trace.o @@ -77,10 +82,10 @@ HOBJS += ../src/utils/trace.o LDFLAGS += -rdynamic CFLAGS += -funwind-tables ifdef CONFIG_WPA_TRACE_BFD -CFLAGS += -DWPA_TRACE_BFD -LIBS += -lbfd -LIBS_c += -lbfd -LIBS_h += -lbfd +CFLAGS += -DPACKAGE="hostapd" -DWPA_TRACE_BFD +LIBS += -lbfd -ldl -liberty -lz +LIBS_c += -lbfd -ldl -liberty -lz +LIBS_h += -lbfd -ldl -liberty -lz endif endif @@ -95,6 +100,7 @@ ifeq ($(CONFIG_ELOOP), eloop) LIBS += -lrt LIBS_c += -lrt LIBS_h += -lrt +LIBS_n += -lrt endif OBJS += ../src/utils/common.o @@ -179,6 +185,10 @@ CFLAGS += -DCONFIG_PEERKEY OBJS += ../src/ap/peerkey_auth.o endif +ifdef CONFIG_HS20 +NEED_AES_OMAC1=y +endif + ifdef CONFIG_IEEE80211W CFLAGS += -DCONFIG_IEEE80211W NEED_SHA256=y @@ -366,10 +376,6 @@ NEED_AES_UNWRAP=y endif ifdef CONFIG_WPS -ifdef CONFIG_WPS2 -CFLAGS += -DCONFIG_WPS2 -endif - CFLAGS += -DCONFIG_WPS -DEAP_SERVER_WSC OBJS += ../src/utils/uuid.o OBJS += ../src/ap/wps_hostapd.o @@ -512,7 +518,8 @@ endif OBJS += ../src/crypto/crypto_gnutls.o HOBJS += ../src/crypto/crypto_gnutls.o ifdef NEED_FIPS186_2_PRF -OBJS += ../src/crypto/fips_prf_gnutls.o +OBJS += ../src/crypto/fips_prf_internal.o +SHA1OBJS += ../src/crypto/sha1-internal.o endif LIBS += -lgcrypt LIBS_h += -lgcrypt @@ -539,7 +546,8 @@ LIBS += -lssl3 endif OBJS += ../src/crypto/crypto_nss.o ifdef NEED_FIPS186_2_PRF -OBJS += ../src/crypto/fips_prf_nss.o +OBJS += ../src/crypto/fips_prf_internal.o +SHA1OBJS += ../src/crypto/sha1-internal.o endif LIBS += -lnss3 LIBS_h += -lnss3 diff --git a/hostapd/README-WPS b/hostapd/README-WPS index 654b5bc..bb7d35f 100644 --- a/hostapd/README-WPS +++ b/hostapd/README-WPS @@ -63,7 +63,6 @@ includes WPS support and uses madwifi driver interface: CONFIG_DRIVER_MADWIFI=y CFLAGS += -I/usr/src/madwifi-0.9.3 CONFIG_WPS=y -CONFIG_WPS2=y CONFIG_WPS_UPNP=y Following parameter can be used to enable support for NFC config method: diff --git a/hostapd/android.config b/hostapd/android.config index 81a2e2c..ad83308 100644 --- a/hostapd/android.config +++ b/hostapd/android.config @@ -108,8 +108,6 @@ CONFIG_IEEE80211W=y # Wi-Fi Protected Setup (WPS) CONFIG_WPS=y -# Enable WSC 2.0 support -CONFIG_WPS2=y # Enable UPnP support for external WPS Registrars #CONFIG_WPS_UPNP=y diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 54e4af9..be40398 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -1,6 +1,6 @@ /* * hostapd / Configuration file parser - * Copyright (c) 2003-2013, Jouni Malinen <j@w1.fi> + * Copyright (c) 2003-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -22,6 +22,14 @@ #include "config_file.h" +#ifndef CONFIG_NO_RADIUS +#ifdef EAP_SERVER +static struct hostapd_radius_attr * +hostapd_parse_radius_attr(const char *value); +#endif /* EAP_SERVER */ +#endif /* CONFIG_NO_RADIUS */ + + #ifndef CONFIG_NO_VLAN static int hostapd_config_read_vlan_file(struct hostapd_bss_config *bss, const char *fname) @@ -129,6 +137,8 @@ static int hostapd_config_read_maclist(const char *fname, } while (fgets(buf, sizeof(buf), f)) { + int i, rem = 0; + line++; if (buf[0] == '#') @@ -143,14 +153,32 @@ static int hostapd_config_read_maclist(const char *fname, } if (buf[0] == '\0') continue; + pos = buf; + if (buf[0] == '-') { + rem = 1; + pos++; + } - if (hwaddr_aton(buf, addr)) { + if (hwaddr_aton(pos, addr)) { wpa_printf(MSG_ERROR, "Invalid MAC address '%s' at " - "line %d in '%s'", buf, line, fname); + "line %d in '%s'", pos, line, fname); fclose(f); return -1; } + if (rem) { + i = 0; + while (i < *num) { + if (os_memcmp((*acl)[i].addr, addr, ETH_ALEN) == + 0) { + os_remove_in_array(*acl, *num, + sizeof(**acl), i); + (*num)--; + } else + i++; + } + continue; + } vlan_id = 0; pos = buf; while (*pos != '\0' && *pos != ' ' && *pos != '\t') @@ -188,7 +216,7 @@ static int hostapd_config_read_eap_user(const char *fname, FILE *f; char buf[512], *pos, *start, *pos2; int line = 0, ret = 0, num_methods; - struct hostapd_eap_user *user, *tail = NULL; + struct hostapd_eap_user *user = NULL, *tail = NULL; if (!fname) return 0; @@ -222,6 +250,28 @@ static int hostapd_config_read_eap_user(const char *fname, if (buf[0] == '\0') continue; +#ifndef CONFIG_NO_RADIUS + if (user && os_strncmp(buf, "radius_accept_attr=", 19) == 0) { + struct hostapd_radius_attr *attr, *a; + attr = hostapd_parse_radius_attr(buf + 19); + if (attr == NULL) { + wpa_printf(MSG_ERROR, "Invalid radius_auth_req_attr: %s", + buf + 19); + user = NULL; /* already in the BSS list */ + goto failed; + } + if (user->accept_attr == NULL) { + user->accept_attr = attr; + } else { + a = user->accept_attr; + while (a->next) + a = a->next; + a->next = attr; + } + continue; + } +#endif /* CONFIG_NO_RADIUS */ + user = NULL; if (buf[0] != '"' && buf[0] != '*') { @@ -316,6 +366,10 @@ static int hostapd_config_read_eap_user(const char *fname, EAP_TTLS_AUTH_MSCHAPV2; goto skip_eap; } + if (os_strcmp(start, "MACACL") == 0) { + user->macacl = 1; + goto skip_eap; + } wpa_printf(MSG_ERROR, "Unsupported EAP type " "'%s' on line %d in '%s'", start, line, fname); @@ -330,7 +384,7 @@ static int hostapd_config_read_eap_user(const char *fname, break; start = pos3; } - if (num_methods == 0 && user->ttls_auth == 0) { + if (num_methods == 0 && user->ttls_auth == 0 && !user->macacl) { wpa_printf(MSG_ERROR, "No EAP types configured on " "line %d in '%s'", line, fname); goto failed; @@ -448,11 +502,8 @@ static int hostapd_config_read_eap_user(const char *fname, continue; failed: - if (user) { - os_free(user->password); - os_free(user->identity); - os_free(user); - } + if (user) + hostapd_config_free_eap_user(user); ret = -1; break; } @@ -1018,8 +1069,8 @@ static int hostapd_config_ht_capab(struct hostapd_config *conf, conf->ht_capab |= HT_CAP_INFO_MAX_AMSDU_SIZE; if (os_strstr(capab, "[DSSS_CCK-40]")) conf->ht_capab |= HT_CAP_INFO_DSSS_CCK40MHZ; - if (os_strstr(capab, "[PSMP]")) - conf->ht_capab |= HT_CAP_INFO_PSMP_SUPP; + if (os_strstr(capab, "[40-INTOLERANT]")) + conf->ht_capab |= HT_CAP_INFO_40MHZ_INTOLERANT; if (os_strstr(capab, "[LSIG-TXOP-PROT]")) conf->ht_capab |= HT_CAP_INFO_LSIG_TXOP_PROTECT_SUPPORT; @@ -1040,8 +1091,6 @@ static int hostapd_config_vht_capab(struct hostapd_config *conf, conf->vht_capab |= VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; if (os_strstr(capab, "[VHT160-80PLUS80]")) conf->vht_capab |= VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ; - if (os_strstr(capab, "[VHT160-80PLUS80]")) - conf->vht_capab |= VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ; if (os_strstr(capab, "[RXLDPC]")) conf->vht_capab |= VHT_CAP_RXLDPC; if (os_strstr(capab, "[SHORT-GI-80]")) @@ -1076,8 +1125,20 @@ static int hostapd_config_vht_capab(struct hostapd_config *conf, conf->vht_capab |= VHT_CAP_VHT_TXOP_PS; if (os_strstr(capab, "[HTC-VHT]")) conf->vht_capab |= VHT_CAP_HTC_VHT; - if (os_strstr(capab, "[MAX-A-MPDU-LEN-EXP0]")) - conf->vht_capab |= VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT; + if (os_strstr(capab, "[MAX-A-MPDU-LEN-EXP7]")) + conf->vht_capab |= VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX; + else if (os_strstr(capab, "[MAX-A-MPDU-LEN-EXP6]")) + conf->vht_capab |= VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_6; + else if (os_strstr(capab, "[MAX-A-MPDU-LEN-EXP5]")) + conf->vht_capab |= VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_5; + else if (os_strstr(capab, "[MAX-A-MPDU-LEN-EXP4]")) + conf->vht_capab |= VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_4; + else if (os_strstr(capab, "[MAX-A-MPDU-LEN-EXP3]")) + conf->vht_capab |= VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_3; + else if (os_strstr(capab, "[MAX-A-MPDU-LEN-EXP2]")) + conf->vht_capab |= VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_2; + else if (os_strstr(capab, "[MAX-A-MPDU-LEN-EXP1]")) + conf->vht_capab |= VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_1; if (os_strstr(capab, "[VHT-LINK-ADAPT2]") && (conf->vht_capab & VHT_CAP_HTC_VHT)) conf->vht_capab |= VHT_CAP_VHT_LINK_ADAPTATION_VHT_UNSOL_MFB; @@ -1197,7 +1258,7 @@ static int parse_3gpp_cell_net(struct hostapd_bss_config *bss, char *buf, count = 1; for (pos = buf; *pos; pos++) { - if ((*pos < '0' && *pos > '9') && *pos != ';' && *pos != ',') + if ((*pos < '0' || *pos > '9') && *pos != ';' && *pos != ',') goto fail; if (*pos == ';') count++; @@ -1539,7 +1600,7 @@ static int hs20_parse_wan_metrics(struct hostapd_bss_config *bss, char *buf, fail: wpa_printf(MSG_ERROR, "Line %d: Invalid hs20_wan_metrics '%s'", - line, pos); + line, buf); os_free(wan_metrics); return -1; } @@ -1556,6 +1617,197 @@ static int hs20_parse_oper_friendly_name(struct hostapd_bss_config *bss, } return 0; } + + +static int hs20_parse_icon(struct hostapd_bss_config *bss, char *pos) +{ + struct hs20_icon *icon; + char *end; + + icon = os_realloc_array(bss->hs20_icons, bss->hs20_icons_count + 1, + sizeof(struct hs20_icon)); + if (icon == NULL) + return -1; + bss->hs20_icons = icon; + icon = &bss->hs20_icons[bss->hs20_icons_count]; + os_memset(icon, 0, sizeof(*icon)); + + icon->width = atoi(pos); + pos = os_strchr(pos, ':'); + if (pos == NULL) + return -1; + pos++; + + icon->height = atoi(pos); + pos = os_strchr(pos, ':'); + if (pos == NULL) + return -1; + pos++; + + end = os_strchr(pos, ':'); + if (end == NULL || end - pos > 3) + return -1; + os_memcpy(icon->language, pos, end - pos); + pos = end + 1; + + end = os_strchr(pos, ':'); + if (end == NULL || end - pos > 255) + return -1; + os_memcpy(icon->type, pos, end - pos); + pos = end + 1; + + end = os_strchr(pos, ':'); + if (end == NULL || end - pos > 255) + return -1; + os_memcpy(icon->name, pos, end - pos); + pos = end + 1; + + if (os_strlen(pos) > 255) + return -1; + os_memcpy(icon->file, pos, os_strlen(pos)); + + bss->hs20_icons_count++; + + return 0; +} + + +static int hs20_parse_osu_ssid(struct hostapd_bss_config *bss, + char *pos, int line) +{ + size_t slen; + char *str; + + str = wpa_config_parse_string(pos, &slen); + if (str == NULL || slen < 1 || slen > HOSTAPD_MAX_SSID_LEN) { + wpa_printf(MSG_ERROR, "Line %d: Invalid SSID '%s'", line, pos); + os_free(str); + return -1; + } + + os_memcpy(bss->osu_ssid, str, slen); + bss->osu_ssid_len = slen; + os_free(str); + + return 0; +} + + +static int hs20_parse_osu_server_uri(struct hostapd_bss_config *bss, + char *pos, int line) +{ + struct hs20_osu_provider *p; + + p = os_realloc_array(bss->hs20_osu_providers, + bss->hs20_osu_providers_count + 1, sizeof(*p)); + if (p == NULL) + return -1; + + bss->hs20_osu_providers = p; + bss->last_osu = &bss->hs20_osu_providers[bss->hs20_osu_providers_count]; + bss->hs20_osu_providers_count++; + os_memset(bss->last_osu, 0, sizeof(*p)); + bss->last_osu->server_uri = os_strdup(pos); + + return 0; +} + + +static int hs20_parse_osu_friendly_name(struct hostapd_bss_config *bss, + char *pos, int line) +{ + if (bss->last_osu == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line); + return -1; + } + + if (parse_lang_string(&bss->last_osu->friendly_name, + &bss->last_osu->friendly_name_count, pos)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid osu_friendly_name '%s'", + line, pos); + return -1; + } + + return 0; +} + + +static int hs20_parse_osu_nai(struct hostapd_bss_config *bss, + char *pos, int line) +{ + if (bss->last_osu == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line); + return -1; + } + + os_free(bss->last_osu->osu_nai); + bss->last_osu->osu_nai = os_strdup(pos); + if (bss->last_osu->osu_nai == NULL) + return -1; + + return 0; +} + + +static int hs20_parse_osu_method_list(struct hostapd_bss_config *bss, char *pos, + int line) +{ + if (bss->last_osu == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line); + return -1; + } + + if (hostapd_parse_intlist(&bss->last_osu->method_list, pos)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid osu_method_list", line); + return -1; + } + + return 0; +} + + +static int hs20_parse_osu_icon(struct hostapd_bss_config *bss, char *pos, + int line) +{ + char **n; + struct hs20_osu_provider *p = bss->last_osu; + + if (p == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line); + return -1; + } + + n = os_realloc_array(p->icons, p->icons_count + 1, sizeof(char *)); + if (n == NULL) + return -1; + p->icons = n; + p->icons[p->icons_count] = os_strdup(pos); + if (p->icons[p->icons_count] == NULL) + return -1; + p->icons_count++; + + return 0; +} + + +static int hs20_parse_osu_service_desc(struct hostapd_bss_config *bss, + char *pos, int line) +{ + if (bss->last_osu == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line); + return -1; + } + + if (parse_lang_string(&bss->last_osu->service_desc, + &bss->last_osu->service_desc_count, pos)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid osu_service_desc '%s'", + line, pos); + return -1; + } + + return 0; +} + #endif /* CONFIG_HS20 */ @@ -1588,1331 +1840,1330 @@ static int hostapd_config_fill(struct hostapd_config *conf, struct hostapd_bss_config *bss, char *buf, char *pos, int line) { - int errors = 0; - - { - if (os_strcmp(buf, "interface") == 0) { - os_strlcpy(conf->bss[0]->iface, pos, - sizeof(conf->bss[0]->iface)); - } else if (os_strcmp(buf, "bridge") == 0) { - os_strlcpy(bss->bridge, pos, sizeof(bss->bridge)); - } else if (os_strcmp(buf, "vlan_bridge") == 0) { - os_strlcpy(bss->vlan_bridge, pos, - sizeof(bss->vlan_bridge)); - } else if (os_strcmp(buf, "wds_bridge") == 0) { - os_strlcpy(bss->wds_bridge, pos, - sizeof(bss->wds_bridge)); - } else if (os_strcmp(buf, "driver") == 0) { - int j; - /* clear to get error below if setting is invalid */ - conf->driver = NULL; - for (j = 0; wpa_drivers[j]; j++) { - if (os_strcmp(pos, wpa_drivers[j]->name) == 0) - { - conf->driver = wpa_drivers[j]; - break; - } - } - if (conf->driver == NULL) { - wpa_printf(MSG_ERROR, "Line %d: invalid/" - "unknown driver '%s'", line, pos); - errors++; - } - } else if (os_strcmp(buf, "debug") == 0) { - wpa_printf(MSG_DEBUG, "Line %d: DEPRECATED: 'debug' " - "configuration variable is not used " - "anymore", line); - } else if (os_strcmp(buf, "logger_syslog_level") == 0) { - bss->logger_syslog_level = atoi(pos); - } else if (os_strcmp(buf, "logger_stdout_level") == 0) { - bss->logger_stdout_level = atoi(pos); - } else if (os_strcmp(buf, "logger_syslog") == 0) { - bss->logger_syslog = atoi(pos); - } else if (os_strcmp(buf, "logger_stdout") == 0) { - bss->logger_stdout = atoi(pos); - } else if (os_strcmp(buf, "dump_file") == 0) { - wpa_printf(MSG_INFO, "Line %d: DEPRECATED: 'dump_file' configuration variable is not used anymore", - line); - } else if (os_strcmp(buf, "ssid") == 0) { - bss->ssid.ssid_len = os_strlen(pos); - if (bss->ssid.ssid_len > HOSTAPD_MAX_SSID_LEN || - bss->ssid.ssid_len < 1) { - wpa_printf(MSG_ERROR, "Line %d: invalid SSID " - "'%s'", line, pos); - errors++; - } else { - os_memcpy(bss->ssid.ssid, pos, - bss->ssid.ssid_len); - bss->ssid.ssid_set = 1; - } - } else if (os_strcmp(buf, "ssid2") == 0) { - size_t slen; - char *str = wpa_config_parse_string(pos, &slen); - if (str == NULL || slen < 1 || - slen > HOSTAPD_MAX_SSID_LEN) { - wpa_printf(MSG_ERROR, "Line %d: invalid SSID " - "'%s'", line, pos); - errors++; - } else { - os_memcpy(bss->ssid.ssid, str, slen); - bss->ssid.ssid_len = slen; - bss->ssid.ssid_set = 1; + if (os_strcmp(buf, "interface") == 0) { + os_strlcpy(conf->bss[0]->iface, pos, + sizeof(conf->bss[0]->iface)); + } else if (os_strcmp(buf, "bridge") == 0) { + os_strlcpy(bss->bridge, pos, sizeof(bss->bridge)); + } else if (os_strcmp(buf, "vlan_bridge") == 0) { + os_strlcpy(bss->vlan_bridge, pos, sizeof(bss->vlan_bridge)); + } else if (os_strcmp(buf, "wds_bridge") == 0) { + os_strlcpy(bss->wds_bridge, pos, sizeof(bss->wds_bridge)); + } else if (os_strcmp(buf, "driver") == 0) { + int j; + /* clear to get error below if setting is invalid */ + conf->driver = NULL; + for (j = 0; wpa_drivers[j]; j++) { + if (os_strcmp(pos, wpa_drivers[j]->name) == 0) { + conf->driver = wpa_drivers[j]; + break; } + } + if (conf->driver == NULL) { + wpa_printf(MSG_ERROR, + "Line %d: invalid/unknown driver '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "debug") == 0) { + wpa_printf(MSG_DEBUG, "Line %d: DEPRECATED: 'debug' configuration variable is not used anymore", + line); + } else if (os_strcmp(buf, "logger_syslog_level") == 0) { + bss->logger_syslog_level = atoi(pos); + } else if (os_strcmp(buf, "logger_stdout_level") == 0) { + bss->logger_stdout_level = atoi(pos); + } else if (os_strcmp(buf, "logger_syslog") == 0) { + bss->logger_syslog = atoi(pos); + } else if (os_strcmp(buf, "logger_stdout") == 0) { + bss->logger_stdout = atoi(pos); + } else if (os_strcmp(buf, "dump_file") == 0) { + wpa_printf(MSG_INFO, "Line %d: DEPRECATED: 'dump_file' configuration variable is not used anymore", + line); + } else if (os_strcmp(buf, "ssid") == 0) { + bss->ssid.ssid_len = os_strlen(pos); + if (bss->ssid.ssid_len > HOSTAPD_MAX_SSID_LEN || + bss->ssid.ssid_len < 1) { + wpa_printf(MSG_ERROR, "Line %d: invalid SSID '%s'", + line, pos); + return 1; + } + os_memcpy(bss->ssid.ssid, pos, bss->ssid.ssid_len); + bss->ssid.ssid_set = 1; + } else if (os_strcmp(buf, "ssid2") == 0) { + size_t slen; + char *str = wpa_config_parse_string(pos, &slen); + if (str == NULL || slen < 1 || slen > HOSTAPD_MAX_SSID_LEN) { + wpa_printf(MSG_ERROR, "Line %d: invalid SSID '%s'", + line, pos); os_free(str); - } else if (os_strcmp(buf, "utf8_ssid") == 0) { - bss->ssid.utf8_ssid = atoi(pos) > 0; - } else if (os_strcmp(buf, "macaddr_acl") == 0) { - bss->macaddr_acl = atoi(pos); - if (bss->macaddr_acl != ACCEPT_UNLESS_DENIED && - bss->macaddr_acl != DENY_UNLESS_ACCEPTED && - bss->macaddr_acl != USE_EXTERNAL_RADIUS_AUTH) { - wpa_printf(MSG_ERROR, "Line %d: unknown " - "macaddr_acl %d", - line, bss->macaddr_acl); - } - } else if (os_strcmp(buf, "accept_mac_file") == 0) { - if (hostapd_config_read_maclist(pos, &bss->accept_mac, - &bss->num_accept_mac)) - { - wpa_printf(MSG_ERROR, "Line %d: Failed to " - "read accept_mac_file '%s'", - line, pos); - errors++; - } - } else if (os_strcmp(buf, "deny_mac_file") == 0) { - if (hostapd_config_read_maclist(pos, &bss->deny_mac, - &bss->num_deny_mac)) { - wpa_printf(MSG_ERROR, "Line %d: Failed to " - "read deny_mac_file '%s'", - line, pos); - errors++; - } - } else if (os_strcmp(buf, "wds_sta") == 0) { - bss->wds_sta = atoi(pos); - } else if (os_strcmp(buf, "start_disabled") == 0) { - bss->start_disabled = atoi(pos); - } else if (os_strcmp(buf, "ap_isolate") == 0) { - bss->isolate = atoi(pos); - } else if (os_strcmp(buf, "ap_max_inactivity") == 0) { - bss->ap_max_inactivity = atoi(pos); - } else if (os_strcmp(buf, "skip_inactivity_poll") == 0) { - bss->skip_inactivity_poll = atoi(pos); - } else if (os_strcmp(buf, "country_code") == 0) { - os_memcpy(conf->country, pos, 2); - /* FIX: make this configurable */ - conf->country[2] = ' '; - } else if (os_strcmp(buf, "ieee80211d") == 0) { - conf->ieee80211d = atoi(pos); - } else if (os_strcmp(buf, "ieee80211h") == 0) { - conf->ieee80211h = atoi(pos); - } else if (os_strcmp(buf, "ieee8021x") == 0) { - bss->ieee802_1x = atoi(pos); - } else if (os_strcmp(buf, "eapol_version") == 0) { - bss->eapol_version = atoi(pos); - if (bss->eapol_version < 1 || - bss->eapol_version > 2) { - wpa_printf(MSG_ERROR, "Line %d: invalid EAPOL " - "version (%d): '%s'.", - line, bss->eapol_version, pos); - errors++; - } else - wpa_printf(MSG_DEBUG, "eapol_version=%d", - bss->eapol_version); + return 1; + } + os_memcpy(bss->ssid.ssid, str, slen); + bss->ssid.ssid_len = slen; + bss->ssid.ssid_set = 1; + os_free(str); + } else if (os_strcmp(buf, "utf8_ssid") == 0) { + bss->ssid.utf8_ssid = atoi(pos) > 0; + } else if (os_strcmp(buf, "macaddr_acl") == 0) { + bss->macaddr_acl = atoi(pos); + if (bss->macaddr_acl != ACCEPT_UNLESS_DENIED && + bss->macaddr_acl != DENY_UNLESS_ACCEPTED && + bss->macaddr_acl != USE_EXTERNAL_RADIUS_AUTH) { + wpa_printf(MSG_ERROR, "Line %d: unknown macaddr_acl %d", + line, bss->macaddr_acl); + } + } else if (os_strcmp(buf, "accept_mac_file") == 0) { + if (hostapd_config_read_maclist(pos, &bss->accept_mac, + &bss->num_accept_mac)) { + wpa_printf(MSG_ERROR, "Line %d: Failed to read accept_mac_file '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "deny_mac_file") == 0) { + if (hostapd_config_read_maclist(pos, &bss->deny_mac, + &bss->num_deny_mac)) { + wpa_printf(MSG_ERROR, "Line %d: Failed to read deny_mac_file '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "wds_sta") == 0) { + bss->wds_sta = atoi(pos); + } else if (os_strcmp(buf, "start_disabled") == 0) { + bss->start_disabled = atoi(pos); + } else if (os_strcmp(buf, "ap_isolate") == 0) { + bss->isolate = atoi(pos); + } else if (os_strcmp(buf, "ap_max_inactivity") == 0) { + bss->ap_max_inactivity = atoi(pos); + } else if (os_strcmp(buf, "skip_inactivity_poll") == 0) { + bss->skip_inactivity_poll = atoi(pos); + } else if (os_strcmp(buf, "country_code") == 0) { + os_memcpy(conf->country, pos, 2); + /* FIX: make this configurable */ + conf->country[2] = ' '; + } else if (os_strcmp(buf, "ieee80211d") == 0) { + conf->ieee80211d = atoi(pos); + } else if (os_strcmp(buf, "ieee80211h") == 0) { + conf->ieee80211h = atoi(pos); + } else if (os_strcmp(buf, "ieee8021x") == 0) { + bss->ieee802_1x = atoi(pos); + } else if (os_strcmp(buf, "eapol_version") == 0) { + bss->eapol_version = atoi(pos); + if (bss->eapol_version < 1 || bss->eapol_version > 2) { + wpa_printf(MSG_ERROR, + "Line %d: invalid EAPOL version (%d): '%s'.", + line, bss->eapol_version, pos); + return 1; + } + wpa_printf(MSG_DEBUG, "eapol_version=%d", bss->eapol_version); #ifdef EAP_SERVER - } else if (os_strcmp(buf, "eap_authenticator") == 0) { - bss->eap_server = atoi(pos); - wpa_printf(MSG_ERROR, "Line %d: obsolete " - "eap_authenticator used; this has been " - "renamed to eap_server", line); - } else if (os_strcmp(buf, "eap_server") == 0) { - bss->eap_server = atoi(pos); - } else if (os_strcmp(buf, "eap_user_file") == 0) { - if (hostapd_config_read_eap_user(pos, bss)) - errors++; - } else if (os_strcmp(buf, "ca_cert") == 0) { - os_free(bss->ca_cert); - bss->ca_cert = os_strdup(pos); - } else if (os_strcmp(buf, "server_cert") == 0) { - os_free(bss->server_cert); - bss->server_cert = os_strdup(pos); - } else if (os_strcmp(buf, "private_key") == 0) { - os_free(bss->private_key); - bss->private_key = os_strdup(pos); - } else if (os_strcmp(buf, "private_key_passwd") == 0) { - os_free(bss->private_key_passwd); - bss->private_key_passwd = os_strdup(pos); - } else if (os_strcmp(buf, "check_crl") == 0) { - bss->check_crl = atoi(pos); - } else if (os_strcmp(buf, "ocsp_stapling_response") == 0) { - os_free(bss->ocsp_stapling_response); - bss->ocsp_stapling_response = os_strdup(pos); - } else if (os_strcmp(buf, "dh_file") == 0) { - os_free(bss->dh_file); - bss->dh_file = os_strdup(pos); - } else if (os_strcmp(buf, "fragment_size") == 0) { - bss->fragment_size = atoi(pos); + } else if (os_strcmp(buf, "eap_authenticator") == 0) { + bss->eap_server = atoi(pos); + wpa_printf(MSG_ERROR, "Line %d: obsolete eap_authenticator used; this has been renamed to eap_server", line); + } else if (os_strcmp(buf, "eap_server") == 0) { + bss->eap_server = atoi(pos); + } else if (os_strcmp(buf, "eap_user_file") == 0) { + if (hostapd_config_read_eap_user(pos, bss)) + return 1; + } else if (os_strcmp(buf, "ca_cert") == 0) { + os_free(bss->ca_cert); + bss->ca_cert = os_strdup(pos); + } else if (os_strcmp(buf, "server_cert") == 0) { + os_free(bss->server_cert); + bss->server_cert = os_strdup(pos); + } else if (os_strcmp(buf, "private_key") == 0) { + os_free(bss->private_key); + bss->private_key = os_strdup(pos); + } else if (os_strcmp(buf, "private_key_passwd") == 0) { + os_free(bss->private_key_passwd); + bss->private_key_passwd = os_strdup(pos); + } else if (os_strcmp(buf, "check_crl") == 0) { + bss->check_crl = atoi(pos); + } else if (os_strcmp(buf, "ocsp_stapling_response") == 0) { + os_free(bss->ocsp_stapling_response); + bss->ocsp_stapling_response = os_strdup(pos); + } else if (os_strcmp(buf, "dh_file") == 0) { + os_free(bss->dh_file); + bss->dh_file = os_strdup(pos); + } else if (os_strcmp(buf, "fragment_size") == 0) { + bss->fragment_size = atoi(pos); #ifdef EAP_SERVER_FAST - } else if (os_strcmp(buf, "pac_opaque_encr_key") == 0) { - os_free(bss->pac_opaque_encr_key); - bss->pac_opaque_encr_key = os_malloc(16); - if (bss->pac_opaque_encr_key == NULL) { - wpa_printf(MSG_ERROR, "Line %d: No memory for " - "pac_opaque_encr_key", line); - errors++; - } else if (hexstr2bin(pos, bss->pac_opaque_encr_key, - 16)) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "pac_opaque_encr_key", line); - errors++; - } - } else if (os_strcmp(buf, "eap_fast_a_id") == 0) { - size_t idlen = os_strlen(pos); - if (idlen & 1) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "eap_fast_a_id", line); - errors++; - } else { - os_free(bss->eap_fast_a_id); - bss->eap_fast_a_id = os_malloc(idlen / 2); - if (bss->eap_fast_a_id == NULL || - hexstr2bin(pos, bss->eap_fast_a_id, - idlen / 2)) { - wpa_printf(MSG_ERROR, "Line %d: " - "Failed to parse " - "eap_fast_a_id", line); - errors++; - } else - bss->eap_fast_a_id_len = idlen / 2; - } - } else if (os_strcmp(buf, "eap_fast_a_id_info") == 0) { - os_free(bss->eap_fast_a_id_info); - bss->eap_fast_a_id_info = os_strdup(pos); - } else if (os_strcmp(buf, "eap_fast_prov") == 0) { - bss->eap_fast_prov = atoi(pos); - } else if (os_strcmp(buf, "pac_key_lifetime") == 0) { - bss->pac_key_lifetime = atoi(pos); - } else if (os_strcmp(buf, "pac_key_refresh_time") == 0) { - bss->pac_key_refresh_time = atoi(pos); + } else if (os_strcmp(buf, "pac_opaque_encr_key") == 0) { + os_free(bss->pac_opaque_encr_key); + bss->pac_opaque_encr_key = os_malloc(16); + if (bss->pac_opaque_encr_key == NULL) { + wpa_printf(MSG_ERROR, + "Line %d: No memory for pac_opaque_encr_key", + line); + return 1; + } else if (hexstr2bin(pos, bss->pac_opaque_encr_key, 16)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid pac_opaque_encr_key", + line); + return 1; + } + } else if (os_strcmp(buf, "eap_fast_a_id") == 0) { + size_t idlen = os_strlen(pos); + if (idlen & 1) { + wpa_printf(MSG_ERROR, "Line %d: Invalid eap_fast_a_id", + line); + return 1; + } + os_free(bss->eap_fast_a_id); + bss->eap_fast_a_id = os_malloc(idlen / 2); + if (bss->eap_fast_a_id == NULL || + hexstr2bin(pos, bss->eap_fast_a_id, idlen / 2)) { + wpa_printf(MSG_ERROR, "Line %d: Failed to parse eap_fast_a_id", + line); + os_free(bss->eap_fast_a_id); + bss->eap_fast_a_id = NULL; + return 1; + } else { + bss->eap_fast_a_id_len = idlen / 2; + } + } else if (os_strcmp(buf, "eap_fast_a_id_info") == 0) { + os_free(bss->eap_fast_a_id_info); + bss->eap_fast_a_id_info = os_strdup(pos); + } else if (os_strcmp(buf, "eap_fast_prov") == 0) { + bss->eap_fast_prov = atoi(pos); + } else if (os_strcmp(buf, "pac_key_lifetime") == 0) { + bss->pac_key_lifetime = atoi(pos); + } else if (os_strcmp(buf, "pac_key_refresh_time") == 0) { + bss->pac_key_refresh_time = atoi(pos); #endif /* EAP_SERVER_FAST */ #ifdef EAP_SERVER_SIM - } else if (os_strcmp(buf, "eap_sim_db") == 0) { - os_free(bss->eap_sim_db); - bss->eap_sim_db = os_strdup(pos); - } else if (os_strcmp(buf, "eap_sim_aka_result_ind") == 0) { - bss->eap_sim_aka_result_ind = atoi(pos); + } else if (os_strcmp(buf, "eap_sim_db") == 0) { + os_free(bss->eap_sim_db); + bss->eap_sim_db = os_strdup(pos); + } else if (os_strcmp(buf, "eap_sim_aka_result_ind") == 0) { + bss->eap_sim_aka_result_ind = atoi(pos); #endif /* EAP_SERVER_SIM */ #ifdef EAP_SERVER_TNC - } else if (os_strcmp(buf, "tnc") == 0) { - bss->tnc = atoi(pos); + } else if (os_strcmp(buf, "tnc") == 0) { + bss->tnc = atoi(pos); #endif /* EAP_SERVER_TNC */ #ifdef EAP_SERVER_PWD - } else if (os_strcmp(buf, "pwd_group") == 0) { - bss->pwd_group = atoi(pos); + } else if (os_strcmp(buf, "pwd_group") == 0) { + bss->pwd_group = atoi(pos); #endif /* EAP_SERVER_PWD */ #endif /* EAP_SERVER */ - } else if (os_strcmp(buf, "eap_message") == 0) { - char *term; - bss->eap_req_id_text = os_strdup(pos); - if (bss->eap_req_id_text == NULL) { - wpa_printf(MSG_ERROR, "Line %d: Failed to " - "allocate memory for " - "eap_req_id_text", line); - errors++; - return errors; - } - bss->eap_req_id_text_len = - os_strlen(bss->eap_req_id_text); - term = os_strstr(bss->eap_req_id_text, "\\0"); - if (term) { - *term++ = '\0'; - os_memmove(term, term + 1, - bss->eap_req_id_text_len - - (term - bss->eap_req_id_text) - 1); - bss->eap_req_id_text_len--; - } - } else if (os_strcmp(buf, "wep_key_len_broadcast") == 0) { - bss->default_wep_key_len = atoi(pos); - if (bss->default_wep_key_len > 13) { - wpa_printf(MSG_ERROR, "Line %d: invalid WEP " - "key len %lu (= %lu bits)", line, - (unsigned long) - bss->default_wep_key_len, - (unsigned long) - bss->default_wep_key_len * 8); - errors++; - } - } else if (os_strcmp(buf, "wep_key_len_unicast") == 0) { - bss->individual_wep_key_len = atoi(pos); - if (bss->individual_wep_key_len < 0 || - bss->individual_wep_key_len > 13) { - wpa_printf(MSG_ERROR, "Line %d: invalid WEP " - "key len %d (= %d bits)", line, - bss->individual_wep_key_len, - bss->individual_wep_key_len * 8); - errors++; - } - } else if (os_strcmp(buf, "wep_rekey_period") == 0) { - bss->wep_rekeying_period = atoi(pos); - if (bss->wep_rekeying_period < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "period %d", - line, bss->wep_rekeying_period); - errors++; - } - } else if (os_strcmp(buf, "eap_reauth_period") == 0) { - bss->eap_reauth_period = atoi(pos); - if (bss->eap_reauth_period < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "period %d", - line, bss->eap_reauth_period); - errors++; - } - } else if (os_strcmp(buf, "eapol_key_index_workaround") == 0) { - bss->eapol_key_index_workaround = atoi(pos); + } else if (os_strcmp(buf, "eap_message") == 0) { + char *term; + os_free(bss->eap_req_id_text); + bss->eap_req_id_text = os_strdup(pos); + if (bss->eap_req_id_text == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Failed to allocate memory for eap_req_id_text", + line); + return 1; + } + bss->eap_req_id_text_len = os_strlen(bss->eap_req_id_text); + term = os_strstr(bss->eap_req_id_text, "\\0"); + if (term) { + *term++ = '\0'; + os_memmove(term, term + 1, + bss->eap_req_id_text_len - + (term - bss->eap_req_id_text) - 1); + bss->eap_req_id_text_len--; + } + } else if (os_strcmp(buf, "wep_key_len_broadcast") == 0) { + bss->default_wep_key_len = atoi(pos); + if (bss->default_wep_key_len > 13) { + wpa_printf(MSG_ERROR, "Line %d: invalid WEP key len %lu (= %lu bits)", + line, + (unsigned long) bss->default_wep_key_len, + (unsigned long) + bss->default_wep_key_len * 8); + return 1; + } + } else if (os_strcmp(buf, "wep_key_len_unicast") == 0) { + bss->individual_wep_key_len = atoi(pos); + if (bss->individual_wep_key_len < 0 || + bss->individual_wep_key_len > 13) { + wpa_printf(MSG_ERROR, "Line %d: invalid WEP key len %d (= %d bits)", + line, bss->individual_wep_key_len, + bss->individual_wep_key_len * 8); + return 1; + } + } else if (os_strcmp(buf, "wep_rekey_period") == 0) { + bss->wep_rekeying_period = atoi(pos); + if (bss->wep_rekeying_period < 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid period %d", + line, bss->wep_rekeying_period); + return 1; + } + } else if (os_strcmp(buf, "eap_reauth_period") == 0) { + bss->eap_reauth_period = atoi(pos); + if (bss->eap_reauth_period < 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid period %d", + line, bss->eap_reauth_period); + return 1; + } + } else if (os_strcmp(buf, "eapol_key_index_workaround") == 0) { + bss->eapol_key_index_workaround = atoi(pos); #ifdef CONFIG_IAPP - } else if (os_strcmp(buf, "iapp_interface") == 0) { - bss->ieee802_11f = 1; - os_strlcpy(bss->iapp_iface, pos, - sizeof(bss->iapp_iface)); + } else if (os_strcmp(buf, "iapp_interface") == 0) { + bss->ieee802_11f = 1; + os_strlcpy(bss->iapp_iface, pos, sizeof(bss->iapp_iface)); #endif /* CONFIG_IAPP */ - } else if (os_strcmp(buf, "own_ip_addr") == 0) { - if (hostapd_parse_ip_addr(pos, &bss->own_ip_addr)) { - wpa_printf(MSG_ERROR, "Line %d: invalid IP " - "address '%s'", line, pos); - errors++; - } - } else if (os_strcmp(buf, "nas_identifier") == 0) { - bss->nas_identifier = os_strdup(pos); + } else if (os_strcmp(buf, "own_ip_addr") == 0) { + if (hostapd_parse_ip_addr(pos, &bss->own_ip_addr)) { + wpa_printf(MSG_ERROR, + "Line %d: invalid IP address '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "nas_identifier") == 0) { + os_free(bss->nas_identifier); + bss->nas_identifier = os_strdup(pos); #ifndef CONFIG_NO_RADIUS - } else if (os_strcmp(buf, "auth_server_addr") == 0) { - if (hostapd_config_read_radius_addr( - &bss->radius->auth_servers, - &bss->radius->num_auth_servers, pos, 1812, - &bss->radius->auth_server)) { - wpa_printf(MSG_ERROR, "Line %d: invalid IP " - "address '%s'", line, pos); - errors++; - } - } else if (bss->radius->auth_server && - os_strcmp(buf, "auth_server_port") == 0) { - bss->radius->auth_server->port = atoi(pos); - } else if (bss->radius->auth_server && - os_strcmp(buf, "auth_server_shared_secret") == 0) { - int len = os_strlen(pos); - if (len == 0) { - /* RFC 2865, Ch. 3 */ - wpa_printf(MSG_ERROR, "Line %d: empty shared " - "secret is not allowed.", line); - errors++; - } - bss->radius->auth_server->shared_secret = - (u8 *) os_strdup(pos); - bss->radius->auth_server->shared_secret_len = len; - } else if (os_strcmp(buf, "acct_server_addr") == 0) { - if (hostapd_config_read_radius_addr( - &bss->radius->acct_servers, - &bss->radius->num_acct_servers, pos, 1813, - &bss->radius->acct_server)) { - wpa_printf(MSG_ERROR, "Line %d: invalid IP " - "address '%s'", line, pos); - errors++; - } - } else if (bss->radius->acct_server && - os_strcmp(buf, "acct_server_port") == 0) { - bss->radius->acct_server->port = atoi(pos); - } else if (bss->radius->acct_server && - os_strcmp(buf, "acct_server_shared_secret") == 0) { - int len = os_strlen(pos); - if (len == 0) { - /* RFC 2865, Ch. 3 */ - wpa_printf(MSG_ERROR, "Line %d: empty shared " - "secret is not allowed.", line); - errors++; - } - bss->radius->acct_server->shared_secret = - (u8 *) os_strdup(pos); - bss->radius->acct_server->shared_secret_len = len; - } else if (os_strcmp(buf, "radius_retry_primary_interval") == - 0) { - bss->radius->retry_primary_interval = atoi(pos); - } else if (os_strcmp(buf, "radius_acct_interim_interval") == 0) - { - bss->acct_interim_interval = atoi(pos); - } else if (os_strcmp(buf, "radius_request_cui") == 0) { - bss->radius_request_cui = atoi(pos); - } else if (os_strcmp(buf, "radius_auth_req_attr") == 0) { - struct hostapd_radius_attr *attr, *a; - attr = hostapd_parse_radius_attr(pos); - if (attr == NULL) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "radius_auth_req_attr", line); - errors++; - } else if (bss->radius_auth_req_attr == NULL) { - bss->radius_auth_req_attr = attr; - } else { - a = bss->radius_auth_req_attr; - while (a->next) - a = a->next; - a->next = attr; - } - } else if (os_strcmp(buf, "radius_acct_req_attr") == 0) { - struct hostapd_radius_attr *attr, *a; - attr = hostapd_parse_radius_attr(pos); - if (attr == NULL) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "radius_acct_req_attr", line); - errors++; - } else if (bss->radius_acct_req_attr == NULL) { - bss->radius_acct_req_attr = attr; - } else { - a = bss->radius_acct_req_attr; - while (a->next) - a = a->next; - a->next = attr; - } - } else if (os_strcmp(buf, "radius_das_port") == 0) { - bss->radius_das_port = atoi(pos); - } else if (os_strcmp(buf, "radius_das_client") == 0) { - if (hostapd_parse_das_client(bss, pos) < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "DAS client", line); - errors++; - } - } else if (os_strcmp(buf, "radius_das_time_window") == 0) { - bss->radius_das_time_window = atoi(pos); - } else if (os_strcmp(buf, "radius_das_require_event_timestamp") - == 0) { - bss->radius_das_require_event_timestamp = atoi(pos); + } else if (os_strcmp(buf, "auth_server_addr") == 0) { + if (hostapd_config_read_radius_addr( + &bss->radius->auth_servers, + &bss->radius->num_auth_servers, pos, 1812, + &bss->radius->auth_server)) { + wpa_printf(MSG_ERROR, + "Line %d: invalid IP address '%s'", + line, pos); + return 1; + } + } else if (bss->radius->auth_server && + os_strcmp(buf, "auth_server_port") == 0) { + bss->radius->auth_server->port = atoi(pos); + } else if (bss->radius->auth_server && + os_strcmp(buf, "auth_server_shared_secret") == 0) { + int len = os_strlen(pos); + if (len == 0) { + /* RFC 2865, Ch. 3 */ + wpa_printf(MSG_ERROR, "Line %d: empty shared secret is not allowed", + line); + return 1; + } + os_free(bss->radius->auth_server->shared_secret); + bss->radius->auth_server->shared_secret = (u8 *) os_strdup(pos); + bss->radius->auth_server->shared_secret_len = len; + } else if (os_strcmp(buf, "acct_server_addr") == 0) { + if (hostapd_config_read_radius_addr( + &bss->radius->acct_servers, + &bss->radius->num_acct_servers, pos, 1813, + &bss->radius->acct_server)) { + wpa_printf(MSG_ERROR, + "Line %d: invalid IP address '%s'", + line, pos); + return 1; + } + } else if (bss->radius->acct_server && + os_strcmp(buf, "acct_server_port") == 0) { + bss->radius->acct_server->port = atoi(pos); + } else if (bss->radius->acct_server && + os_strcmp(buf, "acct_server_shared_secret") == 0) { + int len = os_strlen(pos); + if (len == 0) { + /* RFC 2865, Ch. 3 */ + wpa_printf(MSG_ERROR, "Line %d: empty shared secret is not allowed", + line); + return 1; + } + os_free(bss->radius->acct_server->shared_secret); + bss->radius->acct_server->shared_secret = (u8 *) os_strdup(pos); + bss->radius->acct_server->shared_secret_len = len; + } else if (os_strcmp(buf, "radius_retry_primary_interval") == 0) { + bss->radius->retry_primary_interval = atoi(pos); + } else if (os_strcmp(buf, "radius_acct_interim_interval") == 0) { + bss->acct_interim_interval = atoi(pos); + } else if (os_strcmp(buf, "radius_request_cui") == 0) { + bss->radius_request_cui = atoi(pos); + } else if (os_strcmp(buf, "radius_auth_req_attr") == 0) { + struct hostapd_radius_attr *attr, *a; + attr = hostapd_parse_radius_attr(pos); + if (attr == NULL) { + wpa_printf(MSG_ERROR, + "Line %d: invalid radius_auth_req_attr", + line); + return 1; + } else if (bss->radius_auth_req_attr == NULL) { + bss->radius_auth_req_attr = attr; + } else { + a = bss->radius_auth_req_attr; + while (a->next) + a = a->next; + a->next = attr; + } + } else if (os_strcmp(buf, "radius_acct_req_attr") == 0) { + struct hostapd_radius_attr *attr, *a; + attr = hostapd_parse_radius_attr(pos); + if (attr == NULL) { + wpa_printf(MSG_ERROR, + "Line %d: invalid radius_acct_req_attr", + line); + return 1; + } else if (bss->radius_acct_req_attr == NULL) { + bss->radius_acct_req_attr = attr; + } else { + a = bss->radius_acct_req_attr; + while (a->next) + a = a->next; + a->next = attr; + } + } else if (os_strcmp(buf, "radius_das_port") == 0) { + bss->radius_das_port = atoi(pos); + } else if (os_strcmp(buf, "radius_das_client") == 0) { + if (hostapd_parse_das_client(bss, pos) < 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid DAS client", + line); + return 1; + } + } else if (os_strcmp(buf, "radius_das_time_window") == 0) { + bss->radius_das_time_window = atoi(pos); + } else if (os_strcmp(buf, "radius_das_require_event_timestamp") == 0) { + bss->radius_das_require_event_timestamp = atoi(pos); #endif /* CONFIG_NO_RADIUS */ - } else if (os_strcmp(buf, "auth_algs") == 0) { - bss->auth_algs = atoi(pos); - if (bss->auth_algs == 0) { - wpa_printf(MSG_ERROR, "Line %d: no " - "authentication algorithms allowed", - line); - errors++; - } - } else if (os_strcmp(buf, "max_num_sta") == 0) { - bss->max_num_sta = atoi(pos); - if (bss->max_num_sta < 0 || - bss->max_num_sta > MAX_STA_COUNT) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "max_num_sta=%d; allowed range " - "0..%d", line, bss->max_num_sta, - MAX_STA_COUNT); - errors++; - } - } else if (os_strcmp(buf, "wpa") == 0) { - bss->wpa = atoi(pos); - } else if (os_strcmp(buf, "wpa_group_rekey") == 0) { - bss->wpa_group_rekey = atoi(pos); - } else if (os_strcmp(buf, "wpa_strict_rekey") == 0) { - bss->wpa_strict_rekey = atoi(pos); - } else if (os_strcmp(buf, "wpa_gmk_rekey") == 0) { - bss->wpa_gmk_rekey = atoi(pos); - } else if (os_strcmp(buf, "wpa_ptk_rekey") == 0) { - bss->wpa_ptk_rekey = atoi(pos); - } else if (os_strcmp(buf, "wpa_passphrase") == 0) { - int len = os_strlen(pos); - if (len < 8 || len > 63) { - wpa_printf(MSG_ERROR, "Line %d: invalid WPA " - "passphrase length %d (expected " - "8..63)", line, len); - errors++; - } else { - os_free(bss->ssid.wpa_passphrase); - bss->ssid.wpa_passphrase = os_strdup(pos); - if (bss->ssid.wpa_passphrase) { - os_free(bss->ssid.wpa_psk); - bss->ssid.wpa_psk = NULL; - bss->ssid.wpa_passphrase_set = 1; - } - } - } else if (os_strcmp(buf, "wpa_psk") == 0) { + } else if (os_strcmp(buf, "auth_algs") == 0) { + bss->auth_algs = atoi(pos); + if (bss->auth_algs == 0) { + wpa_printf(MSG_ERROR, "Line %d: no authentication algorithms allowed", + line); + return 1; + } + } else if (os_strcmp(buf, "max_num_sta") == 0) { + bss->max_num_sta = atoi(pos); + if (bss->max_num_sta < 0 || + bss->max_num_sta > MAX_STA_COUNT) { + wpa_printf(MSG_ERROR, "Line %d: Invalid max_num_sta=%d; allowed range 0..%d", + line, bss->max_num_sta, MAX_STA_COUNT); + return 1; + } + } else if (os_strcmp(buf, "wpa") == 0) { + bss->wpa = atoi(pos); + } else if (os_strcmp(buf, "wpa_group_rekey") == 0) { + bss->wpa_group_rekey = atoi(pos); + } else if (os_strcmp(buf, "wpa_strict_rekey") == 0) { + bss->wpa_strict_rekey = atoi(pos); + } else if (os_strcmp(buf, "wpa_gmk_rekey") == 0) { + bss->wpa_gmk_rekey = atoi(pos); + } else if (os_strcmp(buf, "wpa_ptk_rekey") == 0) { + bss->wpa_ptk_rekey = atoi(pos); + } else if (os_strcmp(buf, "wpa_passphrase") == 0) { + int len = os_strlen(pos); + if (len < 8 || len > 63) { + wpa_printf(MSG_ERROR, "Line %d: invalid WPA passphrase length %d (expected 8..63)", + line, len); + return 1; + } + os_free(bss->ssid.wpa_passphrase); + bss->ssid.wpa_passphrase = os_strdup(pos); + if (bss->ssid.wpa_passphrase) { os_free(bss->ssid.wpa_psk); - bss->ssid.wpa_psk = - os_zalloc(sizeof(struct hostapd_wpa_psk)); - if (bss->ssid.wpa_psk == NULL) - errors++; - else if (hexstr2bin(pos, bss->ssid.wpa_psk->psk, - PMK_LEN) || - pos[PMK_LEN * 2] != '\0') { - wpa_printf(MSG_ERROR, "Line %d: Invalid PSK " - "'%s'.", line, pos); - errors++; - } else { - bss->ssid.wpa_psk->group = 1; - os_free(bss->ssid.wpa_passphrase); - bss->ssid.wpa_passphrase = NULL; - bss->ssid.wpa_psk_set = 1; - } - } else if (os_strcmp(buf, "wpa_psk_file") == 0) { - os_free(bss->ssid.wpa_psk_file); - bss->ssid.wpa_psk_file = os_strdup(pos); - if (!bss->ssid.wpa_psk_file) { - wpa_printf(MSG_ERROR, "Line %d: allocation " - "failed", line); - errors++; - } - } else if (os_strcmp(buf, "wpa_key_mgmt") == 0) { - bss->wpa_key_mgmt = - hostapd_config_parse_key_mgmt(line, pos); - if (bss->wpa_key_mgmt == -1) - errors++; - } else if (os_strcmp(buf, "wpa_psk_radius") == 0) { - bss->wpa_psk_radius = atoi(pos); - if (bss->wpa_psk_radius != PSK_RADIUS_IGNORED && - bss->wpa_psk_radius != PSK_RADIUS_ACCEPTED && - bss->wpa_psk_radius != PSK_RADIUS_REQUIRED) { - wpa_printf(MSG_ERROR, "Line %d: unknown " - "wpa_psk_radius %d", - line, bss->wpa_psk_radius); - errors++; - } - } else if (os_strcmp(buf, "wpa_pairwise") == 0) { - bss->wpa_pairwise = - hostapd_config_parse_cipher(line, pos); - if (bss->wpa_pairwise == -1 || - bss->wpa_pairwise == 0) - errors++; - else if (bss->wpa_pairwise & - (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | - WPA_CIPHER_WEP104)) { - wpa_printf(MSG_ERROR, "Line %d: unsupported " - "pairwise cipher suite '%s'", - bss->wpa_pairwise, pos); - errors++; - } - } else if (os_strcmp(buf, "rsn_pairwise") == 0) { - bss->rsn_pairwise = - hostapd_config_parse_cipher(line, pos); - if (bss->rsn_pairwise == -1 || - bss->rsn_pairwise == 0) - errors++; - else if (bss->rsn_pairwise & - (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | - WPA_CIPHER_WEP104)) { - wpa_printf(MSG_ERROR, "Line %d: unsupported " - "pairwise cipher suite '%s'", - bss->rsn_pairwise, pos); - errors++; - } + bss->ssid.wpa_psk = NULL; + bss->ssid.wpa_passphrase_set = 1; + } + } else if (os_strcmp(buf, "wpa_psk") == 0) { + os_free(bss->ssid.wpa_psk); + bss->ssid.wpa_psk = os_zalloc(sizeof(struct hostapd_wpa_psk)); + if (bss->ssid.wpa_psk == NULL) + return 1; + if (hexstr2bin(pos, bss->ssid.wpa_psk->psk, PMK_LEN) || + pos[PMK_LEN * 2] != '\0') { + wpa_printf(MSG_ERROR, "Line %d: Invalid PSK '%s'.", + line, pos); + os_free(bss->ssid.wpa_psk); + bss->ssid.wpa_psk = NULL; + return 1; + } + bss->ssid.wpa_psk->group = 1; + os_free(bss->ssid.wpa_passphrase); + bss->ssid.wpa_passphrase = NULL; + bss->ssid.wpa_psk_set = 1; + } else if (os_strcmp(buf, "wpa_psk_file") == 0) { + os_free(bss->ssid.wpa_psk_file); + bss->ssid.wpa_psk_file = os_strdup(pos); + if (!bss->ssid.wpa_psk_file) { + wpa_printf(MSG_ERROR, "Line %d: allocation failed", + line); + return 1; + } + } else if (os_strcmp(buf, "wpa_key_mgmt") == 0) { + bss->wpa_key_mgmt = hostapd_config_parse_key_mgmt(line, pos); + if (bss->wpa_key_mgmt == -1) + return 1; + } else if (os_strcmp(buf, "wpa_psk_radius") == 0) { + bss->wpa_psk_radius = atoi(pos); + if (bss->wpa_psk_radius != PSK_RADIUS_IGNORED && + bss->wpa_psk_radius != PSK_RADIUS_ACCEPTED && + bss->wpa_psk_radius != PSK_RADIUS_REQUIRED) { + wpa_printf(MSG_ERROR, + "Line %d: unknown wpa_psk_radius %d", + line, bss->wpa_psk_radius); + return 1; + } + } else if (os_strcmp(buf, "wpa_pairwise") == 0) { + bss->wpa_pairwise = hostapd_config_parse_cipher(line, pos); + if (bss->wpa_pairwise == -1 || bss->wpa_pairwise == 0) + return 1; + if (bss->wpa_pairwise & + (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104)) { + wpa_printf(MSG_ERROR, "Line %d: unsupported pairwise cipher suite '%s'", + bss->wpa_pairwise, pos); + return 1; + } + } else if (os_strcmp(buf, "rsn_pairwise") == 0) { + bss->rsn_pairwise = hostapd_config_parse_cipher(line, pos); + if (bss->rsn_pairwise == -1 || bss->rsn_pairwise == 0) + return 1; + if (bss->rsn_pairwise & + (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104)) { + wpa_printf(MSG_ERROR, "Line %d: unsupported pairwise cipher suite '%s'", + bss->rsn_pairwise, pos); + return 1; + } #ifdef CONFIG_RSN_PREAUTH - } else if (os_strcmp(buf, "rsn_preauth") == 0) { - bss->rsn_preauth = atoi(pos); - } else if (os_strcmp(buf, "rsn_preauth_interfaces") == 0) { - bss->rsn_preauth_interfaces = os_strdup(pos); + } else if (os_strcmp(buf, "rsn_preauth") == 0) { + bss->rsn_preauth = atoi(pos); + } else if (os_strcmp(buf, "rsn_preauth_interfaces") == 0) { + os_free(bss->rsn_preauth_interfaces); + bss->rsn_preauth_interfaces = os_strdup(pos); #endif /* CONFIG_RSN_PREAUTH */ #ifdef CONFIG_PEERKEY - } else if (os_strcmp(buf, "peerkey") == 0) { - bss->peerkey = atoi(pos); + } else if (os_strcmp(buf, "peerkey") == 0) { + bss->peerkey = atoi(pos); #endif /* CONFIG_PEERKEY */ #ifdef CONFIG_IEEE80211R - } else if (os_strcmp(buf, "mobility_domain") == 0) { - if (os_strlen(pos) != 2 * MOBILITY_DOMAIN_ID_LEN || - hexstr2bin(pos, bss->mobility_domain, - MOBILITY_DOMAIN_ID_LEN) != 0) { - wpa_printf(MSG_DEBUG, "Line %d: Invalid " - "mobility_domain '%s'", line, pos); - errors++; - return errors; - } - } else if (os_strcmp(buf, "r1_key_holder") == 0) { - if (os_strlen(pos) != 2 * FT_R1KH_ID_LEN || - hexstr2bin(pos, bss->r1_key_holder, - FT_R1KH_ID_LEN) != 0) { - wpa_printf(MSG_DEBUG, "Line %d: Invalid " - "r1_key_holder '%s'", line, pos); - errors++; - return errors; - } - } else if (os_strcmp(buf, "r0_key_lifetime") == 0) { - bss->r0_key_lifetime = atoi(pos); - } else if (os_strcmp(buf, "reassociation_deadline") == 0) { - bss->reassociation_deadline = atoi(pos); - } else if (os_strcmp(buf, "r0kh") == 0) { - if (add_r0kh(bss, pos) < 0) { - wpa_printf(MSG_DEBUG, "Line %d: Invalid " - "r0kh '%s'", line, pos); - errors++; - return errors; - } - } else if (os_strcmp(buf, "r1kh") == 0) { - if (add_r1kh(bss, pos) < 0) { - wpa_printf(MSG_DEBUG, "Line %d: Invalid " - "r1kh '%s'", line, pos); - errors++; - return errors; - } - } else if (os_strcmp(buf, "pmk_r1_push") == 0) { - bss->pmk_r1_push = atoi(pos); - } else if (os_strcmp(buf, "ft_over_ds") == 0) { - bss->ft_over_ds = atoi(pos); + } else if (os_strcmp(buf, "mobility_domain") == 0) { + if (os_strlen(pos) != 2 * MOBILITY_DOMAIN_ID_LEN || + hexstr2bin(pos, bss->mobility_domain, + MOBILITY_DOMAIN_ID_LEN) != 0) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid mobility_domain '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "r1_key_holder") == 0) { + if (os_strlen(pos) != 2 * FT_R1KH_ID_LEN || + hexstr2bin(pos, bss->r1_key_holder, FT_R1KH_ID_LEN) != 0) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid r1_key_holder '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "r0_key_lifetime") == 0) { + bss->r0_key_lifetime = atoi(pos); + } else if (os_strcmp(buf, "reassociation_deadline") == 0) { + bss->reassociation_deadline = atoi(pos); + } else if (os_strcmp(buf, "r0kh") == 0) { + if (add_r0kh(bss, pos) < 0) { + wpa_printf(MSG_DEBUG, "Line %d: Invalid r0kh '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "r1kh") == 0) { + if (add_r1kh(bss, pos) < 0) { + wpa_printf(MSG_DEBUG, "Line %d: Invalid r1kh '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "pmk_r1_push") == 0) { + bss->pmk_r1_push = atoi(pos); + } else if (os_strcmp(buf, "ft_over_ds") == 0) { + bss->ft_over_ds = atoi(pos); #endif /* CONFIG_IEEE80211R */ #ifndef CONFIG_NO_CTRL_IFACE - } else if (os_strcmp(buf, "ctrl_interface") == 0) { - os_free(bss->ctrl_interface); - bss->ctrl_interface = os_strdup(pos); - } else if (os_strcmp(buf, "ctrl_interface_group") == 0) { + } else if (os_strcmp(buf, "ctrl_interface") == 0) { + os_free(bss->ctrl_interface); + bss->ctrl_interface = os_strdup(pos); + } else if (os_strcmp(buf, "ctrl_interface_group") == 0) { #ifndef CONFIG_NATIVE_WINDOWS - struct group *grp; - char *endp; - const char *group = pos; - - grp = getgrnam(group); - if (grp) { - bss->ctrl_interface_gid = grp->gr_gid; - bss->ctrl_interface_gid_set = 1; - wpa_printf(MSG_DEBUG, "ctrl_interface_group=%d" - " (from group name '%s')", - bss->ctrl_interface_gid, group); - return errors; - } + struct group *grp; + char *endp; + const char *group = pos; - /* Group name not found - try to parse this as gid */ - bss->ctrl_interface_gid = strtol(group, &endp, 10); - if (*group == '\0' || *endp != '\0') { - wpa_printf(MSG_DEBUG, "Line %d: Invalid group " - "'%s'", line, group); - errors++; - return errors; - } + grp = getgrnam(group); + if (grp) { + bss->ctrl_interface_gid = grp->gr_gid; bss->ctrl_interface_gid_set = 1; - wpa_printf(MSG_DEBUG, "ctrl_interface_group=%d", - bss->ctrl_interface_gid); + wpa_printf(MSG_DEBUG, "ctrl_interface_group=%d (from group name '%s')", + bss->ctrl_interface_gid, group); + return 0; + } + + /* Group name not found - try to parse this as gid */ + bss->ctrl_interface_gid = strtol(group, &endp, 10); + if (*group == '\0' || *endp != '\0') { + wpa_printf(MSG_DEBUG, "Line %d: Invalid group '%s'", + line, group); + return 1; + } + bss->ctrl_interface_gid_set = 1; + wpa_printf(MSG_DEBUG, "ctrl_interface_group=%d", + bss->ctrl_interface_gid); #endif /* CONFIG_NATIVE_WINDOWS */ #endif /* CONFIG_NO_CTRL_IFACE */ #ifdef RADIUS_SERVER - } else if (os_strcmp(buf, "radius_server_clients") == 0) { - os_free(bss->radius_server_clients); - bss->radius_server_clients = os_strdup(pos); - } else if (os_strcmp(buf, "radius_server_auth_port") == 0) { - bss->radius_server_auth_port = atoi(pos); - } else if (os_strcmp(buf, "radius_server_ipv6") == 0) { - bss->radius_server_ipv6 = atoi(pos); + } else if (os_strcmp(buf, "radius_server_clients") == 0) { + os_free(bss->radius_server_clients); + bss->radius_server_clients = os_strdup(pos); + } else if (os_strcmp(buf, "radius_server_auth_port") == 0) { + bss->radius_server_auth_port = atoi(pos); + } else if (os_strcmp(buf, "radius_server_acct_port") == 0) { + bss->radius_server_acct_port = atoi(pos); + } else if (os_strcmp(buf, "radius_server_ipv6") == 0) { + bss->radius_server_ipv6 = atoi(pos); #endif /* RADIUS_SERVER */ - } else if (os_strcmp(buf, "test_socket") == 0) { - os_free(bss->test_socket); - bss->test_socket = os_strdup(pos); - } else if (os_strcmp(buf, "use_pae_group_addr") == 0) { - bss->use_pae_group_addr = atoi(pos); - } else if (os_strcmp(buf, "hw_mode") == 0) { - if (os_strcmp(pos, "a") == 0) - conf->hw_mode = HOSTAPD_MODE_IEEE80211A; - else if (os_strcmp(pos, "b") == 0) - conf->hw_mode = HOSTAPD_MODE_IEEE80211B; - else if (os_strcmp(pos, "g") == 0) - conf->hw_mode = HOSTAPD_MODE_IEEE80211G; - else if (os_strcmp(pos, "ad") == 0) - conf->hw_mode = HOSTAPD_MODE_IEEE80211AD; - else { - wpa_printf(MSG_ERROR, "Line %d: unknown " - "hw_mode '%s'", line, pos); - errors++; - } - } else if (os_strcmp(buf, "wps_rf_bands") == 0) { - if (os_strcmp(pos, "a") == 0) - bss->wps_rf_bands = WPS_RF_50GHZ; - else if (os_strcmp(pos, "g") == 0 || - os_strcmp(pos, "b") == 0) - bss->wps_rf_bands = WPS_RF_24GHZ; - else if (os_strcmp(pos, "ag") == 0 || - os_strcmp(pos, "ga") == 0) - bss->wps_rf_bands = - WPS_RF_24GHZ | WPS_RF_50GHZ; - else { - wpa_printf(MSG_ERROR, "Line %d: unknown " - "wps_rf_band '%s'", line, pos); - errors++; - } - } else if (os_strcmp(buf, "channel") == 0) { - if (os_strcmp(pos, "acs_survey") == 0) { + } else if (os_strcmp(buf, "test_socket") == 0) { + os_free(bss->test_socket); + bss->test_socket = os_strdup(pos); + } else if (os_strcmp(buf, "use_pae_group_addr") == 0) { + bss->use_pae_group_addr = atoi(pos); + } else if (os_strcmp(buf, "hw_mode") == 0) { + if (os_strcmp(pos, "a") == 0) + conf->hw_mode = HOSTAPD_MODE_IEEE80211A; + else if (os_strcmp(pos, "b") == 0) + conf->hw_mode = HOSTAPD_MODE_IEEE80211B; + else if (os_strcmp(pos, "g") == 0) + conf->hw_mode = HOSTAPD_MODE_IEEE80211G; + else if (os_strcmp(pos, "ad") == 0) + conf->hw_mode = HOSTAPD_MODE_IEEE80211AD; + else { + wpa_printf(MSG_ERROR, "Line %d: unknown hw_mode '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "wps_rf_bands") == 0) { + if (os_strcmp(pos, "a") == 0) + bss->wps_rf_bands = WPS_RF_50GHZ; + else if (os_strcmp(pos, "g") == 0 || + os_strcmp(pos, "b") == 0) + bss->wps_rf_bands = WPS_RF_24GHZ; + else if (os_strcmp(pos, "ag") == 0 || + os_strcmp(pos, "ga") == 0) + bss->wps_rf_bands = WPS_RF_24GHZ | WPS_RF_50GHZ; + else { + wpa_printf(MSG_ERROR, + "Line %d: unknown wps_rf_band '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "channel") == 0) { + if (os_strcmp(pos, "acs_survey") == 0) { #ifndef CONFIG_ACS - wpa_printf(MSG_ERROR, "Line %d: tries to enable ACS but CONFIG_ACS disabled", - line); - errors++; + wpa_printf(MSG_ERROR, "Line %d: tries to enable ACS but CONFIG_ACS disabled", + line); + return 1; +#else /* CONFIG_ACS */ + conf->channel = 0; #endif /* CONFIG_ACS */ - conf->channel = 0; - } else - conf->channel = atoi(pos); - } else if (os_strcmp(buf, "beacon_int") == 0) { - int val = atoi(pos); - /* MIB defines range as 1..65535, but very small values - * cause problems with the current implementation. - * Since it is unlikely that this small numbers are - * useful in real life scenarios, do not allow beacon - * period to be set below 15 TU. */ - if (val < 15 || val > 65535) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "beacon_int %d (expected " - "15..65535)", line, val); - errors++; - } else - conf->beacon_int = val; + } else + conf->channel = atoi(pos); + } else if (os_strcmp(buf, "chanlist") == 0) { + if (hostapd_parse_intlist(&conf->chanlist, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid channel list", + line); + return 1; + } + } else if (os_strcmp(buf, "beacon_int") == 0) { + int val = atoi(pos); + /* MIB defines range as 1..65535, but very small values + * cause problems with the current implementation. + * Since it is unlikely that this small numbers are + * useful in real life scenarios, do not allow beacon + * period to be set below 15 TU. */ + if (val < 15 || val > 65535) { + wpa_printf(MSG_ERROR, "Line %d: invalid beacon_int %d (expected 15..65535)", + line, val); + return 1; + } + conf->beacon_int = val; #ifdef CONFIG_ACS - } else if (os_strcmp(buf, "acs_num_scans") == 0) { - int val = atoi(pos); - if (val <= 0 || val > 100) { - wpa_printf(MSG_ERROR, "Line %d: invalid acs_num_scans %d (expected 1..100)", - line, val); - errors++; - } else - conf->acs_num_scans = val; + } else if (os_strcmp(buf, "acs_num_scans") == 0) { + int val = atoi(pos); + if (val <= 0 || val > 100) { + wpa_printf(MSG_ERROR, "Line %d: invalid acs_num_scans %d (expected 1..100)", + line, val); + return 1; + } + conf->acs_num_scans = val; #endif /* CONFIG_ACS */ - } else if (os_strcmp(buf, "dtim_period") == 0) { - bss->dtim_period = atoi(pos); - if (bss->dtim_period < 1 || bss->dtim_period > 255) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "dtim_period %d", - line, bss->dtim_period); - errors++; - } - } else if (os_strcmp(buf, "rts_threshold") == 0) { - conf->rts_threshold = atoi(pos); - if (conf->rts_threshold < 0 || - conf->rts_threshold > 2347) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "rts_threshold %d", - line, conf->rts_threshold); - errors++; - } - } else if (os_strcmp(buf, "fragm_threshold") == 0) { - conf->fragm_threshold = atoi(pos); - if (conf->fragm_threshold < 256 || - conf->fragm_threshold > 2346) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "fragm_threshold %d", - line, conf->fragm_threshold); - errors++; - } - } else if (os_strcmp(buf, "send_probe_response") == 0) { - int val = atoi(pos); - if (val != 0 && val != 1) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "send_probe_response %d (expected " - "0 or 1)", line, val); - } else - conf->send_probe_response = val; - } else if (os_strcmp(buf, "supported_rates") == 0) { - if (hostapd_parse_intlist(&conf->supported_rates, pos)) - { - wpa_printf(MSG_ERROR, "Line %d: invalid rate " - "list", line); - errors++; - } - } else if (os_strcmp(buf, "basic_rates") == 0) { - if (hostapd_parse_intlist(&conf->basic_rates, pos)) { - wpa_printf(MSG_ERROR, "Line %d: invalid rate " - "list", line); - errors++; - } - } else if (os_strcmp(buf, "preamble") == 0) { - if (atoi(pos)) - conf->preamble = SHORT_PREAMBLE; - else - conf->preamble = LONG_PREAMBLE; - } else if (os_strcmp(buf, "ignore_broadcast_ssid") == 0) { - bss->ignore_broadcast_ssid = atoi(pos); - } else if (os_strcmp(buf, "wep_default_key") == 0) { - bss->ssid.wep.idx = atoi(pos); - if (bss->ssid.wep.idx > 3) { - wpa_printf(MSG_ERROR, "Invalid " - "wep_default_key index %d", - bss->ssid.wep.idx); - errors++; - } - } else if (os_strcmp(buf, "wep_key0") == 0 || - os_strcmp(buf, "wep_key1") == 0 || - os_strcmp(buf, "wep_key2") == 0 || - os_strcmp(buf, "wep_key3") == 0) { - if (hostapd_config_read_wep(&bss->ssid.wep, - buf[7] - '0', pos)) { - wpa_printf(MSG_ERROR, "Line %d: invalid WEP " - "key '%s'", line, buf); - errors++; - } + } else if (os_strcmp(buf, "dtim_period") == 0) { + bss->dtim_period = atoi(pos); + if (bss->dtim_period < 1 || bss->dtim_period > 255) { + wpa_printf(MSG_ERROR, "Line %d: invalid dtim_period %d", + line, bss->dtim_period); + return 1; + } + } else if (os_strcmp(buf, "rts_threshold") == 0) { + conf->rts_threshold = atoi(pos); + if (conf->rts_threshold < 0 || conf->rts_threshold > 2347) { + wpa_printf(MSG_ERROR, + "Line %d: invalid rts_threshold %d", + line, conf->rts_threshold); + return 1; + } + } else if (os_strcmp(buf, "fragm_threshold") == 0) { + conf->fragm_threshold = atoi(pos); + if (conf->fragm_threshold < 256 || + conf->fragm_threshold > 2346) { + wpa_printf(MSG_ERROR, + "Line %d: invalid fragm_threshold %d", + line, conf->fragm_threshold); + return 1; + } + } else if (os_strcmp(buf, "send_probe_response") == 0) { + int val = atoi(pos); + if (val != 0 && val != 1) { + wpa_printf(MSG_ERROR, "Line %d: invalid send_probe_response %d (expected 0 or 1)", + line, val); + return 1; + } + conf->send_probe_response = val; + } else if (os_strcmp(buf, "supported_rates") == 0) { + if (hostapd_parse_intlist(&conf->supported_rates, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid rate list", + line); + return 1; + } + } else if (os_strcmp(buf, "basic_rates") == 0) { + if (hostapd_parse_intlist(&conf->basic_rates, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid rate list", + line); + return 1; + } + } else if (os_strcmp(buf, "preamble") == 0) { + if (atoi(pos)) + conf->preamble = SHORT_PREAMBLE; + else + conf->preamble = LONG_PREAMBLE; + } else if (os_strcmp(buf, "ignore_broadcast_ssid") == 0) { + bss->ignore_broadcast_ssid = atoi(pos); + } else if (os_strcmp(buf, "wep_default_key") == 0) { + bss->ssid.wep.idx = atoi(pos); + if (bss->ssid.wep.idx > 3) { + wpa_printf(MSG_ERROR, + "Invalid wep_default_key index %d", + bss->ssid.wep.idx); + return 1; + } + } else if (os_strcmp(buf, "wep_key0") == 0 || + os_strcmp(buf, "wep_key1") == 0 || + os_strcmp(buf, "wep_key2") == 0 || + os_strcmp(buf, "wep_key3") == 0) { + if (hostapd_config_read_wep(&bss->ssid.wep, + buf[7] - '0', pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid WEP key '%s'", + line, buf); + return 1; + } #ifndef CONFIG_NO_VLAN - } else if (os_strcmp(buf, "dynamic_vlan") == 0) { - bss->ssid.dynamic_vlan = atoi(pos); - } else if (os_strcmp(buf, "vlan_file") == 0) { - if (hostapd_config_read_vlan_file(bss, pos)) { - wpa_printf(MSG_ERROR, "Line %d: failed to " - "read VLAN file '%s'", line, pos); - errors++; - } - } else if (os_strcmp(buf, "vlan_naming") == 0) { - bss->ssid.vlan_naming = atoi(pos); - if (bss->ssid.vlan_naming >= DYNAMIC_VLAN_NAMING_END || - bss->ssid.vlan_naming < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "naming scheme %d", line, - bss->ssid.vlan_naming); - errors++; - } + } else if (os_strcmp(buf, "dynamic_vlan") == 0) { + bss->ssid.dynamic_vlan = atoi(pos); + } else if (os_strcmp(buf, "vlan_file") == 0) { + if (hostapd_config_read_vlan_file(bss, pos)) { + wpa_printf(MSG_ERROR, "Line %d: failed to read VLAN file '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "vlan_naming") == 0) { + bss->ssid.vlan_naming = atoi(pos); + if (bss->ssid.vlan_naming >= DYNAMIC_VLAN_NAMING_END || + bss->ssid.vlan_naming < 0) { + wpa_printf(MSG_ERROR, + "Line %d: invalid naming scheme %d", + line, bss->ssid.vlan_naming); + return 1; + } #ifdef CONFIG_FULL_DYNAMIC_VLAN - } else if (os_strcmp(buf, "vlan_tagged_interface") == 0) { - bss->ssid.vlan_tagged_interface = os_strdup(pos); + } else if (os_strcmp(buf, "vlan_tagged_interface") == 0) { + os_free(bss->ssid.vlan_tagged_interface); + bss->ssid.vlan_tagged_interface = os_strdup(pos); #endif /* CONFIG_FULL_DYNAMIC_VLAN */ #endif /* CONFIG_NO_VLAN */ - } else if (os_strcmp(buf, "ap_table_max_size") == 0) { - conf->ap_table_max_size = atoi(pos); - } else if (os_strcmp(buf, "ap_table_expiration_time") == 0) { - conf->ap_table_expiration_time = atoi(pos); - } else if (os_strncmp(buf, "tx_queue_", 9) == 0) { - if (hostapd_config_tx_queue(conf, buf, pos)) { - wpa_printf(MSG_ERROR, "Line %d: invalid TX " - "queue item", line); - errors++; - } - } else if (os_strcmp(buf, "wme_enabled") == 0 || - os_strcmp(buf, "wmm_enabled") == 0) { - bss->wmm_enabled = atoi(pos); - } else if (os_strcmp(buf, "uapsd_advertisement_enabled") == 0) { - bss->wmm_uapsd = atoi(pos); - } else if (os_strncmp(buf, "wme_ac_", 7) == 0 || - os_strncmp(buf, "wmm_ac_", 7) == 0) { - if (hostapd_config_wmm_ac(conf->wmm_ac_params, buf, - pos)) { - wpa_printf(MSG_ERROR, "Line %d: invalid WMM " - "ac item", line); - errors++; - } - } else if (os_strcmp(buf, "bss") == 0) { - if (hostapd_config_bss(conf, pos)) { - wpa_printf(MSG_ERROR, "Line %d: invalid bss " - "item", line); - errors++; - } - } else if (os_strcmp(buf, "bssid") == 0) { - if (hwaddr_aton(pos, bss->bssid)) { - wpa_printf(MSG_ERROR, "Line %d: invalid bssid " - "item", line); - errors++; - } + } else if (os_strcmp(buf, "ap_table_max_size") == 0) { + conf->ap_table_max_size = atoi(pos); + } else if (os_strcmp(buf, "ap_table_expiration_time") == 0) { + conf->ap_table_expiration_time = atoi(pos); + } else if (os_strncmp(buf, "tx_queue_", 9) == 0) { + if (hostapd_config_tx_queue(conf, buf, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid TX queue item", + line); + return 1; + } + } else if (os_strcmp(buf, "wme_enabled") == 0 || + os_strcmp(buf, "wmm_enabled") == 0) { + bss->wmm_enabled = atoi(pos); + } else if (os_strcmp(buf, "uapsd_advertisement_enabled") == 0) { + bss->wmm_uapsd = atoi(pos); + } else if (os_strncmp(buf, "wme_ac_", 7) == 0 || + os_strncmp(buf, "wmm_ac_", 7) == 0) { + if (hostapd_config_wmm_ac(conf->wmm_ac_params, buf, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid WMM ac item", + line); + return 1; + } + } else if (os_strcmp(buf, "bss") == 0) { + if (hostapd_config_bss(conf, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid bss item", + line); + return 1; + } + } else if (os_strcmp(buf, "bssid") == 0) { + if (hwaddr_aton(pos, bss->bssid)) { + wpa_printf(MSG_ERROR, "Line %d: invalid bssid item", + line); + return 1; + } #ifdef CONFIG_IEEE80211W - } else if (os_strcmp(buf, "ieee80211w") == 0) { - bss->ieee80211w = atoi(pos); - } else if (os_strcmp(buf, "assoc_sa_query_max_timeout") == 0) { - bss->assoc_sa_query_max_timeout = atoi(pos); - if (bss->assoc_sa_query_max_timeout == 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "assoc_sa_query_max_timeout", line); - errors++; - } - } else if (os_strcmp(buf, "assoc_sa_query_retry_timeout") == 0) - { - bss->assoc_sa_query_retry_timeout = atoi(pos); - if (bss->assoc_sa_query_retry_timeout == 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "assoc_sa_query_retry_timeout", - line); - errors++; - } + } else if (os_strcmp(buf, "ieee80211w") == 0) { + bss->ieee80211w = atoi(pos); + } else if (os_strcmp(buf, "group_mgmt_cipher") == 0) { + if (os_strcmp(pos, "AES-128-CMAC") == 0) { + bss->group_mgmt_cipher = WPA_CIPHER_AES_128_CMAC; + } else if (os_strcmp(pos, "BIP-GMAC-128") == 0) { + bss->group_mgmt_cipher = WPA_CIPHER_BIP_GMAC_128; + } else if (os_strcmp(pos, "BIP-GMAC-256") == 0) { + bss->group_mgmt_cipher = WPA_CIPHER_BIP_GMAC_256; + } else if (os_strcmp(pos, "BIP-CMAC-256") == 0) { + bss->group_mgmt_cipher = WPA_CIPHER_BIP_CMAC_256; + } else { + wpa_printf(MSG_ERROR, "Line %d: invalid group_mgmt_cipher: %s", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "assoc_sa_query_max_timeout") == 0) { + bss->assoc_sa_query_max_timeout = atoi(pos); + if (bss->assoc_sa_query_max_timeout == 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid assoc_sa_query_max_timeout", + line); + return 1; + } + } else if (os_strcmp(buf, "assoc_sa_query_retry_timeout") == 0) { + bss->assoc_sa_query_retry_timeout = atoi(pos); + if (bss->assoc_sa_query_retry_timeout == 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid assoc_sa_query_retry_timeout", + line); + return 1; + } #endif /* CONFIG_IEEE80211W */ #ifdef CONFIG_IEEE80211N - } else if (os_strcmp(buf, "ieee80211n") == 0) { - conf->ieee80211n = atoi(pos); - } else if (os_strcmp(buf, "ht_capab") == 0) { - if (hostapd_config_ht_capab(conf, pos) < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "ht_capab", line); - errors++; - } - } else if (os_strcmp(buf, "require_ht") == 0) { - conf->require_ht = atoi(pos); - } else if (os_strcmp(buf, "obss_interval") == 0) { - conf->obss_interval = atoi(pos); + } else if (os_strcmp(buf, "ieee80211n") == 0) { + conf->ieee80211n = atoi(pos); + } else if (os_strcmp(buf, "ht_capab") == 0) { + if (hostapd_config_ht_capab(conf, pos) < 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid ht_capab", + line); + return 1; + } + } else if (os_strcmp(buf, "require_ht") == 0) { + conf->require_ht = atoi(pos); + } else if (os_strcmp(buf, "obss_interval") == 0) { + conf->obss_interval = atoi(pos); #endif /* CONFIG_IEEE80211N */ #ifdef CONFIG_IEEE80211AC - } else if (os_strcmp(buf, "ieee80211ac") == 0) { - conf->ieee80211ac = atoi(pos); - } else if (os_strcmp(buf, "vht_capab") == 0) { - if (hostapd_config_vht_capab(conf, pos) < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "vht_capab", line); - errors++; - } - } else if (os_strcmp(buf, "require_vht") == 0) { - conf->require_vht = atoi(pos); - } else if (os_strcmp(buf, "vht_oper_chwidth") == 0) { - conf->vht_oper_chwidth = atoi(pos); - } else if (os_strcmp(buf, "vht_oper_centr_freq_seg0_idx") == 0) - { - conf->vht_oper_centr_freq_seg0_idx = atoi(pos); - } else if (os_strcmp(buf, "vht_oper_centr_freq_seg1_idx") == 0) - { - conf->vht_oper_centr_freq_seg1_idx = atoi(pos); + } else if (os_strcmp(buf, "ieee80211ac") == 0) { + conf->ieee80211ac = atoi(pos); + } else if (os_strcmp(buf, "vht_capab") == 0) { + if (hostapd_config_vht_capab(conf, pos) < 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid vht_capab", + line); + return 1; + } + } else if (os_strcmp(buf, "require_vht") == 0) { + conf->require_vht = atoi(pos); + } else if (os_strcmp(buf, "vht_oper_chwidth") == 0) { + conf->vht_oper_chwidth = atoi(pos); + } else if (os_strcmp(buf, "vht_oper_centr_freq_seg0_idx") == 0) { + conf->vht_oper_centr_freq_seg0_idx = atoi(pos); + } else if (os_strcmp(buf, "vht_oper_centr_freq_seg1_idx") == 0) { + conf->vht_oper_centr_freq_seg1_idx = atoi(pos); #endif /* CONFIG_IEEE80211AC */ - } else if (os_strcmp(buf, "max_listen_interval") == 0) { - bss->max_listen_interval = atoi(pos); - } else if (os_strcmp(buf, "disable_pmksa_caching") == 0) { - bss->disable_pmksa_caching = atoi(pos); - } else if (os_strcmp(buf, "okc") == 0) { - bss->okc = atoi(pos); + } else if (os_strcmp(buf, "max_listen_interval") == 0) { + bss->max_listen_interval = atoi(pos); + } else if (os_strcmp(buf, "disable_pmksa_caching") == 0) { + bss->disable_pmksa_caching = atoi(pos); + } else if (os_strcmp(buf, "okc") == 0) { + bss->okc = atoi(pos); #ifdef CONFIG_WPS - } else if (os_strcmp(buf, "wps_state") == 0) { - bss->wps_state = atoi(pos); - if (bss->wps_state < 0 || bss->wps_state > 2) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "wps_state", line); - errors++; - } - } else if (os_strcmp(buf, "wps_independent") == 0) { - bss->wps_independent = atoi(pos); - } else if (os_strcmp(buf, "ap_setup_locked") == 0) { - bss->ap_setup_locked = atoi(pos); - } else if (os_strcmp(buf, "uuid") == 0) { - if (uuid_str2bin(pos, bss->uuid)) { - wpa_printf(MSG_ERROR, "Line %d: invalid UUID", - line); - errors++; - } - } else if (os_strcmp(buf, "wps_pin_requests") == 0) { - os_free(bss->wps_pin_requests); - bss->wps_pin_requests = os_strdup(pos); - } else if (os_strcmp(buf, "device_name") == 0) { - if (os_strlen(pos) > 32) { - wpa_printf(MSG_ERROR, "Line %d: Too long " - "device_name", line); - errors++; - } - os_free(bss->device_name); - bss->device_name = os_strdup(pos); - } else if (os_strcmp(buf, "manufacturer") == 0) { - if (os_strlen(pos) > 64) { - wpa_printf(MSG_ERROR, "Line %d: Too long " - "manufacturer", line); - errors++; - } - os_free(bss->manufacturer); - bss->manufacturer = os_strdup(pos); - } else if (os_strcmp(buf, "model_name") == 0) { - if (os_strlen(pos) > 32) { - wpa_printf(MSG_ERROR, "Line %d: Too long " - "model_name", line); - errors++; - } - os_free(bss->model_name); - bss->model_name = os_strdup(pos); - } else if (os_strcmp(buf, "model_number") == 0) { - if (os_strlen(pos) > 32) { - wpa_printf(MSG_ERROR, "Line %d: Too long " - "model_number", line); - errors++; - } - os_free(bss->model_number); - bss->model_number = os_strdup(pos); - } else if (os_strcmp(buf, "serial_number") == 0) { - if (os_strlen(pos) > 32) { - wpa_printf(MSG_ERROR, "Line %d: Too long " - "serial_number", line); - errors++; - } - os_free(bss->serial_number); - bss->serial_number = os_strdup(pos); - } else if (os_strcmp(buf, "device_type") == 0) { - if (wps_dev_type_str2bin(pos, bss->device_type)) - errors++; - } else if (os_strcmp(buf, "config_methods") == 0) { - os_free(bss->config_methods); - bss->config_methods = os_strdup(pos); - } else if (os_strcmp(buf, "os_version") == 0) { - if (hexstr2bin(pos, bss->os_version, 4)) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "os_version", line); - errors++; - } - } else if (os_strcmp(buf, "ap_pin") == 0) { - os_free(bss->ap_pin); - bss->ap_pin = os_strdup(pos); - } else if (os_strcmp(buf, "skip_cred_build") == 0) { - bss->skip_cred_build = atoi(pos); - } else if (os_strcmp(buf, "extra_cred") == 0) { - os_free(bss->extra_cred); - bss->extra_cred = - (u8 *) os_readfile(pos, &bss->extra_cred_len); - if (bss->extra_cred == NULL) { - wpa_printf(MSG_ERROR, "Line %d: could not " - "read Credentials from '%s'", - line, pos); - errors++; - } - } else if (os_strcmp(buf, "wps_cred_processing") == 0) { - bss->wps_cred_processing = atoi(pos); - } else if (os_strcmp(buf, "ap_settings") == 0) { - os_free(bss->ap_settings); - bss->ap_settings = - (u8 *) os_readfile(pos, &bss->ap_settings_len); - if (bss->ap_settings == NULL) { - wpa_printf(MSG_ERROR, "Line %d: could not " - "read AP Settings from '%s'", - line, pos); - errors++; - } - } else if (os_strcmp(buf, "upnp_iface") == 0) { - bss->upnp_iface = os_strdup(pos); - } else if (os_strcmp(buf, "friendly_name") == 0) { - os_free(bss->friendly_name); - bss->friendly_name = os_strdup(pos); - } else if (os_strcmp(buf, "manufacturer_url") == 0) { - os_free(bss->manufacturer_url); - bss->manufacturer_url = os_strdup(pos); - } else if (os_strcmp(buf, "model_description") == 0) { - os_free(bss->model_description); - bss->model_description = os_strdup(pos); - } else if (os_strcmp(buf, "model_url") == 0) { - os_free(bss->model_url); - bss->model_url = os_strdup(pos); - } else if (os_strcmp(buf, "upc") == 0) { - os_free(bss->upc); - bss->upc = os_strdup(pos); - } else if (os_strcmp(buf, "pbc_in_m1") == 0) { - bss->pbc_in_m1 = atoi(pos); - } else if (os_strcmp(buf, "server_id") == 0) { - os_free(bss->server_id); - bss->server_id = os_strdup(pos); + } else if (os_strcmp(buf, "wps_state") == 0) { + bss->wps_state = atoi(pos); + if (bss->wps_state < 0 || bss->wps_state > 2) { + wpa_printf(MSG_ERROR, "Line %d: invalid wps_state", + line); + return 1; + } + } else if (os_strcmp(buf, "wps_independent") == 0) { + bss->wps_independent = atoi(pos); + } else if (os_strcmp(buf, "ap_setup_locked") == 0) { + bss->ap_setup_locked = atoi(pos); + } else if (os_strcmp(buf, "uuid") == 0) { + if (uuid_str2bin(pos, bss->uuid)) { + wpa_printf(MSG_ERROR, "Line %d: invalid UUID", line); + return 1; + } + } else if (os_strcmp(buf, "wps_pin_requests") == 0) { + os_free(bss->wps_pin_requests); + bss->wps_pin_requests = os_strdup(pos); + } else if (os_strcmp(buf, "device_name") == 0) { + if (os_strlen(pos) > 32) { + wpa_printf(MSG_ERROR, "Line %d: Too long " + "device_name", line); + return 1; + } + os_free(bss->device_name); + bss->device_name = os_strdup(pos); + } else if (os_strcmp(buf, "manufacturer") == 0) { + if (os_strlen(pos) > 64) { + wpa_printf(MSG_ERROR, "Line %d: Too long manufacturer", + line); + return 1; + } + os_free(bss->manufacturer); + bss->manufacturer = os_strdup(pos); + } else if (os_strcmp(buf, "model_name") == 0) { + if (os_strlen(pos) > 32) { + wpa_printf(MSG_ERROR, "Line %d: Too long model_name", + line); + return 1; + } + os_free(bss->model_name); + bss->model_name = os_strdup(pos); + } else if (os_strcmp(buf, "model_number") == 0) { + if (os_strlen(pos) > 32) { + wpa_printf(MSG_ERROR, "Line %d: Too long model_number", + line); + return 1; + } + os_free(bss->model_number); + bss->model_number = os_strdup(pos); + } else if (os_strcmp(buf, "serial_number") == 0) { + if (os_strlen(pos) > 32) { + wpa_printf(MSG_ERROR, "Line %d: Too long serial_number", + line); + return 1; + } + os_free(bss->serial_number); + bss->serial_number = os_strdup(pos); + } else if (os_strcmp(buf, "device_type") == 0) { + if (wps_dev_type_str2bin(pos, bss->device_type)) + return 1; + } else if (os_strcmp(buf, "config_methods") == 0) { + os_free(bss->config_methods); + bss->config_methods = os_strdup(pos); + } else if (os_strcmp(buf, "os_version") == 0) { + if (hexstr2bin(pos, bss->os_version, 4)) { + wpa_printf(MSG_ERROR, "Line %d: invalid os_version", + line); + return 1; + } + } else if (os_strcmp(buf, "ap_pin") == 0) { + os_free(bss->ap_pin); + bss->ap_pin = os_strdup(pos); + } else if (os_strcmp(buf, "skip_cred_build") == 0) { + bss->skip_cred_build = atoi(pos); + } else if (os_strcmp(buf, "extra_cred") == 0) { + os_free(bss->extra_cred); + bss->extra_cred = (u8 *) os_readfile(pos, &bss->extra_cred_len); + if (bss->extra_cred == NULL) { + wpa_printf(MSG_ERROR, "Line %d: could not read Credentials from '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "wps_cred_processing") == 0) { + bss->wps_cred_processing = atoi(pos); + } else if (os_strcmp(buf, "ap_settings") == 0) { + os_free(bss->ap_settings); + bss->ap_settings = + (u8 *) os_readfile(pos, &bss->ap_settings_len); + if (bss->ap_settings == NULL) { + wpa_printf(MSG_ERROR, "Line %d: could not read AP Settings from '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "upnp_iface") == 0) { + os_free(bss->upnp_iface); + bss->upnp_iface = os_strdup(pos); + } else if (os_strcmp(buf, "friendly_name") == 0) { + os_free(bss->friendly_name); + bss->friendly_name = os_strdup(pos); + } else if (os_strcmp(buf, "manufacturer_url") == 0) { + os_free(bss->manufacturer_url); + bss->manufacturer_url = os_strdup(pos); + } else if (os_strcmp(buf, "model_description") == 0) { + os_free(bss->model_description); + bss->model_description = os_strdup(pos); + } else if (os_strcmp(buf, "model_url") == 0) { + os_free(bss->model_url); + bss->model_url = os_strdup(pos); + } else if (os_strcmp(buf, "upc") == 0) { + os_free(bss->upc); + bss->upc = os_strdup(pos); + } else if (os_strcmp(buf, "pbc_in_m1") == 0) { + bss->pbc_in_m1 = atoi(pos); + } else if (os_strcmp(buf, "server_id") == 0) { + os_free(bss->server_id); + bss->server_id = os_strdup(pos); #ifdef CONFIG_WPS_NFC - } else if (os_strcmp(buf, "wps_nfc_dev_pw_id") == 0) { - bss->wps_nfc_dev_pw_id = atoi(pos); - if (bss->wps_nfc_dev_pw_id < 0x10 || - bss->wps_nfc_dev_pw_id > 0xffff) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "wps_nfc_dev_pw_id value", line); - errors++; - } - bss->wps_nfc_pw_from_config = 1; - } else if (os_strcmp(buf, "wps_nfc_dh_pubkey") == 0) { - wpabuf_free(bss->wps_nfc_dh_pubkey); - bss->wps_nfc_dh_pubkey = hostapd_parse_bin(pos); - bss->wps_nfc_pw_from_config = 1; - } else if (os_strcmp(buf, "wps_nfc_dh_privkey") == 0) { - wpabuf_free(bss->wps_nfc_dh_privkey); - bss->wps_nfc_dh_privkey = hostapd_parse_bin(pos); - bss->wps_nfc_pw_from_config = 1; - } else if (os_strcmp(buf, "wps_nfc_dev_pw") == 0) { - wpabuf_free(bss->wps_nfc_dev_pw); - bss->wps_nfc_dev_pw = hostapd_parse_bin(pos); - bss->wps_nfc_pw_from_config = 1; + } else if (os_strcmp(buf, "wps_nfc_dev_pw_id") == 0) { + bss->wps_nfc_dev_pw_id = atoi(pos); + if (bss->wps_nfc_dev_pw_id < 0x10 || + bss->wps_nfc_dev_pw_id > 0xffff) { + wpa_printf(MSG_ERROR, "Line %d: Invalid wps_nfc_dev_pw_id value", + line); + return 1; + } + bss->wps_nfc_pw_from_config = 1; + } else if (os_strcmp(buf, "wps_nfc_dh_pubkey") == 0) { + wpabuf_free(bss->wps_nfc_dh_pubkey); + bss->wps_nfc_dh_pubkey = hostapd_parse_bin(pos); + bss->wps_nfc_pw_from_config = 1; + } else if (os_strcmp(buf, "wps_nfc_dh_privkey") == 0) { + wpabuf_free(bss->wps_nfc_dh_privkey); + bss->wps_nfc_dh_privkey = hostapd_parse_bin(pos); + bss->wps_nfc_pw_from_config = 1; + } else if (os_strcmp(buf, "wps_nfc_dev_pw") == 0) { + wpabuf_free(bss->wps_nfc_dev_pw); + bss->wps_nfc_dev_pw = hostapd_parse_bin(pos); + bss->wps_nfc_pw_from_config = 1; #endif /* CONFIG_WPS_NFC */ #endif /* CONFIG_WPS */ #ifdef CONFIG_P2P_MANAGER - } else if (os_strcmp(buf, "manage_p2p") == 0) { - int manage = atoi(pos); - if (manage) - bss->p2p |= P2P_MANAGE; - else - bss->p2p &= ~P2P_MANAGE; - } else if (os_strcmp(buf, "allow_cross_connection") == 0) { - if (atoi(pos)) - bss->p2p |= P2P_ALLOW_CROSS_CONNECTION; - else - bss->p2p &= ~P2P_ALLOW_CROSS_CONNECTION; + } else if (os_strcmp(buf, "manage_p2p") == 0) { + if (atoi(pos)) + bss->p2p |= P2P_MANAGE; + else + bss->p2p &= ~P2P_MANAGE; + } else if (os_strcmp(buf, "allow_cross_connection") == 0) { + if (atoi(pos)) + bss->p2p |= P2P_ALLOW_CROSS_CONNECTION; + else + bss->p2p &= ~P2P_ALLOW_CROSS_CONNECTION; #endif /* CONFIG_P2P_MANAGER */ - } else if (os_strcmp(buf, "disassoc_low_ack") == 0) { - bss->disassoc_low_ack = atoi(pos); - } else if (os_strcmp(buf, "tdls_prohibit") == 0) { - int val = atoi(pos); - if (val) - bss->tdls |= TDLS_PROHIBIT; - else - bss->tdls &= ~TDLS_PROHIBIT; - } else if (os_strcmp(buf, "tdls_prohibit_chan_switch") == 0) { - int val = atoi(pos); - if (val) - bss->tdls |= TDLS_PROHIBIT_CHAN_SWITCH; - else - bss->tdls &= ~TDLS_PROHIBIT_CHAN_SWITCH; + } else if (os_strcmp(buf, "disassoc_low_ack") == 0) { + bss->disassoc_low_ack = atoi(pos); + } else if (os_strcmp(buf, "tdls_prohibit") == 0) { + if (atoi(pos)) + bss->tdls |= TDLS_PROHIBIT; + else + bss->tdls &= ~TDLS_PROHIBIT; + } else if (os_strcmp(buf, "tdls_prohibit_chan_switch") == 0) { + if (atoi(pos)) + bss->tdls |= TDLS_PROHIBIT_CHAN_SWITCH; + else + bss->tdls &= ~TDLS_PROHIBIT_CHAN_SWITCH; #ifdef CONFIG_RSN_TESTING - } else if (os_strcmp(buf, "rsn_testing") == 0) { - extern int rsn_testing; - rsn_testing = atoi(pos); + } else if (os_strcmp(buf, "rsn_testing") == 0) { + extern int rsn_testing; + rsn_testing = atoi(pos); #endif /* CONFIG_RSN_TESTING */ - } else if (os_strcmp(buf, "time_advertisement") == 0) { - bss->time_advertisement = atoi(pos); - } else if (os_strcmp(buf, "time_zone") == 0) { - size_t tz_len = os_strlen(pos); - if (tz_len < 4 || tz_len > 255) { - wpa_printf(MSG_DEBUG, "Line %d: invalid " - "time_zone", line); - errors++; - return errors; - } - os_free(bss->time_zone); - bss->time_zone = os_strdup(pos); - if (bss->time_zone == NULL) - errors++; + } else if (os_strcmp(buf, "time_advertisement") == 0) { + bss->time_advertisement = atoi(pos); + } else if (os_strcmp(buf, "time_zone") == 0) { + size_t tz_len = os_strlen(pos); + if (tz_len < 4 || tz_len > 255) { + wpa_printf(MSG_DEBUG, "Line %d: invalid time_zone", + line); + return 1; + } + os_free(bss->time_zone); + bss->time_zone = os_strdup(pos); + if (bss->time_zone == NULL) + return 1; #ifdef CONFIG_WNM - } else if (os_strcmp(buf, "wnm_sleep_mode") == 0) { - bss->wnm_sleep_mode = atoi(pos); - } else if (os_strcmp(buf, "bss_transition") == 0) { - bss->bss_transition = atoi(pos); + } else if (os_strcmp(buf, "wnm_sleep_mode") == 0) { + bss->wnm_sleep_mode = atoi(pos); + } else if (os_strcmp(buf, "bss_transition") == 0) { + bss->bss_transition = atoi(pos); #endif /* CONFIG_WNM */ #ifdef CONFIG_INTERWORKING - } else if (os_strcmp(buf, "interworking") == 0) { - bss->interworking = atoi(pos); - } else if (os_strcmp(buf, "access_network_type") == 0) { - bss->access_network_type = atoi(pos); - if (bss->access_network_type < 0 || - bss->access_network_type > 15) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "access_network_type", line); - errors++; - } - } else if (os_strcmp(buf, "internet") == 0) { - bss->internet = atoi(pos); - } else if (os_strcmp(buf, "asra") == 0) { - bss->asra = atoi(pos); - } else if (os_strcmp(buf, "esr") == 0) { - bss->esr = atoi(pos); - } else if (os_strcmp(buf, "uesa") == 0) { - bss->uesa = atoi(pos); - } else if (os_strcmp(buf, "venue_group") == 0) { - bss->venue_group = atoi(pos); - bss->venue_info_set = 1; - } else if (os_strcmp(buf, "venue_type") == 0) { - bss->venue_type = atoi(pos); - bss->venue_info_set = 1; - } else if (os_strcmp(buf, "hessid") == 0) { - if (hwaddr_aton(pos, bss->hessid)) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "hessid", line); - errors++; - } - } else if (os_strcmp(buf, "roaming_consortium") == 0) { - if (parse_roaming_consortium(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "venue_name") == 0) { - if (parse_venue_name(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "network_auth_type") == 0) { - u8 auth_type; - u16 redirect_url_len; - if (hexstr2bin(pos, &auth_type, 1)) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "network_auth_type '%s'", - line, pos); - errors++; - return errors; - } - if (auth_type == 0 || auth_type == 2) - redirect_url_len = os_strlen(pos + 2); - else - redirect_url_len = 0; - os_free(bss->network_auth_type); - bss->network_auth_type = - os_malloc(redirect_url_len + 3 + 1); - if (bss->network_auth_type == NULL) { - errors++; - return errors; - } - *bss->network_auth_type = auth_type; - WPA_PUT_LE16(bss->network_auth_type + 1, - redirect_url_len); - if (redirect_url_len) - os_memcpy(bss->network_auth_type + 3, - pos + 2, redirect_url_len); - bss->network_auth_type_len = 3 + redirect_url_len; - } else if (os_strcmp(buf, "ipaddr_type_availability") == 0) { - if (hexstr2bin(pos, &bss->ipaddr_type_availability, 1)) - { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "ipaddr_type_availability '%s'", - line, pos); - bss->ipaddr_type_configured = 0; - errors++; - return errors; - } - bss->ipaddr_type_configured = 1; - } else if (os_strcmp(buf, "domain_name") == 0) { - int j, num_domains, domain_len, domain_list_len = 0; - char *tok_start, *tok_prev; - u8 *domain_list, *domain_ptr; - - domain_list_len = os_strlen(pos) + 1; - domain_list = os_malloc(domain_list_len); - if (domain_list == NULL) { - errors++; - return errors; - } - - domain_ptr = domain_list; - tok_prev = pos; - num_domains = 1; - while ((tok_prev = os_strchr(tok_prev, ','))) { - num_domains++; - tok_prev++; - } - tok_prev = pos; - for (j = 0; j < num_domains; j++) { - tok_start = os_strchr(tok_prev, ','); - if (tok_start) { - domain_len = tok_start - tok_prev; - *domain_ptr = domain_len; - os_memcpy(domain_ptr + 1, tok_prev, - domain_len); - domain_ptr += domain_len + 1; - tok_prev = ++tok_start; - } else { - domain_len = os_strlen(tok_prev); - *domain_ptr = domain_len; - os_memcpy(domain_ptr + 1, tok_prev, - domain_len); - domain_ptr += domain_len + 1; - } + } else if (os_strcmp(buf, "interworking") == 0) { + bss->interworking = atoi(pos); + } else if (os_strcmp(buf, "access_network_type") == 0) { + bss->access_network_type = atoi(pos); + if (bss->access_network_type < 0 || + bss->access_network_type > 15) { + wpa_printf(MSG_ERROR, + "Line %d: invalid access_network_type", + line); + return 1; + } + } else if (os_strcmp(buf, "internet") == 0) { + bss->internet = atoi(pos); + } else if (os_strcmp(buf, "asra") == 0) { + bss->asra = atoi(pos); + } else if (os_strcmp(buf, "esr") == 0) { + bss->esr = atoi(pos); + } else if (os_strcmp(buf, "uesa") == 0) { + bss->uesa = atoi(pos); + } else if (os_strcmp(buf, "venue_group") == 0) { + bss->venue_group = atoi(pos); + bss->venue_info_set = 1; + } else if (os_strcmp(buf, "venue_type") == 0) { + bss->venue_type = atoi(pos); + bss->venue_info_set = 1; + } else if (os_strcmp(buf, "hessid") == 0) { + if (hwaddr_aton(pos, bss->hessid)) { + wpa_printf(MSG_ERROR, "Line %d: invalid hessid", line); + return 1; + } + } else if (os_strcmp(buf, "roaming_consortium") == 0) { + if (parse_roaming_consortium(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "venue_name") == 0) { + if (parse_venue_name(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "network_auth_type") == 0) { + u8 auth_type; + u16 redirect_url_len; + if (hexstr2bin(pos, &auth_type, 1)) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid network_auth_type '%s'", + line, pos); + return 1; + } + if (auth_type == 0 || auth_type == 2) + redirect_url_len = os_strlen(pos + 2); + else + redirect_url_len = 0; + os_free(bss->network_auth_type); + bss->network_auth_type = os_malloc(redirect_url_len + 3 + 1); + if (bss->network_auth_type == NULL) + return 1; + *bss->network_auth_type = auth_type; + WPA_PUT_LE16(bss->network_auth_type + 1, redirect_url_len); + if (redirect_url_len) + os_memcpy(bss->network_auth_type + 3, pos + 2, + redirect_url_len); + bss->network_auth_type_len = 3 + redirect_url_len; + } else if (os_strcmp(buf, "ipaddr_type_availability") == 0) { + if (hexstr2bin(pos, &bss->ipaddr_type_availability, 1)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid ipaddr_type_availability '%s'", + line, pos); + bss->ipaddr_type_configured = 0; + return 1; + } + bss->ipaddr_type_configured = 1; + } else if (os_strcmp(buf, "domain_name") == 0) { + int j, num_domains, domain_len, domain_list_len = 0; + char *tok_start, *tok_prev; + u8 *domain_list, *domain_ptr; + + domain_list_len = os_strlen(pos) + 1; + domain_list = os_malloc(domain_list_len); + if (domain_list == NULL) + return 1; + + domain_ptr = domain_list; + tok_prev = pos; + num_domains = 1; + while ((tok_prev = os_strchr(tok_prev, ','))) { + num_domains++; + tok_prev++; + } + tok_prev = pos; + for (j = 0; j < num_domains; j++) { + tok_start = os_strchr(tok_prev, ','); + if (tok_start) { + domain_len = tok_start - tok_prev; + *domain_ptr = domain_len; + os_memcpy(domain_ptr + 1, tok_prev, domain_len); + domain_ptr += domain_len + 1; + tok_prev = ++tok_start; + } else { + domain_len = os_strlen(tok_prev); + *domain_ptr = domain_len; + os_memcpy(domain_ptr + 1, tok_prev, domain_len); + domain_ptr += domain_len + 1; } + } - os_free(bss->domain_name); - bss->domain_name = domain_list; - bss->domain_name_len = domain_list_len; - } else if (os_strcmp(buf, "anqp_3gpp_cell_net") == 0) { - if (parse_3gpp_cell_net(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "nai_realm") == 0) { - if (parse_nai_realm(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "gas_frag_limit") == 0) { - bss->gas_frag_limit = atoi(pos); - } else if (os_strcmp(buf, "gas_comeback_delay") == 0) { - bss->gas_comeback_delay = atoi(pos); - } else if (os_strcmp(buf, "qos_map_set") == 0) { - if (parse_qos_map_set(bss, pos, line) < 0) - errors++; + os_free(bss->domain_name); + bss->domain_name = domain_list; + bss->domain_name_len = domain_list_len; + } else if (os_strcmp(buf, "anqp_3gpp_cell_net") == 0) { + if (parse_3gpp_cell_net(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "nai_realm") == 0) { + if (parse_nai_realm(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "gas_frag_limit") == 0) { + bss->gas_frag_limit = atoi(pos); + } else if (os_strcmp(buf, "gas_comeback_delay") == 0) { + bss->gas_comeback_delay = atoi(pos); + } else if (os_strcmp(buf, "qos_map_set") == 0) { + if (parse_qos_map_set(bss, pos, line) < 0) + return 1; #endif /* CONFIG_INTERWORKING */ #ifdef CONFIG_RADIUS_TEST - } else if (os_strcmp(buf, "dump_msk_file") == 0) { - os_free(bss->dump_msk_file); - bss->dump_msk_file = os_strdup(pos); + } else if (os_strcmp(buf, "dump_msk_file") == 0) { + os_free(bss->dump_msk_file); + bss->dump_msk_file = os_strdup(pos); #endif /* CONFIG_RADIUS_TEST */ #ifdef CONFIG_HS20 - } else if (os_strcmp(buf, "hs20") == 0) { - bss->hs20 = atoi(pos); - } else if (os_strcmp(buf, "disable_dgaf") == 0) { - bss->disable_dgaf = atoi(pos); - } else if (os_strcmp(buf, "hs20_oper_friendly_name") == 0) { - if (hs20_parse_oper_friendly_name(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "hs20_wan_metrics") == 0) { - if (hs20_parse_wan_metrics(bss, pos, line) < 0) { - errors++; - return errors; - } - } else if (os_strcmp(buf, "hs20_conn_capab") == 0) { - if (hs20_parse_conn_capab(bss, pos, line) < 0) { - errors++; - return errors; - } - } else if (os_strcmp(buf, "hs20_operating_class") == 0) { - u8 *oper_class; - size_t oper_class_len; - oper_class_len = os_strlen(pos); - if (oper_class_len < 2 || (oper_class_len & 0x01)) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "hs20_operating_class '%s'", - line, pos); - errors++; - return errors; - } - oper_class_len /= 2; - oper_class = os_malloc(oper_class_len); - if (oper_class == NULL) { - errors++; - return errors; - } - if (hexstr2bin(pos, oper_class, oper_class_len)) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "hs20_operating_class '%s'", - line, pos); - os_free(oper_class); - errors++; - return errors; - } - os_free(bss->hs20_operating_class); - bss->hs20_operating_class = oper_class; - bss->hs20_operating_class_len = oper_class_len; + } else if (os_strcmp(buf, "hs20") == 0) { + bss->hs20 = atoi(pos); + } else if (os_strcmp(buf, "disable_dgaf") == 0) { + bss->disable_dgaf = atoi(pos); + } else if (os_strcmp(buf, "osen") == 0) { + bss->osen = atoi(pos); + } else if (os_strcmp(buf, "anqp_domain_id") == 0) { + bss->anqp_domain_id = atoi(pos); + } else if (os_strcmp(buf, "hs20_deauth_req_timeout") == 0) { + bss->hs20_deauth_req_timeout = atoi(pos); + } else if (os_strcmp(buf, "hs20_oper_friendly_name") == 0) { + if (hs20_parse_oper_friendly_name(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "hs20_wan_metrics") == 0) { + if (hs20_parse_wan_metrics(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "hs20_conn_capab") == 0) { + if (hs20_parse_conn_capab(bss, pos, line) < 0) { + return 1; + } + } else if (os_strcmp(buf, "hs20_operating_class") == 0) { + u8 *oper_class; + size_t oper_class_len; + oper_class_len = os_strlen(pos); + if (oper_class_len < 2 || (oper_class_len & 0x01)) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid hs20_operating_class '%s'", + line, pos); + return 1; + } + oper_class_len /= 2; + oper_class = os_malloc(oper_class_len); + if (oper_class == NULL) + return 1; + if (hexstr2bin(pos, oper_class, oper_class_len)) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid hs20_operating_class '%s'", + line, pos); + os_free(oper_class); + return 1; + } + os_free(bss->hs20_operating_class); + bss->hs20_operating_class = oper_class; + bss->hs20_operating_class_len = oper_class_len; + } else if (os_strcmp(buf, "hs20_icon") == 0) { + if (hs20_parse_icon(bss, pos) < 0) { + wpa_printf(MSG_ERROR, "Line %d: Invalid hs20_icon '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "osu_ssid") == 0) { + if (hs20_parse_osu_ssid(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_server_uri") == 0) { + if (hs20_parse_osu_server_uri(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_friendly_name") == 0) { + if (hs20_parse_osu_friendly_name(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_nai") == 0) { + if (hs20_parse_osu_nai(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_method_list") == 0) { + if (hs20_parse_osu_method_list(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_icon") == 0) { + if (hs20_parse_osu_icon(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_service_desc") == 0) { + if (hs20_parse_osu_service_desc(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "subscr_remediation_url") == 0) { + os_free(bss->subscr_remediation_url); + bss->subscr_remediation_url = os_strdup(pos); + } else if (os_strcmp(buf, "subscr_remediation_method") == 0) { + bss->subscr_remediation_method = atoi(pos); #endif /* CONFIG_HS20 */ #ifdef CONFIG_TESTING_OPTIONS -#define PARSE_TEST_PROBABILITY(_val) \ - } else if (os_strcmp(buf, #_val) == 0) { \ - char *end; \ - \ - conf->_val = strtod(pos, &end); \ - if (*end || conf->_val < 0.0d || \ - conf->_val > 1.0d) { \ - wpa_printf(MSG_ERROR, \ - "Line %d: Invalid value '%s'", \ - line, pos); \ - errors++; \ - return errors; \ - } - PARSE_TEST_PROBABILITY(ignore_probe_probability) - PARSE_TEST_PROBABILITY(ignore_auth_probability) - PARSE_TEST_PROBABILITY(ignore_assoc_probability) - PARSE_TEST_PROBABILITY(ignore_reassoc_probability) - PARSE_TEST_PROBABILITY(corrupt_gtk_rekey_mic_probability) - } else if (os_strcmp(buf, "bss_load_test") == 0) { - WPA_PUT_LE16(bss->bss_load_test, atoi(pos)); - pos = os_strchr(pos, ':'); - if (pos == NULL) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "bss_load_test", line); - return 1; - } - pos++; - bss->bss_load_test[2] = atoi(pos); - pos = os_strchr(pos, ':'); - if (pos == NULL) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "bss_load_test", line); - return 1; - } - pos++; - WPA_PUT_LE16(&bss->bss_load_test[3], atoi(pos)); - bss->bss_load_test_set = 1; +#define PARSE_TEST_PROBABILITY(_val) \ + } else if (os_strcmp(buf, #_val) == 0) { \ + char *end; \ + \ + conf->_val = strtod(pos, &end); \ + if (*end || conf->_val < 0.0 || \ + conf->_val > 1.0) { \ + wpa_printf(MSG_ERROR, \ + "Line %d: Invalid value '%s'", \ + line, pos); \ + return 1; \ + } + PARSE_TEST_PROBABILITY(ignore_probe_probability) + PARSE_TEST_PROBABILITY(ignore_auth_probability) + PARSE_TEST_PROBABILITY(ignore_assoc_probability) + PARSE_TEST_PROBABILITY(ignore_reassoc_probability) + PARSE_TEST_PROBABILITY(corrupt_gtk_rekey_mic_probability) + } else if (os_strcmp(buf, "bss_load_test") == 0) { + WPA_PUT_LE16(bss->bss_load_test, atoi(pos)); + pos = os_strchr(pos, ':'); + if (pos == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Invalid bss_load_test", + line); + return 1; + } + pos++; + bss->bss_load_test[2] = atoi(pos); + pos = os_strchr(pos, ':'); + if (pos == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Invalid bss_load_test", + line); + return 1; + } + pos++; + WPA_PUT_LE16(&bss->bss_load_test[3], atoi(pos)); + bss->bss_load_test_set = 1; #endif /* CONFIG_TESTING_OPTIONS */ - } else if (os_strcmp(buf, "vendor_elements") == 0) { - struct wpabuf *elems; - size_t len = os_strlen(pos); - if (len & 0x01) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "vendor_elements '%s'", line, pos); - return 1; - } - len /= 2; - if (len == 0) { - wpabuf_free(bss->vendor_elements); - bss->vendor_elements = NULL; - return 0; - } + } else if (os_strcmp(buf, "vendor_elements") == 0) { + struct wpabuf *elems; + size_t len = os_strlen(pos); + if (len & 0x01) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid vendor_elements '%s'", + line, pos); + return 1; + } + len /= 2; + if (len == 0) { + wpabuf_free(bss->vendor_elements); + bss->vendor_elements = NULL; + return 0; + } - elems = wpabuf_alloc(len); - if (elems == NULL) - return 1; + elems = wpabuf_alloc(len); + if (elems == NULL) + return 1; - if (hexstr2bin(pos, wpabuf_put(elems, len), len)) { - wpabuf_free(elems); - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "vendor_elements '%s'", line, pos); - return 1; - } + if (hexstr2bin(pos, wpabuf_put(elems, len), len)) { + wpabuf_free(elems); + wpa_printf(MSG_ERROR, + "Line %d: Invalid vendor_elements '%s'", + line, pos); + return 1; + } - wpabuf_free(bss->vendor_elements); - bss->vendor_elements = elems; - } else if (os_strcmp(buf, "sae_anti_clogging_threshold") == 0) { - bss->sae_anti_clogging_threshold = atoi(pos); - } else if (os_strcmp(buf, "sae_groups") == 0) { - if (hostapd_parse_intlist(&bss->sae_groups, pos)) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "sae_groups value '%s'", line, pos); - return 1; - } - } else { - wpa_printf(MSG_ERROR, "Line %d: unknown configuration " - "item '%s'", line, buf); - errors++; + wpabuf_free(bss->vendor_elements); + bss->vendor_elements = elems; + } else if (os_strcmp(buf, "sae_anti_clogging_threshold") == 0) { + bss->sae_anti_clogging_threshold = atoi(pos); + } else if (os_strcmp(buf, "sae_groups") == 0) { + if (hostapd_parse_intlist(&bss->sae_groups, pos)) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid sae_groups value '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "local_pwr_constraint") == 0) { + int val = atoi(pos); + if (val < 0 || val > 255) { + wpa_printf(MSG_ERROR, "Line %d: Invalid local_pwr_constraint %d (expected 0..255)", + line, val); + return 1; } + conf->local_pwr_constraint = val; + } else if (os_strcmp(buf, "spectrum_mgmt_required") == 0) { + conf->spectrum_mgmt_required = atoi(pos); + } else { + wpa_printf(MSG_ERROR, + "Line %d: unknown configuration item '%s'", + line, buf); + return 1; } - return errors; + return 0; } @@ -2987,7 +3238,7 @@ struct hostapd_config * hostapd_config_read(const char *fname) fclose(f); for (i = 0; i < conf->num_bss; i++) - hostapd_set_security_params(conf->bss[i]); + hostapd_set_security_params(conf->bss[i], 1); if (hostapd_config_check(conf, 1)) errors++; @@ -3019,7 +3270,7 @@ int hostapd_set_iface(struct hostapd_config *conf, } for (i = 0; i < conf->num_bss; i++) - hostapd_set_security_params(conf->bss[i]); + hostapd_set_security_params(conf->bss[i], 0); if (hostapd_config_check(conf, 0)) { wpa_printf(MSG_ERROR, "Configuration check failed"); diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index 4a9da5f..6265265 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -30,6 +30,7 @@ #include "ap/wps_hostapd.h" #include "ap/ctrl_iface_ap.h" #include "ap/ap_drv_ops.h" +#include "ap/hs20.h" #include "ap/wnm_ap.h" #include "ap/wpa_auth.h" #include "wps/wps_defs.h" @@ -617,6 +618,82 @@ static int hostapd_ctrl_iface_wps_get_status(struct hostapd_data *hapd, #endif /* CONFIG_WPS */ +#ifdef CONFIG_HS20 + +static int hostapd_ctrl_iface_hs20_wnm_notif(struct hostapd_data *hapd, + const char *cmd) +{ + u8 addr[ETH_ALEN]; + const char *url; + + if (hwaddr_aton(cmd, addr)) + return -1; + url = cmd + 17; + if (*url == '\0') { + url = NULL; + } else { + if (*url != ' ') + return -1; + url++; + if (*url == '\0') + url = NULL; + } + + return hs20_send_wnm_notification(hapd, addr, 1, url); +} + + +static int hostapd_ctrl_iface_hs20_deauth_req(struct hostapd_data *hapd, + const char *cmd) +{ + u8 addr[ETH_ALEN]; + int code, reauth_delay, ret; + const char *pos; + size_t url_len; + struct wpabuf *req; + + /* <STA MAC Addr> <Code(0/1)> <Re-auth-Delay(sec)> [URL] */ + if (hwaddr_aton(cmd, addr)) + return -1; + + pos = os_strchr(cmd, ' '); + if (pos == NULL) + return -1; + pos++; + code = atoi(pos); + + pos = os_strchr(pos, ' '); + if (pos == NULL) + return -1; + pos++; + reauth_delay = atoi(pos); + + url_len = 0; + pos = os_strchr(pos, ' '); + if (pos) { + pos++; + url_len = os_strlen(pos); + } + + req = wpabuf_alloc(4 + url_len); + if (req == NULL) + return -1; + wpabuf_put_u8(req, code); + wpabuf_put_le16(req, reauth_delay); + wpabuf_put_u8(req, url_len); + if (pos) + wpabuf_put_data(req, pos, url_len); + + wpa_printf(MSG_DEBUG, "HS 2.0: Send WNM-Notification to " MACSTR + " to indicate imminent deauthentication (code=%d " + "reauth_delay=%d)", MAC2STR(addr), code, reauth_delay); + ret = hs20_send_wnm_notification_deauth_req(hapd, addr, req); + wpabuf_free(req); + return ret; +} + +#endif /* CONFIG_HS20 */ + #ifdef CONFIG_INTERWORKING @@ -863,6 +940,14 @@ static int hostapd_ctrl_iface_get_config(struct hostapd_data *hapd, return pos - buf; pos += ret; } +#ifdef CONFIG_SAE + if (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_FT_SAE) { + ret = os_snprintf(pos, end - pos, "FT-SAE "); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + } +#endif /* CONFIG_SAE */ #endif /* CONFIG_IEEE80211R */ #ifdef CONFIG_IEEE80211W if (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_PSK_SHA256) { @@ -878,6 +963,14 @@ static int hostapd_ctrl_iface_get_config(struct hostapd_data *hapd, pos += ret; } #endif /* CONFIG_IEEE80211W */ +#ifdef CONFIG_SAE + if (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_SAE) { + ret = os_snprintf(pos, end - pos, "SAE "); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + } +#endif /* CONFIG_SAE */ ret = os_snprintf(pos, end - pos, "\n"); if (ret < 0 || ret >= end - pos) @@ -983,7 +1076,37 @@ static int hostapd_ctrl_iface_set(struct hostapd_data *hapd, char *cmd) hapd->ext_mgmt_frame_handling = atoi(value); #endif /* CONFIG_TESTING_OPTIONS */ } else { + struct sta_info *sta; + int vlan_id; + ret = hostapd_set_iface(hapd->iconf, hapd->conf, cmd, value); + if (ret) + return ret; + + if (os_strcasecmp(cmd, "deny_mac_file") == 0) { + 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 || vlan_id == sta->vlan_id)) + ap_sta_disconnect( + hapd, sta, sta->addr, + WLAN_REASON_UNSPECIFIED); + } + } else if (hapd->conf->macaddr_acl == DENY_UNLESS_ACCEPTED && + os_strcasecmp(cmd, "accept_mac_file") == 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 && vlan_id != sta->vlan_id)) + ap_sta_disconnect( + hapd, sta, sta->addr, + WLAN_REASON_UNSPECIFIED); + } + } } return ret; @@ -1158,6 +1281,63 @@ static int hostapd_ctrl_iface_mib(struct hostapd_data *hapd, char *reply, } +static int hostapd_ctrl_iface_vendor(struct hostapd_data *hapd, char *cmd, + char *buf, size_t buflen) +{ + int ret; + char *pos; + u8 *data = NULL; + unsigned int vendor_id, subcmd; + struct wpabuf *reply; + size_t data_len = 0; + + /* cmd: <vendor id> <subcommand id> [<hex formatted data>] */ + vendor_id = strtoul(cmd, &pos, 16); + if (!isblank(*pos)) + return -EINVAL; + + subcmd = strtoul(pos, &pos, 10); + + if (*pos != '\0') { + if (!isblank(*pos++)) + return -EINVAL; + data_len = os_strlen(pos); + } + + if (data_len) { + data_len /= 2; + data = os_malloc(data_len); + if (!data) + return -ENOBUFS; + + if (hexstr2bin(pos, data, data_len)) { + wpa_printf(MSG_DEBUG, + "Vendor command: wrong parameter format"); + os_free(data); + return -EINVAL; + } + } + + reply = wpabuf_alloc((buflen - 1) / 2); + if (!reply) { + os_free(data); + return -ENOBUFS; + } + + ret = hostapd_drv_vendor_cmd(hapd, vendor_id, subcmd, data, data_len, + reply); + + if (ret == 0) + ret = wpa_snprintf_hex(buf, buflen, wpabuf_head_u8(reply), + wpabuf_len(reply)); + + wpabuf_free(reply); + os_free(data); + + return ret; +} + + static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, void *sock_ctx) { @@ -1318,6 +1498,14 @@ static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, if (hostapd_ctrl_iface_send_qos_map_conf(hapd, buf + 18)) reply_len = -1; #endif /* CONFIG_INTERWORKING */ +#ifdef CONFIG_HS20 + } else if (os_strncmp(buf, "HS20_WNM_NOTIF ", 15) == 0) { + if (hostapd_ctrl_iface_hs20_wnm_notif(hapd, buf + 15)) + reply_len = -1; + } else if (os_strncmp(buf, "HS20_DEAUTH_REQ ", 16) == 0) { + if (hostapd_ctrl_iface_hs20_deauth_req(hapd, buf + 16)) + reply_len = -1; +#endif /* CONFIG_HS20 */ #ifdef CONFIG_WNM } else if (os_strncmp(buf, "DISASSOC_IMMINENT ", 18) == 0) { if (hostapd_ctrl_iface_disassoc_imminent(hapd, buf + 18)) @@ -1355,6 +1543,10 @@ static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, } else if (os_strncmp(buf, "CHAN_SWITCH ", 12) == 0) { if (hostapd_ctrl_iface_chan_switch(hapd, buf + 12)) reply_len = -1; + } else if (os_strncmp(buf, "VENDOR ", 7) == 0) { + reply_len = hostapd_ctrl_iface_vendor(hapd, buf + 7, reply, + reply_size); + } else { os_memcpy(reply, "UNKNOWN COMMAND\n", 16); reply_len = 16; @@ -1650,6 +1842,12 @@ static void hostapd_global_ctrl_iface_receive(int sock, void *eloop_ctx, } else if (os_strncmp(buf, "REMOVE ", 7) == 0) { if (hostapd_ctrl_iface_remove(interfaces, buf + 7) < 0) reply_len = -1; +#ifdef CONFIG_MODULE_TESTS + } else if (os_strcmp(buf, "MODULE_TESTS") == 0) { + int hapd_module_tests(void); + if (hapd_module_tests() < 0) + reply_len = -1; +#endif /* CONFIG_MODULE_TESTS */ } else { wpa_printf(MSG_DEBUG, "Unrecognized global ctrl_iface command " "ignored"); diff --git a/hostapd/defconfig b/hostapd/defconfig index e329a11..5b74b64 100644 --- a/hostapd/defconfig +++ b/hostapd/defconfig @@ -55,10 +55,7 @@ CONFIG_RSN_PREAUTH=y CONFIG_PEERKEY=y # IEEE 802.11w (management frame protection) -# This version is an experimental implementation based on IEEE 802.11w/D1.0 -# draft and is subject to change since the standard has not yet been finalized. -# Driver support is also needed for IEEE 802.11w. -#CONFIG_IEEE80211W=y +CONFIG_IEEE80211W=y # Integrated EAP server CONFIG_EAP=y @@ -116,8 +113,6 @@ CONFIG_EAP_TTLS=y # Wi-Fi Protected Setup (WPS) #CONFIG_WPS=y -# Enable WSC 2.0 support -#CONFIG_WPS2=y # Enable UPnP support for external WPS Registrars #CONFIG_WPS_UPNP=y # Enable WPS support with NFC config method diff --git a/hostapd/eap_register.c b/hostapd/eap_register.c index 981e539..8477c21 100644 --- a/hostapd/eap_register.c +++ b/hostapd/eap_register.c @@ -44,6 +44,13 @@ int eap_server_register_methods(void) ret = eap_server_unauth_tls_register(); #endif /* EAP_SERVER_TLS */ +#ifdef EAP_SERVER_TLS +#ifdef CONFIG_HS20 + if (ret == 0) + ret = eap_server_wfa_unauth_tls_register(); +#endif /* CONFIG_HS20 */ +#endif /* EAP_SERVER_TLS */ + #ifdef EAP_SERVER_MSCHAPV2 if (ret == 0) ret = eap_server_mschapv2_register(); diff --git a/hostapd/hapd_module_tests.c b/hostapd/hapd_module_tests.c new file mode 100644 index 0000000..f7887eb --- /dev/null +++ b/hostapd/hapd_module_tests.c @@ -0,0 +1,17 @@ +/* + * hostapd module tests + * Copyright (c) 2014, Jouni Malinen <j@w1.fi> + * + * 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" + +int hapd_module_tests(void) +{ + wpa_printf(MSG_INFO, "hostapd module tests"); + return 0; +} diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index da7817f..a7ab0f6 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -108,6 +108,20 @@ ssid=test # (default: 0 = disabled) #ieee80211h=1 +# Add Power Constraint element to Beacon and Probe Response frames +# This config option adds Power Constraint element when applicable and Country +# element is added. Power Constraint element is required by Transmit Power +# Control. This can be used only with ieee80211d=1. +# Valid values are 0..255. +#local_pwr_constraint=3 + +# Set Spectrum Management subfield in the Capability Information field. +# This config option forces the Spectrum Management bit to be set. When this +# option is not set, the value of the Spectrum Management bit depends on whether +# DFS or TPC is required by regulatory authorities. This can be used only with +# ieee80211d=1 and local_pwr_constraint configured. +#spectrum_mgmt_required=1 + # Operation mode (a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g, # ad = IEEE 802.11ad (60 GHz); a/g options are used with IEEE 802.11n, too, to # specify band) @@ -140,6 +154,14 @@ channel=1 # Defaults: #acs_num_scans=5 +# Channel list restriction. This option allows hostapd to select one of the +# provided channels when a channel should be automatically selected. This +# is currently only used for DFS when the current channels becomes unavailable +# due to radar interference, and is currently only useful when ieee80211h=1 is +# set. +# Default: not set (allow any enabled channel to be selected) +#chanlist=100 104 108 112 116 + # Beacon interval in kus (1.024 ms) (default: 100; range 15..65535) beacon_int=100 @@ -431,7 +453,7 @@ wmm_ac_vo_acm=0 # LDPC coding capability: [LDPC] = supported # Supported channel width set: [HT40-] = both 20 MHz and 40 MHz with secondary # channel below the primary channel; [HT40+] = both 20 MHz and 40 MHz -# with secondary channel below the primary channel +# with secondary channel above the primary channel # (20 MHz only if neither is set) # Note: There are limits on which channels can be used with HT40- and # HT40+. Following table shows the channels that may be available for @@ -458,7 +480,7 @@ wmm_ac_vo_acm=0 # Maximum A-MSDU length: [MAX-AMSDU-7935] for 7935 octets (3839 octets if not # set) # DSSS/CCK Mode in 40 MHz: [DSSS_CCK-40] = allowed (not allowed if not set) -# PSMP support: [PSMP] (disabled if not set) +# 40 MHz intolerant [40-INTOLERANT] (not advertised if not set) # L-SIG TXOP protection support: [LSIG-TXOP-PROT] (disabled if not set) #ht_capab=[HT40-][SHORT-GI-20][SHORT-GI-40] @@ -971,6 +993,11 @@ own_ip_addr=127.0.0.1 # The UDP port number for the RADIUS authentication server #radius_server_auth_port=1812 +# The UDP port number for the RADIUS accounting server +# Commenting this out or setting this to 0 can be used to disable RADIUS +# accounting while still enabling RADIUS authentication. +#radius_server_acct_port=1813 + # Use IPv6 with RADIUS server (IPv4 will also be supported using IPv6 API) #radius_server_ipv6=1 @@ -1077,6 +1104,17 @@ own_ip_addr=127.0.0.1 # 2 = required #ieee80211w=0 +# Group management cipher suite +# Default: AES-128-CMAC (BIP) +# Other options (depending on driver support): +# BIP-GMAC-128 +# BIP-GMAC-256 +# BIP-CMAC-256 +# Note: All the stations connecting to the BSS will also need to support the +# selected cipher. The default AES-128-CMAC is the only option that is commonly +# available in deployed devices. +#group_mgmt_cipher=AES-128-CMAC + # Association SA Query maximum timeout (in TU = 1.024 ms; for MFP) # (maximum time to wait for a SA Query response) # dot11AssociationSAQueryMaximumTimeout, 1...4294967295 @@ -1521,6 +1559,8 @@ own_ip_addr=127.0.0.1 # accordance with IETF RFC 4282 # NAI Realm(s): Semi-colon delimited NAI Realm(s) # EAP Method: <EAP Method>[:<[AuthParam1:Val1]>][<[AuthParam2:Val2]>][...] +# EAP Method types, see: +# http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml#eap-numbers-4 # AuthParam (Table 8-188 in IEEE Std 802.11-2012): # ID 2 = Non-EAP Inner Authentication Type # 1 = PAP, 2 = CHAP, 3 = MSCHAP, 4 = MSCHAPV2 @@ -1563,6 +1603,21 @@ own_ip_addr=127.0.0.1 # forging such frames to other stations in the BSS. #disable_dgaf=1 +# OSU Server-Only Authenticated L2 Encryption Network +#osen=1 + +# ANQP Domain ID (0..65535) +# An identifier for a set of APs in an ESS that share the same common ANQP +# information. 0 = Some of the ANQP information is unique to this AP (default). +#anqp_domain_id=1234 + +# Deauthentication request timeout +# If the RADIUS server indicates that the station is not allowed to connect to +# the BSS/ESS, the AP can allow the station some time to download a +# notification page (URL included in the message). This parameter sets that +# timeout in seconds. +#hs20_deauth_req_timeout=60 + # Operator Friendly Name # This parameter can be used to configure one or more Operator Friendly Name # Duples. Each entry has a two or three character language code (ISO-639) @@ -1606,6 +1661,32 @@ own_ip_addr=127.0.0.1 # channels 36-48): #hs20_operating_class=5173 +# OSU icons +# <Icon Width>:<Icon Height>:<Language code>:<Icon Type>:<Name>:<file path> +#hs20_icon=32:32:eng:image/png:icon32:/tmp/icon32.png +#hs20_icon=64:64:eng:image/png:icon64:/tmp/icon64.png + +# OSU SSID (see ssid2 for format description) +# This is the SSID used for all OSU connections to all the listed OSU Providers. +#osu_ssid="example" + +# OSU Providers +# One or more sets of following parameter. Each OSU provider is started by the +# mandatory osu_server_uri item. The other parameters add information for the +# last added OSU provider. +# +#osu_server_uri=https://example.com/osu/ +#osu_friendly_name=eng:Example operator +#osu_friendly_name=fin:Esimerkkipalveluntarjoaja +#osu_nai=anonymous@example.com +#osu_method_list=1 0 +#osu_icon=icon32 +#osu_icon=icon64 +#osu_service_desc=eng:Example services +#osu_service_desc=fin:Esimerkkipalveluja +# +#osu_server_uri=... + ##### TESTING OPTIONS ######################################################### # # The options in this section are only available when the build configuration @@ -1649,6 +1730,11 @@ own_ip_addr=127.0.0.1 # - is not the same as the MAC address of the radio # - is not the same as any other explicitly specified BSSID # +# Not all drivers support multiple BSSes. The exact mechanism for determining +# the driver capabilities is driver specific. With the current (i.e., a recent +# kernel) drivers using nl80211, this information can be checked with "iw list" +# (search for "valid interface combinations"). +# # Please note that hostapd uses some of the values configured for the first BSS # as the defaults for the following BSSes. However, it is recommended that all # BSSes include explicit configuration of all relevant configuration items. diff --git a/hostapd/hostapd.eap_user b/hostapd/hostapd.eap_user index 12a2c61..00edc95 100644 --- a/hostapd/hostapd.eap_user +++ b/hostapd/hostapd.eap_user @@ -48,6 +48,12 @@ # TTLS-CHAP, TTLS-MSCHAP, TTLS-MSCHAPV2. TTLS-PAP and TTLS-CHAP require a # plaintext password while TTLS-MSCHAP and TTLS-MSCHAPV2 can use NT password # hash. +# +# Arbitrary RADIUS attributes can be added into Access-Accept packets similarly +# to the way radius_auth_req_attr is used for Access-Request packet in +# hostapd.conf. For EAP server, this is configured separately for each user +# entry with radius_accept_attr=<value> line(s) following the main user entry +# line. # Phase 1 users "user" MD5 "password" diff --git a/hostapd/hostapd.eap_user_sqlite b/hostapd/hostapd.eap_user_sqlite index f688327..826db34 100644 --- a/hostapd/hostapd.eap_user_sqlite +++ b/hostapd/hostapd.eap_user_sqlite @@ -2,6 +2,7 @@ CREATE TABLE users( identity TEXT PRIMARY KEY, methods TEXT, password TEXT, + remediation TEXT, phase2 INTEGER ); @@ -15,3 +16,11 @@ INSERT INTO users(identity,methods,password,phase2) VALUES ('DOMAIN\mschapv2 use INSERT INTO wildcards(identity,methods) VALUES ('','TTLS,TLS'); INSERT INTO wildcards(identity,methods) VALUES ('0','AKA'); + +CREATE TABLE authlog( + timestamp TEXT, + session TEXT, + nas_ip TEXT, + username TEXT, + note TEXT +); diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c index eee8504..1c4a84c 100644 --- a/hostapd/hostapd_cli.c +++ b/hostapd/hostapd_cli.c @@ -743,6 +743,51 @@ static int hostapd_cli_cmd_send_qos_map_conf(struct wpa_ctrl *ctrl, } +static int hostapd_cli_cmd_hs20_wnm_notif(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + char buf[300]; + int res; + + if (argc < 2) { + printf("Invalid 'hs20_wnm_notif' command - two arguments (STA " + "addr and URL) are needed\n"); + return -1; + } + + res = os_snprintf(buf, sizeof(buf), "HS20_WNM_NOTIF %s %s", + argv[0], argv[1]); + if (res < 0 || res >= (int) sizeof(buf)) + return -1; + return wpa_ctrl_command(ctrl, buf); +} + + +static int hostapd_cli_cmd_hs20_deauth_req(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + char buf[300]; + int res; + + if (argc < 3) { + printf("Invalid 'hs20_deauth_req' command - at least three arguments (STA addr, Code, Re-auth Delay) are needed\n"); + return -1; + } + + if (argc > 3) + res = os_snprintf(buf, sizeof(buf), + "HS20_DEAUTH_REQ %s %s %s %s", + argv[0], argv[1], argv[2], argv[3]); + else + res = os_snprintf(buf, sizeof(buf), + "HS20_DEAUTH_REQ %s %s %s", + argv[0], argv[1], argv[2]); + if (res < 0 || res >= (int) sizeof(buf)) + return -1; + return wpa_ctrl_command(ctrl, buf); +} + + static int hostapd_cli_cmd_quit(struct wpa_ctrl *ctrl, int argc, char *argv[]) { hostapd_cli_quit = 1; @@ -797,8 +842,8 @@ static int hostapd_cli_cmd_interface(struct wpa_ctrl *ctrl, int argc, } hostapd_cli_close_connection(); - free(ctrl_ifname); - ctrl_ifname = strdup(argv[0]); + os_free(ctrl_ifname); + ctrl_ifname = os_strdup(argv[0]); if (hostapd_cli_open_connection(ctrl_ifname)) { printf("Connected to interface '%s.\n", ctrl_ifname); @@ -895,6 +940,27 @@ static int hostapd_cli_cmd_chan_switch(struct wpa_ctrl *ctrl, } +static int hostapd_cli_cmd_vendor(struct wpa_ctrl *ctrl, int argc, char *argv[]) +{ + char cmd[256]; + int res; + + if (argc < 2 || argc > 3) { + printf("Invalid vendor command\n" + "usage: <vendor id> <command id> [<hex formatted command argument>]\n"); + return -1; + } + + res = os_snprintf(cmd, sizeof(cmd), "VENDOR %s %s %s", argv[0], argv[1], + argc == 3 ? argv[2] : ""); + if (res < 0 || (size_t) res >= sizeof(cmd) - 1) { + printf("Too long VENDOR command.\n"); + return -1; + } + return wpa_ctrl_command(ctrl, cmd); +} + + struct hostapd_cli_cmd { const char *cmd; int (*handler)(struct wpa_ctrl *ctrl, int argc, char *argv[]); @@ -941,6 +1007,9 @@ static struct hostapd_cli_cmd hostapd_cli_commands[] = { { "set_qos_map_set", hostapd_cli_cmd_set_qos_map_set }, { "send_qos_map_conf", hostapd_cli_cmd_send_qos_map_conf }, { "chan_switch", hostapd_cli_cmd_chan_switch }, + { "hs20_wnm_notif", hostapd_cli_cmd_hs20_wnm_notif }, + { "hs20_deauth_req", hostapd_cli_cmd_hs20_deauth_req }, + { "vendor", hostapd_cli_cmd_vendor }, { NULL, NULL } }; diff --git a/hostapd/main.c b/hostapd/main.c index 5a1b0a9..a9d7da5 100644 --- a/hostapd/main.c +++ b/hostapd/main.c @@ -14,6 +14,7 @@ #include "utils/common.h" #include "utils/eloop.h" +#include "utils/uuid.h" #include "crypto/random.h" #include "crypto/tls.h" #include "common/version.h" @@ -91,7 +92,8 @@ static void hostapd_logger_cb(void *ctx, const u8 *addr, unsigned int module, if (hapd && hapd->conf && addr) os_snprintf(format, maxlen, "%s: STA " MACSTR "%s%s: %s", hapd->conf->iface, MAC2STR(addr), - module_str ? " " : "", module_str, txt); + module_str ? " " : "", module_str ? module_str : "", + txt); else if (hapd && hapd->conf) os_snprintf(format, maxlen, "%s:%s%s %s", hapd->conf->iface, module_str ? " " : "", @@ -501,6 +503,27 @@ static int hostapd_get_ctrl_iface_group(struct hapd_interfaces *interfaces, } +#ifdef CONFIG_WPS +static int gen_uuid(const char *txt_addr) +{ + u8 addr[ETH_ALEN]; + u8 uuid[UUID_LEN]; + char buf[100]; + + if (hwaddr_aton(txt_addr, addr) < 0) + return -1; + + uuid_gen_mac_addr(addr, uuid); + if (uuid_bin2str(uuid, buf, sizeof(buf)) < 0) + return -1; + + printf("%s\n", buf); + + return 0; +} +#endif /* CONFIG_WPS */ + + int main(int argc, char *argv[]) { struct hapd_interfaces interfaces; @@ -531,7 +554,7 @@ int main(int argc, char *argv[]) interfaces.global_ctrl_sock = -1; for (;;) { - c = getopt(argc, argv, "b:Bde:f:hKP:Ttvg:G:"); + c = getopt(argc, argv, "b:Bde:f:hKP:Ttu:vg:G:"); if (c < 0) break; switch (c) { @@ -588,6 +611,10 @@ int main(int argc, char *argv[]) bss_config = tmp_bss; bss_config[num_bss_configs++] = optarg; break; +#ifdef CONFIG_WPS + case 'u': + return gen_uuid(optarg); +#endif /* CONFIG_WPS */ default: usage(); break; @@ -701,8 +728,14 @@ int main(int argc, char *argv[]) out: hostapd_global_ctrl_iface_deinit(&interfaces); /* Deinitialize all interfaces */ - for (i = 0; i < interfaces.count; i++) + for (i = 0; i < interfaces.count; i++) { + if (!interfaces.iface[i]) + continue; + interfaces.iface[i]->driver_ap_teardown = + !!(interfaces.iface[i]->drv_flags & + WPA_DRIVER_FLAGS_AP_TEARDOWN_SUPPORT); hostapd_interface_deinit_free(interfaces.iface[i]); + } os_free(interfaces.iface); hostapd_global_deinit(pid_file); diff --git a/hostapd/wps-ap-nfc.py b/hostapd/wps-ap-nfc.py index 58e538a..2fc3012 100644..100755 --- a/hostapd/wps-ap-nfc.py +++ b/hostapd/wps-ap-nfc.py @@ -22,6 +22,20 @@ import wpaspy wpas_ctrl = '/var/run/hostapd' continue_loop = True +summary_file = None +success_file = None + +def summary(txt): + print txt + if summary_file: + with open(summary_file, 'a') as f: + f.write(txt + "\n") + +def success_report(txt): + summary(txt) + if success_file: + with open(success_file, 'a') as f: + f.write(txt + "\n") def wpas_connect(): ifaces = [] @@ -48,7 +62,7 @@ def wpas_connect(): def wpas_tag_read(message): wpas = wpas_connect() if (wpas == None): - return + return False if "FAIL" in wpas.request("WPS_NFC_TAG_READ " + str(message).encode("hex")): return False return True @@ -58,21 +72,30 @@ def wpas_get_config_token(): wpas = wpas_connect() if (wpas == None): return None - return wpas.request("WPS_NFC_CONFIG_TOKEN NDEF").rstrip().decode("hex") + ret = wpas.request("WPS_NFC_CONFIG_TOKEN NDEF") + if "FAIL" in ret: + return None + return ret.rstrip().decode("hex") def wpas_get_password_token(): wpas = wpas_connect() if (wpas == None): return None - return wpas.request("WPS_NFC_TOKEN NDEF").rstrip().decode("hex") + ret = wpas.request("WPS_NFC_TOKEN NDEF") + if "FAIL" in ret: + return None + return ret.rstrip().decode("hex") def wpas_get_handover_sel(): wpas = wpas_connect() if (wpas == None): return None - return wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR").rstrip().decode("hex") + ret = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR") + if "FAIL" in ret: + return None + return ret.rstrip().decode("hex") def wpas_report_handover(req, sel): @@ -90,8 +113,25 @@ class HandoverServer(nfc.handover.HandoverServer): self.ho_server_processing = False self.success = False + # override to avoid parser error in request/response.pretty() in nfcpy + # due to new WSC handover format + def _process_request(self, request): + summary("received handover request {}".format(request.type)) + response = nfc.ndef.Message("\xd1\x02\x01Hs\x12") + if not request.type == 'urn:nfc:wkt:Hr': + summary("not a handover request") + else: + try: + request = nfc.ndef.HandoverRequestMessage(request) + except nfc.ndef.DecodeError as e: + summary("error decoding 'Hr' message: {}".format(e)) + else: + response = self.process_request(request) + summary("send handover response {}".format(response.type)) + return response + def process_request(self, request): - print "HandoverServer - request received" + summary("HandoverServer - request received") try: print "Parsed handover request: " + request.pretty() except Exception, e: @@ -103,14 +143,17 @@ class HandoverServer(nfc.handover.HandoverServer): for carrier in request.carriers: print "Remote carrier type: " + carrier.type if carrier.type == "application/vnd.wfa.wsc": - print "WPS carrier type match - add WPS carrier record" + summary("WPS carrier type match - add WPS carrier record") data = wpas_get_handover_sel() if data is None: - print "Could not get handover select carrier record from hostapd" + summary("Could not get handover select carrier record from hostapd") continue print "Handover select carrier record from hostapd:" print data.encode("hex") - wpas_report_handover(carrier.record, data) + if "OK" in wpas_report_handover(carrier.record, data): + success_report("Handover reported successfully") + else: + summary("Handover report rejected") message = nfc.ndef.Message(data); sel.add_carrier(message[0], "active", message[1:]) @@ -122,7 +165,7 @@ class HandoverServer(nfc.handover.HandoverServer): print e print str(sel).encode("hex") - print "Sending handover select" + summary("Sending handover select") self.success = True return sel @@ -133,19 +176,23 @@ def wps_tag_read(tag): for record in tag.ndef.message: print "record type " + record.type if record.type == "application/vnd.wfa.wsc": - print "WPS tag - send to hostapd" + summary("WPS tag - send to hostapd") success = wpas_tag_read(tag.ndef.message) break else: - print "Empty tag" + summary("Empty tag") + + if success: + success_report("Tag read succeeded") return success def rdwr_connected_write(tag): - print "Tag found - writing" + summary("Tag found - writing - " + str(tag)) global write_data tag.ndef.message = str(write_data) + success_report("Tag write succeeded") print "Done - remove tag" global only_one if only_one: @@ -156,12 +203,12 @@ def rdwr_connected_write(tag): time.sleep(0.1) def wps_write_config_tag(clf, wait_remove=True): - print "Write WPS config token" + summary("Write WPS config token") global write_data, write_wait_remove write_wait_remove = wait_remove write_data = wpas_get_config_token() if write_data == None: - print "Could not get WPS config token from hostapd" + summary("Could not get WPS config token from hostapd") return print "Touch an NFC tag" @@ -169,12 +216,12 @@ def wps_write_config_tag(clf, wait_remove=True): def wps_write_password_tag(clf, wait_remove=True): - print "Write WPS password token" + summary("Write WPS password token") global write_data, write_wait_remove write_wait_remove = wait_remove write_data = wpas_get_password_token() if write_data == None: - print "Could not get WPS password token from hostapd" + summary("Could not get WPS password token from hostapd") return print "Touch an NFC tag" @@ -183,7 +230,7 @@ def wps_write_password_tag(clf, wait_remove=True): def rdwr_connected(tag): global only_one, no_wait - print "Tag connected: " + str(tag) + summary("Tag connected: " + str(tag)) if tag.ndef: print "NDEF tag: " + tag.type @@ -196,7 +243,8 @@ def rdwr_connected(tag): global continue_loop continue_loop = False else: - print "Not an NDEF tag - remove tag" + summary("Not an NDEF tag - remove tag") + return True return not no_wait @@ -229,6 +277,10 @@ def main(): help='run only one operation and exit') parser.add_argument('--no-wait', action='store_true', help='do not wait for tag to be removed before exiting') + parser.add_argument('--summary', + help='summary file for writing status updates') + parser.add_argument('--success', + help='success file for writing success update') parser.add_argument('command', choices=['write-config', 'write-password'], nargs='?') @@ -240,6 +292,14 @@ def main(): global no_wait no_wait = args.no_wait + if args.summary: + global summary_file + summary_file = args.summary + + if args.success: + global success_file + success_file = args.success + logging.basicConfig(level=args.loglevel) try: diff --git a/hs20/client/Android.mk b/hs20/client/Android.mk new file mode 100644 index 0000000..b7bd932 --- /dev/null +++ b/hs20/client/Android.mk @@ -0,0 +1,73 @@ +LOCAL_PATH := $(call my-dir) + +INCLUDES = $(LOCAL_PATH) +INCLUDES += $(LOCAL_PATH)/../../src/utils +INCLUDES += $(LOCAL_PATH)/../../src/common +INCLUDES += $(LOCAL_PATH)/../../src +INCLUDES += external/openssl/include +INCLUDES += external/libxml2/include +INCLUDES += external/curl/include +INCLUDES += external/webkit/Source/WebKit/gtk +INCLUDES += external/icu4c/common + + +#GTKCFLAGS := $(shell pkg-config --cflags gtk+-2.0 webkit-1.0) +#GTKLIBS := $(shell pkg-config --libs gtk+-2.0 webkit-1.0) +#CFLAGS += $(GTKCFLAGS) +#LIBS += $(GTKLIBS) + +L_CFLAGS += -DCONFIG_CTRL_IFACE +L_CFLAGS += -DCONFIG_CTRL_IFACE_UNIX +L_CFLAGS += -DCONFIG_CTRL_IFACE_CLIENT_DIR=\"/data/misc/wifi/sockets\" +L_CFLAGS += -DLIBXML_SCHEMAS_ENABLED +L_CFLAGS += -DLIBXML_REGEXP_ENABLED + +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 + +L_CFLAGS += -DEAP_TLS_OPENSSL + +#CFLAGS += $(shell xml2-config --cflags) +#LIBS += $(shell xml2-config --libs) + + +######################## +include $(CLEAR_VARS) +LOCAL_MODULE := hs20-osu-client +LOCAL_MODULE_TAGS := optional + +LOCAL_SHARED_LIBRARIES := libc libcutils +LOCAL_SHARED_LIBRARIES += libcrypto libssl +#LOCAL_SHARED_LIBRARIES += libxml2 +LOCAL_STATIC_LIBRARIES += libxml2 +LOCAL_SHARED_LIBRARIES += libicuuc +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..ca67b54 --- /dev/null +++ b/hs20/client/Makefile @@ -0,0 +1,94 @@ +all: hs20-osu-client + +ifndef CC +CC=gcc +endif + +ifndef LDO +LDO=$(CC) +endif + +Q=@ +E=echo +ifeq ($(V), 1) +Q= +E=true +endif + +ifndef CFLAGS +CFLAGS = -MMD -O2 -Wall -g +endif + +CFLAGS += -I../../src/utils +CFLAGS += -I../../src/common +CFLAGS += -I../../src + +ifndef CONFIG_NO_BROWSER +ifndef CONFIG_BROWSER_SYSTEM +GTKCFLAGS := $(shell pkg-config --cflags gtk+-3.0 webkitgtk-3.0) +GTKLIBS := $(shell pkg-config --libs gtk+-3.0 webkitgtk-3.0) +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) +LIBS += -lcurl + +CFLAGS += -DEAP_TLS_OPENSSL +LIBS += -lssl -lcrypto + +hs20-osu-client: $(OBJS) + $(Q)$(LDO) $(LDFLAGS) -o hs20-osu-client $(OBJS) $(LIBS) + @$(E) " LD " $@ + +%.o: %.c + $(Q)$(CC) -c -o $@ $(CFLAGS) $< + @$(E) " CC " $< + +clean: + rm -f core *~ *.o *.d hs20-osu-client + rm -f ../../src/utils/*.o + rm -f ../../src/utils/*.d + rm -f ../../src/common/*.o + rm -f ../../src/common/*.d + rm -f ../../src/crypto/*.o + rm -f ../../src/crypto/*.d + rm -f ../../src/wps/*.o + rm -f ../../src/wps/*.d + +-include $(OBJS:%.o=%.d) 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 @@ +<DevDetail xmlns="urn:oma:mo:oma-dm-devdetail:1.0"> + <Ext> + <org.wi-fi> + <Wi-Fi> + <EAPMethodList> + <EAPMethod1> + <EAPType>13</EAPType> + </EAPMethod1> + <EAPMethod2> + <EAPType>21</EAPType> + <InnerMethod>MS-CHAP-V2</InnerMethod> + </EAPMethod2> + <EAPMethod3> + <EAPType>18</EAPType> + </EAPMethod3> + <EAPMethod4> + <EAPType>23</EAPType> + </EAPMethod4> + <EAPMethod5> + <EAPType>50</EAPType> + </EAPMethod5> + </EAPMethodList> + <ManufacturingCertificate>false</ManufacturingCertificate> + <Wi-FiMACAddress>020102030405</Wi-FiMACAddress> + <IMSI>310026000000000</IMSI> + <IMEI_MEID>imei:490123456789012</IMEI_MEID> + <ClientTriggerRedirectURI>http://localhost:12345/</ClientTriggerRedirectURI> + <Ops> + <launchBrowserToURI></launchBrowserToURI> + <negotiateClientCertTLS></negotiateClientCertTLS> + <getCertificate></getCertificate> + </Ops> + </Wi-Fi> + </org.wi-fi> + </Ext> + <URI> + <MaxDepth>0</MaxDepth> + <MaxTotLen>0</MaxTotLen> + <MaxSegLen>0</MaxSegLen> + </URI> + <DevType>MobilePhone</DevType> + <OEM>Manufacturer</OEM> + <FwV>1.0</FwV> + <SwV>1.0</SwV> + <HwV>1.0</HwV> + <LrgObj>false</LrgObj> +</DevDetail> 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 @@ +<DevInfo xmlns="urn:oma:mo:oma-dm-devinfo:1.0"> + <DevId>urn:Example:HS20-station:123456</DevId> + <Man>Manufacturer</Man> + <Mod>HS20-station</Mod> + <DmV>1.2</DmV> + <Lang>en</Lang> +</DevInfo> diff --git a/hs20/client/est.c b/hs20/client/est.c new file mode 100644 index 0000000..ec05bc4 --- /dev/null +++ b/hs20/client/est.c @@ -0,0 +1,715 @@ +/* + * 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 <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/pkcs7.h> +#include <openssl/rsa.h> +#include <openssl/asn1.h> +#include <openssl/asn1t.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> + +#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) +{ + PKCS7 *p7 = NULL; + const unsigned char *p = pkcs7; + STACK_OF(X509) *certs; + int i, num, ret = -1; + BIO *out = NULL; + + 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; + } + + 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: + PKCS7_free(p7); + 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((unsigned char *) 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; + +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; + + num = SKM_sk_num(AttrOrOID, csrattrs->attrs); + for (i = 0; i < num; i++) { + AttrOrOID *ao = SKM_sk_value(AttrOrOID, csrattrs->attrs, i); + 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; + RSA *rsa; + 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; + + 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; + + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) + goto fail; + + if (key_pem) { + FILE *f = fopen(key_pem, "wb"); + if (f == NULL) + goto fail; + if (!PEM_write_RSAPrivateKey(f, rsa, 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_conf_nid(NULL, NULL, NID_basic_constraints, + "CA:FALSE"); + if (ex == NULL || + !sk_X509_EXTENSION_push(exts, ex)) + goto fail; + + ex = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, + "nonRepudiation,digitalSignature,keyEncipherment"); + if (ex == NULL || + !sk_X509_EXTENSION_push(exts, ex)) + goto fail; + + ex = X509V3_EXT_conf_nid(NULL, 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; + + X509_REQ_print(out, req); + 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; + X509_REQ_print_fp(f, req); + 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((unsigned char *) 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; + FILE *f; + 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); + f = fopen("Cert/est-resp.raw", "w"); + if (f) { + fwrite(resp, resp_len, 1, f); + fclose(f); + } + + pkcs7 = base64_decode((unsigned char *) 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..82e9106 --- /dev/null +++ b/hs20/client/oma_dm_client.c @@ -0,0 +1,1370 @@ +/* + * 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; + + 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); + 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); + 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); + 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); + 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); + 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); + 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..ea269ab --- /dev/null +++ b/hs20/client/osu_client.c @@ -0,0 +1,3203 @@ +/* + * 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 <time.h> +#include <sys/stat.h> + +#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" + + +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; +} + + +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); + 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"); + unlink("Cert/est-resp.raw"); + 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; + unsigned 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((unsigned char *) 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, + "PolicyUpdate/TrustRoot"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No PolicyUpdate/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_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 -1; + } + + 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; + + 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); + cmd_dl_polupd_ca(ctx, pps_fname, fname); + snprintf(fname, sizeof(fname), "%s/aaa-ca.pem", dir); + cmd_dl_aaa_ca(ctx, pps_fname, fname); + + 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); + 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", + fqdn); + 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; + } + } + + 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); + 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/<X+>/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/<X+>/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/<X+>/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/<X+>/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/<X+>/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/<X+>/DLBandwidth = %s", + dl); + if (ul) + wpa_printf(MSG_INFO, "- Policy/MinBackhaulThreshold/<X+>/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/<X+>/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/<X+>/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/<X+>/SSID = %s", ssid); + if (hessid) + wpa_printf(MSG_INFO, "- HomeSP/NetworkID/<X+>/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/<X+>/HomeOI = %s", + homeoi); + } else if (strcasecmp(name, "HomeOIRequired") == 0) { + str = xml_node_get_text(ctx->xml, child); + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList/<X+>/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/<X+> without HomeOI ignored"); + return; + } + + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList/<X+> '%s' required=%d", + homeoi, required); + + if (required) { + if (set_cred(ctx->ifname, id, "required_roaming_consortium", + homeoi) < 0) + wpa_printf(MSG_INFO, "Failed to set cred required_roaming_consortium"); + } else { + if (set_cred_quoted(ctx->ifname, id, "roaming_consortium", + homeoi) < 0) + wpa_printf(MSG_INFO, "Failed to set cred roaming_consortium"); + } + + 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/<X+>/FQDN = %s", + fqdn); + } else + wpa_printf(MSG_INFO, "Unknown OtherHomePartners node '%s'", + name); + } + + if (fqdn == NULL) { + wpa_printf(MSG_INFO, "- HomeSP/OtherHomePartners/<X+> 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); + /* TODO: Set to wpa_supplicant */ + 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(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/EAPMethod - TODO"); +} + + +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]; + + 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"); + } + + snprintf(buf, sizeof(buf), "%s/SP/%s/client-cert.pem", dir, fqdn); + 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"); + } + } + + snprintf(buf, sizeof(buf), "%s/SP/%s/client-key.pem", dir, fqdn); + 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]; + + 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; + snprintf(buf, sizeof(buf), "%s/SP/%s/aaa-ca.pem", dir, fqdn); + 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_nai[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; + + 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)); + snprintf(last->bssid, sizeof(last->bssid), "%s", + buf + 13); + continue; + } + if (!last) + continue; + + if (strncmp(buf, "uri=", 4) == 0) { + snprintf(last->url, sizeof(last->url), "%s", buf + 4); + continue; + } + + if (strncmp(buf, "methods=", 8) == 0) { + last->methods = strtol(buf + 8, NULL, 16); + continue; + } + + if (strncmp(buf, "osu_ssid=", 9) == 0) { + snprintf(last->osu_ssid, sizeof(last->osu_ssid), + "%s", buf + 9); + continue; + } + + if (os_strncmp(buf, "osu_nai=", 8) == 0) { + os_snprintf(last->osu_nai, sizeof(last->osu_nai), + "%s", buf + 8); + 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++]; + snprintf(txt->lang, sizeof(txt->lang), "%s", buf + 14); + snprintf(txt->text, sizeof(txt->text), "%s", pos); + } + + 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++]; + snprintf(txt->lang, sizeof(txt->lang), "%s", buf + 5); + snprintf(txt->text, sizeof(txt->text), "%s", pos); + } + + 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'; + snprintf(icon->lang, sizeof(icon->lang), "%s", pos); + pos = end + 1; + + end = strchr(pos, ':'); + if (end) + *end = '\0'; + snprintf(icon->mime_type, sizeof(icon->mime_type), + "%s", pos); + if (!pos) + continue; + pos = end + 1; + + end = strchr(pos, ':'); + if (end) + *end = '\0'; + snprintf(icon->filename, sizeof(icon->filename), + "%s", pos); + 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 *url, + unsigned int methods, int no_prod_assoc, + const char *osu_nai) +{ + int id; + const char *ifname = ctx->ifname; + char buf[200]; + struct wpa_ctrl *mon; + int res; + + id = add_network(ifname); + if (id < 0) + return -1; + if (set_network_quoted(ifname, id, "ssid", ssid) < 0) + return -1; + 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 (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") < 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 (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"); + 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) + res = cmd_prov(ctx, url); + else if (methods & 0x01) + 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, 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 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, "<html><head>" + "<meta http-equiv=\"Content-type\" content=\"text/html; " + "charset=utf-8\"<title>Select service operator</title>" + "</head><body><h1>Select service operator</h1>\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, "<p>\n" + "<a href=\"http://localhost:12345/osu/%d\">" + "<table><tr><td>", (int) i + 1); +#else /* ANDROID */ + fprintf(f, "<p>\n" + "<a href=\"osu://%d\">" + "<table><tr><td>", (int) i + 1); +#endif /* ANDROID */ + for (j = 0; j < last->icon_count; j++) { + fprintf(f, "<img src=\"osu-icon-%d.%s\">\n", + last->icon[j].id, + strcasecmp(last->icon[j].mime_type, + "image/png") == 0 ? "png" : "icon"); + } + fprintf(f, "<td>"); + for (j = 0; j < last->friendly_name_count; j++) { + fprintf(f, "<small>[%s]</small> %s<br>\n", + last->friendly_name[j].lang, + last->friendly_name[j].text); + } + fprintf(f, "<tr><td colspan=2>"); + for (j = 0; j < last->serv_desc_count; j++) { + fprintf(f, "<small>[%s]</small> %s<br>\n", + last->serv_desc[j].lang, + last->serv_desc[j].text); + } + fprintf(f, "</table></a><br><small>BSSID: %s<br>\n" + "SSID: %s<br>\n", + last->bssid, last->osu_ssid); + if (last->osu_nai) + fprintf(f, "NAI: %s<br>\n", last->osu_nai); + fprintf(f, "URL: %s<br>\n" + "methods:%s%s<br>\n" + "</small></p>\n", + last->url, + last->methods & 0x01 ? " OMA-DM" : "", + last->methods & 0x02 ? " SOAP-XML-SPP" : ""); + } + + fprintf(f, "</body></html>\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); + +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); + 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) + ret = cmd_prov(ctx, last->url); + else if (last->methods & 0x01) + ret = cmd_oma_dm_prov(ctx, last->url); + else + ret = -1; + } else if (connect) + ret = osu_connect(ctx, last->bssid, last->osu_ssid, + last->url, last->methods, + no_prod_assoc, last->osu_nai); + } 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) < 0 && errno != EEXIST) { + wpa_printf(MSG_INFO, "mkdir(%s) failed: %s", + fname, strerror(errno)); + return -1; + } + + 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 void 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; + } + os_free(ctx->fqdn); + ctx->fqdn = os_strdup(buf); + if (ctx->fqdn == NULL) + return; + 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; + } + 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; + } + 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; + } + + 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; + } + tmp = xml_node_get_text(ctx->xml, node); + if (tmp == NULL) { + wpa_printf(MSG_INFO, "No HomeSP/FQDN text found from PPS"); + return; + } + ctx->fqdn = os_strdup(tmp); + xml_node_get_text_free(ctx->xml, tmp); + if (!ctx->fqdn) { + wpa_printf(MSG_INFO, "No FQDN known"); + return; + } + } + + 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; + } + + 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); + os_free(cred_password); + xml_node_free(ctx->xml, pps); +} + + +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]; + 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", + buf); + 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); + os_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; + 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; + unsigned int i, j; + int found; + char *host = NULL; + + wpa_printf(MSG_INFO, "osu_cert_cb(osu_cert_validation=%d)", + !ctx->no_osu_cert_validation); + + host = get_hostname(ctx->server_url); + + 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->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, "Looking for icon file name '%s' match", + 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, "Comparing to '%s' uri_len=%d name_len=%d", + logo->uri, (int) uri_len, (int) name_len); + if (uri_len < 1 + name_len) + 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) + continue; + if (os_memcmp(logo->hash, ctx->icon_hash[j], 32) == 0) { + found = 1; + break; + } + } + + if (!found) { + wpa_printf(MSG_INFO, "No icon hash match found"); + write_result(ctx, "No icon hash match 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) { + wpa_printf(MSG_ERROR, "devinfo.xml not found"); + return -1; + } + + 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); + + if (ctx->devid == NULL) { + wpa_printf(MSG_ERROR, "Could not fetch DevId from devinfo.xml"); + return -1; + } + + 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 [-dddqqKt] [-S<station ifname>] \\\n" + " [-w<wpa_supplicant ctrl_iface dir>] " + "[-r<result file>] [-f<debug file>] \\\n" + " [-s<summary file>] \\\n" + " <command> [arguments..]\n" + "commands:\n" + "- to_tnds <XML MO> <XML MO in TNDS format> [URN]\n" + "- to_tnds2 <XML MO> <XML MO in TNDS format (Path) " + "[URN]>\n" + "- from_tnds <XML MO in TNDS format> <XML MO>\n" + "- set_pps <PerProviderSubscription XML file name>\n" + "- get_fqdn <PerProviderSubscription XML file name>\n" + "- pol_upd [Server URL] [PPS] [CA cert]\n" + "- sub_rem <Server URL> [PPS] [CA cert]\n" + "- prov <Server URL> [CA cert]\n" + "- oma_dm_prov <Server URL> [CA cert]\n" + "- sim_prov <Server URL> [CA cert]\n" + "- oma_dm_sim_prov <Server URL> [CA cert]\n" + "- signup [CA cert]\n" + "- dl_osu_ca <PPS> <CA file>\n" + "- dl_polupd_ca <PPS> <CA file>\n" + "- dl_aaa_ca <PPS> <CA file>\n" + "- browser <URL>\n" + "- parse_cert <X.509 certificate (DER)>\n" + "- osu_select <OSU info directory> [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:hi:KNO:qr:s:S:tw:"); + 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': + 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 'w': + wpas_ctrl_path = optarg; + break; + case 'h': + default: + usage(); + exit(0); + break; + } + } + + 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); + } + if (argc - optind < 2) + wpa_printf(MSG_ERROR, "Server URL missing from command line"); + else + 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) { + if (argc - optind < 2) { + usage(); + exit(0); + } + ret = cmd_pol_upd(&ctx, argc > 2 ? 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]; + 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]); + 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..9a7059e --- /dev/null +++ b/hs20/client/osu_client.h @@ -0,0 +1,118 @@ +/* + * 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; +#define WORKAROUND_OCSP_OPTIONAL 0x00000001 + unsigned long int workarounds; +}; + + +/* 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..302a050 --- /dev/null +++ b/hs20/client/spp_client.c @@ -0,0 +1,995 @@ +/* + * 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 <sys/stat.h> + +#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" + + +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", &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; + + fnode = node_from_file(ctx, fname); + if (!fnode) + 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, "<![CDATA["); + cdata_end = 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); + 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; + } + return 0; + } + + 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"); + + 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/hs20/server/Makefile b/hs20/server/Makefile new file mode 100644 index 0000000..587633b --- /dev/null +++ b/hs20/server/Makefile @@ -0,0 +1,45 @@ +all: hs20_spp_server + +ifndef CC +CC=gcc +endif + +ifndef LDO +LDO=$(CC) +endif + +ifndef CFLAGS +CFLAGS = -MMD -O2 -Wall -g +endif + +CFLAGS += -I../../src/utils +CFLAGS += -I../../src/crypto + +LIBS += -lsqlite3 + +# Using glibc < 2.17 requires -lrt for clock_gettime() +LIBS += -lrt + +OBJS=spp_server.o +OBJS += hs20_spp_server.o +OBJS += ../../src/utils/xml-utils.o +OBJS += ../../src/utils/base64.o +OBJS += ../../src/utils/common.o +OBJS += ../../src/utils/os_unix.o +OBJS += ../../src/utils/wpa_debug.o +OBJS += ../../src/crypto/md5-internal.o +CFLAGS += $(shell xml2-config --cflags) +LIBS += $(shell xml2-config --libs) +OBJS += ../../src/utils/xml_libxml2.o + +hs20_spp_server: $(OBJS) + $(LDO) $(LDFLAGS) -o hs20_spp_server $(OBJS) $(LIBS) + +clean: + rm -f core *~ *.o *.d hs20_spp_server + rm -f ../../src/utils/*.o + rm -f ../../src/utils/*.d + rm -f ../../src/crypto/*.o + rm -f ../../src/crypto/*.d + +-include $(OBJS:%.o=%.d) diff --git a/hs20/server/ca/clean.sh b/hs20/server/ca/clean.sh new file mode 100755 index 0000000..c69a1f5 --- /dev/null +++ b/hs20/server/ca/clean.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +for i in server-client server server-revoked user ocsp; do + rm -f $i.csr $i.key $i.pem +done + +rm -f openssl.cnf.tmp +rm -r demoCA +rm -f ca.pem logo.asn1 logo.der server.der ocsp-server-cache.der +#rm -r rootCA diff --git a/hs20/server/ca/est-csrattrs.cnf b/hs20/server/ca/est-csrattrs.cnf new file mode 100644 index 0000000..b50ea00 --- /dev/null +++ b/hs20/server/ca/est-csrattrs.cnf @@ -0,0 +1,17 @@ +asn1 = SEQUENCE:attrs + +[attrs] +#oid1 = OID:challengePassword +attr1 = SEQUENCE:extreq +oid2 = OID:sha256WithRSAEncryption + +[extreq] +oid = OID:extensionRequest +vals = SET:extreqvals + +[extreqvals] + +oid1 = OID:macAddress +#oid2 = OID:imei +#oid3 = OID:meid +#oid4 = OID:DevId diff --git a/hs20/server/ca/est-csrattrs.sh b/hs20/server/ca/est-csrattrs.sh new file mode 100755 index 0000000..0b73a04 --- /dev/null +++ b/hs20/server/ca/est-csrattrs.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +openssl asn1parse -genconf est-csrattrs.cnf -out est-csrattrs.der -oid hs20.oid +base64 est-csrattrs.der > est-attrs.b64 diff --git a/hs20/server/ca/hs20.oid b/hs20/server/ca/hs20.oid new file mode 100644 index 0000000..a829ff2 --- /dev/null +++ b/hs20/server/ca/hs20.oid @@ -0,0 +1,7 @@ +1.3.6.1.1.1.1.22 macAddress +1.2.840.113549.1.9.14 extensionRequest +1.3.6.1.4.1.40808.1.1.1 id-wfa-hotspot-friendlyName +1.3.6.1.4.1.40808.1.1.2 id-kp-HS2.0Auth +1.3.6.1.4.1.40808.1.1.3 imei +1.3.6.1.4.1.40808.1.1.4 meid +1.3.6.1.4.1.40808.1.1.5 DevId diff --git a/hs20/server/ca/ocsp-req.sh b/hs20/server/ca/ocsp-req.sh new file mode 100755 index 0000000..931a206 --- /dev/null +++ b/hs20/server/ca/ocsp-req.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +for i in *.pem; do + echo "===[ $i ]===================" + openssl ocsp -text -CAfile ca.pem -verify_other demoCA/cacert.pem -trust_other -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ + +# openssl ocsp -text -CAfile rootCA/cacert.pem -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ + +# openssl ocsp -text -CAfile rootCA/cacert.pem -verify_other demoCA/cacert.pem -trust_other -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ +# openssl ocsp -text -CAfile rootCA/cacert.pem -VAfile ca.pem -trust_other -issuer demoCA/cacert.pem -cert $i -url http://localhost:8888/ +done diff --git a/hs20/server/ca/ocsp-responder-ica.sh b/hs20/server/ca/ocsp-responder-ica.sh new file mode 100755 index 0000000..116c6e1 --- /dev/null +++ b/hs20/server/ca/ocsp-responder-ica.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +openssl ocsp -index demoCA/index.txt -port 8888 -nmin 5 -rsigner demoCA/cacert.pem -rkey demoCA/private/cakey-plain.pem -CA demoCA/cacert.pem -resp_no_certs -text diff --git a/hs20/server/ca/ocsp-responder.sh b/hs20/server/ca/ocsp-responder.sh new file mode 100755 index 0000000..8cebd74 --- /dev/null +++ b/hs20/server/ca/ocsp-responder.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +openssl ocsp -index demoCA/index.txt -port 8888 -nmin 5 -rsigner ocsp.pem -rkey ocsp.key -CA demoCA/cacert.pem -text diff --git a/hs20/server/ca/ocsp-update-cache.sh b/hs20/server/ca/ocsp-update-cache.sh new file mode 100755 index 0000000..8ddef9b --- /dev/null +++ b/hs20/server/ca/ocsp-update-cache.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +openssl ocsp \ + -no_nonce \ + -CAfile ca.pem \ + -verify_other demoCA/cacert.pem \ + -issuer demoCA/cacert.pem \ + -cert server.pem \ + -url http://localhost:8888/ \ + -respout ocsp-server-cache.der diff --git a/hs20/server/ca/openssl-root.cnf b/hs20/server/ca/openssl-root.cnf new file mode 100644 index 0000000..5b220fe --- /dev/null +++ b/hs20/server/ca/openssl-root.cnf @@ -0,0 +1,125 @@ +# OpenSSL configuration file for Hotspot 2.0 PKI (Root CA) + +HOME = . +RANDFILE = $ENV::HOME/.rnd +oid_section = new_oids + +[ new_oids ] + +#logotypeoid=1.3.6.1.5.5.7.1.12 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./rootCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several certificates with same subject +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = optional +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +input_password = whatever +output_password = whatever + +string_mask = utf8only + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = US +countryName_min = 2 +countryName_max = 2 + +localityName = Locality Name (eg, city) +localityName_default = Tuusula + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = WFA Hotspot 2.0 + +##organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = +#@OU@ + +commonName = Common Name (e.g. server FQDN or YOUR name) +#@CN@ +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +[ req_attributes ] + +[ v3_req ] + +# Extensions to add to a certificate request +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName=DNS:example.com,DNS:another.example.com + +[ v3_ca ] + +# Hotspot 2.0 PKI requirements +subjectKeyIdentifier=hash +basicConstraints = critical,CA:true +keyUsage = critical, cRLSign, keyCertSign + +[ crl_ext ] + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ v3_OCSP ] + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = OCSPSigning diff --git a/hs20/server/ca/openssl.cnf b/hs20/server/ca/openssl.cnf new file mode 100644 index 0000000..a939f08 --- /dev/null +++ b/hs20/server/ca/openssl.cnf @@ -0,0 +1,200 @@ +# OpenSSL configuration file for Hotspot 2.0 PKI (Intermediate CA) + +HOME = . +RANDFILE = $ENV::HOME/.rnd +oid_section = new_oids + +[ new_oids ] + +#logotypeoid=1.3.6.1.5.5.7.1.12 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several certificates with same subject +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = ext_client # The extentions to add to the cert + +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +copy_extensions = copy + +default_days = 365 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = supplied +stateOrProvinceName = optional +organizationName = supplied +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_osu_server ] +countryName = match +stateOrProvinceName = optional +organizationName = match +organizationalUnitName = supplied +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +input_password = whatever +output_password = whatever + +string_mask = utf8only + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = FI +countryName_min = 2 +countryName_max = 2 + +localityName = Locality Name (eg, city) +localityName_default = Tuusula + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = w1.fi + +##organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = +#@OU@ + +commonName = Common Name (e.g. server FQDN or YOUR name) +#@CN@ +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +[ req_attributes ] + +[ v3_ca ] + +# Hotspot 2.0 PKI requirements +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid:always,issuer +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, cRLSign, keyCertSign +authorityInfoAccess = OCSP;URI:http://osu.w1.fi:8888/ +# For SP intermediate CA +#subjectAltName=critical,otherName:1.3.6.1.4.1.40808.1.1.1;UTF8String:engExample OSU +#nameConstraints=permitted;DNS:.w1.fi +#1.3.6.1.5.5.7.1.12=ASN1:SEQUENCE:LogotypeExtn + +[ v3_osu_server ] + +basicConstraints = critical, CA:true, pathlen:0 +keyUsage = critical, keyEncipherment +#@ALTNAME@ + +#logotypeoid=ASN1:SEQUENCE:LogotypeExtn +1.3.6.1.5.5.7.1.12=ASN1:SEQUENCE:LogotypeExtn +[LogotypeExtn] +communityLogos=EXP:0,SEQUENCE:LogotypeInfo +[LogotypeInfo] +# note: implicit tag converted to explicit for CHOICE +direct=EXP:0,SEQUENCE:LogotypeData +[LogotypeData] +image=SEQUENCE:LogotypeImage +[LogotypeImage] +imageDetails=SEQUENCE:LogotypeDetails +imageInfo=SEQUENCE:LogotypeImageInfo +[LogotypeDetails] +mediaType=IA5STRING:image/png +logotypeHash=SEQUENCE:HashAlgAndValues +logotypeURI=SEQUENCE:URI +[HashAlgAndValues] +value1=SEQUENCE:HashAlgAndValueSHA256 +#value2=SEQUENCE:HashAlgAndValueSHA1 +[HashAlgAndValueSHA256] +hashAlg=SEQUENCE:sha256_alg +hashValue=FORMAT:HEX,OCTETSTRING:4532f7ec36424381617c03c6ce87b55a51d6e7177ffafda243cebf280a68954d +[HashAlgAndValueSHA1] +hashAlg=SEQUENCE:sha1_alg +hashValue=FORMAT:HEX,OCTETSTRING:5e1d5085676eede6b02da14d31c523ec20ffba0b +[sha256_alg] +algorithm=OID:sha256 +[sha1_alg] +algorithm=OID:sha1 +[URI] +uri=IA5STRING:http://osu.w1.fi/w1fi_logo.png +[LogotypeImageInfo] +# default value color(1), component optional +#type=IMP:0,INTEGER:1 +fileSize=INTEGER:7549 +xSize=INTEGER:128 +ySize=INTEGER:80 +language=IMP:4,IA5STRING:zxx + +[ crl_ext ] + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ v3_OCSP ] + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = OCSPSigning + +[ ext_client ] + +basicConstraints=CA:FALSE +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer +authorityInfoAccess = OCSP;URI:http://osu.w1.fi:8888/ +#@ALTNAME@ +extendedKeyUsage = clientAuth + +[ ext_server ] + +# Hotspot 2.0 PKI requirements +basicConstraints=critical, CA:FALSE +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer +authorityInfoAccess = OCSP;URI:http://osu.w1.fi:8888/ +#@ALTNAME@ +extendedKeyUsage = critical, serverAuth +keyUsage = critical, keyEncipherment diff --git a/hs20/server/ca/setup.sh b/hs20/server/ca/setup.sh new file mode 100755 index 0000000..f61bf73 --- /dev/null +++ b/hs20/server/ca/setup.sh @@ -0,0 +1,125 @@ +#!/bin/sh + +if [ -z "$OPENSSL" ]; then + OPENSSL=openssl +fi +export OPENSSL_CONF=$PWD/openssl.cnf +PASS=whatever + +fail() +{ + echo "$*" + exit 1 +} + +echo +echo "---[ Root CA ]----------------------------------------------------------" +echo + +cat openssl-root.cnf | sed "s/#@CN@/commonName_default = Hotspot 2.0 Trust Root CA - 99/" > openssl.cnf.tmp +mkdir -p rootCA/certs rootCA/crl rootCA/newcerts rootCA/private +touch rootCA/index.txt +if [ -e rootCA/private/cakey.pem ]; then + echo " * Use existing Root CA" +else + echo " * Generate Root CA private key" + $OPENSSL req -config openssl.cnf.tmp -batch -new -newkey rsa:4096 -keyout rootCA/private/cakey.pem -out rootCA/careq.pem || fail "Failed to generate Root CA private key" + echo " * Sign Root CA certificate" + $OPENSSL ca -config openssl.cnf.tmp -md sha256 -create_serial -out rootCA/cacert.pem -days 10957 -batch -keyfile rootCA/private/cakey.pem -passin pass:$PASS -selfsign -extensions v3_ca -outdir rootCA/newcerts -infiles rootCA/careq.pem || fail "Failed to sign Root CA certificate" +fi +if [ ! -e rootCA/crlnumber ]; then + echo 00 > rootCA/crlnumber +fi + +echo +echo "---[ Intermediate CA ]--------------------------------------------------" +echo + +cat openssl.cnf | sed "s/#@CN@/commonName_default = w1.fi Hotspot 2.0 Intermediate CA/" > openssl.cnf.tmp +mkdir -p demoCA/certs demoCA/crl demoCA/newcerts demoCA/private +touch demoCA/index.txt +if [ -e demoCA/private/cakey.pem ]; then + echo " * Use existing Intermediate CA" +else + echo " * Generate Intermediate CA private key" + $OPENSSL req -config openssl.cnf.tmp -batch -new -newkey rsa:2048 -keyout demoCA/private/cakey.pem -out demoCA/careq.pem || fail "Failed to generate Intermediate CA private key" + echo " * Sign Intermediate CA certificate" + $OPENSSL ca -config openssl.cnf.tmp -md sha256 -create_serial -out demoCA/cacert.pem -days 3652 -batch -keyfile rootCA/private/cakey.pem -cert rootCA/cacert.pem -passin pass:$PASS -extensions v3_ca -infiles demoCA/careq.pem || fail "Failed to sign Intermediate CA certificate" + # horrible from security view point, but for testing purposes since OCSP responder does not seem to support -passin + openssl rsa -in demoCA/private/cakey.pem -out demoCA/private/cakey-plain.pem -passin pass:$PASS +fi +if [ ! -e demoCA/crlnumber ]; then + echo 00 > demoCA/crlnumber +fi + +echo +echo "OCSP responder" +echo + +cat openssl.cnf | sed "s/#@CN@/commonName_default = ocsp.w1.fi/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out ocsp.csr -keyout ocsp.key -extensions v3_OCSP +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -keyfile demoCA/private/cakey.pem -passin pass:$PASS -in ocsp.csr -out ocsp.pem -days 730 -extensions v3_OCSP + +echo +echo "---[ Server - to be revoked ] ------------------------------------------" +echo + +cat openssl.cnf | sed "s/#@CN@/commonName_default = osu-revoked.w1.fi/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out server-revoked.csr -keyout server-revoked.key +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in server-revoked.csr -out server-revoked.pem -key $PASS -days 730 -extensions ext_server +$OPENSSL ca -revoke server-revoked.pem -key $PASS + +echo +echo "---[ Server - with client ext key use ] ---------------------------------" +echo + +cat openssl.cnf | sed "s/#@CN@/commonName_default = osu-client.w1.fi/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out server-client.csr -keyout server-client.key +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in server-client.csr -out server-client.pem -key $PASS -days 730 -extensions ext_client + +echo +echo "---[ User ]-------------------------------------------------------------" +echo + +cat openssl.cnf | sed "s/#@CN@/commonName_default = User/" > openssl.cnf.tmp +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -new -newkey rsa:2048 -nodes -out user.csr -keyout user.key +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in user.csr -out user.pem -key $PASS -days 730 -extensions ext_client + +echo +echo "---[ Server ]-----------------------------------------------------------" +echo + +ALT="DNS:osu.w1.fi" +ALT="$ALT,otherName:1.3.6.1.4.1.40808.1.1.1;UTF8String:engw1.fi TESTING USE" +ALT="$ALT,otherName:1.3.6.1.4.1.40808.1.1.1;UTF8String:finw1.fi TESTIKÄYTTÖ" + +cat openssl.cnf | + sed "s/#@CN@/commonName_default = osu.w1.fi/" | + sed "s/^##organizationalUnitName/organizationalUnitName/" | + sed "s/#@OU@/organizationalUnitName_default = Hotspot 2.0 Online Sign Up Server/" | + sed "s/#@ALTNAME@/subjectAltName=critical,$ALT/" \ + > openssl.cnf.tmp +echo $OPENSSL req -config $PWD/openssl.cnf.tmp -batch -sha256 -new -newkey rsa:2048 -nodes -out server.csr -keyout server.key -reqexts v3_osu_server +$OPENSSL req -config $PWD/openssl.cnf.tmp -batch -sha256 -new -newkey rsa:2048 -nodes -out server.csr -keyout server.key -reqexts v3_osu_server || fail "Failed to generate server request" +$OPENSSL ca -config $PWD/openssl.cnf.tmp -batch -md sha256 -in server.csr -out server.pem -key $PASS -days 730 -extensions ext_server -policy policy_osu_server || fail "Failed to sign server certificate" + +#dump logotype details for debugging +$OPENSSL x509 -in server.pem -out server.der -outform DER +openssl asn1parse -in server.der -inform DER | grep HEX | tail -1 | sed 's/.*://' | xxd -r -p > logo.der +openssl asn1parse -in logo.der -inform DER > logo.asn1 + + +echo +echo "---[ CRL ]---------------------------------------------------------------" +echo + +$OPENSSL ca -config $PWD/openssl.cnf -gencrl -md sha256 -out demoCA/crl/crl.pem -passin pass:$PASS + +echo +echo "---[ Verify ]------------------------------------------------------------" +echo + +$OPENSSL verify -CAfile rootCA/cacert.pem demoCA/cacert.pem +$OPENSSL verify -CAfile rootCA/cacert.pem -untrusted demoCA/cacert.pem *.pem + +cat rootCA/cacert.pem demoCA/cacert.pem > ca.pem diff --git a/hs20/server/ca/w1fi_logo.png b/hs20/server/ca/w1fi_logo.png Binary files differnew file mode 100644 index 0000000..ac7c259 --- /dev/null +++ b/hs20/server/ca/w1fi_logo.png diff --git a/hs20/server/hs20-osu-server.txt b/hs20/server/hs20-osu-server.txt new file mode 100644 index 0000000..80985f7 --- /dev/null +++ b/hs20/server/hs20-osu-server.txt @@ -0,0 +1,196 @@ +Hotspot 2.0 OSU server +====================== + +The information in this document is based on the assumption that Ubuntu +12.04 server (64-bit) distribution is used and the web server is +Apache2. Neither of these are requirements for the installation, but if +other combinations are used, the package names and configuration +parameters may need to be adjusted. + +NOTE: This implementation and the example configuration here is meant +only for testing purposes in a lab environment. This design is not +secure to be installed in a publicly available Internet server without +considerable amount of modification and review for security issues. + +NOTE: While this describes use on Ubuntu 12.04, the version of Apache2 +included in that distribution is not new enough to support all OSU +server validation steps. In other words, it may be most adapt the steps +described here to Ubuntu 13.10. + + +Build dependencies +------------------ + +Ubuntu 12.04 server +- default installation +- upgraded to latest package versions + sudo apt-get update + sudo apt-get upgrade + +Packages needed for running the service: + sudo apt-get install sqlite3 + sudo apt-get install apache2 + sudo apt-get install php5-sqlite libapache2-mod-php5 + +Additional packages needed for building the components: + sudo apt-get install build-essential + sudo apt-get install libsqlite3-dev + sudo apt-get install libssl-dev + sudo apt-get install libxml2-dev + + +Installation location +--------------------- + +Select a location for the installation root directory. The example here +assumes /home/user/hs20-server to be used, but this can be changed by +editing couple of files as indicated below. + +sudo mkdir -p /home/user/hs20-server +sudo chown $USER /home/user/hs20-server +mkdir -p /home/user/hs20-server/spp +mkdir -p /home/user/hs20-server/AS + + +Build +----- + +# hostapd as RADIUS server +cd hostapd + +#example build configuration +cat > .config <<EOF +CONFIG_DRIVER_NONE=y +CONFIG_PKCS12=y +CONFIG_RADIUS_SERVER=y +CONFIG_EAP=y +CONFIG_EAP_TLS=y +CONFIG_EAP_MSCHAPV2=y +CONFIG_EAP_PEAP=y +CONFIG_EAP_GTC=y +CONFIG_EAP_TTLS=y +CONFIG_EAP_SIM=y +CONFIG_EAP_AKA=y +CONFIG_EAP_AKA_PRIME=y +CONFIG_SQLITE=y +CONFIG_HS20=y +EOF + +make hostapd hlr_auc_gw +cp hostapd hlr_auc_gw /home/user/hs20-server/AS + +# build hs20_spp_server +cd ../hs20/server +make clean +make +cp hs20_spp_server /home/user/hs20-server/spp +# prepare database (web server user/group needs to have write access) +mkdir -p /home/user/hs20-server/AS/DB +sudo chgrp www-data /home/user/hs20-server/AS/DB +sudo chmod g+w /home/user/hs20-server/AS/DB +sqlite3 /home/user/hs20-server/AS/DB/eap_user.db < sql.txt +sudo chgrp www-data /home/user/hs20-server/AS/DB/eap_user.db +sudo chmod g+w /home/user/hs20-server/AS/DB/eap_user.db +# add example configuration (note: need to update URLs to match the system) +sqlite3 /home/user/hs20-server/AS/DB/eap_user.db < sql-example.txt + +# copy PHP scripts +# Modify config.php if different installation directory is used. +# Modify PHP scripts to get the desired behavior for user interaction (or use +# the examples as-is for initial testing). +cp -r www /home/user/hs20-server + + +# Configure subscription policies +mkdir -p /home/user/hs20-server/spp/policy +cat > /home/user/hs20-server/spp/policy/default.xml <<EOF +<Policy> + <PolicyUpdate> + <UpdateInterval>30</UpdateInterval> + <UpdateMethod>ClientInitiated</UpdateMethod> + <Restriction>Unrestricted</Restriction> + <URI>https://policy-server.osu.example.com/hs20/spp.php</URI> + </PolicyUpdate> +</Policy> +EOF + + +# Install Hotspot 2.0 SPP and OMA DM XML schema/DTD files + +# XML schema for SPP +# Copy the latest XML schema into /home/user/hs20-server/spp/spp.xsd + +# OMA DM Device Description Framework DTD +# Copy into /home/user/hs20-server/spp/dm_ddf-v1_2.dtd +# http://www.openmobilealliance.org/tech/DTD/dm_ddf-v1_2.dtd + + +# Configure RADIUS authentication service +# Note: Change the URL to match the setup +# Note: Install AAA server key/certificate and root CA in Key directory + +cat > /home/user/hs20-server/AS/as-sql.conf <<EOF +driver=none +radius_server_clients=as.radius_clients +eap_server=1 +eap_user_file=sqlite:DB/eap_user.db +ca_cert=Key/ca.pem +server_cert=Key/server.pem +private_key=Key/server.key +private_key_passwd=passphrase +eap_sim_db=unix:/tmp/hlr_auc_gw.sock db=eap_sim.db +subscr_remediation_url=https://subscription-server.osu.example.com/hs20/spp.php +EOF + +# Set RADIUS passphrase for the APs +# Note: Modify to match the setup +cat > /home/user/hs20-server/AS/as.radius_clients <<EOF +0.0.0.0/0 radius +EOF + + +Start RADIUS authentication server +---------------------------------- + +cd /home/user/hs20-server/AS +./hostapd -B as-sql.conf + + +Configure web server +-------------------- + +Edit /etc/apache2/sites-available/default-ssl + +Add following block just before "SSL Engine Switch" line": + + Alias /hs20/ "/home/user/hs20-server/www/" + <Directory "/home/user/hs20-server/www/"> + Options Indexes MultiViews FollowSymLinks + AllowOverride None + Order allow,deny + Allow from all + </Directory> + +Update SSL configuration to use the OSU server certificate/key. + +Enable default-ssl site and restart Apache2: + sudo a2ensite default-ssl + sudo a2enmod ssl + sudo service apache2 restart + + +Management UI +------------- + +The sample PHP scripts include a management UI for testing +purposes. That is available at https://<server>/hs20/users.php + + +AP configuration +---------------- + +APs can now be configured to use the OSU server as the RADIUS +authentication server. In addition, the OSU Provider List ANQP element +should be configured to use the SPP (SOAP+XML) option and with the +following Server URL: +https://<server>/hs20/spp.php/signup?realm=example.com diff --git a/hs20/server/hs20_spp_server.c b/hs20/server/hs20_spp_server.c new file mode 100644 index 0000000..591f66b --- /dev/null +++ b/hs20/server/hs20_spp_server.c @@ -0,0 +1,187 @@ +/* + * Hotspot 2.0 SPP server - standalone version + * Copyright (c) 2012-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 <time.h> +#include <sqlite3.h> + +#include "common.h" +#include "xml-utils.h" +#include "spp_server.h" + + +static void write_timestamp(FILE *f) +{ + time_t t; + struct tm *tm; + + time(&t); + tm = localtime(&t); + + fprintf(f, "%04u-%02u-%02u %02u:%02u:%02u ", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + + +void debug_print(struct hs20_svc *ctx, int print, const char *fmt, ...) +{ + va_list ap; + + if (ctx->debug_log == NULL) + return; + + write_timestamp(ctx->debug_log); + va_start(ap, fmt); + vfprintf(ctx->debug_log, fmt, ap); + va_end(ap); + + fprintf(ctx->debug_log, "\n"); +} + + +void debug_dump_node(struct hs20_svc *ctx, const char *title, xml_node_t *node) +{ + char *str; + + if (ctx->debug_log == NULL) + return; + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + + write_timestamp(ctx->debug_log); + fprintf(ctx->debug_log, "%s: '%s'\n", title, str); + os_free(str); +} + + +static int process(struct hs20_svc *ctx) +{ + int dmacc = 0; + xml_node_t *soap, *spp, *resp; + char *user, *realm, *post, *str; + + ctx->addr = getenv("HS20ADDR"); + if (ctx->addr) + debug_print(ctx, 1, "Connection from %s", ctx->addr); + + user = getenv("HS20USER"); + if (user && strlen(user) == 0) + user = NULL; + realm = getenv("HS20REALM"); + if (realm == NULL) { + debug_print(ctx, 1, "HS20REALM not set"); + return -1; + } + post = getenv("HS20POST"); + if (post == NULL) { + debug_print(ctx, 1, "HS20POST not set"); + return -1; + } + + soap = xml_node_from_buf(ctx->xml, post); + if (soap == NULL) { + debug_print(ctx, 1, "Could not parse SOAP data"); + return -1; + } + debug_dump_node(ctx, "Received SOAP message", soap); + spp = soap_get_body(ctx->xml, soap); + if (spp == NULL) { + debug_print(ctx, 1, "Could not get SPP message"); + xml_node_free(ctx->xml, soap); + return -1; + } + debug_dump_node(ctx, "Received SPP message", spp); + + resp = hs20_spp_server_process(ctx, spp, user, realm, dmacc); + xml_node_free(ctx->xml, soap); + if (resp == NULL && user == NULL) { + debug_print(ctx, 1, "Request HTTP authentication"); + return 2; /* Request authentication */ + } + if (resp == NULL) { + debug_print(ctx, 1, "No response"); + return -1; + } + + soap = soap_build_envelope(ctx->xml, resp); + if (soap == NULL) { + debug_print(ctx, 1, "SOAP envelope building failed"); + return -1; + } + str = xml_node_to_str(ctx->xml, soap); + xml_node_free(ctx->xml, soap); + if (str == NULL) { + debug_print(ctx, 1, "Could not get node string"); + return -1; + } + printf("%s", str); + free(str); + + return 0; +} + + +static void usage(void) +{ + printf("usage:\n" + "hs20_spp_server -r<root directory> [-f<debug log>]\n"); +} + + +int main(int argc, char *argv[]) +{ + struct hs20_svc ctx; + int ret; + + os_memset(&ctx, 0, sizeof(ctx)); + for (;;) { + int c = getopt(argc, argv, "f:r:"); + if (c < 0) + break; + switch (c) { + case 'f': + if (ctx.debug_log) + break; + ctx.debug_log = fopen(optarg, "a"); + if (ctx.debug_log == NULL) { + printf("Could not write to %s\n", optarg); + return -1; + } + break; + case 'r': + ctx.root_dir = optarg; + break; + default: + usage(); + return -1; + } + } + if (ctx.root_dir == NULL) { + usage(); + return -1; + } + ctx.xml = xml_node_init_ctx(&ctx, NULL); + if (ctx.xml == NULL) + return -1; + if (hs20_spp_server_init(&ctx) < 0) { + xml_node_deinit_ctx(ctx.xml); + return -1; + } + + ret = process(&ctx); + debug_print(&ctx, 1, "process() --> %d", ret); + + xml_node_deinit_ctx(ctx.xml); + hs20_spp_server_deinit(&ctx); + if (ctx.debug_log) + fclose(ctx.debug_log); + + return ret; +} diff --git a/hs20/server/spp_server.c b/hs20/server/spp_server.c new file mode 100644 index 0000000..4d77d0e --- /dev/null +++ b/hs20/server/spp_server.c @@ -0,0 +1,2263 @@ +/* + * Hotspot 2.0 SPP server + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <time.h> +#include <errno.h> +#include <sqlite3.h> + +#include "common.h" +#include "base64.h" +#include "md5_i.h" +#include "xml-utils.h" +#include "spp_server.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_OMA_DM_DMACC "urn:oma:mo:oma-dm-dmacc:1.0" +#define URN_HS20_PPS "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0" + + +/* TODO: timeout to expire sessions */ + +enum hs20_session_operation { + NO_OPERATION, + UPDATE_PASSWORD, + CONTINUE_SUBSCRIPTION_REMEDIATION, + CONTINUE_POLICY_UPDATE, + USER_REMEDIATION, + SUBSCRIPTION_REGISTRATION, + POLICY_REMEDIATION, + POLICY_UPDATE, + FREE_REMEDIATION, +}; + + +static char * db_get_session_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *session_id, + const char *field); +static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm, + const char *field); +static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user, + const char *realm, int use_dmacc); + + +static int db_add_session(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid, const char *pw, + const char *redirect_uri, + enum hs20_session_operation operation) +{ + char *sql; + int ret = 0; + + sql = sqlite3_mprintf("INSERT INTO sessions(timestamp,id,user,realm," + "operation,password,redirect_uri) " + "VALUES " + "(strftime('%%Y-%%m-%%d %%H:%%M:%%f','now')," + "%Q,%Q,%Q,%d,%Q,%Q)", + sessionid, user ? user : "", realm ? realm : "", + operation, pw ? pw : "", + redirect_uri ? redirect_uri : ""); + if (sql == NULL) + return -1; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session entry into sqlite " + "database: %s", sqlite3_errmsg(ctx->db)); + ret = -1; + } + sqlite3_free(sql); + return ret; +} + + +static void db_update_session_password(struct hs20_svc *ctx, const char *user, + const char *realm, const char *sessionid, + const char *pw) +{ + char *sql; + + sql = sqlite3_mprintf("UPDATE sessions SET password=%Q WHERE id=%Q AND " + "user=%Q AND realm=%Q", + pw, sessionid, user, realm); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update session password: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_pps(struct hs20_svc *ctx, const char *user, + const char *realm, const char *sessionid, + xml_node_t *node) +{ + char *str; + char *sql; + + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + sql = sqlite3_mprintf("UPDATE sessions SET pps=%Q WHERE id=%Q AND " + "user=%Q AND realm=%Q", + str, sessionid, user, realm); + free(str); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session pps: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_devinfo(struct hs20_svc *ctx, const char *sessionid, + xml_node_t *node) +{ + char *str; + char *sql; + + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + sql = sqlite3_mprintf("UPDATE sessions SET devinfo=%Q WHERE id=%Q", + str, sessionid); + free(str); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session devinfo: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_add_session_devdetail(struct hs20_svc *ctx, + const char *sessionid, + xml_node_t *node) +{ + char *str; + char *sql; + + str = xml_node_to_str(ctx->xml, node); + if (str == NULL) + return; + sql = sqlite3_mprintf("UPDATE sessions SET devdetail=%Q WHERE id=%Q", + str, sessionid); + free(str); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add session devdetail: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_remove_session(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid) +{ + char *sql; + + if (user == NULL || realm == NULL) { + sql = sqlite3_mprintf("DELETE FROM sessions WHERE " + "id=%Q", sessionid); + } else { + sql = sqlite3_mprintf("DELETE FROM sessions WHERE " + "user=%Q AND realm=%Q AND id=%Q", + user, realm, sessionid); + } + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to delete session entry from " + "sqlite database: %s", sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void hs20_eventlog(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid, const char *notes, + const char *dump) +{ + char *sql; + char *user_buf = NULL, *realm_buf = NULL; + + debug_print(ctx, 1, "eventlog: %s", notes); + + if (user == NULL) { + user_buf = db_get_session_val(ctx, NULL, NULL, sessionid, + "user"); + user = user_buf; + realm_buf = db_get_session_val(ctx, NULL, NULL, sessionid, + "realm"); + realm = realm_buf; + } + + sql = sqlite3_mprintf("INSERT INTO eventlog" + "(user,realm,sessionid,timestamp,notes,dump,addr)" + " VALUES (%Q,%Q,%Q," + "strftime('%%Y-%%m-%%d %%H:%%M:%%f','now')," + "%Q,%Q,%Q)", + user, realm, sessionid, notes, + dump ? dump : "", ctx->addr ? ctx->addr : ""); + free(user_buf); + free(realm_buf); + if (sql == NULL) + return; + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add eventlog entry into sqlite " + "database: %s", sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void hs20_eventlog_node(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *sessionid, const char *notes, + xml_node_t *node) +{ + char *str; + + if (node) + str = xml_node_to_str(ctx->xml, node); + else + str = NULL; + hs20_eventlog(ctx, user, realm, sessionid, notes, str); + free(str); +} + + +static void db_update_mo_str(struct hs20_svc *ctx, const char *user, + const char *realm, const char *name, + const char *str) +{ + char *sql; + if (user == NULL || realm == NULL || name == NULL) + return; + sql = sqlite3_mprintf("UPDATE users SET %s=%Q " + "WHERE identity=%Q AND realm=%Q AND phase2=1", + name, str, user, realm); + if (sql == NULL) + return; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update user MO entry in sqlite " + "database: %s", sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); +} + + +static void db_update_mo(struct hs20_svc *ctx, const char *user, + const char *realm, const char *name, xml_node_t *mo) +{ + char *str; + + str = xml_node_to_str(ctx->xml, mo); + if (str == NULL) + return; + + db_update_mo_str(ctx, user, realm, name, str); + free(str); +} + + +static void add_text_node(struct hs20_svc *ctx, xml_node_t *parent, + const char *name, const char *value) +{ + xml_node_create_text(ctx->xml, parent, NULL, name, value ? value : ""); +} + + +static void add_text_node_conf(struct hs20_svc *ctx, const char *realm, + xml_node_t *parent, const char *name, + const char *field) +{ + char *val; + val = db_get_osu_config_val(ctx, realm, field); + xml_node_create_text(ctx->xml, parent, NULL, name, val ? val : ""); + os_free(val); +} + + +static int new_password(char *buf, int buflen) +{ + int i; + + if (buflen < 1) + return -1; + buf[buflen - 1] = '\0'; + if (os_get_random((unsigned char *) buf, buflen - 1) < 0) + return -1; + + for (i = 0; i < buflen - 1; i++) { + unsigned char val = buf[i]; + val %= 2 * 26 + 10; + if (val < 26) + buf[i] = 'a' + val; + else if (val < 2 * 26) + buf[i] = 'A' + val - 26; + else + buf[i] = '0' + val - 2 * 26; + } + + return 0; +} + + +struct get_db_field_data { + const char *field; + char *value; +}; + + +static int get_db_field(void *ctx, int argc, char *argv[], char *col[]) +{ + struct get_db_field_data *data = ctx; + int i; + + for (i = 0; i < argc; i++) { + if (os_strcmp(col[i], data->field) == 0 && argv[i]) { + os_free(data->value); + data->value = os_strdup(argv[i]); + break; + } + } + + return 0; +} + + +static char * db_get_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *field, int dmacc) +{ + char *cmd; + struct get_db_field_data data; + + cmd = sqlite3_mprintf("SELECT %s FROM users WHERE " + "%s=%Q AND realm=%Q AND phase2=1", + field, dmacc ? "osu_user" : "identity", + user, realm); + if (cmd == NULL) + return NULL; + memset(&data, 0, sizeof(data)); + data.field = field; + if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) + { + debug_print(ctx, 1, "Could not find user '%s'", user); + sqlite3_free(cmd); + return NULL; + } + sqlite3_free(cmd); + + debug_print(ctx, 1, "DB: user='%s' realm='%s' field='%s' dmacc=%d --> " + "value='%s'", user, realm, field, dmacc, data.value); + + return data.value; +} + + +static int db_update_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *field, + const char *val, int dmacc) +{ + char *cmd; + int ret; + + cmd = sqlite3_mprintf("UPDATE users SET %s=%Q WHERE " + "%s=%Q AND realm=%Q AND phase2=1", + field, val, dmacc ? "osu_user" : "identity", user, + realm); + if (cmd == NULL) + return -1; + debug_print(ctx, 1, "DB: %s", cmd); + if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, + "Failed to update user in sqlite database: %s", + sqlite3_errmsg(ctx->db)); + ret = -1; + } else { + debug_print(ctx, 1, + "DB: user='%s' realm='%s' field='%s' set to '%s'", + user, realm, field, val); + ret = 0; + } + sqlite3_free(cmd); + + return ret; +} + + +static char * db_get_session_val(struct hs20_svc *ctx, const char *user, + const char *realm, const char *session_id, + const char *field) +{ + char *cmd; + struct get_db_field_data data; + + if (user == NULL || realm == NULL) { + cmd = sqlite3_mprintf("SELECT %s FROM sessions WHERE " + "id=%Q", field, session_id); + } else { + cmd = sqlite3_mprintf("SELECT %s FROM sessions WHERE " + "user=%Q AND realm=%Q AND id=%Q", + field, user, realm, session_id); + } + if (cmd == NULL) + return NULL; + debug_print(ctx, 1, "DB: %s", cmd); + memset(&data, 0, sizeof(data)); + data.field = field; + if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) + { + debug_print(ctx, 1, "DB: Could not find session %s: %s", + session_id, sqlite3_errmsg(ctx->db)); + sqlite3_free(cmd); + return NULL; + } + sqlite3_free(cmd); + + debug_print(ctx, 1, "DB: return '%s'", data.value); + return data.value; +} + + +static int update_password(struct hs20_svc *ctx, const char *user, + const char *realm, const char *pw, int dmacc) +{ + char *cmd; + + cmd = sqlite3_mprintf("UPDATE users SET password=%Q, " + "remediation='' " + "WHERE %s=%Q AND phase2=1", + pw, dmacc ? "osu_user" : "identity", + user); + if (cmd == NULL) + return -1; + debug_print(ctx, 1, "DB: %s", cmd); + if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update database for user '%s'", + user); + } + sqlite3_free(cmd); + + return 0; +} + + +static int add_eap_ttls(struct hs20_svc *ctx, xml_node_t *parent) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, "EAPMethod"); + if (node == NULL) + return -1; + + add_text_node(ctx, node, "EAPType", "21"); + add_text_node(ctx, node, "InnerMethod", "MS-CHAP-V2"); + + return 0; +} + + +static xml_node_t * build_username_password(struct hs20_svc *ctx, + xml_node_t *parent, + const char *user, const char *pw) +{ + xml_node_t *node; + char *b64; + + node = xml_node_create(ctx->xml, parent, NULL, "UsernamePassword"); + if (node == NULL) + return NULL; + + add_text_node(ctx, node, "Username", user); + + b64 = (char *) base64_encode((unsigned char *) pw, strlen(pw), NULL); + if (b64 == NULL) + return NULL; + add_text_node(ctx, node, "Password", b64); + free(b64); + + return node; +} + + +static int add_username_password(struct hs20_svc *ctx, xml_node_t *cred, + const char *user, const char *pw) +{ + xml_node_t *node; + + node = build_username_password(ctx, cred, user, pw); + if (node == NULL) + return -1; + + add_text_node(ctx, node, "MachineManaged", "TRUE"); + add_text_node(ctx, node, "SoftTokenApp", ""); + add_eap_ttls(ctx, node); + + return 0; +} + + +static void add_creation_date(struct hs20_svc *ctx, xml_node_t *cred) +{ + char str[30]; + time_t now; + struct tm tm; + + time(&now); + gmtime_r(&now, &tm); + snprintf(str, sizeof(str), "%04u-%02u-%02uT%02u:%02u:%02uZ", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + xml_node_create_text(ctx->xml, cred, NULL, "CreationDate", str); +} + + +static xml_node_t * build_credential_pw(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *pw) +{ + xml_node_t *cred; + + cred = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "Credential"); + if (cred == NULL) { + debug_print(ctx, 1, "Failed to create Credential node"); + return NULL; + } + add_creation_date(ctx, cred); + if (add_username_password(ctx, cred, user, pw) < 0) { + xml_node_free(ctx->xml, cred); + return NULL; + } + add_text_node(ctx, cred, "Realm", realm); + + return cred; +} + + +static xml_node_t * build_credential(struct hs20_svc *ctx, + const char *user, const char *realm, + char *new_pw, size_t new_pw_len) +{ + if (new_password(new_pw, new_pw_len) < 0) + return NULL; + debug_print(ctx, 1, "Update password to '%s'", new_pw); + return build_credential_pw(ctx, user, realm, new_pw); +} + + +static xml_node_t * build_credential_cert(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *cert_fingerprint) +{ + xml_node_t *cred, *cert; + + cred = xml_node_create_root(ctx->xml, NULL, NULL, NULL, "Credential"); + if (cred == NULL) { + debug_print(ctx, 1, "Failed to create Credential node"); + return NULL; + } + add_creation_date(ctx, cred); + cert = xml_node_create(ctx->xml, cred, NULL, "DigitalCertificate"); + add_text_node(ctx, cert, "CertificateType", "x509v3"); + add_text_node(ctx, cert, "CertSHA256Fingerprint", cert_fingerprint); + add_text_node(ctx, cred, "Realm", realm); + + return cred; +} + + +static xml_node_t * build_post_dev_data_response(struct hs20_svc *ctx, + xml_namespace_t **ret_ns, + const char *session_id, + const char *status, + const char *error_code) +{ + xml_node_t *spp_node = NULL; + xml_namespace_t *ns; + + spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, + "sppPostDevDataResponse"); + 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, ns, "sessionID", session_id); + xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", status); + + if (error_code) { + xml_node_t *node; + 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 add_update_node(struct hs20_svc *ctx, xml_node_t *spp_node, + xml_namespace_t *ns, const char *uri, + xml_node_t *upd_node) +{ + xml_node_t *node, *tnds; + char *str; + + tnds = mo_to_tnds(ctx->xml, upd_node, 0, NULL, NULL); + if (!tnds) + return -1; + + str = xml_node_to_str(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (str == NULL) + return -1; + node = xml_node_create_text(ctx->xml, spp_node, ns, "updateNode", str); + free(str); + + xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", uri); + + return 0; +} + + +static xml_node_t * build_sub_rem_resp(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, + int machine_rem, int dmacc) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *cred; + char buf[400]; + char new_pw[33]; + char *real_user = NULL; + char *status; + char *cert; + + if (dmacc) { + real_user = db_get_val(ctx, user, realm, "identity", dmacc); + if (real_user == NULL) { + debug_print(ctx, 1, "Could not find user identity for " + "dmacc user '%s'", user); + return NULL; + } + } + + cert = db_get_val(ctx, user, realm, "cert", dmacc); + if (cert && cert[0] == '\0') + cert = NULL; + if (cert) { + cred = build_credential_cert(ctx, real_user ? real_user : user, + realm, cert); + } else { + cred = build_credential(ctx, real_user ? real_user : user, + realm, new_pw, sizeof(new_pw)); + } + free(real_user); + if (!cred) { + debug_print(ctx, 1, "Could not build credential"); + return NULL; + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) { + debug_print(ctx, 1, "Could not build sppPostDevDataResponse"); + return NULL; + } + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Credential1/Credential", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) { + debug_print(ctx, 1, "Could not add update node"); + xml_node_free(ctx->xml, spp_node); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + machine_rem ? "machine remediation" : + "user remediation", cred); + xml_node_free(ctx->xml, cred); + + if (cert) { + debug_print(ctx, 1, "Certificate credential - no need for DB " + "password update on success notification"); + } else { + debug_print(ctx, 1, "Request DB password update on success " + "notification"); + db_add_session(ctx, user, realm, session_id, new_pw, NULL, + UPDATE_PASSWORD); + } + + return spp_node; +} + + +static xml_node_t * machine_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id, int dmacc) +{ + return build_sub_rem_resp(ctx, user, realm, session_id, 1, dmacc); +} + + +static xml_node_t * policy_remediation(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, int dmacc) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *policy; + char buf[400]; + const char *status; + + hs20_eventlog(ctx, user, realm, session_id, + "requires policy remediation", NULL); + + db_add_session(ctx, user, realm, session_id, NULL, NULL, + POLICY_REMEDIATION); + + policy = build_policy(ctx, user, realm, dmacc); + if (!policy) { + return build_post_dev_data_response( + ctx, NULL, session_id, + "No update available at this time", NULL); + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Credential1/Policy", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) { + xml_node_free(ctx->xml, spp_node); + xml_node_free(ctx->xml, policy); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "policy update (sub rem)", policy); + xml_node_free(ctx->xml, policy); + + return spp_node; +} + + +static xml_node_t * browser_remediation(struct hs20_svc *ctx, + const char *session_id, + const char *redirect_uri, + const char *uri) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *exec_node; + + if (redirect_uri == NULL) { + debug_print(ctx, 1, "Missing redirectURI attribute for user " + "remediation"); + return NULL; + } + debug_print(ctx, 1, "redirectURI %s", redirect_uri); + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + xml_node_create_text(ctx->xml, exec_node, ns, "launchBrowserToURI", + uri); + return spp_node; +} + + +static xml_node_t * user_remediation(struct hs20_svc *ctx, const char *user, + const char *realm, const char *session_id, + const char *redirect_uri) +{ + char uri[300], *val; + + hs20_eventlog(ctx, user, realm, session_id, + "requires user remediation", NULL); + val = db_get_osu_config_val(ctx, realm, "remediation_url"); + if (val == NULL) + return NULL; + + db_add_session(ctx, user, realm, session_id, NULL, redirect_uri, + USER_REMEDIATION); + + snprintf(uri, sizeof(uri), "%s%s", val, session_id); + os_free(val); + return browser_remediation(ctx, session_id, redirect_uri, uri); +} + + +static xml_node_t * free_remediation(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, + const char *redirect_uri) +{ + char uri[300], *val; + + hs20_eventlog(ctx, user, realm, session_id, + "requires free/public account remediation", NULL); + val = db_get_osu_config_val(ctx, realm, "free_remediation_url"); + if (val == NULL) + return NULL; + + db_add_session(ctx, user, realm, session_id, NULL, redirect_uri, + FREE_REMEDIATION); + + snprintf(uri, sizeof(uri), "%s%s", val, session_id); + os_free(val); + return browser_remediation(ctx, session_id, redirect_uri, uri); +} + + +static xml_node_t * no_sub_rem(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id) +{ + const char *status; + + hs20_eventlog(ctx, user, realm, session_id, + "no subscription mediation available", NULL); + + status = "No update available at this time"; + return build_post_dev_data_response(ctx, NULL, session_id, status, + NULL); +} + + +static xml_node_t * hs20_subscription_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id, + int dmacc, + const char *redirect_uri) +{ + char *type, *identity; + xml_node_t *ret; + char *free_account; + + identity = db_get_val(ctx, user, realm, "identity", dmacc); + if (identity == NULL || strlen(identity) == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "user not found in database for remediation", + NULL); + os_free(identity); + return build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", + "Not found"); + } + os_free(identity); + + free_account = db_get_osu_config_val(ctx, realm, "free_account"); + if (free_account && strcmp(free_account, user) == 0) { + free(free_account); + return no_sub_rem(ctx, user, realm, session_id); + } + free(free_account); + + type = db_get_val(ctx, user, realm, "remediation", dmacc); + if (type && strcmp(type, "free") != 0) { + char *val; + int shared = 0; + val = db_get_val(ctx, user, realm, "shared", dmacc); + if (val) + shared = atoi(val); + free(val); + if (shared) { + free(type); + return no_sub_rem(ctx, user, realm, session_id); + } + } + if (type && strcmp(type, "user") == 0) + ret = user_remediation(ctx, user, realm, session_id, + redirect_uri); + else if (type && strcmp(type, "free") == 0) + ret = free_remediation(ctx, user, realm, session_id, + redirect_uri); + else if (type && strcmp(type, "policy") == 0) + ret = policy_remediation(ctx, user, realm, session_id, dmacc); + else + ret = machine_remediation(ctx, user, realm, session_id, dmacc); + free(type); + + return ret; +} + + +static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user, + const char *realm, int use_dmacc) +{ + char *policy_id; + char fname[200]; + xml_node_t *policy, *node; + + policy_id = db_get_val(ctx, user, realm, "policy", use_dmacc); + if (policy_id == NULL || strlen(policy_id) == 0) { + free(policy_id); + policy_id = strdup("default"); + if (policy_id == NULL) + return NULL; + } + + snprintf(fname, sizeof(fname), "%s/spp/policy/%s.xml", + ctx->root_dir, policy_id); + free(policy_id); + debug_print(ctx, 1, "Use policy file %s", fname); + + policy = node_from_file(ctx->xml, fname); + if (policy == NULL) + return NULL; + + node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate/URI"); + if (node) { + char *url; + url = db_get_osu_config_val(ctx, realm, "policy_url"); + if (url == NULL) { + xml_node_free(ctx->xml, policy); + return NULL; + } + xml_node_set_text(ctx->xml, node, url); + free(url); + } + + node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate"); + if (node && use_dmacc) { + char *pw; + pw = db_get_val(ctx, user, realm, "osu_password", use_dmacc); + if (pw == NULL || + build_username_password(ctx, node, user, pw) == NULL) { + debug_print(ctx, 1, "Failed to add Policy/PolicyUpdate/" + "UsernamePassword"); + free(pw); + xml_node_free(ctx->xml, policy); + return NULL; + } + free(pw); + } + + return policy; +} + + +static xml_node_t * hs20_policy_update(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *session_id, int dmacc) +{ + xml_namespace_t *ns; + xml_node_t *spp_node; + xml_node_t *policy; + char buf[400]; + const char *status; + char *identity; + + identity = db_get_val(ctx, user, realm, "identity", dmacc); + if (identity == NULL || strlen(identity) == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "user not found in database for policy update", + NULL); + os_free(identity); + return build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", + "Not found"); + } + os_free(identity); + + policy = build_policy(ctx, user, realm, dmacc); + if (!policy) { + return build_post_dev_data_response( + ctx, NULL, session_id, + "No update available at this time", NULL); + } + + db_add_session(ctx, user, realm, session_id, NULL, NULL, POLICY_UPDATE); + + status = "Update complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Credential1/Policy", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, policy) < 0) { + xml_node_free(ctx->xml, spp_node); + xml_node_free(ctx->xml, policy); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, "policy update", + policy); + xml_node_free(ctx->xml, policy); + + return spp_node; +} + + +static xml_node_t * spp_get_mo(struct hs20_svc *ctx, xml_node_t *node, + const char *urn, int *valid, char **ret_err) +{ + xml_node_t *child, *tnds, *mo; + const char *name; + char *mo_urn; + char *str; + char fname[200]; + + *valid = -1; + if (ret_err) + *ret_err = 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 (strcmp(name, "moContainer") != 0) + continue; + mo_urn = xml_node_get_attr_value_ns(ctx->xml, child, SPP_NS_URI, + "moURN"); + if (strcasecmp(urn, mo_urn) == 0) { + xml_node_get_attr_value_free(ctx->xml, mo_urn); + break; + } + xml_node_get_attr_value_free(ctx->xml, mo_urn); + } + + if (child == NULL) + return NULL; + + debug_print(ctx, 1, "moContainer text for %s", urn); + debug_dump_node(ctx, "moContainer", child); + + str = xml_node_get_text(ctx->xml, child); + debug_print(ctx, 1, "moContainer payload: '%s'", str); + tnds = xml_node_from_buf(ctx->xml, str); + xml_node_get_text_free(ctx->xml, str); + if (tnds == NULL) { + debug_print(ctx, 1, "could not parse moContainer text"); + return NULL; + } + + snprintf(fname, sizeof(fname), "%s/spp/dm_ddf-v1_2.dtd", ctx->root_dir); + if (xml_validate_dtd(ctx->xml, tnds, fname, ret_err) == 0) + *valid = 1; + else if (ret_err && *ret_err && + os_strcmp(*ret_err, "No declaration for attribute xmlns of element MgmtTree\n") == 0) { + free(*ret_err); + debug_print(ctx, 1, "Ignore OMA-DM DDF DTD validation error for MgmtTree namespace declaration with xmlns attribute"); + *ret_err = NULL; + *valid = 1; + } else + *valid = 0; + + mo = tnds_to_mo(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (mo == NULL) { + debug_print(ctx, 1, "invalid moContainer for %s", urn); + } + + return mo; +} + + +static xml_node_t * spp_exec_upload_mo(struct hs20_svc *ctx, + const char *session_id, const char *urn) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node, *exec_node; + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + + node = xml_node_create(ctx->xml, exec_node, ns, "uploadMO"); + xml_node_add_attr(ctx->xml, node, ns, "moURN", urn); + + return spp_node; +} + + +static xml_node_t * hs20_subscription_registration(struct hs20_svc *ctx, + const char *realm, + const char *session_id, + const char *redirect_uri) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *exec_node; + char uri[300], *val; + + if (db_add_session(ctx, NULL, realm, session_id, NULL, redirect_uri, + SUBSCRIPTION_REGISTRATION) < 0) + return NULL; + val = db_get_osu_config_val(ctx, realm, "signup_url"); + if (val == NULL) + return NULL; + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + + snprintf(uri, sizeof(uri), "%s%s", val, session_id); + os_free(val); + xml_node_create_text(ctx->xml, exec_node, ns, "launchBrowserToURI", + uri); + return spp_node; +} + + +static xml_node_t * hs20_user_input_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + return build_sub_rem_resp(ctx, user, realm, session_id, 0, dmacc); +} + + +static char * db_get_osu_config_val(struct hs20_svc *ctx, const char *realm, + const char *field) +{ + char *cmd; + struct get_db_field_data data; + + cmd = sqlite3_mprintf("SELECT value FROM osu_config WHERE realm=%Q AND " + "field=%Q", realm, field); + if (cmd == NULL) + return NULL; + debug_print(ctx, 1, "DB: %s", cmd); + memset(&data, 0, sizeof(data)); + data.field = "value"; + if (sqlite3_exec(ctx->db, cmd, get_db_field, &data, NULL) != SQLITE_OK) + { + debug_print(ctx, 1, "DB: Could not find osu_config %s: %s", + realm, sqlite3_errmsg(ctx->db)); + sqlite3_free(cmd); + return NULL; + } + sqlite3_free(cmd); + + debug_print(ctx, 1, "DB: return '%s'", data.value); + return data.value; +} + + +static xml_node_t * build_pps(struct hs20_svc *ctx, + const char *user, const char *realm, + const char *pw, const char *cert, + int machine_managed) +{ + xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp; + xml_node_t *cred, *eap, *userpw; + + pps = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "PerProviderSubscription"); + if (pps == NULL) + return NULL; + + add_text_node(ctx, pps, "UpdateIdentifier", "1"); + + c = xml_node_create(ctx->xml, pps, NULL, "Credential1"); + + add_text_node(ctx, c, "CredentialPriority", "1"); + + aaa = xml_node_create(ctx->xml, c, NULL, "AAAServerTrustRoot"); + aaa1 = xml_node_create(ctx->xml, aaa, NULL, "AAA1"); + add_text_node_conf(ctx, realm, aaa1, "CertURL", + "aaa_trust_root_cert_url"); + add_text_node_conf(ctx, realm, aaa1, "CertSHA256Fingerprint", + "aaa_trust_root_cert_fingerprint"); + + upd = xml_node_create(ctx->xml, c, NULL, "SubscriptionUpdate"); + add_text_node(ctx, upd, "UpdateInterval", "4294967295"); + add_text_node(ctx, upd, "UpdateMethod", "ClientInitiated"); + add_text_node(ctx, upd, "Restriction", "HomeSP"); + add_text_node_conf(ctx, realm, upd, "URI", "spp_http_auth_url"); + trust = xml_node_create(ctx->xml, upd, NULL, "TrustRoot"); + add_text_node_conf(ctx, realm, trust, "CertURL", "trust_root_cert_url"); + add_text_node_conf(ctx, realm, trust, "CertSHA256Fingerprint", + "trust_root_cert_fingerprint"); + + homesp = xml_node_create(ctx->xml, c, NULL, "HomeSP"); + add_text_node_conf(ctx, realm, homesp, "FriendlyName", "friendly_name"); + add_text_node_conf(ctx, realm, homesp, "FQDN", "fqdn"); + + xml_node_create(ctx->xml, c, NULL, "SubscriptionParameters"); + + cred = xml_node_create(ctx->xml, c, NULL, "Credential"); + add_creation_date(ctx, cred); + if (cert) { + xml_node_t *dc; + dc = xml_node_create(ctx->xml, cred, NULL, + "DigitalCertificate"); + add_text_node(ctx, dc, "CertificateType", "x509v3"); + add_text_node(ctx, dc, "CertSHA256Fingerprint", cert); + } else { + userpw = build_username_password(ctx, cred, user, pw); + add_text_node(ctx, userpw, "MachineManaged", + machine_managed ? "TRUE" : "FALSE"); + eap = xml_node_create(ctx->xml, userpw, NULL, "EAPMethod"); + add_text_node(ctx, eap, "EAPType", "21"); + add_text_node(ctx, eap, "InnerMethod", "MS-CHAP-V2"); + } + add_text_node(ctx, cred, "Realm", realm); + + return pps; +} + + +static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx, + const char *session_id, + const char *user, + const char *realm) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *enroll, *exec_node; + char *val; + char password[11]; + char *b64; + + if (new_password(password, sizeof(password)) < 0) + return NULL; + + spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK", + NULL); + if (spp_node == NULL) + return NULL; + + exec_node = xml_node_create(ctx->xml, spp_node, ns, "exec"); + + enroll = xml_node_create(ctx->xml, exec_node, ns, "getCertificate"); + xml_node_add_attr(ctx->xml, enroll, NULL, "enrollmentProtocol", "EST"); + + val = db_get_osu_config_val(ctx, realm, "est_url"); + xml_node_create_text(ctx->xml, enroll, ns, "enrollmentServerURI", + val ? val : ""); + os_free(val); + xml_node_create_text(ctx->xml, enroll, ns, "estUserID", user); + + b64 = (char *) base64_encode((unsigned char *) password, + strlen(password), NULL); + if (b64 == NULL) { + xml_node_free(ctx->xml, spp_node); + return NULL; + } + xml_node_create_text(ctx->xml, enroll, ns, "estPassword", b64); + free(b64); + + db_update_session_password(ctx, user, realm, session_id, password); + + return spp_node; +} + + +static xml_node_t * hs20_user_input_registration(struct hs20_svc *ctx, + const char *session_id, + int enrollment_done) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node = NULL; + xml_node_t *pps, *tnds; + char buf[400]; + char *str; + char *user, *realm, *pw, *type, *mm; + const char *status; + int cert = 0; + int machine_managed = 0; + char *fingerprint; + + user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); + realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); + pw = db_get_session_val(ctx, NULL, NULL, session_id, "password"); + + if (!user || !realm || !pw) { + debug_print(ctx, 1, "Could not find session info from DB for " + "the new subscription"); + free(user); + free(realm); + free(pw); + return NULL; + } + + mm = db_get_session_val(ctx, NULL, NULL, session_id, "machine_managed"); + if (mm && atoi(mm)) + machine_managed = 1; + free(mm); + + type = db_get_session_val(ctx, NULL, NULL, session_id, "type"); + if (type && strcmp(type, "cert") == 0) + cert = 1; + free(type); + + if (cert && !enrollment_done) { + xml_node_t *ret; + hs20_eventlog(ctx, user, realm, session_id, + "request client certificate enrollment", NULL); + ret = spp_exec_get_certificate(ctx, session_id, user, realm); + free(user); + free(realm); + free(pw); + return ret; + } + + if (!cert && strlen(pw) == 0) { + machine_managed = 1; + free(pw); + pw = malloc(11); + if (pw == NULL || new_password(pw, 11) < 0) { + free(user); + free(realm); + free(pw); + return NULL; + } + } + + status = "Provisioning complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert"); + pps = build_pps(ctx, user, realm, pw, + fingerprint ? fingerprint : NULL, machine_managed); + free(fingerprint); + if (!pps) { + xml_node_free(ctx->xml, spp_node); + free(user); + free(realm); + free(pw); + return NULL; + } + + debug_print(ctx, 1, "Request DB subscription registration on success " + "notification"); + db_add_session_pps(ctx, user, realm, session_id, pps); + + hs20_eventlog_node(ctx, user, realm, session_id, + "new subscription", pps); + free(user); + free(pw); + + tnds = mo_to_tnds(ctx->xml, pps, 0, URN_HS20_PPS, NULL); + xml_node_free(ctx->xml, pps); + if (!tnds) { + xml_node_free(ctx->xml, spp_node); + free(realm); + return NULL; + } + + str = xml_node_to_str(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (str == NULL) { + xml_node_free(ctx->xml, spp_node); + free(realm); + return NULL; + } + + node = xml_node_create_text(ctx->xml, spp_node, ns, "addMO", str); + free(str); + snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription", realm); + free(realm); + xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", buf); + xml_node_add_attr(ctx->xml, node, ns, "moURN", URN_HS20_PPS); + + return spp_node; +} + + +static xml_node_t * hs20_user_input_free_remediation(struct hs20_svc *ctx, + const char *user, + const char *realm, + const char *session_id) +{ + xml_namespace_t *ns; + xml_node_t *spp_node; + xml_node_t *cred; + char buf[400]; + char *status; + char *free_account, *pw; + + free_account = db_get_osu_config_val(ctx, realm, "free_account"); + if (free_account == NULL) + return NULL; + pw = db_get_val(ctx, free_account, realm, "password", 0); + if (pw == NULL) { + free(free_account); + return NULL; + } + + cred = build_credential_pw(ctx, free_account, realm, pw); + free(free_account); + free(pw); + if (!cred) { + xml_node_free(ctx->xml, cred); + return NULL; + } + + status = "Remediation complete, request sppUpdateResponse"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + + snprintf(buf, sizeof(buf), + "./Wi-Fi/%s/PerProviderSubscription/Credential1/Credential", + realm); + + if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) { + xml_node_free(ctx->xml, spp_node); + return NULL; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "free/public remediation", cred); + xml_node_free(ctx->xml, cred); + + return spp_node; +} + + +static xml_node_t * hs20_user_input_complete(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + char *val; + enum hs20_session_operation oper; + + val = db_get_session_val(ctx, user, realm, session_id, "operation"); + if (val == NULL) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + return NULL; + } + oper = atoi(val); + free(val); + + if (oper == USER_REMEDIATION) { + return hs20_user_input_remediation(ctx, user, realm, dmacc, + session_id); + } + + if (oper == FREE_REMEDIATION) { + return hs20_user_input_free_remediation(ctx, user, realm, + session_id); + } + + if (oper == SUBSCRIPTION_REGISTRATION) { + return hs20_user_input_registration(ctx, session_id, 0); + } + + debug_print(ctx, 1, "User session %s not in state for user input " + "completion", session_id); + return NULL; +} + + +static xml_node_t * hs20_cert_enroll_completed(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + char *val; + enum hs20_session_operation oper; + + val = db_get_session_val(ctx, user, realm, session_id, "operation"); + if (val == NULL) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + return NULL; + } + oper = atoi(val); + free(val); + + if (oper == SUBSCRIPTION_REGISTRATION) + return hs20_user_input_registration(ctx, session_id, 1); + + debug_print(ctx, 1, "User session %s not in state for certificate " + "enrollment completion", session_id); + return NULL; +} + + +static xml_node_t * hs20_cert_enroll_failed(struct hs20_svc *ctx, + const char *user, + const char *realm, int dmacc, + const char *session_id) +{ + char *val; + enum hs20_session_operation oper; + xml_node_t *spp_node, *node; + char *status; + xml_namespace_t *ns; + + val = db_get_session_val(ctx, user, realm, session_id, "operation"); + if (val == NULL) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + return NULL; + } + oper = atoi(val); + free(val); + + if (oper != SUBSCRIPTION_REGISTRATION) { + debug_print(ctx, 1, "User session %s not in state for " + "enrollment failure", session_id); + return NULL; + } + + status = "Error occurred"; + spp_node = build_post_dev_data_response(ctx, &ns, session_id, status, + NULL); + if (spp_node == NULL) + return NULL; + node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); + xml_node_add_attr(ctx->xml, node, NULL, "errorCode", + "Credentials cannot be provisioned at this time"); + db_remove_session(ctx, user, realm, session_id); + + return spp_node; +} + + +static xml_node_t * hs20_spp_post_dev_data(struct hs20_svc *ctx, + xml_node_t *node, + const char *user, + const char *realm, + const char *session_id, + int dmacc) +{ + const char *req_reason; + char *redirect_uri = NULL; + char *req_reason_buf = NULL; + char str[200]; + xml_node_t *ret = NULL, *devinfo = NULL, *devdetail = NULL; + xml_node_t *mo; + char *version; + int valid; + char *supp, *pos; + char *err; + + version = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI, + "sppVersion"); + if (version == NULL || strstr(version, "1.0") == NULL) { + ret = build_post_dev_data_response( + ctx, NULL, session_id, "Error occurred", + "SPP version not supported"); + hs20_eventlog_node(ctx, user, realm, session_id, + "Unsupported sppVersion", ret); + xml_node_get_attr_value_free(ctx->xml, version); + return ret; + } + xml_node_get_attr_value_free(ctx->xml, version); + + mo = get_node(ctx->xml, node, "supportedMOList"); + if (mo == NULL) { + ret = build_post_dev_data_response( + ctx, NULL, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, + "No supportedMOList element", ret); + return ret; + } + supp = xml_node_get_text(ctx->xml, mo); + for (pos = supp; pos && *pos; pos++) + *pos = tolower(*pos); + if (supp == NULL || + strstr(supp, URN_OMA_DM_DEVINFO) == NULL || + strstr(supp, URN_OMA_DM_DEVDETAIL) == NULL || + strstr(supp, URN_HS20_PPS) == NULL) { + xml_node_get_text_free(ctx->xml, supp); + ret = build_post_dev_data_response( + ctx, NULL, session_id, "Error occurred", + "One or more mandatory MOs not supported"); + hs20_eventlog_node(ctx, user, realm, session_id, + "Unsupported MOs", ret); + return ret; + } + xml_node_get_text_free(ctx->xml, supp); + + req_reason_buf = xml_node_get_attr_value(ctx->xml, node, + "requestReason"); + if (req_reason_buf == NULL) { + debug_print(ctx, 1, "No requestReason attribute"); + return NULL; + } + req_reason = req_reason_buf; + + redirect_uri = xml_node_get_attr_value(ctx->xml, node, "redirectURI"); + + debug_print(ctx, 1, "requestReason: %s sessionID: %s redirectURI: %s", + req_reason, session_id, redirect_uri); + snprintf(str, sizeof(str), "sppPostDevData: requestReason=%s", + req_reason); + hs20_eventlog(ctx, user, realm, session_id, str, NULL); + + devinfo = spp_get_mo(ctx, node, URN_OMA_DM_DEVINFO, &valid, &err); + if (devinfo == NULL) { + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, + "No DevInfo moContainer in sppPostDevData", + ret); + os_free(err); + goto out; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "Received DevInfo MO", devinfo); + if (valid == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "OMA-DM DDF DTD validation errors in DevInfo MO", + err); + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + os_free(err); + goto out; + } + os_free(err); + if (user) + db_update_mo(ctx, user, realm, "devinfo", devinfo); + + devdetail = spp_get_mo(ctx, node, URN_OMA_DM_DEVDETAIL, &valid, &err); + if (devdetail == NULL) { + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, + "No DevDetail moContainer in sppPostDevData", + ret); + os_free(err); + goto out; + } + + hs20_eventlog_node(ctx, user, realm, session_id, + "Received DevDetail MO", devdetail); + if (valid == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "OMA-DM DDF DTD validation errors " + "in DevDetail MO", err); + ret = build_post_dev_data_response(ctx, NULL, session_id, + "Error occurred", "Other"); + os_free(err); + goto out; + } + os_free(err); + if (user) + db_update_mo(ctx, user, realm, "devdetail", devdetail); + + if (user) + mo = spp_get_mo(ctx, node, URN_HS20_PPS, &valid, &err); + else { + mo = NULL; + err = NULL; + } + if (user && mo) { + hs20_eventlog_node(ctx, user, realm, session_id, + "Received PPS MO", mo); + if (valid == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "OMA-DM DDF DTD validation errors " + "in PPS MO", err); + xml_node_get_attr_value_free(ctx->xml, redirect_uri); + os_free(err); + return build_post_dev_data_response( + ctx, NULL, session_id, + "Error occurred", "Other"); + } + db_update_mo(ctx, user, realm, "pps", mo); + db_update_val(ctx, user, realm, "fetch_pps", "0", dmacc); + xml_node_free(ctx->xml, mo); + } + os_free(err); + + if (user && !mo) { + char *fetch; + int fetch_pps; + + fetch = db_get_val(ctx, user, realm, "fetch_pps", dmacc); + fetch_pps = fetch ? atoi(fetch) : 0; + free(fetch); + + if (fetch_pps) { + enum hs20_session_operation oper; + if (strcasecmp(req_reason, "Subscription remediation") + == 0) + oper = CONTINUE_SUBSCRIPTION_REMEDIATION; + else if (strcasecmp(req_reason, "Policy update") == 0) + oper = CONTINUE_POLICY_UPDATE; + else + oper = NO_OPERATION; + if (db_add_session(ctx, user, realm, session_id, NULL, + NULL, oper) < 0) + goto out; + + ret = spp_exec_upload_mo(ctx, session_id, + URN_HS20_PPS); + hs20_eventlog_node(ctx, user, realm, session_id, + "request PPS MO upload", + ret); + goto out; + } + } + + if (user && strcasecmp(req_reason, "MO upload") == 0) { + char *val = db_get_session_val(ctx, user, realm, session_id, + "operation"); + enum hs20_session_operation oper; + if (!val) { + debug_print(ctx, 1, "No session %s found to continue", + session_id); + goto out; + } + oper = atoi(val); + free(val); + if (oper == CONTINUE_SUBSCRIPTION_REMEDIATION) + req_reason = "Subscription remediation"; + else if (oper == CONTINUE_POLICY_UPDATE) + req_reason = "Policy update"; + else { + debug_print(ctx, 1, + "No pending operation in session %s", + session_id); + goto out; + } + } + + if (strcasecmp(req_reason, "Subscription registration") == 0) { + ret = hs20_subscription_registration(ctx, realm, session_id, + redirect_uri); + hs20_eventlog_node(ctx, user, realm, session_id, + "subscription registration response", + ret); + goto out; + } + if (user && strcasecmp(req_reason, "Subscription remediation") == 0) { + ret = hs20_subscription_remediation(ctx, user, realm, + session_id, dmacc, + redirect_uri); + hs20_eventlog_node(ctx, user, realm, session_id, + "subscription remediation response", + ret); + goto out; + } + if (user && strcasecmp(req_reason, "Policy update") == 0) { + ret = hs20_policy_update(ctx, user, realm, session_id, dmacc); + hs20_eventlog_node(ctx, user, realm, session_id, + "policy update response", + ret); + goto out; + } + + if (strcasecmp(req_reason, "User input completed") == 0) { + if (devinfo) + db_add_session_devinfo(ctx, session_id, devinfo); + if (devdetail) + db_add_session_devdetail(ctx, session_id, devdetail); + ret = hs20_user_input_complete(ctx, user, realm, dmacc, + session_id); + hs20_eventlog_node(ctx, user, realm, session_id, + "user input completed response", ret); + goto out; + } + + if (strcasecmp(req_reason, "Certificate enrollment completed") == 0) { + ret = hs20_cert_enroll_completed(ctx, user, realm, dmacc, + session_id); + hs20_eventlog_node(ctx, user, realm, session_id, + "certificate enrollment response", ret); + goto out; + } + + if (strcasecmp(req_reason, "Certificate enrollment failed") == 0) { + ret = hs20_cert_enroll_failed(ctx, user, realm, dmacc, + session_id); + hs20_eventlog_node(ctx, user, realm, session_id, + "certificate enrollment failed response", + ret); + goto out; + } + + debug_print(ctx, 1, "Unsupported requestReason '%s' user '%s'", + req_reason, user); +out: + xml_node_get_attr_value_free(ctx->xml, req_reason_buf); + xml_node_get_attr_value_free(ctx->xml, redirect_uri); + if (devinfo) + xml_node_free(ctx->xml, devinfo); + if (devdetail) + xml_node_free(ctx->xml, devdetail); + return ret; +} + + +static xml_node_t * build_spp_exchange_complete(struct hs20_svc *ctx, + const char *session_id, + const char *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, + "sppExchangeComplete"); + + + 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", status); + + if (error_code) { + node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); + xml_node_add_attr(ctx->xml, node, NULL, "errorCode", + error_code); + } + + return spp_node; +} + + +static int add_subscription(struct hs20_svc *ctx, const char *session_id) +{ + char *user, *realm, *pw, *pw_mm, *pps, *str; + char *sql; + int ret = -1; + char *free_account; + int free_acc; + char *type; + int cert = 0; + char *cert_pem, *fingerprint; + + user = db_get_session_val(ctx, NULL, NULL, session_id, "user"); + realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm"); + pw = db_get_session_val(ctx, NULL, NULL, session_id, "password"); + pw_mm = db_get_session_val(ctx, NULL, NULL, session_id, + "machine_managed"); + pps = db_get_session_val(ctx, NULL, NULL, session_id, "pps"); + cert_pem = db_get_session_val(ctx, NULL, NULL, session_id, "cert_pem"); + fingerprint = db_get_session_val(ctx, NULL, NULL, session_id, "cert"); + type = db_get_session_val(ctx, NULL, NULL, session_id, "type"); + if (type && strcmp(type, "cert") == 0) + cert = 1; + free(type); + + if (!user || !realm || !pw) { + debug_print(ctx, 1, "Could not find session info from DB for " + "the new subscription"); + goto out; + } + + free_account = db_get_osu_config_val(ctx, realm, "free_account"); + free_acc = free_account && strcmp(free_account, user) == 0; + free(free_account); + + debug_print(ctx, 1, + "New subscription: user='%s' realm='%s' free_acc=%d", + user, realm, free_acc); + debug_print(ctx, 1, "New subscription: pps='%s'", pps); + + sql = sqlite3_mprintf("UPDATE eventlog SET user=%Q, realm=%Q WHERE " + "sessionid=%Q AND (user='' OR user IS NULL)", + user, realm, session_id); + if (sql) { + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to update eventlog in " + "sqlite database: %s", + sqlite3_errmsg(ctx->db)); + } + sqlite3_free(sql); + } + + if (free_acc) { + hs20_eventlog(ctx, user, realm, session_id, + "completed shared free account registration", + NULL); + ret = 0; + goto out; + } + + sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2," + "methods,cert,cert_pem,machine_managed) VALUES " + "(%Q,%Q,1,%Q,%Q,%Q,%d)", + user, realm, cert ? "TLS" : "TTLS-MSCHAPV2", + fingerprint ? fingerprint : "", + cert_pem ? cert_pem : "", + pw_mm && atoi(pw_mm) ? 1 : 0); + if (sql == NULL) + goto out; + debug_print(ctx, 1, "DB: %s", sql); + if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) { + debug_print(ctx, 1, "Failed to add user in sqlite database: %s", + sqlite3_errmsg(ctx->db)); + sqlite3_free(sql); + goto out; + } + sqlite3_free(sql); + + if (cert) + ret = 0; + else + ret = update_password(ctx, user, realm, pw, 0); + if (ret < 0) { + sql = sqlite3_mprintf("DELETE FROM users WHERE identity=%Q AND " + "realm=%Q AND phase2=1", + user, realm); + if (sql) { + debug_print(ctx, 1, "DB: %s", sql); + sqlite3_exec(ctx->db, sql, NULL, NULL, NULL); + sqlite3_free(sql); + } + } + + if (pps) + db_update_mo_str(ctx, user, realm, "pps", pps); + + str = db_get_session_val(ctx, NULL, NULL, session_id, "devinfo"); + if (str) { + db_update_mo_str(ctx, user, realm, "devinfo", str); + free(str); + } + + str = db_get_session_val(ctx, NULL, NULL, session_id, "devdetail"); + if (str) { + db_update_mo_str(ctx, user, realm, "devdetail", str); + free(str); + } + + if (ret == 0) { + hs20_eventlog(ctx, user, realm, session_id, + "completed subscription registration", NULL); + } + +out: + free(user); + free(realm); + free(pw); + free(pw_mm); + free(pps); + free(cert_pem); + free(fingerprint); + return ret; +} + + +static xml_node_t * hs20_spp_update_response(struct hs20_svc *ctx, + xml_node_t *node, + const char *user, + const char *realm, + const char *session_id, + int dmacc) +{ + char *status; + xml_node_t *ret; + char *val; + enum hs20_session_operation oper; + + status = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI, + "sppStatus"); + if (status == NULL) { + debug_print(ctx, 1, "No sppStatus attribute"); + return NULL; + } + + debug_print(ctx, 1, "sppUpdateResponse: sppStatus: %s sessionID: %s", + status, session_id); + + val = db_get_session_val(ctx, user, realm, session_id, "operation"); + if (!val) { + debug_print(ctx, 1, + "No session active for user: %s sessionID: %s", + user, session_id); + oper = NO_OPERATION; + } else + oper = atoi(val); + + if (strcasecmp(status, "OK") == 0) { + char *new_pw = NULL; + + xml_node_get_attr_value_free(ctx->xml, status); + + if (oper == USER_REMEDIATION) { + new_pw = db_get_session_val(ctx, user, realm, + session_id, "password"); + if (new_pw == NULL || strlen(new_pw) == 0) { + free(new_pw); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, "No password " + "had been assigned for " + "session", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + oper = UPDATE_PASSWORD; + } + if (oper == UPDATE_PASSWORD) { + if (!new_pw) { + new_pw = db_get_session_val(ctx, user, realm, + session_id, + "password"); + if (!new_pw) { + db_remove_session(ctx, user, realm, + session_id); + return NULL; + } + } + debug_print(ctx, 1, "Update user '%s' password in DB", + user); + if (update_password(ctx, user, realm, new_pw, dmacc) < + 0) { + debug_print(ctx, 1, "Failed to update user " + "'%s' password in DB", user); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, "Failed to " + "update database", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + hs20_eventlog(ctx, user, realm, + session_id, "Updated user password " + "in database", NULL); + } + if (oper == SUBSCRIPTION_REGISTRATION) { + if (add_subscription(ctx, session_id) < 0) { + debug_print(ctx, 1, "Failed to add " + "subscription into DB"); + ret = build_spp_exchange_complete( + ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, + session_id, "Failed to " + "update database", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + } + if (oper == POLICY_REMEDIATION || oper == POLICY_UPDATE) { + char *val; + val = db_get_val(ctx, user, realm, "remediation", + dmacc); + if (val && strcmp(val, "policy") == 0) + db_update_val(ctx, user, realm, "remediation", + "", dmacc); + free(val); + } + ret = build_spp_exchange_complete( + ctx, session_id, + "Exchange complete, release TLS connection", NULL); + hs20_eventlog_node(ctx, user, realm, session_id, + "Exchange completed", ret); + db_remove_session(ctx, user, realm, session_id); + return ret; + } + + ret = build_spp_exchange_complete(ctx, session_id, "Error occurred", + "Other"); + hs20_eventlog_node(ctx, user, realm, session_id, "Error occurred", ret); + db_remove_session(ctx, user, realm, session_id); + xml_node_get_attr_value_free(ctx->xml, status); + return ret; +} + + +#define SPP_SESSION_ID_LEN 16 + +static char * gen_spp_session_id(void) +{ + FILE *f; + int i; + char *session; + + session = os_malloc(SPP_SESSION_ID_LEN * 2 + 1); + if (session == NULL) + return NULL; + + f = fopen("/dev/urandom", "r"); + if (f == NULL) { + os_free(session); + return NULL; + } + for (i = 0; i < SPP_SESSION_ID_LEN; i++) + os_snprintf(session + i * 2, 3, "%02x", fgetc(f)); + + fclose(f); + return session; +} + +xml_node_t * hs20_spp_server_process(struct hs20_svc *ctx, xml_node_t *node, + const char *auth_user, + const char *auth_realm, int dmacc) +{ + xml_node_t *ret = NULL; + char *session_id; + const char *op_name; + char *xml_err; + char fname[200]; + + debug_dump_node(ctx, "received request", node); + + if (!dmacc && auth_user && auth_realm) { + char *real; + real = db_get_val(ctx, auth_user, auth_realm, "identity", 0); + if (!real) { + real = db_get_val(ctx, auth_user, auth_realm, + "identity", 1); + if (real) + dmacc = 1; + } + os_free(real); + } + + snprintf(fname, sizeof(fname), "%s/spp/spp.xsd", ctx->root_dir); + if (xml_validate(ctx->xml, node, fname, &xml_err) < 0) { + /* + * We may not be able to extract the sessionID from invalid + * input, but well, we can try. + */ + session_id = xml_node_get_attr_value_ns(ctx->xml, node, + SPP_NS_URI, + "sessionID"); + debug_print(ctx, 1, "SPP message failed validation"); + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "SPP message failed validation", node); + hs20_eventlog(ctx, auth_user, auth_realm, session_id, + "Validation errors", xml_err); + os_free(xml_err); + xml_node_get_attr_value_free(ctx->xml, session_id); + /* TODO: what to return here? */ + ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "SppValidationError"); + return ret; + } + + session_id = xml_node_get_attr_value_ns(ctx->xml, node, SPP_NS_URI, + "sessionID"); + if (session_id) { + char *tmp; + debug_print(ctx, 1, "Received sessionID %s", session_id); + tmp = os_strdup(session_id); + xml_node_get_attr_value_free(ctx->xml, session_id); + if (tmp == NULL) + return NULL; + session_id = tmp; + } else { + session_id = gen_spp_session_id(); + if (session_id == NULL) { + debug_print(ctx, 1, "Failed to generate sessionID"); + return NULL; + } + debug_print(ctx, 1, "Generated sessionID %s", session_id); + } + + op_name = xml_node_get_localname(ctx->xml, node); + if (op_name == NULL) { + debug_print(ctx, 1, "Could not get op_name"); + return NULL; + } + + if (strcmp(op_name, "sppPostDevData") == 0) { + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "sppPostDevData received and validated", + node); + ret = hs20_spp_post_dev_data(ctx, node, auth_user, auth_realm, + session_id, dmacc); + } else if (strcmp(op_name, "sppUpdateResponse") == 0) { + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "sppUpdateResponse received and validated", + node); + ret = hs20_spp_update_response(ctx, node, auth_user, + auth_realm, session_id, dmacc); + } else { + hs20_eventlog_node(ctx, auth_user, auth_realm, session_id, + "Unsupported SPP message received and " + "validated", node); + debug_print(ctx, 1, "Unsupported operation '%s'", op_name); + /* TODO: what to return here? */ + ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "SppUnknownCommandError"); + } + os_free(session_id); + + if (ret == NULL) { + /* TODO: what to return here? */ + ret = xml_node_create_root(ctx->xml, NULL, NULL, NULL, + "SppInternalError"); + } + + return ret; +} + + +int hs20_spp_server_init(struct hs20_svc *ctx) +{ + char fname[200]; + ctx->db = NULL; + snprintf(fname, sizeof(fname), "%s/AS/DB/eap_user.db", ctx->root_dir); + if (sqlite3_open(fname, &ctx->db)) { + printf("Failed to open sqlite database: %s\n", + sqlite3_errmsg(ctx->db)); + sqlite3_close(ctx->db); + return -1; + } + + return 0; +} + + +void hs20_spp_server_deinit(struct hs20_svc *ctx) +{ + sqlite3_close(ctx->db); + ctx->db = NULL; +} diff --git a/hs20/server/spp_server.h b/hs20/server/spp_server.h new file mode 100644 index 0000000..7b27be3 --- /dev/null +++ b/hs20/server/spp_server.h @@ -0,0 +1,32 @@ +/* + * Hotspot 2.0 SPP server + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef SPP_SERVER_H +#define SPP_SERVER_H + +struct hs20_svc { + const void *ctx; + struct xml_node_ctx *xml; + char *root_dir; + FILE *debug_log; + sqlite3 *db; + const char *addr; +}; + + +void debug_print(struct hs20_svc *ctx, int print, const char *fmt, ...) + __attribute__ ((format (printf, 3, 4))); +void debug_dump_node(struct hs20_svc *ctx, const char *title, xml_node_t *node); + +xml_node_t * hs20_spp_server_process(struct hs20_svc *ctx, xml_node_t *node, + const char *auth_user, + const char *auth_realm, int dmacc); +int hs20_spp_server_init(struct hs20_svc *ctx); +void hs20_spp_server_deinit(struct hs20_svc *ctx); + +#endif /* SPP_SERVER_H */ diff --git a/hs20/server/sql-example.txt b/hs20/server/sql-example.txt new file mode 100644 index 0000000..20dcf2f --- /dev/null +++ b/hs20/server/sql-example.txt @@ -0,0 +1,17 @@ +INSERT INTO osu_config(realm,field,value) VALUES('example.com','fqdn','example.com'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','friendly_name','Example Operator'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','spp_http_auth_url','https://subscription-server.osu.example.com/hs20/spp.php?realm=example.com'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','trust_root_cert_url','https://osu-server.osu.example.com/hs20/files/spp-root-ca.der'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','trust_root_cert_fingerprint','5b393a9246865569485c2605c3304e48212b449367858299beba9384c4cf4647'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','aaa_trust_root_cert_url','https://osu-server.osu.example.com/hs20/files/aaa-root-ca.der'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','aaa_trust_root_cert_fingerprint','5b393a9246865569485c2605c3304e48212b449367858299beba9384c4cf4647'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','free_account','free'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','policy_url','https://subscription-server.osu.example.com/hs20/spp.php?realm=example.com'); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','remediation_url','https://subscription-server.osu.example.com/hs20/remediation.php?session_id='); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','free_remediation_url','https://subscription-server.osu.example.com/hs20/free-remediation.php?session_id='); +INSERT INTO osu_config(realm,field,value) VALUES('example.com','signup_url','https://subscription-server.osu.example.com/hs20/signup.php?session_id='); + + +INSERT INTO users(identity,realm,methods,password,phase2,shared) VALUES('free','example.com','TTLS-MSCHAPV2','free',1,1); + +INSERT INTO wildcards(identity,methods) VALUES('','TTLS,TLS'); diff --git a/hs20/server/sql.txt b/hs20/server/sql.txt new file mode 100644 index 0000000..6609538 --- /dev/null +++ b/hs20/server/sql.txt @@ -0,0 +1,59 @@ +CREATE TABLE eventlog( + user TEXT, + realm TEXT, + sessionid TEXT COLLATE NOCASE, + timestamp TEXT, + notes TEXT, + dump TEXT, + addr TEXT +); + +CREATE TABLE sessions( + timestamp TEXT, + id TEXT COLLATE NOCASE, + user TEXT, + realm TEXT, + password TEXT, + machine_managed BOOLEAN, + operation INTEGER, + type TEXT, + pps TEXT, + redirect_uri TEXT, + devinfo TEXT, + devdetail TEXT, + cert TEXT, + cert_pem TEXT +); + +CREATE index sessions_id_index ON sessions(id); + +CREATE TABLE osu_config( + realm TEXT, + field TEXT, + value TEXT +); + +CREATE TABLE users( + identity TEXT PRIMARY KEY, + methods TEXT, + password TEXT, + machine_managed BOOLEAN, + remediation TEXT, + phase2 INTEGER, + realm TEXT, + policy TEXT, + devinfo TEXT, + devdetail TEXT, + pps TEXT, + fetch_pps INTEGER, + osu_user TEXT, + osu_password TEXT, + shared INTEGER, + cert TEXT, + cert_pem TEXT +); + +CREATE TABLE wildcards( + identity TEXT PRIMARY KEY, + methods TEXT +); diff --git a/hs20/server/www/add-free.php b/hs20/server/www/add-free.php new file mode 100644 index 0000000..1efc655 --- /dev/null +++ b/hs20/server/www/add-free.php @@ -0,0 +1,50 @@ +<?php + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (isset($_POST["id"])) + $id = preg_replace("/[^a-fA-F0-9]/", "", $_POST["id"]); +else + die("Missing session id"); +if (strlen($id) < 32) + die("Invalid session id"); + +$row = $db->query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} + +$uri = $row['redirect_uri']; +$rowid = $row['rowid']; +$realm = $row['realm']; + +$row = $db->query("SELECT value FROM osu_config WHERE realm='$realm' AND field='free_account'")->fetch(); +if (!$row || strlen($row['value']) == 0) { + die("Free account disabled"); +} + +$user = $row['value']; + +$row = $db->query("SELECT password FROM users WHERE identity='$user' AND realm='$realm'")->fetch(); +if (!$row) + die("Free account not found"); + +$pw = $row['password']; + +if (!$db->exec("UPDATE sessions SET user='$user', password='$pw', realm='$realm', machine_managed='1' WHERE rowid=$rowid")) { + die("Failed to update session database"); +} + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('$user', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'completed user input response for a new PPS MO')"); + +header("Location: $uri", true, 302); + +?> diff --git a/hs20/server/www/add-mo.php b/hs20/server/www/add-mo.php new file mode 100644 index 0000000..a3b4513 --- /dev/null +++ b/hs20/server/www/add-mo.php @@ -0,0 +1,56 @@ +<?php + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (isset($_POST["id"])) + $id = preg_replace("/[^a-fA-F0-9]/", "", $_POST["id"]); +else + die("Missing session id"); + +$user = $_POST["user"]; +$pw = $_POST["password"]; +if (strlen($id) < 32 || !isset($user) || !isset($pw)) { + die("Invalid POST data"); +} + +if (strlen($user) < 1 || strncasecmp($user, "cert-", 5) == 0) { + echo "<html><body><p><red>Invalid username</red></p>\n"; + echo "<a href=\"signup.php?session_id=$id\">Try again</a>\n"; + echo "</body></html>\n"; + exit; +} + +$row = $db->query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} +$realm = $row['realm']; + +$userrow = $db->query("SELECT identity FROM users WHERE identity='$user' AND realm='$realm'")->fetch(); +if ($userrow) { + echo "<html><body><p><red>Selected username is not available</red></p>\n"; + echo "<a href=\"signup.php?session_id=$id\">Try again</a>\n"; + echo "</body></html>\n"; + exit; +} + +$uri = $row['redirect_uri']; +$rowid = $row['rowid']; + +if (!$db->exec("UPDATE sessions SET user='$user', password='$pw', realm='$realm', type='password' WHERE rowid=$rowid")) { + die("Failed to update session database"); +} + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('$user', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'completed user input response for a new PPS MO')"); + +header("Location: $uri", true, 302); + +?> diff --git a/hs20/server/www/cert-enroll.php b/hs20/server/www/cert-enroll.php new file mode 100644 index 0000000..f023ca5 --- /dev/null +++ b/hs20/server/www/cert-enroll.php @@ -0,0 +1,39 @@ +<?php + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (isset($_GET["id"])) + $id = preg_replace("/[^a-fA-F0-9]/", "", $_GET["id"]); +else + die("Missing session id"); +if (strlen($id) < 32) + die("Invalid session id"); + +$row = $db->query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} + +$uri = $row['redirect_uri']; +$rowid = $row['rowid']; +$realm = $row['realm']; + +$user = sha1(mt_rand()); + +if (!$db->exec("UPDATE sessions SET user='$user', type='cert' WHERE rowid=$rowid")) { + die("Failed to update session database"); +} + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'completed user input response for client certificate enrollment')"); + +header("Location: $uri", true, 302); + +?> diff --git a/hs20/server/www/config.php b/hs20/server/www/config.php new file mode 100644 index 0000000..e3af435 --- /dev/null +++ b/hs20/server/www/config.php @@ -0,0 +1,4 @@ +<?php +$osu_root = "/home/user/hs20-server"; +$osu_db = "sqlite:$osu_root/AS/DB/eap_user.db"; +?> diff --git a/hs20/server/www/est.php b/hs20/server/www/est.php new file mode 100644 index 0000000..a45648b --- /dev/null +++ b/hs20/server/www/est.php @@ -0,0 +1,198 @@ +<?php + +require('config.php'); + +$params = split("/", $_SERVER["PATH_INFO"], 3); +$realm = $params[1]; +$cmd = $params[2]; +$method = $_SERVER["REQUEST_METHOD"]; + +unset($user); +unset($rowid); + +if (!empty($_SERVER['PHP_AUTH_DIGEST'])) { + $needed = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, + 'uri'=>1, 'response'=>1); + $data = array(); + $keys = implode('|', array_keys($needed)); + preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', + $_SERVER['PHP_AUTH_DIGEST'], $matches, PREG_SET_ORDER); + foreach ($matches as $m) { + $data[$m[1]] = $m[3] ? $m[3] : $m[4]; + unset($needed[$m[1]]); + } + if ($needed) { + error_log("EST: Missing auth parameter"); + die('Authentication failed'); + } + $user = $data['username']; + if (strlen($user) < 1) { + error_log("EST: Empty username"); + die('Authentication failed'); + } + + $db = new PDO($osu_db); + if (!$db) { + error_log("EST: Could not access database"); + die("Could not access database"); + } + + $sql = "SELECT rowid,password,operation FROM sessions " . + "WHERE user='$user' AND realm='$realm'"; + $q = $db->query($sql); + if (!$q) { + error_log("EST: Session not found for user=$user realm=$realm"); + die("Session not found"); + } + $row = $q->fetch(); + if (!$row) { + error_log("EST: Session fetch failed for user=$user realm=$realm"); + die('Session not found'); + } + $rowid = $row['rowid']; + + $oper = $row['operation']; + if ($oper != '5') { + error_log("EST: Unexpected operation $oper for user=$user realm=$realm"); + die("Session not found"); + } + $pw = $row['password']; + if (strlen($pw) < 1) { + error_log("EST: Empty password for user=$user realm=$realm"); + die('Authentication failed'); + } + + $A1 = md5($user . ':' . $realm . ':' . $pw); + $A2 = md5($method . ':' . $data['uri']); + $resp = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . + $data['cnonce'] . ':' . $data['qop'] . ':' . $A2); + if ($data['response'] != $resp) { + error_log("EST: Incorrect authentication response for user=$user realm=$realm"); + die('Authentication failed'); + } +} + + +if ($method == "GET" && $cmd == "cacerts") { + $fname = "$osu_root/est/$realm-cacerts.pkcs7"; + if (!file_exists($fname)) { + error_log("EST: cacerts - unknown realm $realm"); + die("Unknown realm"); + } + + header("Content-Transfer-Encoding: base64"); + header("Content-Type: application/pkcs7-mime"); + + $data = file_get_contents($fname); + echo wordwrap(base64_encode($data), 72, "\n", true); + echo "\n"; + error_log("EST: cacerts"); +} else if ($method == "GET" && $cmd == "csrattrs") { + header("Content-Transfer-Encoding: base64"); + header("Content-Type: application/csrattrs"); + readfile("$osu_root/est/est-attrs.b64"); + error_log("EST: csrattrs"); +} else if ($method == "POST" && $cmd == "simpleenroll") { + if (!isset($user) || strlen($user) == 0) { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Digest realm="'.$realm. + '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); + error_log("EST: simpleenroll - require authentication"); + die('Authentication required'); + } + if (!isset($_SERVER["CONTENT_TYPE"])) { + error_log("EST: simpleenroll without Content-Type"); + die("Missing Content-Type"); + } + if (!stristr($_SERVER["CONTENT_TYPE"], "application/pkcs10")) { + error_log("EST: simpleenroll - unexpected Content-Type: " . + $_SERVER["CONTENT_TYPE"]); + die("Unexpected Content-Type"); + } + + $data = file_get_contents("php://input"); + error_log("EST: simpleenroll - POST data from php://input: " . $data); + $req = base64_decode($data); + if ($req == FALSE) { + error_log("EST: simpleenroll - Invalid base64-encoded PKCS#10 data"); + die("Invalid base64-encoded PKCS#10 data"); + } + $cadir = "$osu_root/est"; + $reqfile = "$cadir/tmp/cert-req.pkcs10"; + $f = fopen($reqfile, "wb"); + fwrite($f, $req); + fclose($f); + + $req_pem = "$reqfile.pem"; + if (file_exists($req_pem)) + unlink($req_pem); + exec("openssl req -in $reqfile -inform DER -out $req_pem -outform PEM"); + if (!file_exists($req_pem)) { + error_log("EST: simpleenroll - Failed to parse certificate request"); + die("Failed to parse certificate request"); + } + + /* FIX: validate request and add HS 2.0 extensions to cert */ + $cert_pem = "$cadir/tmp/req-signed.pem"; + if (file_exists($cert_pem)) + unlink($cert_pem); + exec("openssl x509 -req -in $req_pem -CAkey $cadir/cakey.pem -out $cert_pem -CA $cadir/cacert.pem -CAserial $cadir/serial -days 365 -text"); + if (!file_exists($cert_pem)) { + error_log("EST: simpleenroll - Failed to sign certificate"); + die("Failed to sign certificate"); + } + + $cert = file_get_contents($cert_pem); + $handle = popen("openssl x509 -in $cert_pem -serial -noout", "r"); + $serial = fread($handle, 200); + pclose($handle); + $pattern = "/serial=(?P<snhex>[0-9a-fA-F:]*)/m"; + preg_match($pattern, $serial, $matches); + if (!isset($matches['snhex']) || strlen($matches['snhex']) < 1) { + error_log("EST: simpleenroll - Could not get serial number"); + die("Could not get serial number"); + } + $sn = str_replace(":", "", strtoupper($matches['snhex'])); + + $user = "cert-$sn"; + error_log("EST: user = $user"); + + $cert_der = "$cadir/tmp/req-signed.der"; + if (file_exists($cert_der)) + unlink($cert_der); + exec("openssl x509 -in $cert_pem -inform PEM -out $cert_der -outform DER"); + if (!file_exists($cert_der)) { + error_log("EST: simpleenroll - Failed to convert certificate"); + die("Failed to convert certificate"); + } + $der = file_get_contents($cert_der); + $fingerprint = hash("sha256", $der); + + $pkcs7 = "$cadir/tmp/est-client.pkcs7"; + if (file_exists($pkcs7)) + unlink($pkcs7); + exec("openssl crl2pkcs7 -nocrl -certfile $cert_pem -out $pkcs7 -outform DER"); + if (!file_exists($pkcs7)) { + error_log("EST: simpleenroll - Failed to prepare PKCS#7 file"); + die("Failed to prepare PKCS#7 file"); + } + + if (!$db->exec("UPDATE sessions SET user='$user', cert='$fingerprint', cert_pem='$cert' WHERE rowid=$rowid")) { + error_log("EST: simpleenroll - Failed to update session database"); + die("Failed to update session database"); + } + + header("Content-Transfer-Encoding: base64"); + header("Content-Type: application/pkcs7-mime"); + + $data = file_get_contents($pkcs7); + $resp = wordwrap(base64_encode($data), 72, "\n", true); + echo $resp . "\n"; + error_log("EST: simpleenroll - PKCS#7 response: " . $resp); +} else { + header("HTTP/1.0 404 Not Found"); + error_log("EST: Unexpected method or path"); + die("Unexpected method or path"); +} + +?> diff --git a/hs20/server/www/free-remediation.php b/hs20/server/www/free-remediation.php new file mode 100644 index 0000000..5648b30 --- /dev/null +++ b/hs20/server/www/free-remediation.php @@ -0,0 +1,19 @@ +<html> +<head> +<title>Hotspot 2.0 - public and free hotspot - remediation</title> +</head> +<body> + +<h3>Hotspot 2.0 - public and free hotspot</h3> + +<p>Terms and conditions have changed. You need to accept the new terms +to continue using this network.</p> + +<p>Terms and conditions..</p> + +<?php +echo "<a href=\"redirect.php?id=" . $_GET["session_id"] . "\">Accept</a><br>\n"; +?> + +</body> +</html> diff --git a/hs20/server/www/free.php b/hs20/server/www/free.php new file mode 100644 index 0000000..8195069 --- /dev/null +++ b/hs20/server/www/free.php @@ -0,0 +1,23 @@ +<html> +<head> +<title>Hotspot 2.0 - public and free hotspot</title> +</head> +<body> + +<?php + +$id = $_GET["session_id"]; + +echo "<h3>Hotspot 2.0 - public and free hotspot</h3>\n"; + +echo "<form action=\"add-free.php\" method=\"POST\">\n"; +echo "<input type=\"hidden\" name=\"id\" value=\"$id\">\n"; + +?> + +<p>Terms and conditions..</p> +<input type="submit" value="Accept"> +</form> + +</body> +</html> diff --git a/hs20/server/www/redirect.php b/hs20/server/www/redirect.php new file mode 100644 index 0000000..8fc9cd6 --- /dev/null +++ b/hs20/server/www/redirect.php @@ -0,0 +1,32 @@ +<?php + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (isset($_GET["id"])) + $id = preg_replace("/[^a-fA-F0-9]/", "", $_GET["id"]); +else + $id = 0; + +$row = $db->query("SELECT rowid,* FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} + +$uri = $row['redirect_uri']; + +header("Location: $uri", true, 302); + +$user = $row['user']; +$realm = $row['realm']; + +$db->exec("INSERT INTO eventlog(user,realm,sessionid,timestamp,notes) " . + "VALUES ('$user', '$realm', '$id', " . + "strftime('%Y-%m-%d %H:%M:%f','now'), " . + "'redirected after user input')"); + +?> diff --git a/hs20/server/www/remediation.php b/hs20/server/www/remediation.php new file mode 100644 index 0000000..392a7bd --- /dev/null +++ b/hs20/server/www/remediation.php @@ -0,0 +1,18 @@ +<html> +<head> +<title>Hotspot 2.0 subscription remediation</title> +</head> +<body> + +<?php + +echo "SessionID: " . $_GET["session_id"] . "<br>\n"; + +echo "<a href=\"redirect.php?id=" . $_GET["session_id"] . "\">Complete user subscription remediation</a><br>\n"; + +?> + +This will provide a new machine-generated password. + +</body> +</html> diff --git a/hs20/server/www/signup.php b/hs20/server/www/signup.php new file mode 100644 index 0000000..a626704 --- /dev/null +++ b/hs20/server/www/signup.php @@ -0,0 +1,46 @@ +<html> +<head> +<title>Hotspot 2.0 signup</title> +</head> +<body> + +<?php + +$id = $_GET["session_id"]; + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +$row = $db->query("SELECT realm FROM sessions WHERE id='$id'")->fetch(); +if ($row == false) { + die("Session not found"); +} +$realm = $row['realm']; + +echo "<h3>Sign up for a subscription - $realm</h3>\n"; + +$row = $db->query("SELECT value FROM osu_config WHERE realm='$realm' AND field='free_account'")->fetch(); +if ($row && strlen($row['value']) > 0) { + echo "<p><a href=\"free.php?session_id=$id\">Sign up for free access</a></p>\n"; +} + +echo "<form action=\"add-mo.php\" method=\"POST\">\n"; +echo "<input type=\"hidden\" name=\"id\" value=\"$id\">\n"; +?> +Select a username and password. Leave password empty to get automatically +generated and machine managed password.<br> +Username: <input type="text" name="user"><br> +Password: <input type="password" name="password"><br> +<input type="submit" value="Complete subscription registration"> +</form> + +<?php +echo "<p><a href=\"cert-enroll.php?id=$id\">Enroll a client certificate</a></p>\n" +?> + +</body> +</html> diff --git a/hs20/server/www/spp.php b/hs20/server/www/spp.php new file mode 100644 index 0000000..dde4434 --- /dev/null +++ b/hs20/server/www/spp.php @@ -0,0 +1,127 @@ +<?php + +require('config.php'); + +if (!stristr($_SERVER["CONTENT_TYPE"], "application/soap+xml")) { + error_log("spp.php - Unexpected Content-Type " . $_SERVER["CONTENT_TYPE"]); + die("Unexpected Content-Type"); +} + +if ($_SERVER["REQUEST_METHOD"] != "POST") { + error_log("spp.php - Unexpected method " . $_SERVER["REQUEST_METHOD"]); + die("Unexpected method"); +} + +if (isset($_GET["realm"])) { + $realm = $_GET["realm"]; + $realm = PREG_REPLACE("/[^0-9a-zA-Z\.\-]/i", '', $realm); +} else { + error_log("spp.php - Realm not specified"); + die("Realm not specified"); +} + +unset($user); +putenv("HS20CERT"); + +if (!empty($_SERVER['PHP_AUTH_DIGEST'])) { + $needed = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, + 'uri'=>1, 'response'=>1); + $data = array(); + $keys = implode('|', array_keys($needed)); + preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', + $_SERVER['PHP_AUTH_DIGEST'], $matches, PREG_SET_ORDER); + foreach ($matches as $m) { + $data[$m[1]] = $m[3] ? $m[3] : $m[4]; + unset($needed[$m[1]]); + } + if ($needed) { + error_log("spp.php - Authentication failed - missing: " . print_r($needed)); + die('Authentication failed'); + } + $user = $data['username']; + if (strlen($user) < 1) { + error_log("spp.php - Authentication failed - empty username"); + die('Authentication failed'); + } + + + $db = new PDO($osu_db); + if (!$db) { + error_log("spp.php - Could not access database"); + die("Could not access database"); + } + $row = $db->query("SELECT password FROM users " . + "WHERE identity='$user' AND realm='$realm'")->fetch(); + if (!$row) { + $row = $db->query("SELECT osu_password FROM users " . + "WHERE osu_user='$user' AND realm='$realm'")->fetch(); + $pw = $row['osu_password']; + } else + $pw = $row['password']; + if (!$row) { + error_log("spp.php - Authentication failed - user '$user' not found"); + die('Authentication failed'); + } + if (strlen($pw) < 1) { + error_log("spp.php - Authentication failed - empty password"); + die('Authentication failed'); + } + + $A1 = md5($user . ':' . $realm . ':' . $pw); + $A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']); + $resp = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . + $data['cnonce'] . ':' . $data['qop'] . ':' . $A2); + if ($data['response'] != $resp) { + error_log("Authentication failure - response mismatch"); + die('Authentication failed'); + } +} else if (isset($_SERVER["SSL_CLIENT_VERIFY"]) && + $_SERVER["SSL_CLIENT_VERIFY"] == "SUCCESS" && + isset($_SERVER["SSL_CLIENT_M_SERIAL"])) { + $user = "cert-" . $_SERVER["SSL_CLIENT_M_SERIAL"]; + putenv("HS20CERT=yes"); +} else if (!isset($_SERVER["PATH_INFO"]) || + $_SERVER["PATH_INFO"] != "/signup") { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Digest realm="'.$realm. + '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); + error_log("spp.php - Authentication required (not signup)"); + die('Authentication required (not signup)'); +} + + +if (isset($user) && strlen($user) > 0) + putenv("HS20USER=$user"); +else + putenv("HS20USER"); + +putenv("HS20REALM=$realm"); +putenv("HS20POST=$HTTP_RAW_POST_DATA"); +$addr = $_SERVER["REMOTE_ADDR"]; +putenv("HS20ADDR=$addr"); + +$last = exec("$osu_root/spp/hs20_spp_server -r$osu_root -f/tmp/hs20_spp_server.log", $output, $ret); + +if ($ret == 2) { + if (empty($_SERVER['PHP_AUTH_DIGEST'])) { + header('HTTP/1.1 401 Unauthorized'); + header('WWW-Authenticate: Digest realm="'.$realm. + '",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"'); + error_log("spp.php - Authentication required (ret 2)"); + die('Authentication required'); + } else { + error_log("spp.php - Unexpected authentication error"); + die("Unexpected authentication error"); + } +} +if ($ret != 0) { + error_log("spp.php - Failed to process SPP request"); + die("Failed to process SPP request"); +} +//error_log("spp.php: Response: " . implode($output)); + +header("Content-Type: application/soap+xml"); + +echo implode($output); + +?> diff --git a/hs20/server/www/users.php b/hs20/server/www/users.php new file mode 100644 index 0000000..c340a33 --- /dev/null +++ b/hs20/server/www/users.php @@ -0,0 +1,349 @@ +<?php + +require('config.php'); + +$db = new PDO($osu_db); +if (!$db) { + die($sqliteerror); +} + +if (isset($_GET["id"])) { + $id = $_GET["id"]; + if (!is_numeric($id)) + $id = 0; +} else + $id = 0; +if (isset($_GET["cmd"])) + $cmd = $_GET["cmd"]; +else + $cmd = ''; + +if ($cmd == 'eventlog' && $id > 0) { + $row = $db->query("SELECT dump FROM eventlog WHERE rowid=$id")->fetch(); + $dump = $row['dump']; + if ($dump[0] == '<') { + header("Content-type: text/xml"); + echo "<?xml version=\"1.0\"?>\n"; + echo $dump; + } else { + header("Content-type: text/plain"); + echo $dump; + } + exit; +} + +if ($cmd == 'mo' && $id > 0) { + $mo = $_GET["mo"]; + if (!isset($mo)) + exit; + if ($mo != "devinfo" && $mo != "devdetail" && $mo != "pps") + exit; + $row = $db->query("SELECT $mo FROM users WHERE rowid=$id")->fetch(); + header("Content-type: text/xml"); + echo "<?xml version=\"1.0\"?>\n"; + echo $row[$mo]; + exit; +} + +if ($cmd == 'cert' && $id > 0) { + $row = $db->query("SELECT cert_pem FROM users WHERE rowid=$id")->fetch(); + header("Content-type: text/plain"); + echo $row['cert_pem']; + exit; +} + +?> + +<html> +<head><title>HS 2.0 users</title></head> +<body> + +<?php + +if ($cmd == 'subrem-clear' && $id > 0) { + $db->exec("UPDATE users SET remediation='' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-user' && $id > 0) { + $db->exec("UPDATE users SET remediation='user' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-machine' && $id > 0) { + $db->exec("UPDATE users SET remediation='machine' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-policy' && $id > 0) { + $db->exec("UPDATE users SET remediation='policy' WHERE rowid=$id"); +} +if ($cmd == 'subrem-add-free' && $id > 0) { + $db->exec("UPDATE users SET remediation='free' WHERE rowid=$id"); +} +if ($cmd == 'fetch-pps-on' && $id > 0) { + $db->exec("UPDATE users SET fetch_pps=1 WHERE rowid=$id"); +} +if ($cmd == 'fetch-pps-off' && $id > 0) { + $db->exec("UPDATE users SET fetch_pps=0 WHERE rowid=$id"); +} +if ($cmd == 'reset-pw' && $id > 0) { + $db->exec("UPDATE users SET password='ChangeMe' WHERE rowid=$id"); +} +if ($cmd == "policy" && $id > 0 && isset($_GET["policy"])) { + $policy = $_GET["policy"]; + if ($policy == "no-policy" || + is_readable("$osu_root/spp/policy/$policy.xml")) { + $db->exec("UPDATE users SET policy='$policy' WHERE rowid=$id"); + } +} +if ($cmd == "account-type" && $id > 0 && isset($_GET["type"])) { + $type = $_GET["type"]; + if ($type == "shared") + $db->exec("UPDATE users SET shared=1 WHERE rowid=$id"); + if ($type == "default") + $db->exec("UPDATE users SET shared=0 WHERE rowid=$id"); +} + +if ($cmd == "set-osu-cred" && $id > 0) { + $osu_user = $_POST["osu_user"]; + $osu_password = $_POST["osu_password"]; + if (strlen($osu_user) == 0) + $osu_password = ""; + $db->exec("UPDATE users SET osu_user='$osu_user', osu_password='$osu_password' WHERE rowid=$id"); +} + +$dump = 0; + +if ($id > 0) { + +if (isset($_GET["dump"])) { + $dump = $_GET["dump"]; + if (!is_numeric($dump)) + $dump = 0; +} else + $dump = 0; + +echo "[<a href=\"users.php\">All users</a>] "; +if ($dump == 0) + echo "[<a href=\"users.php?id=$id&dump=1\">Include debug dump</a>] "; +else + echo "[<a href=\"users.php?id=$id\">Without debug dump</a>] "; +echo "<br>\n"; + +$row = $db->query("SELECT rowid,* FROM users WHERE rowid=$id")->fetch(); + +echo "<H3>" . $row['identity'] . "@" . $row['realm'] . "</H3>\n"; + +echo "MO: "; +if (strlen($row['devinfo']) > 0) { + echo "[<a href=\"users.php?cmd=mo&id=$id&mo=devinfo\">DevInfo</a>]\n"; +} +if (strlen($row['devdetail']) > 0) { + echo "[<a href=\"users.php?cmd=mo&id=$id&mo=devdetail\">DevDetail</a>]\n"; +} +if (strlen($row['pps']) > 0) { + echo "[<a href=\"users.php?cmd=mo&id=$id&mo=pps\">PPS</a>]\n"; +} +if (strlen($row['cert_pem']) > 0) { + echo "[<a href=\"users.php?cmd=cert&id=$id\">Certificate</a>]\n"; +} +echo "<BR>\n"; + +echo "Fetch PPS MO: "; +if ($row['fetch_pps'] == "1") { + echo "On next connection " . + "[<a href=\"users.php?cmd=fetch-pps-off&id=$id\">" . + "do not fetch</a>]<br>\n"; +} else { + echo "Do not fetch " . + "[<a href=\"users.php?cmd=fetch-pps-on&id=$id\">" . + "request fetch</a>]<br>\n"; +} + +$cert = $row['cert']; +if (strlen($cert) > 0) { + echo "Certificate fingerprint: $cert<br>\n"; +} + +echo "Remediation: "; +$rem = $row['remediation']; +if ($rem == "") { + echo "Not required"; + echo " [<a href=\"users.php?cmd=subrem-add-user&id=" . + $row['rowid'] . "\">add:user</a>]"; + echo " [<a href=\"users.php?cmd=subrem-add-machine&id=" . + $row['rowid'] . "\">add:machine</a>]"; + echo " [<a href=\"users.php?cmd=subrem-add-policy&id=" . + $row['rowid'] . "\">add:policy</a>]"; + echo " [<a href=\"users.php?cmd=subrem-add-free&id=" . + $row['rowid'] . "\">add:free</a>]"; +} else if ($rem == "user") { + echo "User [<a href=\"users.php?cmd=subrem-clear&id=" . + $row['rowid'] . "\">clear</a>]"; +} else if ($rem == "policy") { + echo "Policy [<a href=\"users.php?cmd=subrem-clear&id=" . + $row['rowid'] . "\">clear</a>]"; +} else if ($rem == "free") { + echo "Free [<a href=\"users.php?cmd=subrem-clear&id=" . + $row['rowid'] . "\">clear</a>]"; +} else { + echo "Machine [<a href=\"users.php?cmd=subrem-clear&id=" . + $row['rowid'] . "\">clear</a>]"; +} +echo "<br>\n"; + +echo "<form>Policy: <select name=\"policy\" " . + "onChange=\"window.location='users.php?cmd=policy&id=" . + $row['rowid'] . "&policy=' + this.value;\">\n"; +echo "<option value=\"" . $row['policy'] . "\" selected>" . $row['policy'] . + "</option>\n"; +$files = scandir("$osu_root/spp/policy"); +foreach ($files as $file) { + if (!preg_match("/.xml$/", $file)) + continue; + if ($file == $row['policy'] . ".xml") + continue; + $p = substr($file, 0, -4); + echo "<option value=\"$p\">$p</option>\n"; +} +echo "<option value=\"no-policy\">no policy</option>\n"; +echo "</select></form>\n"; + +echo "<form>Account type: <select name=\"type\" " . + "onChange=\"window.location='users.php?cmd=account-type&id=" . + $row['rowid'] . "&type=' + this.value;\">\n"; +if ($row['shared'] > 0) { + $default_sel = ""; + $shared_sel = " selected"; +} else { + $default_sel = " selected"; + $shared_sel = ""; +} +echo "<option value=\"default\"$default_sel>default</option>\n"; +echo "<option value=\"shared\"$shared_sel>shared</option>\n"; +echo "</select></form>\n"; + +echo "Phase 2 method(s): " . $row['methods'] . "<br>\n"; + +echo "<br>\n"; +echo "<a href=\"users.php?cmd=reset-pw&id=" . + $row['rowid'] . "\">Reset AAA password</a><br>\n"; + +echo "<br>\n"; +echo "<form action=\"users.php?cmd=set-osu-cred&id=" . $row['rowid'] . + "\" method=\"POST\">\n"; +echo "OSU credentials (if username empty, AAA credentials are used):<br>\n"; +echo "username: <input type=\"text\" name=\"osu_user\" value=\"" . + $row['osu_user'] . "\">\n"; +echo "password: <input type=\"password\" name=\"osu_password\">\n"; +echo "<input type=\"submit\" value=\"Set OSU credentials\">\n"; +echo "</form>\n"; + +echo "<hr>\n"; + +$user = $row['identity']; +$osu_user = $row['osu_user']; +$realm = $row['realm']; +} + +if ($id > 0 || ($id == 0 && $cmd == 'eventlog')) { + + if ($id == 0) { + echo "[<a href=\"users.php\">All users</a>] "; + echo "<br>\n"; + } + +echo "<table border=1>\n"; +echo "<tr>"; +if ($id == 0) { + echo "<th>user<th>realm"; +} +echo "<th>time<th>address<th>sessionID<th>notes"; +if ($dump > 0) + echo "<th>dump"; +echo "\n"; +if (isset($_GET["limit"])) { + $limit = $_GET["limit"]; + if (!is_numeric($limit)) + $limit = 20; +} else + $limit = 20; +if ($id == 0) + $res = $db->query("SELECT rowid,* FROM eventlog ORDER BY timestamp DESC LIMIT $limit"); +else if (strlen($osu_user) > 0) + $res = $db->query("SELECT rowid,* FROM eventlog WHERE (user='$user' OR user='$osu_user') AND realm='$realm' ORDER BY timestamp DESC LIMIT $limit"); +else + $res = $db->query("SELECT rowid,* FROM eventlog WHERE user='$user' AND realm='$realm' ORDER BY timestamp DESC LIMIT $limit"); +foreach ($res as $row) { + echo "<tr>"; + if ($id == 0) { + echo "<td>" . $row['user'] . "\n"; + echo "<td>" . $row['realm'] . "\n"; + } + echo "<td>" . $row['timestamp'] . "\n"; + echo "<td>" . $row['addr'] . "\n"; + echo "<td>" . $row['sessionid'] . "\n"; + echo "<td>" . $row['notes'] . "\n"; + $d = $row['dump']; + if (strlen($d) > 0) { + echo "[<a href=\"users.php?cmd=eventlog&id=" . $row['rowid'] . + "\">"; + if ($d[0] == '<') + echo "XML"; + else + echo "txt"; + echo "</a>]\n"; + if ($dump > 0) + echo "<td>" . htmlspecialchars($d) . "\n"; + } +} +echo "</table>\n"; + +} + + +if ($id == 0 && $cmd != 'eventlog') { + +echo "[<a href=\"users.php?cmd=eventlog&limit=50\">Eventlog</a>] "; +echo "<br>\n"; + +echo "<table border=1>\n"; +echo "<tr><th>User<th>Realm<th>Remediation<th>Policy<th>Account type<th>Phase 2 method(s)<th>DevId\n"; + +$res = $db->query('SELECT rowid,* FROM users WHERE phase2=1'); +foreach ($res as $row) { + echo "<tr><td><a href=\"users.php?id=" . $row['rowid'] . "\"> " . + $row['identity'] . " </a>"; + echo "<td>" . $row['realm']; + $rem = $row['remediation']; + echo "<td>"; + if ($rem == "") { + echo "Not required"; + } else if ($rem == "user") { + echo "User"; + } else if ($rem == "policy") { + echo "Policy"; + } else if ($rem == "free") { + echo "Free"; + } else { + echo "Machine"; + } + echo "<td>" . $row['policy']; + if ($row['shared'] > 0) + echo "<td>shared"; + else + echo "<td>default"; + echo "<td>" . $row['methods']; + echo "<td>"; + $xml = xml_parser_create(); + xml_parse_into_struct($xml, $row['devinfo'], $devinfo); + foreach($devinfo as $k) { + if ($k['tag'] == 'DEVID') { + echo $k['value']; + break; + } + } + echo "\n"; +} +echo "</table>\n"; + +} + +?> + +</html> diff --git a/src/Makefile b/src/Makefile index d73a175..10e0171 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,4 +1,4 @@ -SUBDIRS=ap common crypto drivers eapol_auth eapol_supp eap_common eap_peer eap_server l2_packet p2p radius rsn_supp tls utils wps +SUBDIRS=ap common crypto drivers eapol_auth eapol_supp eap_common eap_peer eap_server l2_packet p2p pae radius rsn_supp tls utils wps all: for d in $(SUBDIRS); do [ -d $$d ] && $(MAKE) -C $$d; done diff --git a/src/ap/acs.c b/src/ap/acs.c index f58b091..b94b8a4 100644 --- a/src/ap/acs.c +++ b/src/ap/acs.c @@ -284,6 +284,7 @@ static void acs_fail(struct hostapd_iface *iface) { wpa_printf(MSG_ERROR, "ACS: Failed to start"); acs_cleanup(iface); + hostapd_disable_iface(iface); } @@ -367,6 +368,19 @@ static int acs_usable_ht40_chan(struct hostapd_channel_data *chan) } +static int acs_usable_vht80_chan(struct hostapd_channel_data *chan) +{ + const int allowed[] = { 36, 52, 100, 116, 132, 149 }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(allowed); i++) + if (chan->chan == allowed[i]) + return 1; + + return 0; +} + + static int acs_survey_is_sufficient(struct freq_survey *survey) { if (!(survey->filled & SURVEY_HAS_NF)) { @@ -541,6 +555,15 @@ acs_find_ideal_chan(struct hostapd_iface *iface) continue; } + if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A && + iface->conf->ieee80211ac && + iface->conf->vht_oper_chwidth == 1 && + !acs_usable_vht80_chan(chan)) { + wpa_printf(MSG_DEBUG, "ACS: Channel %d: not allowed as primary channel for VHT80", + chan->chan); + continue; + } + factor = 0; if (acs_usable_chan(chan)) factor = chan->interference_factor; @@ -616,23 +639,26 @@ acs_find_ideal_chan(struct hostapd_iface *iface) static void acs_adjust_vht_center_freq(struct hostapd_iface *iface) { + int offset; + wpa_printf(MSG_DEBUG, "ACS: Adjusting VHT center frequency"); switch (iface->conf->vht_oper_chwidth) { case VHT_CHANWIDTH_USE_HT: - iface->conf->vht_oper_centr_freq_seg0_idx = - iface->conf->channel + 2; + offset = 2 * iface->conf->secondary_channel; break; case VHT_CHANWIDTH_80MHZ: - iface->conf->vht_oper_centr_freq_seg0_idx = - iface->conf->channel + 6; + offset = 6; break; default: /* TODO: How can this be calculated? Adjust * acs_find_ideal_chan() */ wpa_printf(MSG_INFO, "ACS: Only VHT20/40/80 is supported now"); - break; + return; } + + iface->conf->vht_oper_centr_freq_seg0_idx = + iface->conf->channel + offset; } @@ -723,7 +749,7 @@ static void acs_scan_complete(struct hostapd_iface *iface) err = hostapd_drv_get_survey(iface->bss[0], 0); if (err) { wpa_printf(MSG_ERROR, "ACS: Failed to get survey data"); - acs_fail(iface); + goto fail; } if (++iface->acs_num_completed_scans < iface->conf->acs_num_scans) { @@ -775,6 +801,7 @@ static int acs_request_scan(struct hostapd_iface *iface) if (hostapd_driver_scan(iface->bss[0], ¶ms) < 0) { wpa_printf(MSG_ERROR, "ACS: Failed to request initial scan"); acs_cleanup(iface); + os_free(params.freqs); return -1; } diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index 368b202..7535b1b 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -73,6 +73,7 @@ void hostapd_config_defaults_bss(struct hostapd_bss_config *bss) #ifdef CONFIG_IEEE80211W bss->assoc_sa_query_max_timeout = 1000; bss->assoc_sa_query_retry_timeout = 201; + bss->group_mgmt_cipher = WPA_CIPHER_AES_128_CMAC; #endif /* CONFIG_IEEE80211W */ #ifdef EAP_SERVER_FAST /* both anonymous and authenticated provisioning */ @@ -106,9 +107,9 @@ struct hostapd_config * hostapd_config_defaults(void) 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, 3000 / 32, 0 }; + { aCWmin - 1, aCWmin, 2, 3008 / 32, 0 }; const struct hostapd_wmm_ac_params ac_vo = /* voice traffic */ - { aCWmin - 2, aCWmin - 1, 2, 1500 / 32, 0 }; + { 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 = @@ -154,6 +155,8 @@ struct hostapd_config * hostapd_config_defaults(void) conf->rts_threshold = -1; /* use driver default: 2347 */ conf->fragm_threshold = -1; /* user driver default: 2346 */ conf->send_probe_response = 1; + /* 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; @@ -171,11 +174,11 @@ struct hostapd_config * hostapd_config_defaults(void) conf->ap_table_expiration_time = 60; #ifdef CONFIG_TESTING_OPTIONS - conf->ignore_probe_probability = 0.0d; - conf->ignore_auth_probability = 0.0d; - conf->ignore_assoc_probability = 0.0d; - conf->ignore_reassoc_probability = 0.0d; - conf->corrupt_gtk_rekey_mic_probability = 0.0d; + 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; #endif /* CONFIG_TESTING_OPTIONS */ #ifdef CONFIG_ACS @@ -336,20 +339,6 @@ int hostapd_setup_wpa_psk(struct hostapd_bss_config *conf) } -int hostapd_wep_key_cmp(struct hostapd_wep_keys *a, struct hostapd_wep_keys *b) -{ - int i; - - if (a->idx != b->idx || a->default_len != b->default_len) - return 1; - for (i = 0; i < NUM_WEP_KEYS; i++) - if (a->len[i] != b->len[i] || - os_memcmp(a->key[i], b->key[i], a->len[i]) != 0) - return 1; - return 0; -} - - static void hostapd_config_free_radius(struct hostapd_radius_server *servers, int num_servers) { @@ -386,8 +375,9 @@ static void hostapd_config_free_radius_attr(struct hostapd_radius_attr *attr) } -static void hostapd_config_free_eap_user(struct hostapd_eap_user *user) +void hostapd_config_free_eap_user(struct hostapd_eap_user *user) { + hostapd_config_free_radius_attr(user->accept_attr); os_free(user->identity); os_free(user->password); os_free(user); @@ -526,6 +516,25 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf) 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) { + size_t i; + 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->service_desc); + } + os_free(conf->hs20_osu_providers); + } + os_free(conf->subscr_remediation_url); #endif /* CONFIG_HS20 */ wpabuf_free(conf->vendor_elements); @@ -554,6 +563,7 @@ void hostapd_config_free(struct hostapd_config *conf) os_free(conf->bss); os_free(conf->supported_rates); os_free(conf->basic_rates); + os_free(conf->chanlist); os_free(conf); } @@ -641,12 +651,11 @@ const u8 * hostapd_get_psk(const struct hostapd_bss_config *conf, struct hostapd_wpa_psk *psk; int next_ok = prev_psk == NULL; - if (p2p_dev_addr) { + 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); - if (!is_zero_ether_addr(p2p_dev_addr)) - addr = NULL; /* Use P2P Device Address for matching */ + addr = NULL; /* Use P2P Device Address for matching */ } else { wpa_printf(MSG_DEBUG, "Searching a PSK for " MACSTR " prev_psk=%p", @@ -772,7 +781,7 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss, } #endif /* CONFIG_IEEE80211N */ -#ifdef CONFIG_WPS2 +#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"); @@ -793,7 +802,7 @@ static int hostapd_config_check_bss(struct hostapd_bss_config *bss, "WPA2/CCMP forced WPS to be disabled"); bss->wps_state = 0; } -#endif /* CONFIG_WPS2 */ +#endif /* CONFIG_WPS */ #ifdef CONFIG_HS20 if (full_config && bss->hs20 && @@ -829,6 +838,18 @@ int hostapd_config_check(struct hostapd_config *conf, int full_config) 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; + } + for (i = 0; i < conf->num_bss; i++) { if (hostapd_config_check_bss(conf->bss[i], conf, full_config)) return -1; @@ -838,7 +859,8 @@ int hostapd_config_check(struct hostapd_config *conf, int full_config) } -void hostapd_set_security_params(struct hostapd_bss_config *bss) +void hostapd_set_security_params(struct hostapd_bss_config *bss, + int full_config) { if (bss->individual_wep_key_len == 0) { /* individual keys are not use; can use key idx0 for @@ -851,8 +873,10 @@ void hostapd_set_security_params(struct hostapd_bss_config *bss) bss->wpa_group = wpa_select_ap_group_cipher(bss->wpa, bss->wpa_pairwise, bss->rsn_pairwise); - bss->radius->auth_server = bss->radius->auth_servers; - bss->radius->acct_server = bss->radius->acct_servers; + 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; @@ -876,6 +900,11 @@ void hostapd_set_security_params(struct hostapd_bss_config *bss) bss->wpa_group = cipher; bss->wpa_pairwise = cipher; bss->rsn_pairwise = cipher; + } 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; bss->wpa_group = WPA_CIPHER_NONE; diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index b4860a0..905aec3 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -45,7 +45,8 @@ typedef enum hostap_security_policy { SECURITY_STATIC_WEP = 1, SECURITY_IEEE_802_1X = 2, SECURITY_WPA_PSK = 3, - SECURITY_WPA = 4 + SECURITY_WPA = 4, + SECURITY_OSEN = 5 } secpolicy; struct hostapd_ssid { @@ -125,7 +126,10 @@ struct hostapd_eap_user { 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; }; struct hostapd_radius_attr { @@ -251,6 +255,7 @@ struct hostapd_bss_config { int wpa_key_mgmt; #ifdef CONFIG_IEEE80211W enum mfp_options ieee80211w; + int group_mgmt_cipher; /* dot11AssociationSAQueryMaximumTimeout (in TUs) */ unsigned int assoc_sa_query_max_timeout; /* dot11AssociationSAQueryRetryTimeout (in TUs) */ @@ -311,6 +316,7 @@ struct hostapd_bss_config { char *radius_server_clients; int radius_server_auth_port; + int radius_server_acct_port; int radius_server_ipv6; char *test_socket; /* UNIX domain socket path for driver_test */ @@ -451,9 +457,11 @@ struct hostapd_bss_config { u8 qos_map_set[16 + 2 * 21]; unsigned int qos_map_set_len; + int osen; #ifdef CONFIG_HS20 int hs20; 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; @@ -461,6 +469,32 @@ struct hostapd_bss_config { 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[HOSTAPD_MAX_SSID_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; + unsigned int service_desc_count; + struct hostapd_lang_string *service_desc; + } *hs20_osu_providers, *last_osu; + size_t hs20_osu_providers_count; + unsigned int hs20_deauth_req_timeout; + char *subscr_remediation_url; + u8 subscr_remediation_method; #endif /* CONFIG_HS20 */ u8 wps_rf_bands; /* RF bands for WPS (WPS_RF_*) */ @@ -493,6 +527,7 @@ struct hostapd_config { int fragm_threshold; u8 send_probe_response; u8 channel; + int *chanlist; enum hostapd_hw_mode hw_mode; /* HOSTAPD_MODE_IEEE80211A, .. */ enum { LONG_PREAMBLE = 0, @@ -518,6 +553,16 @@ struct hostapd_config { 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]; /* @@ -560,13 +605,12 @@ int hostapd_mac_comp(const void *a, const void *b); int hostapd_mac_comp_empty(const void *a); struct hostapd_config * hostapd_config_defaults(void); void hostapd_config_defaults_bss(struct hostapd_bss_config *bss); +void hostapd_config_free_eap_user(struct hostapd_eap_user *user); 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, int *vlan_id); int hostapd_rate_found(int *list, int rate); -int hostapd_wep_key_cmp(struct hostapd_wep_keys *a, - struct hostapd_wep_keys *b); const u8 * hostapd_get_psk(const struct hostapd_bss_config *conf, const u8 *addr, const u8 *p2p_dev_addr, const u8 *prev_psk); @@ -577,6 +621,7 @@ const char * hostapd_get_vlan_id_ifname(struct hostapd_vlan *vlan, struct hostapd_radius_attr * hostapd_config_get_radius_attr(struct hostapd_radius_attr *attr, u8 type); int hostapd_config_check(struct hostapd_config *conf, int full_config); -void hostapd_set_security_params(struct hostapd_bss_config *bss); +void hostapd_set_security_params(struct hostapd_bss_config *bss, + int full_config); #endif /* HOSTAPD_CONFIG_H */ diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c index 893e6d9..cc4ac10 100644 --- a/src/ap/ap_drv_ops.c +++ b/src/ap/ap_drv_ops.c @@ -128,14 +128,14 @@ int hostapd_build_ap_extra_ies(struct hostapd_data *hapd, } #endif /* CONFIG_P2P_MANAGER */ -#ifdef CONFIG_WPS2 +#ifdef CONFIG_WPS if (hapd->conf->wps_state) { struct wpabuf *a = wps_build_assoc_resp_ie(); if (a && wpabuf_resize(&assocresp, wpabuf_len(a)) == 0) wpabuf_put_buf(assocresp, a); wpabuf_free(a); } -#endif /* CONFIG_WPS2 */ +#endif /* CONFIG_WPS */ #ifdef CONFIG_P2P_MANAGER if (hapd->conf->p2p & P2P_MANAGE) { @@ -170,6 +170,17 @@ int hostapd_build_ap_extra_ies(struct hostapd_data *hapd, goto fail; wpabuf_put_data(proberesp, buf, pos - buf); } + + pos = hostapd_eid_osen(hapd, buf); + if (pos != buf) { + if (wpabuf_resize(&beacon, pos - buf) != 0) + goto fail; + wpabuf_put_data(beacon, buf, pos - buf); + + if (wpabuf_resize(&proberesp, pos - buf) != 0) + goto fail; + wpabuf_put_data(proberesp, buf, pos - buf); + } #endif /* CONFIG_HS20 */ if (hapd->conf->vendor_elements) { @@ -269,7 +280,8 @@ int hostapd_set_drv_ieee8021x(struct hostapd_data *hapd, const char *ifname, params.wpa = hapd->conf->wpa; params.ieee802_1x = hapd->conf->ieee802_1x; params.wpa_group = hapd->conf->wpa_group; - params.wpa_pairwise = hapd->conf->wpa_pairwise; + params.wpa_pairwise = hapd->conf->wpa_pairwise | + hapd->conf->rsn_pairwise; params.wpa_key_mgmt = hapd->conf->wpa_key_mgmt; params.rsn_preauth = hapd->conf->rsn_preauth; #ifdef CONFIG_IEEE80211W @@ -346,7 +358,7 @@ int hostapd_sta_add(struct hostapd_data *hapd, u16 listen_interval, const struct ieee80211_ht_capabilities *ht_capab, const struct ieee80211_vht_capabilities *vht_capab, - u32 flags, u8 qosinfo) + u32 flags, u8 qosinfo, u8 vht_opmode) { struct hostapd_sta_add_params params; @@ -364,6 +376,8 @@ int hostapd_sta_add(struct hostapd_data *hapd, params.listen_interval = listen_interval; params.ht_capabilities = ht_capab; params.vht_capabilities = vht_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; return hapd->driver->sta_add(hapd->drv_priv, ¶ms); @@ -490,7 +504,8 @@ int hostapd_set_freq_params(struct hostapd_freq_params *data, int mode, case VHT_CHANWIDTH_USE_HT: if (center_segment1) return -1; - if (5000 + center_segment0 * 5 != data->center_freq1 && + if (center_segment0 != 0 && + 5000 + center_segment0 * 5 != data->center_freq1 && 2407 + center_segment0 * 5 != data->center_freq1) return -1; break; @@ -754,12 +769,16 @@ int hostapd_start_dfs_cac(struct hostapd_iface *iface, int mode, int freq, vht_enabled, sec_channel_offset, vht_oper_chwidth, center_segment0, center_segment1, - iface->current_mode->vht_capab)) + iface->current_mode->vht_capab)) { + wpa_printf(MSG_ERROR, "Can't set freq params"); return -1; + } res = hapd->driver->start_dfs_cac(hapd->drv_priv, &data); - if (!res) + if (!res) { iface->cac_started = 1; + os_get_reltime(&iface->dfs_cac_start); + } return res; } diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index 15a4b26..7cc9d7d 100644 --- a/src/ap/ap_drv_ops.h +++ b/src/ap/ap_drv_ops.h @@ -40,7 +40,7 @@ int hostapd_sta_add(struct hostapd_data *hapd, u16 listen_interval, const struct ieee80211_ht_capabilities *ht_capab, const struct ieee80211_vht_capabilities *vht_capab, - u32 flags, u8 qosinfo); + u32 flags, u8 qosinfo, u8 vht_opmode); 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); @@ -280,4 +280,15 @@ static inline int hostapd_drv_status(struct hostapd_data *hapd, char *buf, return hapd->driver->status(hapd->drv_priv, buf, buflen); } +static inline int hostapd_drv_vendor_cmd(struct hostapd_data *hapd, + int vendor_id, int subcmd, + const u8 *data, size_t data_len, + 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, buf); +} + #endif /* AP_DRV_OPS */ diff --git a/src/ap/ap_list.c b/src/ap/ap_list.c index f9b1540..287d520 100644 --- a/src/ap/ap_list.c +++ b/src/ap/ap_list.c @@ -32,7 +32,8 @@ static int ap_list_beacon_olbc(struct hostapd_iface *iface, struct ap_info *ap) { int i; - if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G || + if (iface->current_mode == NULL || + iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G || iface->conf->channel != ap->channel) return 0; diff --git a/src/ap/ap_mlme.c b/src/ap/ap_mlme.c index a959694..13604ed 100644 --- a/src/ap/ap_mlme.c +++ b/src/ap/ap_mlme.c @@ -16,6 +16,7 @@ #include "wpa_auth.h" #include "sta_info.h" #include "ap_mlme.h" +#include "hostapd.h" #ifndef CONFIG_NO_HOSTAPD_LOGGER @@ -80,7 +81,8 @@ void mlme_deauthenticate_indication(struct hostapd_data *hapd, HOSTAPD_LEVEL_DEBUG, "MLME-DEAUTHENTICATE.indication(" MACSTR ", %d)", MAC2STR(sta->addr), reason_code); - mlme_deletekeys_request(hapd, sta); + if (!hapd->iface->driver_ap_teardown) + mlme_deletekeys_request(hapd, sta); } @@ -118,8 +120,6 @@ void mlme_associate_indication(struct hostapd_data *hapd, struct sta_info *sta) * reassociation procedure that was initiated by that specific peer MAC entity. * * PeerSTAAddress = sta->addr - * - * sta->previous_ap contains the "Current AP" information from ReassocReq. */ void mlme_reassociate_indication(struct hostapd_data *hapd, struct sta_info *sta) diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c index 8bb58a6..86f1cbe 100644 --- a/src/ap/authsrv.c +++ b/src/ap/authsrv.c @@ -79,7 +79,10 @@ static int hostapd_radius_get_eap_user(void *ctx, const u8 *identity, user->password_hash = eap_user->password_hash; } 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; return 0; } @@ -92,6 +95,7 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd) 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.eap_sim_db_priv = hapd->eap_sim_db_priv; srv.ssl_ctx = hapd->ssl_ctx; @@ -112,9 +116,14 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd) srv.eap_req_id_text_len = conf->eap_req_id_text_len; srv.pwd_group = conf->pwd_group; srv.server_id = conf->server_id ? conf->server_id : "hostapd"; + 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; +#endif /* CONFIG_HS20 */ hapd->radius_srv = radius_server_init(&srv); if (hapd->radius_srv == NULL) { diff --git a/src/ap/beacon.c b/src/ap/beacon.c index 5318ecb..2a4acf2 100644 --- a/src/ap/beacon.c +++ b/src/ap/beacon.c @@ -27,6 +27,7 @@ #include "ap_drv_ops.h" #include "beacon.h" #include "hs20.h" +#include "dfs.h" #ifdef NEED_AP_MLME @@ -102,6 +103,70 @@ static u8 * hostapd_eid_erp_info(struct hostapd_data *hapd, u8 *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; + + /* + * 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(u8 *pos, u8 *end, int chan_spacing, struct hostapd_channel_data *start, struct hostapd_channel_data *prev) @@ -315,6 +380,9 @@ static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd, pos = hostapd_eid_country(hapd, pos, epos - pos); + /* Power Constraint element */ + pos = hostapd_eid_pwr_constraint(hapd, pos); + /* ERP Information element */ pos = hostapd_eid_erp_info(hapd, pos); @@ -374,6 +442,7 @@ static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd, #ifdef CONFIG_HS20 pos = hostapd_eid_hs20_indication(hapd, pos); + pos = hostapd_eid_osen(hapd, pos); #endif /* CONFIG_HS20 */ if (hapd->conf->vendor_elements) { @@ -520,12 +589,10 @@ void handle_probe_req(struct hostapd_data *hapd, sta->ssid_probe = &hapd->conf->ssid; } else { if (!(mgmt->da[0] & 0x01)) { - char ssid_txt[33]; - ieee802_11_print_ssid(ssid_txt, elems.ssid, - elems.ssid_len); wpa_printf(MSG_MSGDUMP, "Probe Request from " MACSTR " for foreign SSID '%s' (DA " MACSTR ")%s", - MAC2STR(mgmt->sa), ssid_txt, + MAC2STR(mgmt->sa), + wpa_ssid_txt(elems.ssid, elems.ssid_len), MAC2STR(mgmt->da), elems.ssid_list ? " (SSID list)" : ""); } @@ -533,7 +600,8 @@ void handle_probe_req(struct hostapd_data *hapd, } #ifdef CONFIG_INTERWORKING - if (elems.interworking && elems.interworking_len >= 1) { + 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) { @@ -544,7 +612,7 @@ void handle_probe_req(struct hostapd_data *hapd, } } - if (elems.interworking && + if (hapd->conf->interworking && elems.interworking && (elems.interworking_len == 7 || elems.interworking_len == 9)) { const u8 *hessid; if (elems.interworking_len == 7) @@ -577,7 +645,7 @@ void handle_probe_req(struct hostapd_data *hapd, * with AP configuration */ #ifdef CONFIG_TESTING_OPTIONS - if (hapd->iconf->ignore_probe_probability > 0.0d && + if (hapd->iconf->ignore_probe_probability > 0.0 && drand48() < hapd->iconf->ignore_probe_probability) { wpa_printf(MSG_INFO, "TESTING: ignoring probe request from " MACSTR, @@ -723,6 +791,9 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, tailpos = hostapd_eid_country(hapd, tailpos, tail + BEACON_TAIL_BUF_SIZE - tailpos); + /* Power Constraint element */ + tailpos = hostapd_eid_pwr_constraint(hapd, tailpos); + /* ERP Information element */ tailpos = hostapd_eid_erp_info(hapd, tailpos); @@ -785,6 +856,7 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, #ifdef CONFIG_HS20 tailpos = hostapd_eid_hs20_indication(hapd, tailpos); + tailpos = hostapd_eid_osen(hapd, tailpos); #endif /* CONFIG_HS20 */ if (hapd->conf->vendor_elements) { @@ -810,8 +882,8 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, params->basic_rates = hapd->iface->basic_rates; params->ssid = hapd->conf->ssid.ssid; params->ssid_len = hapd->conf->ssid.ssid_len; - params->pairwise_ciphers = hapd->conf->rsn_pairwise ? - hapd->conf->rsn_pairwise : hapd->conf->wpa_pairwise; + params->pairwise_ciphers = hapd->conf->wpa_pairwise | + hapd->conf->rsn_pairwise; params->group_cipher = hapd->conf->wpa_group; params->key_mgmt_suites = hapd->conf->wpa_key_mgmt; params->auth_algs = hapd->conf->auth_algs; @@ -856,6 +928,10 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd, params->ap_max_inactivity = hapd->conf->ap_max_inactivity; #ifdef CONFIG_HS20 params->disable_dgaf = hapd->conf->disable_dgaf; + if (hapd->conf->osen) { + params->privacy = 1; + params->osen = 1; + } #endif /* CONFIG_HS20 */ return 0; } @@ -875,6 +951,9 @@ void ieee802_11_free_ap_params(struct wpa_driver_ap_params *params) 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 wpabuf *beacon, *proberesp, *assocresp; int res, ret = -1; @@ -896,6 +975,17 @@ int ieee802_11_set_beacon(struct hostapd_data *hapd) params.proberesp_ies = proberesp; params.assocresp_ies = assocresp; + if (iface->current_mode && + hostapd_set_freq_params(&freq, iconf->hw_mode, iface->freq, + iconf->channel, iconf->ieee80211n, + iconf->ieee80211ac, + iconf->secondary_channel, + iconf->vht_oper_chwidth, + iconf->vht_oper_centr_freq_seg0_idx, + iconf->vht_oper_centr_freq_seg1_idx, + iface->current_mode->vht_capab) == 0) + params.freq = &freq; + res = hostapd_drv_set_ap(hapd, ¶ms); hostapd_free_ap_extra_ies(hapd, beacon, proberesp, assocresp); if (res) diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c index 8c0cbab..ccbbab5 100644 --- a/src/ap/ctrl_iface_ap.c +++ b/src/ap/ctrl_iface_ap.c @@ -250,9 +250,8 @@ static int p2p_manager_disconnect(struct hostapd_data *hapd, u16 stype, *pos++ = WLAN_EID_VENDOR_SPECIFIC; *pos++ = 4 + 3 + 1; - WPA_PUT_BE24(pos, OUI_WFA); - pos += 3; - *pos++ = P2P_OUI_TYPE; + WPA_PUT_BE32(pos, P2P_IE_VENDOR_TYPE); + pos += 4; *pos++ = P2P_ATTR_MINOR_REASON_CODE; WPA_PUT_LE16(pos, 1); @@ -282,6 +281,10 @@ int hostapd_ctrl_iface_deauthenticate(struct hostapd_data *hapd, 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; @@ -296,8 +299,7 @@ int hostapd_ctrl_iface_deauthenticate(struct hostapd_data *hapd, 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(WLAN_REASON_PREV_AUTH_NOT_VALID); + mgmt.u.deauth.reason_code = host_to_le16(reason); if (hapd->driver->send_frame(hapd->drv_priv, (u8 *) &mgmt, IEEE80211_HDRLEN + sizeof(mgmt.u.deauth), @@ -314,10 +316,6 @@ int hostapd_ctrl_iface_deauthenticate(struct hostapd_data *hapd, } #endif /* CONFIG_P2P_MANAGER */ - pos = os_strstr(txtaddr, " reason="); - if (pos) - reason = atoi(pos + 8); - hostapd_drv_sta_deauth(hapd, addr, reason); sta = ap_get_sta(hapd, addr); if (sta) @@ -343,6 +341,10 @@ int hostapd_ctrl_iface_disassociate(struct hostapd_data *hapd, 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; @@ -357,8 +359,7 @@ int hostapd_ctrl_iface_disassociate(struct hostapd_data *hapd, 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(WLAN_REASON_PREV_AUTH_NOT_VALID); + mgmt.u.disassoc.reason_code = host_to_le16(reason); if (hapd->driver->send_frame(hapd->drv_priv, (u8 *) &mgmt, IEEE80211_HDRLEN + sizeof(mgmt.u.deauth), @@ -375,10 +376,6 @@ int hostapd_ctrl_iface_disassociate(struct hostapd_data *hapd, } #endif /* CONFIG_P2P_MANAGER */ - pos = os_strstr(txtaddr, " reason="); - if (pos) - reason = atoi(pos + 8); - hostapd_drv_sta_disassoc(hapd, addr, reason); sta = ap_get_sta(hapd, addr); if (sta) @@ -408,6 +405,7 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf, "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), @@ -420,12 +418,35 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf, 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 (ret < 0 || (size_t) ret >= buflen - len) 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; + unsigned int left_time; + + os_reltime_age(&iface->dfs_cac_start, &now); + left_time = iface->dfs_cac_ms / 1000 - now.sec; + ret = os_snprintf(buf + len, buflen - len, + "cac_time_seconds=%u\n" + "cac_time_left_seconds=%u\n", + iface->dfs_cac_ms / 1000, + left_time); + } + if (ret < 0 || (size_t) ret >= buflen - len) + return len; + len += ret; + ret = os_snprintf(buf + len, buflen - len, "channel=%u\n" "secondary_channel=%d\n" @@ -472,9 +493,6 @@ int hostapd_parse_csa_settings(const char *pos, { char *end; - if (!settings) - return -1; - os_memset(settings, 0, sizeof(*settings)); settings->cs_count = strtol(pos, &end, 10); if (pos == end) { diff --git a/src/ap/dfs.c b/src/ap/dfs.c index e4c00f8..c30f6d6 100644 --- a/src/ap/dfs.c +++ b/src/ap/dfs.c @@ -50,9 +50,11 @@ static int dfs_channel_available(struct hostapd_channel_data *chan, /* * 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. + * channel for CSA, unless they are available for immediate use. */ - if (skip_radar && chan->flag & HOSTAPD_CHAN_RADAR) + if (skip_radar && (chan->flag & HOSTAPD_CHAN_RADAR) && + ((chan->flag & HOSTAPD_CHAN_DFS_MASK) != + HOSTAPD_CHAN_DFS_AVAILABLE)) return 0; if (chan->flag & HOSTAPD_CHAN_DISABLED) @@ -78,6 +80,11 @@ static int dfs_is_chan_allowed(struct hostapd_channel_data *chan, int n_chans) * 42, 58, 106, 122, 138, 155 */ int allowed_80[] = { 36, 52, 100, 116, 132, 149 }; + /* + * VHT160 valid channels based on center frequency: + * 50, 114 + */ + int allowed_160[] = { 36, 100 }; int *allowed = allowed_40; unsigned int i, allowed_no = 0; @@ -90,6 +97,10 @@ static int dfs_is_chan_allowed(struct hostapd_channel_data *chan, int n_chans) 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; @@ -130,6 +141,22 @@ static int dfs_chan_range_available(struct hostapd_hw_modes *mode, } +static int is_in_chanlist(struct hostapd_iface *iface, + struct hostapd_channel_data *chan) +{ + int *entry; + + if (!iface->conf->chanlist) + return 1; + + for (entry = iface->conf->chanlist; *entry != -1; entry++) { + if (*entry == chan->chan) + return 1; + } + return 0; +} + + /* * The function assumes HT40+ operation. * Make sure to adjust the following variables after calling this: @@ -162,6 +189,9 @@ static int dfs_find_channel(struct hostapd_iface *iface, if (!dfs_chan_range_available(mode, i, n_chans, skip_radar)) continue; + if (!is_in_chanlist(iface, chan)) + continue; + if (ret_chan && idx == channel_idx) { wpa_printf(MSG_DEBUG, "Selected ch. #%d", chan->chan); *ret_chan = chan; @@ -205,6 +235,7 @@ static void dfs_adjust_vht_center_freq(struct hostapd_iface *iface, break; default: wpa_printf(MSG_INFO, "DFS only VHT20/40/80/160 is supported now"); + *vht_oper_centr_freq_seg0_idx = 0; break; } @@ -257,8 +288,19 @@ static int dfs_get_start_chan_idx(struct hostapd_iface *iface) } } - if (res == -1) - wpa_printf(MSG_DEBUG, "DFS chan_idx seems wrong: -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, + iface->conf->vht_oper_chwidth); + + for (i = 0; i < mode->num_channels; i++) { + wpa_printf(MSG_DEBUG, "Available channel: %d", + mode->channels[i].chan); + } + } return res; } @@ -294,8 +336,15 @@ static int dfs_check_chans_available(struct hostapd_iface *iface, mode = iface->current_mode; - for(i = 0; i < n_chans; i++) { + 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; @@ -316,7 +365,7 @@ static int dfs_check_chans_unavailable(struct hostapd_iface *iface, mode = iface->current_mode; - for(i = 0; i < n_chans; i++) { + for (i = 0; i < n_chans; i++) { channel = &mode->channels[start_chan_idx + i]; if (channel->flag & HOSTAPD_CHAN_DISABLED) res++; @@ -343,6 +392,9 @@ dfs_get_valid_channel(struct hostapd_iface *iface, u32 _rand; wpa_printf(MSG_DEBUG, "DFS: Selecting random channel"); + *secondary_channel = 0; + *vht_oper_centr_freq_seg0_idx = 0; + *vht_oper_centr_freq_seg1_idx = 0; if (iface->current_mode == NULL) return NULL; @@ -521,6 +573,28 @@ static int dfs_are_channels_overlapped(struct hostapd_iface *iface, int freq, } +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++) { + 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 @@ -544,6 +618,10 @@ int hostapd_handle_dfs(struct hostapd_iface *iface) /* Get number of used channels, depend on width */ n_chans = dfs_get_used_n_chans(iface); + /* 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, @@ -566,8 +644,8 @@ int hostapd_handle_dfs(struct hostapd_iface *iface) wpa_printf(MSG_DEBUG, "DFS %d chans unavailable - choose other channel: %s", res, res ? "yes": "no"); if (res) { - int sec; - u8 cf1, cf2; + int sec = 0; + u8 cf1 = 0, cf2 = 0; channel = dfs_get_valid_channel(iface, &sec, &cf1, &cf2, skip_radar); @@ -588,19 +666,26 @@ int hostapd_handle_dfs(struct hostapd_iface *iface) hostapd_set_state(iface, HAPD_IFACE_DFS); wpa_printf(MSG_DEBUG, "DFS start CAC on %d MHz", iface->freq); wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_START - "freq=%d chan=%d sec_chan=%d", + "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); - if (hostapd_start_dfs_cac(iface, iface->conf->hw_mode, - iface->freq, - iface->conf->channel, - iface->conf->ieee80211n, - iface->conf->ieee80211ac, - iface->conf->secondary_channel, - iface->conf->vht_oper_chwidth, - iface->conf->vht_oper_centr_freq_seg0_idx, - iface->conf->vht_oper_centr_freq_seg1_idx)) { - wpa_printf(MSG_DEBUG, "DFS start_dfs_cac() failed"); + iface->conf->channel, iface->conf->secondary_channel, + iface->conf->vht_oper_chwidth, + iface->conf->vht_oper_centr_freq_seg0_idx, + iface->conf->vht_oper_centr_freq_seg1_idx, + 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->secondary_channel, + iface->conf->vht_oper_chwidth, + iface->conf->vht_oper_centr_freq_seg0_idx, + iface->conf->vht_oper_centr_freq_seg1_idx); + + if (res) { + wpa_printf(MSG_ERROR, "DFS start_dfs_cac() failed, %d", res); return -1; } @@ -633,8 +718,8 @@ static int hostapd_dfs_start_channel_switch_cac(struct hostapd_iface *iface) { struct hostapd_channel_data *channel; int secondary_channel; - u8 vht_oper_centr_freq_seg0_idx; - u8 vht_oper_centr_freq_seg1_idx; + u8 vht_oper_centr_freq_seg0_idx = 0; + u8 vht_oper_centr_freq_seg1_idx = 0; int skip_radar = 0; int err = 1; @@ -701,9 +786,33 @@ static int hostapd_dfs_start_channel_switch(struct hostapd_iface *iface) skip_radar); if (!channel) { - /* FIXME: Wait for channel(s) to become available */ + /* + * 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. + */ + skip_radar = 0; + channel = dfs_get_valid_channel(iface, &secondary_channel, + &vht_oper_centr_freq_seg0_idx, + &vht_oper_centr_freq_seg1_idx, + skip_radar); + if (!channel) { + /* FIXME: Wait for channel(s) to become available */ + hostapd_disable_iface(iface); + return err; + } + + iface->freq = channel->freq; + iface->conf->channel = channel->chan; + iface->conf->secondary_channel = secondary_channel; + iface->conf->vht_oper_centr_freq_seg0_idx = + vht_oper_centr_freq_seg0_idx; + iface->conf->vht_oper_centr_freq_seg1_idx = + vht_oper_centr_freq_seg1_idx; + hostapd_disable_iface(iface); - return err; + hostapd_enable_iface(iface); + return 0; } wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d", @@ -801,3 +910,24 @@ int hostapd_dfs_nop_finished(struct hostapd_iface *iface, int freq, cf1, cf2, HOSTAPD_CHAN_DFS_USABLE); return 0; } + + +int hostapd_is_dfs_required(struct hostapd_iface *iface) +{ + int n_chans, start_chan_idx; + + if (!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); + if (start_chan_idx == -1) + return -1; + + /* Get number of used channels, depend on width */ + n_chans = dfs_get_used_n_chans(iface); + + /* Check if any of configured channels require DFS */ + return dfs_check_chans_radar(iface, start_chan_idx, n_chans); +} diff --git a/src/ap/dfs.h b/src/ap/dfs.h index 859ff79..a619c55 100644 --- a/src/ap/dfs.h +++ b/src/ap/dfs.h @@ -21,5 +21,6 @@ int hostapd_dfs_radar_detected(struct hostapd_iface *iface, int freq, 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); #endif /* DFS_H */ diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index 9af9646..fb095ef 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -9,6 +9,7 @@ #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" @@ -30,6 +31,7 @@ #include "ap_config.h" #include "hw_features.h" #include "dfs.h" +#include "beacon.h" int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, @@ -78,6 +80,12 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, 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; @@ -115,6 +123,24 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, } #endif /* CONFIG_P2P */ +#ifdef CONFIG_IEEE80211N +#ifdef NEED_AP_MLME + if (elems.ht_capabilities && + elems.ht_capabilities_len >= + sizeof(struct ieee80211_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 */ +#endif /* CONFIG_IEEE80211N */ + #ifdef CONFIG_INTERWORKING if (elems.ext_capab && elems.ext_capab_len > 4) { if (elems.ext_capab[4] & 0x01) @@ -281,6 +307,29 @@ int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr, 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: @@ -649,6 +698,20 @@ static int hostapd_mgmt_rx(struct hostapd_data *hapd, struct rx_mgmt *rx_mgmt) struct hostapd_frame_info fi; int ret; +#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) @@ -678,6 +741,12 @@ static int hostapd_mgmt_rx(struct hostapd_data *hapd, struct rx_mgmt *rx_mgmt) 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; @@ -932,6 +1001,8 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, break; #endif /* NEED_AP_MLME */ case EVENT_RX_MGMT: + if (!data->rx_mgmt.frame) + break; #ifdef NEED_AP_MLME if (hostapd_mgmt_rx(hapd, &data->rx_mgmt) > 0) break; diff --git a/src/ap/eap_user_db.c b/src/ap/eap_user_db.c index 79d50e5..371a73f 100644 --- a/src/ap/eap_user_db.c +++ b/src/ap/eap_user_db.c @@ -89,6 +89,8 @@ static int get_user_cb(void *ctx, int argc, char *argv[], char *col[]) 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; } } @@ -173,8 +175,8 @@ eap_user_sqlite_get(struct hostapd_data *hapd, const u8 *identity, } os_snprintf(cmd, sizeof(cmd), - "SELECT password,methods FROM users WHERE " - "identity='%s' AND phase2=%d;", id_str, phase2); + "SELECT * FROM users WHERE identity='%s' AND phase2=%d;", + id_str, phase2); wpa_printf(MSG_DEBUG, "DB: %s", cmd); if (sqlite3_exec(db, cmd, get_user_cb, &hapd->tmp_eap_user, NULL) != SQLITE_OK) { diff --git a/src/ap/gas_serv.c b/src/ap/gas_serv.c index b5fb7df..b406880 100644 --- a/src/ap/gas_serv.c +++ b/src/ap/gas_serv.c @@ -71,7 +71,6 @@ gas_dialog_create(struct hostapd_data *hapd, const u8 *addr, u8 dialog_token) continue; dia = &sta->gas_dialog[i]; dia->valid = 1; - dia->index = i; dia->dialog_token = dialog_token; sta->gas_dialog_next = (++i == GAS_DIALOG_MAX) ? 0 : i; return dia; @@ -159,6 +158,10 @@ static void anqp_add_hs_capab_list(struct hostapd_data *hapd, 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_icons_count) + wpabuf_put_u8(buf, HS20_STYPE_ICON_REQUEST); gas_anqp_set_element_len(buf, len); } #endif /* CONFIG_HS20 */ @@ -514,6 +517,169 @@ static void anqp_add_operating_class(struct hostapd_data *hapd, } } + +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[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++) { + size_t j; + struct hs20_icon *icon = NULL; + + for (j = 0; j < bss->hs20_icons_count && !icon; j++) { + if (os_strcmp(p->icons[i], bss->hs20_icons[j].name) == + 0) + icon = &bss->hs20_icons[j]; + } + if (!icon) + continue; /* 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); + } + 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_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); +} + #endif /* CONFIG_HS20 */ @@ -521,11 +687,19 @@ static struct wpabuf * gas_serv_build_gas_resp_payload(struct hostapd_data *hapd, unsigned int request, struct gas_dialog_info *di, - const u8 *home_realm, size_t home_realm_len) + const u8 *home_realm, size_t home_realm_len, + const u8 *icon_name, size_t icon_name_len) { struct wpabuf *buf; + size_t len; - buf = wpabuf_alloc(1400); + len = 1400; + if (request & (ANQP_REQ_NAI_REALM | ANQP_REQ_NAI_HOME_REALM)) + len += 1000; + if (request & ANQP_REQ_ICON_REQUEST) + len += 65536; + + buf = wpabuf_alloc(len); if (buf == NULL) return NULL; @@ -559,44 +733,31 @@ gas_serv_build_gas_resp_payload(struct hostapd_data *hapd, 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); #endif /* CONFIG_HS20 */ return buf; } -static void gas_serv_clear_cached_ies(void *eloop_data, void *user_ctx) -{ - struct gas_dialog_info *dia = eloop_data; - - wpa_printf(MSG_DEBUG, "GAS: Timeout triggered, clearing dialog for " - "dialog token %d", dia->dialog_token); - - gas_serv_dialog_clear(dia); -} - - struct anqp_query_info { unsigned int request; - unsigned int remote_request; const u8 *home_realm_query; size_t home_realm_query_len; - u16 remote_delay; + const u8 *icon_name; + size_t icon_name_len; }; static void set_anqp_req(unsigned int bit, const char *name, int local, - unsigned int remote, u16 remote_delay, struct anqp_query_info *qi) { qi->request |= bit; if (local) { wpa_printf(MSG_DEBUG, "ANQP: %s (local)", name); - } else if (bit & remote) { - wpa_printf(MSG_DEBUG, "ANQP: %s (remote)", name); - qi->remote_request |= bit; - if (remote_delay > qi->remote_delay) - qi->remote_delay = remote_delay; } else { wpa_printf(MSG_DEBUG, "ANQP: %s not available", name); } @@ -608,43 +769,38 @@ static void rx_anqp_query_list_id(struct hostapd_data *hapd, u16 info_id, { switch (info_id) { case ANQP_CAPABILITY_LIST: - set_anqp_req(ANQP_REQ_CAPABILITY_LIST, "Capability List", 1, 0, - 0, qi); + 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, 0, 0, qi); + hapd->conf->venue_name != 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, - 0, 0, qi); + 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, 0, 0, qi); + 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, - 0, 0, qi); + 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, - 0, 0, qi); + 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, - 0, 0, qi); + hapd->conf->anqp_3gpp_cell_net != NULL, qi); break; case ANQP_DOMAIN_NAME: set_anqp_req(ANQP_REQ_DOMAIN_NAME, "Domain Name", - hapd->conf->domain_name != NULL, - 0, 0, qi); + hapd->conf->domain_name != NULL, qi); break; default: wpa_printf(MSG_DEBUG, "ANQP: Unsupported Info Id %u", @@ -676,29 +832,30 @@ static void rx_anqp_hs_query_list(struct hostapd_data *hapd, u8 subtype, switch (subtype) { case HS20_STYPE_CAPABILITY_LIST: set_anqp_req(ANQP_REQ_HS_CAPABILITY_LIST, "HS Capability List", - 1, 0, 0, qi); + 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, - 0, 0, qi); + 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, - 0, 0, qi); + 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, - 0, 0, qi); + qi); break; case HS20_STYPE_OPERATING_CLASS: set_anqp_req(ANQP_REQ_OPERATING_CLASS, "Operating Class", - hapd->conf->hs20_operating_class != NULL, - 0, 0, qi); + 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; default: wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 subtype %u", @@ -725,6 +882,23 @@ static void rx_anqp_hs_nai_home_realm(struct hostapd_data *hapd, } +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(struct hostapd_data *hapd, const u8 *pos, const u8 *end, struct anqp_query_info *qi) @@ -769,6 +943,9 @@ static void rx_anqp_vendor_specific(struct hostapd_data *hapd, 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); @@ -787,7 +964,8 @@ static void gas_serv_req_local_processing(struct hostapd_data *hapd, buf = gas_serv_build_gas_resp_payload(hapd, qi->request, NULL, qi->home_realm_query, - qi->home_realm_query_len); + qi->home_realm_query_len, + qi->icon_name, qi->icon_name_len); wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Locally generated ANQP responses", buf); if (!buf) @@ -811,14 +989,17 @@ static void gas_serv_req_local_processing(struct hostapd_data *hapd, "for " MACSTR " (dialog token %u)", MAC2STR(sa), dialog_token); wpabuf_free(buf); - return; + 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); } - 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( @@ -944,97 +1125,6 @@ static void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd, } -void gas_serv_tx_gas_response(struct hostapd_data *hapd, const u8 *dst, - struct gas_dialog_info *dialog) -{ - struct wpabuf *buf, *tx_buf; - u8 dialog_token = dialog->dialog_token; - size_t frag_len; - - if (dialog->sd_resp == NULL) { - buf = gas_serv_build_gas_resp_payload(hapd, - dialog->all_requested, - dialog, NULL, 0); - wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses", - buf); - if (!buf) - goto tx_gas_response_done; - dialog->sd_resp = buf; - dialog->sd_resp_pos = 0; - } - frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos; - if (frag_len > hapd->gas_frag_limit || dialog->comeback_delay || - hapd->conf->gas_comeback_delay) { - u16 comeback_delay_tus = dialog->comeback_delay + - GAS_SERV_COMEBACK_DELAY_FUDGE; - u32 comeback_delay_secs, comeback_delay_usecs; - - if (hapd->conf->gas_comeback_delay) { - /* Testing - allow overriding of the delay value */ - comeback_delay_tus = hapd->conf->gas_comeback_delay; - } - - wpa_printf(MSG_DEBUG, "GAS: Response frag_len %u (frag limit " - "%u) and comeback delay %u, " - "requesting comebacks", (unsigned int) frag_len, - (unsigned int) hapd->gas_frag_limit, - dialog->comeback_delay); - tx_buf = gas_anqp_build_initial_resp_buf(dialog_token, - WLAN_STATUS_SUCCESS, - comeback_delay_tus, - NULL); - if (tx_buf) { - wpa_msg(hapd->msg_ctx, MSG_DEBUG, - "GAS: Tx GAS Initial Resp (comeback = 10TU)"); - if (dialog->prot) - convert_to_protected_dual(tx_buf); - hostapd_drv_send_action(hapd, hapd->iface->freq, 0, - dst, - wpabuf_head(tx_buf), - wpabuf_len(tx_buf)); - } - wpabuf_free(tx_buf); - - /* start a timer of 1.5 * comeback-delay */ - comeback_delay_tus = comeback_delay_tus + - (comeback_delay_tus / 2); - comeback_delay_secs = (comeback_delay_tus * 1024) / 1000000; - comeback_delay_usecs = (comeback_delay_tus * 1024) - - (comeback_delay_secs * 1000000); - eloop_register_timeout(comeback_delay_secs, - comeback_delay_usecs, - gas_serv_clear_cached_ies, dialog, - NULL); - goto tx_gas_response_done; - } - - 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: Buffer allocation " - "failed"); - goto tx_gas_response_done; - } - tx_buf = gas_anqp_build_initial_resp_buf(dialog_token, - WLAN_STATUS_SUCCESS, 0, buf); - wpabuf_free(buf); - if (tx_buf == NULL) - goto tx_gas_response_done; - wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Tx GAS Initial " - "Response (frag_id %d frag_len %d)", - dialog->sd_frag_id, (int) frag_len); - dialog->sd_frag_id++; - - if (dialog->prot) - convert_to_protected_dual(tx_buf); - hostapd_drv_send_action(hapd, hapd->iface->freq, 0, dst, - wpabuf_head(tx_buf), wpabuf_len(tx_buf)); - wpabuf_free(tx_buf); -tx_gas_response_done: - gas_serv_clear_cached_ies(dialog, NULL); -} - - static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd, const u8 *sa, const u8 *data, size_t len, int prot) @@ -1068,33 +1158,6 @@ static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd, goto send_resp; } - if (dialog->sd_resp == NULL) { - wpa_printf(MSG_DEBUG, "GAS: Remote request 0x%x received 0x%x", - dialog->requested, dialog->received); - if ((dialog->requested & dialog->received) != - dialog->requested) { - wpa_printf(MSG_DEBUG, "GAS: Did not receive response " - "from remote processing"); - gas_serv_dialog_clear(dialog); - tx_buf = gas_anqp_build_comeback_resp_buf( - dialog_token, - WLAN_STATUS_GAS_RESP_NOT_RECEIVED, 0, 0, 0, - NULL); - if (tx_buf == NULL) - return; - goto send_resp; - } - - buf = gas_serv_build_gas_resp_payload(hapd, - dialog->all_requested, - dialog, NULL, 0); - wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses", - buf); - if (!buf) - goto rx_gas_comeback_req_done; - dialog->sd_resp = buf; - dialog->sd_resp_pos = 0; - } frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos; if (frag_len > hapd->gas_frag_limit) { frag_len = hapd->gas_frag_limit; @@ -1107,15 +1170,18 @@ static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd, if (buf == NULL) { wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Failed to allocate " "buffer"); - goto rx_gas_comeback_req_done; + gas_serv_dialog_clear(dialog); + return; } 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) - goto rx_gas_comeback_req_done; + 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); @@ -1140,10 +1206,6 @@ send_resp: hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa, wpabuf_head(tx_buf), wpabuf_len(tx_buf)); wpabuf_free(tx_buf); - return; - -rx_gas_comeback_req_done: - gas_serv_clear_cached_ies(dialog, NULL); } diff --git a/src/ap/gas_serv.h b/src/ap/gas_serv.h index 74739fe..4ec3201 100644 --- a/src/ap/gas_serv.h +++ b/src/ap/gas_serv.h @@ -1,6 +1,6 @@ /* * Generic advertisement service (GAS) server - * Copyright (c) 2011-2012, Qualcomm Atheros, 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. @@ -37,30 +37,22 @@ (0x10000 << HS20_STYPE_NAI_HOME_REALM_QUERY) #define ANQP_REQ_OPERATING_CLASS \ (0x10000 << HS20_STYPE_OPERATING_CLASS) - -/* To account for latencies between hostapd and external ANQP processor */ -#define GAS_SERV_COMEBACK_DELAY_FUDGE 10 -#define GAS_SERV_MIN_COMEBACK_DELAY 100 /* in TU */ +#define ANQP_REQ_OSU_PROVIDERS_LIST \ + (0x10000 << HS20_STYPE_OSU_PROVIDERS_LIST) +#define ANQP_REQ_ICON_REQUEST \ + (0x10000 << HS20_STYPE_ICON_REQUEST) struct gas_dialog_info { u8 valid; - u8 index; struct wpabuf *sd_resp; /* Fragmented response */ u8 dialog_token; size_t sd_resp_pos; /* Offset in sd_resp */ u8 sd_frag_id; - u16 comeback_delay; int prot; /* whether Protected Dual of Public Action frame is used */ - - unsigned int requested; - unsigned int received; - unsigned int all_requested; }; struct hostapd_data; -void gas_serv_tx_gas_response(struct hostapd_data *hapd, const u8 *dst, - struct gas_dialog_info *dialog); struct gas_dialog_info * gas_serv_dialog_find(struct hostapd_data *hapd, const u8 *addr, u8 dialog_token); diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c index f9edf3b..ed73301 100644 --- a/src/ap/hostapd.c +++ b/src/ap/hostapd.c @@ -14,6 +14,7 @@ #include "common/wpa_ctrl.h" #include "radius/radius_client.h" #include "radius/radius_das.h" +#include "eap_server/tncs.h" #include "hostapd.h" #include "authsrv.h" #include "sta_info.h" @@ -33,6 +34,7 @@ #include "p2p_hostapd.h" #include "gas_serv.h" #include "dfs.h" +#include "ieee802_11.h" static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason); @@ -87,7 +89,7 @@ static void hostapd_reload_bss(struct hostapd_data *hapd) else hostapd_set_drv_ieee8021x(hapd, hapd->conf->iface, 0); - if (hapd->conf->wpa && hapd->wpa_auth == NULL) { + 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); @@ -171,6 +173,17 @@ int hostapd_reload_config(struct hostapd_iface *iface) for (j = 0; j < iface->num_bss; j++) { hapd = iface->bss[j]; hapd->iconf = newconf; + hapd->iconf->channel = oldconf->channel; + 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; + hapd->iconf->vht_oper_chwidth = oldconf->vht_oper_chwidth; + hapd->iconf->vht_oper_centr_freq_seg0_idx = + oldconf->vht_oper_centr_freq_seg0_idx; + hapd->iconf->vht_oper_centr_freq_seg1_idx = + oldconf->vht_oper_centr_freq_seg1_idx; hapd->conf = newconf->bss[j]; hostapd_reload_bss(hapd); } @@ -264,10 +277,21 @@ static void hostapd_free_hapd_data(struct hostapd_data *hapd) authsrv_deinit(hapd); - if (hapd->interface_added && - hostapd_if_remove(hapd, WPA_IF_AP_BSS, hapd->conf->iface)) { - wpa_printf(MSG_WARNING, "Failed to remove BSS interface %s", - hapd->conf->iface); + 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; + } } os_free(hapd->probereq_cb); @@ -350,7 +374,7 @@ static void hostapd_cleanup_iface(struct hostapd_iface *iface) static void hostapd_clear_wep(struct hostapd_data *hapd) { - if (hapd->drv_priv) { + if (hapd->drv_priv && !hapd->iface->driver_ap_teardown) { hostapd_set_privacy(hapd, 0); hostapd_broadcast_wep_clear(hapd); } @@ -401,11 +425,15 @@ static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason) if (hostapd_drv_none(hapd) || hapd->drv_priv == NULL) return 0; - 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->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; + } } wpa_dbg(hapd->msg_ctx, MSG_DEBUG, "Deauthenticate all stations"); os_memset(addr, 0xff, ETH_ALEN); @@ -416,6 +444,14 @@ static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason) } +static void hostapd_bss_deinit_no_free(struct hostapd_data *hapd) +{ + hostapd_free_stas(hapd); + hostapd_flush_old_stations(hapd, WLAN_REASON_DEAUTH_LEAVING); + hostapd_clear_wep(hapd); +} + + /** * hostapd_validate_bssid_configuration - Validate BSSID configuration * @iface: Pointer to interface data @@ -529,7 +565,34 @@ static int mac_in_conf(struct hostapd_config *conf, const void *a) static int hostapd_das_nas_mismatch(struct hostapd_data *hapd, struct radius_das_attrs *attr) { - /* TODO */ + 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; } @@ -596,6 +659,8 @@ hostapd_das_disconnect(void *ctx, struct radius_das_attrs *attr) if (sta == NULL) return RADIUS_DAS_SESSION_NOT_FOUND; + 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); @@ -628,6 +693,13 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first) wpa_printf(MSG_DEBUG, "%s(hapd=%p (%s), first=%d)", __func__, hapd, hapd->conf->iface, first); +#ifdef EAP_SERVER_TNC + if (hapd->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__, hapd->conf->iface); @@ -727,7 +799,7 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first) return -1; } - if (wpa_debug_level == MSG_MSGDUMP) + if (wpa_debug_level <= MSG_MSGDUMP) conf->radius->msg_dumps = 1; #ifndef CONFIG_NO_RADIUS hapd->radius = radius_client_init(hapd, conf->radius); @@ -773,7 +845,7 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first) return -1; } - if (hapd->conf->wpa && hostapd_setup_wpa(hapd)) + if ((hapd->conf->wpa || hapd->conf->osen) && hostapd_setup_wpa(hapd)) return -1; if (accounting_init(hapd)) { @@ -980,6 +1052,15 @@ 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) { @@ -1051,7 +1132,7 @@ static int setup_interface2(struct hostapd_iface *iface) if (ret < 0) { wpa_printf(MSG_ERROR, "Could not select hw_mode and " "channel. (%d)", ret); - return -1; + goto fail; } if (ret == 1) { wpa_printf(MSG_DEBUG, "Interface initialization will be completed in a callback (ACS)"); @@ -1059,7 +1140,7 @@ static int setup_interface2(struct hostapd_iface *iface) } ret = hostapd_check_ht_capab(iface); if (ret < 0) - return -1; + goto fail; if (ret == 1) { wpa_printf(MSG_DEBUG, "Interface initialization will " "be completed in a callback"); @@ -1070,6 +1151,13 @@ static int setup_interface2(struct hostapd_iface *iface) wpa_printf(MSG_DEBUG, "DFS support is enabled"); } return hostapd_setup_interface_complete(iface, 0); + +fail: + 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; } @@ -1087,13 +1175,8 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) size_t j; u8 *prev_addr; - if (err) { - wpa_printf(MSG_ERROR, "Interface initialization failed"); - hostapd_set_state(iface, HAPD_IFACE_DISABLED); - if (iface->interfaces && iface->interfaces->terminate_on_error) - eloop_terminate(); - return -1; - } + if (err) + goto fail; wpa_printf(MSG_DEBUG, "Completing interface initialization"); if (iface->conf->channel) { @@ -1110,8 +1193,11 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) #ifdef NEED_AP_MLME /* Check DFS */ res = hostapd_handle_dfs(iface); - if (res <= 0) + if (res <= 0) { + if (res < 0) + goto fail; return res; + } #endif /* NEED_AP_MLME */ if (hostapd_set_freq(hapd, hapd->iconf->hw_mode, iface->freq, @@ -1124,7 +1210,7 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) hapd->iconf->vht_oper_centr_freq_seg1_idx)) { wpa_printf(MSG_ERROR, "Could not set channel for " "kernel driver"); - return -1; + goto fail; } } @@ -1135,7 +1221,7 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_WARNING, "Failed to prepare rates table."); - return -1; + goto fail; } } @@ -1143,14 +1229,14 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) hostapd_set_rts(hapd, hapd->iconf->rts_threshold)) { wpa_printf(MSG_ERROR, "Could not set RTS threshold for " "kernel driver"); - return -1; + goto fail; } if (hapd->iconf->fragm_threshold > -1 && hostapd_set_frag(hapd, hapd->iconf->fragm_threshold)) { wpa_printf(MSG_ERROR, "Could not set fragmentation threshold " "for kernel driver"); - return -1; + goto fail; } prev_addr = hapd->own_addr; @@ -1159,8 +1245,14 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) hapd = iface->bss[j]; if (j) os_memcpy(hapd->own_addr, prev_addr, ETH_ALEN); - if (hostapd_setup_bss(hapd, j == 0)) - return -1; + if (hostapd_setup_bss(hapd, j == 0)) { + do { + hapd = iface->bss[j]; + hostapd_bss_deinit_no_free(hapd); + hostapd_free_hapd_data(hapd); + } while (j-- > 0); + goto fail; + } if (hostapd_mac_comp_empty(hapd->conf->bssid) == 0) prev_addr = hapd->own_addr; } @@ -1175,7 +1267,7 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) if (hostapd_driver_commit(hapd) < 0) { wpa_printf(MSG_ERROR, "%s: Failed to commit driver " "configuration", __func__); - return -1; + goto fail; } /* @@ -1186,7 +1278,7 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) */ for (j = 0; j < iface->num_bss; j++) { if (hostapd_init_wps_complete(iface->bss[j])) - return -1; + goto fail; } hostapd_set_state(iface, HAPD_IFACE_ENABLED); @@ -1200,6 +1292,14 @@ int hostapd_setup_interface_complete(struct hostapd_iface *iface, int err) iface->interfaces->terminate_on_error--; return 0; + +fail: + wpa_printf(MSG_ERROR, "Interface initialization failed"); + hostapd_set_state(iface, HAPD_IFACE_DISABLED); + wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_DISABLED); + if (iface->interfaces && iface->interfaces->terminate_on_error) + eloop_terminate(); + return -1; } @@ -1271,9 +1371,7 @@ static void hostapd_bss_deinit(struct hostapd_data *hapd) { wpa_printf(MSG_DEBUG, "%s: deinit bss %s", __func__, hapd->conf->iface); - hostapd_free_stas(hapd); - hostapd_flush_old_stations(hapd, WLAN_REASON_DEAUTH_LEAVING); - hostapd_clear_wep(hapd); + hostapd_bss_deinit_no_free(hapd); hostapd_cleanup(hapd); } @@ -1286,6 +1384,12 @@ void hostapd_interface_deinit(struct hostapd_iface *iface) if (iface == NULL) return; +#ifdef CONFIG_IEEE80211N +#ifdef NEED_AP_MLME + hostapd_stop_setup_timers(iface); + eloop_cancel_timeout(ap_ht2040_timeout, iface, NULL); +#endif /* NEED_AP_MLME */ +#endif /* CONFIG_IEEE80211N */ eloop_cancel_timeout(channel_list_update_timeout, iface, NULL); iface->wait_channel_update = 0; @@ -1520,14 +1624,39 @@ void hostapd_interface_deinit_free(struct hostapd_iface *iface) hostapd_interface_deinit(iface); wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit", __func__, driver, drv_priv); - if (driver && driver->hapd_deinit && drv_priv) + if (driver && driver->hapd_deinit && drv_priv) { driver->hapd_deinit(drv_priv); + iface->bss[0]->drv_priv = NULL; + } 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); + if (driver && driver->hapd_deinit && drv_priv) { + 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; + } + } +} + + int hostapd_enable_iface(struct hostapd_iface *hapd_iface) { + size_t j; + if (hapd_iface->bss[0]->drv_priv != NULL) { wpa_printf(MSG_ERROR, "Interface %s already enabled", hapd_iface->conf->bss[0]->iface); @@ -1537,6 +1666,8 @@ int hostapd_enable_iface(struct hostapd_iface *hapd_iface) 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; @@ -1548,17 +1679,9 @@ int hostapd_enable_iface(struct hostapd_iface *hapd_iface) return -1; if (hostapd_setup_interface(hapd_iface)) { - const struct wpa_driver_ops *driver; - void *drv_priv; - - driver = hapd_iface->bss[0]->driver; - drv_priv = hapd_iface->bss[0]->drv_priv; - wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit", - __func__, driver, drv_priv); - if (driver && driver->hapd_deinit && drv_priv) { - driver->hapd_deinit(drv_priv); - hapd_iface->bss[0]->drv_priv = NULL; - } + hostapd_deinit_driver(hapd_iface->bss[0]->driver, + hapd_iface->bss[0]->drv_priv, + hapd_iface); return -1; } @@ -1573,7 +1696,7 @@ int hostapd_reload_iface(struct hostapd_iface *hapd_iface) 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]); + 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; @@ -1594,25 +1717,29 @@ int hostapd_disable_iface(struct hostapd_iface *hapd_iface) if (hapd_iface == NULL) return -1; + + 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; - /* whatever hostapd_interface_deinit does */ + hapd_iface->driver_ap_teardown = + !!(hapd_iface->drv_flags & + WPA_DRIVER_FLAGS_AP_TEARDOWN_SUPPORT); + + /* 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_free_stas(hapd); - hostapd_flush_old_stations(hapd, WLAN_REASON_DEAUTH_LEAVING); - hostapd_clear_wep(hapd); + hostapd_bss_deinit_no_free(hapd); hostapd_free_hapd_data(hapd); } - wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit", - __func__, driver, drv_priv); - if (driver && driver->hapd_deinit && drv_priv) { - driver->hapd_deinit(drv_priv); - hapd_iface->bss[0]->drv_priv = NULL; - } + hostapd_deinit_driver(driver, drv_priv, hapd_iface); /* From hostapd_cleanup_iface: These were initialized in * hostapd_setup_interface and hostapd_setup_interface_complete @@ -1778,6 +1905,8 @@ int hostapd_add_iface(struct hapd_interfaces *interfaces, char *buf) if (start_ctrl_iface_bss(hapd) < 0 || (hapd_iface->state == HAPD_IFACE_ENABLED && hostapd_setup_bss(hapd, -1))) { + 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", @@ -1847,14 +1976,17 @@ fail: if (hapd_iface->bss) { for (i = 0; i < hapd_iface->num_bss; i++) { hapd = hapd_iface->bss[i]; - if (hapd && hapd_iface->interfaces && + 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_iface->bss[i]->conf->iface); - os_free(hapd_iface->bss[i]); + hapd->conf->iface); + os_free(hapd); + hapd_iface->bss[i] = NULL; } os_free(hapd_iface->bss); } @@ -1910,6 +2042,10 @@ int hostapd_remove_iface(struct hapd_interfaces *interfaces, char *buf) 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)) { @@ -1922,8 +2058,12 @@ int hostapd_remove_iface(struct hapd_interfaces *interfaces, char *buf) } for (j = 0; j < hapd_iface->conf->num_bss; j++) { - if (!os_strcmp(hapd_iface->conf->bss[j]->iface, buf)) + 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; @@ -1968,7 +2108,8 @@ void hostapd_new_assoc_sta(struct hostapd_data *hapd, struct sta_info *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) { + 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); } diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h index 489ab16..bd85c54 100644 --- a/src/ap/hostapd.h +++ b/src/ap/hostapd.h @@ -24,7 +24,6 @@ enum wps_event; union wps_event_data; struct hostapd_iface; -struct hostapd_dynamic_iface; struct hapd_interfaces { int (*reload_config)(struct hostapd_iface *iface); @@ -37,7 +36,6 @@ struct hapd_interfaces { int (*driver_init)(struct hostapd_iface *iface); size_t count; - size_t count_dynamic; int global_ctrl_sock; char *global_iface_path; char *global_iface_name; @@ -45,7 +43,6 @@ struct hapd_interfaces { gid_t ctrl_iface_group; #endif /* CONFIG_NATIVE_WINDOWS */ struct hostapd_iface **iface; - struct hostapd_dynamic_iface **dynamic_iface; size_t terminate_on_error; }; @@ -273,6 +270,12 @@ struct hostapd_iface { unsigned int wait_channel_update:1; unsigned int cac_started:1; + /* + * When set, indicates that the driver will handle the AP + * teardown: delete global keys, station keys, and stations. + */ + unsigned int driver_ap_teardown: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]; @@ -324,6 +327,9 @@ struct hostapd_iface { /* 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; @@ -345,21 +351,19 @@ struct hostapd_iface { unsigned int cs_c_off_proberesp; int csa_in_progress; + 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; #endif /* CONFIG_ACS */ void (*scan_cb)(struct hostapd_iface *iface); -}; - -/** - * struct hostapd_dynamic_iface - hostapd per dynamically allocated - * or added interface data structure - */ -struct hostapd_dynamic_iface { - char parent[IFNAMSIZ + 1]; - char iface[IFNAMSIZ + 1]; - unsigned int usage; + int num_ht40_scan_tries; }; /* hostapd.c */ diff --git a/src/ap/hs20.c b/src/ap/hs20.c index 45d518b..d7909fa 100644 --- a/src/ap/hs20.c +++ b/src/ap/hs20.c @@ -1,7 +1,7 @@ /* * Hotspot 2.0 AP ANQP processing * Copyright (c) 2009, Atheros Communications, Inc. - * Copyright (c) 2011-2012, Qualcomm Atheros, 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. @@ -13,19 +13,165 @@ #include "common/ieee802_11_defs.h" #include "hostapd.h" #include "ap_config.h" +#include "ap_drv_ops.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++ = 5; + *eid++ = 7; WPA_PUT_BE24(eid, OUI_WFA); eid += 3; *eid++ = HS20_INDICATION_OUI_TYPE; - /* Hotspot Configuration: DGAF Enabled */ - *eid++ = hapd->conf->disable_dgaf ? 0x01 : 0x00; + conf = HS20_VERSION; /* Release Number */ + conf |= HS20_ANQP_DOMAIN_ID_PRESENT; + if (hapd->conf->disable_dgaf) + conf |= HS20_DGAF_DISABLED; + *eid++ = conf; + 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); + } +#ifdef CONFIG_IEEE80211W + if (hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + capab |= WPA_CAPABILITY_MFPC; + if (hapd->conf->ieee80211w == MGMT_FRAME_PROTECTION_REQUIRED) + capab |= WPA_CAPABILITY_MFPR; + } +#endif /* CONFIG_IEEE80211W */ + 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; +} diff --git a/src/ap/hs20.h b/src/ap/hs20.h index 98698ce..152439f 100644 --- a/src/ap/hs20.h +++ b/src/ap/hs20.h @@ -1,6 +1,6 @@ /* * Hotspot 2.0 AP ANQP processing - * Copyright (c) 2011-2012, Qualcomm Atheros, 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. @@ -12,5 +12,11 @@ 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); #endif /* HS20_H */ diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c index 4e66379..4e66d1b 100644 --- a/src/ap/hw_features.c +++ b/src/ap/hw_features.c @@ -19,6 +19,8 @@ #include "ap_config.h" #include "ap_drv_ops.h" #include "acs.h" +#include "ieee802_11.h" +#include "beacon.h" #include "hw_features.h" @@ -111,10 +113,13 @@ int hostapd_get_hw_features(struct hostapd_iface *iface) if ((feature->channels[j].flag & HOSTAPD_CHAN_RADAR) && dfs_enabled) { dfs = 1; - } else if (feature->channels[j].flag & - (HOSTAPD_CHAN_NO_IBSS | - HOSTAPD_CHAN_PASSIVE_SCAN | - HOSTAPD_CHAN_RADAR)) { + } else if (((feature->channels[j].flag & + HOSTAPD_CHAN_RADAR) && + !(iface->drv_flags & + WPA_DRIVER_FLAGS_DFS_OFFLOAD)) || + (feature->channels[j].flag & + (HOSTAPD_CHAN_NO_IBSS | + HOSTAPD_CHAN_PASSIVE_SCAN))) { feature->channels[j].flag |= HOSTAPD_CHAN_DISABLED; } @@ -305,8 +310,8 @@ static void ieee80211n_get_pri_sec_chan(struct wpa_scan_res *bss, if (elems.ht_operation && elems.ht_operation_len >= sizeof(*oper)) { oper = (struct ieee80211_ht_operation *) elems.ht_operation; - *pri_chan = oper->control_chan; - if (oper->ht_param & HT_INFO_HT_PARAM_REC_TRANS_CHNL_WIDTH) { + *pri_chan = oper->primary_chan; + if (oper->ht_param & HT_INFO_HT_PARAM_STA_CHNL_WIDTH) { int sec = oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK; if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE) @@ -327,7 +332,7 @@ static int ieee80211n_check_40mhz_5g(struct hostapd_iface *iface, int match; pri_chan = iface->conf->channel; - sec_chan = iface->conf->secondary_channel * 4; + sec_chan = pri_chan + iface->conf->secondary_channel * 4; pri_freq = hostapd_hw_get_freq(iface->bss[0], pri_chan); if (iface->conf->secondary_channel > 0) sec_freq = pri_freq + 20; @@ -351,6 +356,7 @@ static int ieee80211n_check_40mhz_5g(struct hostapd_iface *iface, "channel to get secondary channel with no Beacons " "from other BSSes"); ieee80211n_switch_pri_sec(iface); + return 1; } /* @@ -389,6 +395,36 @@ static int ieee80211n_check_40mhz_5g(struct hostapd_iface *iface, } +static int ieee80211n_check_20mhz_bss(struct wpa_scan_res *bss, int pri_freq, + int start, int end) +{ + struct ieee802_11_elems elems; + struct ieee80211_ht_operation *oper; + + if (bss->freq < start || bss->freq > end || bss->freq == pri_freq) + return 0; + + ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, 0); + if (!elems.ht_capabilities) { + wpa_printf(MSG_DEBUG, "Found overlapping legacy BSS: " + MACSTR " freq=%d", MAC2STR(bss->bssid), bss->freq); + return 1; + } + + if (elems.ht_operation && + elems.ht_operation_len >= sizeof(*oper)) { + oper = (struct ieee80211_ht_operation *) elems.ht_operation; + if (oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK) + return 0; + + wpa_printf(MSG_DEBUG, "Found overlapping 20 MHz HT BSS: " + MACSTR " freq=%d", MAC2STR(bss->bssid), bss->freq); + return 1; + } + return 0; +} + + static int ieee80211n_check_40mhz_2g4(struct hostapd_iface *iface, struct wpa_scan_results *scan_res) { @@ -410,6 +446,15 @@ static int ieee80211n_check_40mhz_2g4(struct hostapd_iface *iface, int pri = bss->freq; int sec = pri; int sec_chan, pri_chan; + struct ieee802_11_elems elems; + + /* Check for overlapping 20 MHz BSS */ + if (ieee80211n_check_20mhz_bss(bss, pri_freq, affected_start, + affected_end)) { + wpa_printf(MSG_DEBUG, + "Overlapping 20 MHz BSS is found"); + return 0; + } ieee80211n_get_pri_sec_chan(bss, &pri_chan, &sec_chan); @@ -441,7 +486,23 @@ static int ieee80211n_check_40mhz_2g4(struct hostapd_iface *iface, } } - /* TODO: 40 MHz intolerant */ + ieee802_11_parse_elems((u8 *) (bss + 1), bss->ie_len, &elems, + 0); + if (elems.ht_capabilities && + elems.ht_capabilities_len >= + sizeof(struct ieee80211_ht_capabilities)) { + 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) { + wpa_printf(MSG_DEBUG, + "40 MHz Intolerant is set on channel %d in BSS " + MACSTR, pri, MAC2STR(bss->bssid)); + return 0; + } + } } return 1; @@ -471,6 +532,7 @@ static void ieee80211n_check_scan(struct hostapd_iface *iface) 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", @@ -478,9 +540,21 @@ static void ieee80211n_check_scan(struct hostapd_iface *iface) 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. + */ + } } res = ieee80211n_allowed_ht40_channel_pair(iface); + if (!res) { + iface->conf->secondary_channel = 0; + wpa_printf(MSG_INFO, "Fallback to 20 MHz"); + } + hostapd_setup_interface_complete(iface, !res); } @@ -566,9 +640,55 @@ static void ieee80211n_scan_channels_5g(struct hostapd_iface *iface, } +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); + + 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; + 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; if (!iface->conf->secondary_channel) return 0; /* HT40 not used */ @@ -581,13 +701,26 @@ static int ieee80211n_check_40mhz(struct hostapd_iface *iface) ieee80211n_scan_channels_2g4(iface, ¶ms); else ieee80211n_scan_channels_5g(iface, ¶ms); - if (hostapd_driver_scan(iface->bss[0], ¶ms) < 0) { - wpa_printf(MSG_ERROR, "Failed to request a scan of " - "neighboring BSSes"); - os_free(params.freqs); + + 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; } - os_free(params.freqs); iface->scan_cb = ieee80211n_check_scan; return 1; @@ -675,12 +808,6 @@ static int ieee80211n_supported_ht_capab(struct hostapd_iface *iface) return 0; } - if ((conf & HT_CAP_INFO_PSMP_SUPP) && !(hw & HT_CAP_INFO_PSMP_SUPP)) { - wpa_printf(MSG_ERROR, "Driver does not support configured " - "HT capability [PSMP]"); - 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 " @@ -764,7 +891,7 @@ static int ieee80211ac_supported_vht_capab(struct hostapd_iface *iface) VHT_CAP_CHECK(VHT_CAP_MU_BEAMFORMEE_CAPABLE); VHT_CAP_CHECK(VHT_CAP_VHT_TXOP_PS); VHT_CAP_CHECK(VHT_CAP_HTC_VHT); - VHT_CAP_CHECK_MAX(VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT); + VHT_CAP_CHECK_MAX(VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX); VHT_CAP_CHECK(VHT_CAP_VHT_LINK_ADAPTATION_VHT_UNSOL_MFB); VHT_CAP_CHECK(VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB); VHT_CAP_CHECK(VHT_CAP_RX_ANTENNA_PATTERN); @@ -943,6 +1070,15 @@ int hostapd_select_hw_mode(struct hostapd_iface *iface) if (iface->num_hw_features < 1) return -1; + if ((iface->conf->hw_mode == HOSTAPD_MODE_IEEE80211G || + iface->conf->ieee80211n || iface->conf->ieee80211ac) && + iface->conf->channel == 14) { + wpa_printf(MSG_INFO, "Disable OFDM/HT/VHT on channel 14"); + iface->conf->hw_mode = HOSTAPD_MODE_IEEE80211B; + iface->conf->ieee80211n = 0; + iface->conf->ieee80211ac = 0; + } + iface->current_mode = NULL; for (i = 0; i < iface->num_hw_features; i++) { struct hostapd_hw_modes *mode = &iface->hw_features[i]; @@ -973,8 +1109,6 @@ int hostapd_select_hw_mode(struct hostapd_iface *iface) hostapd_notify_bad_chans(iface); return -3; } - - return 0; } diff --git a/src/ap/hw_features.h b/src/ap/hw_features.h index 783ae5e..0f67ab8 100644 --- a/src/ap/hw_features.h +++ b/src/ap/hw_features.h @@ -23,6 +23,7 @@ int hostapd_hw_get_channel(struct hostapd_data *hapd, int freq); int hostapd_check_ht_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); #else /* NEED_AP_MLME */ static inline void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, @@ -61,6 +62,10 @@ static inline int hostapd_prepare_rates(struct hostapd_iface *iface, return 0; } +static inline void hostapd_stop_setup_timers(struct hostapd_iface *iface) +{ +} + #endif /* NEED_AP_MLME */ #endif /* HW_FEATURES_H */ diff --git a/src/ap/iapp.c b/src/ap/iapp.c index bad080f..9b2900f 100644 --- a/src/ap/iapp.c +++ b/src/ap/iapp.c @@ -242,29 +242,22 @@ static void iapp_send_layer2_update(struct iapp_data *iapp, u8 *addr) */ void iapp_new_station(struct iapp_data *iapp, struct sta_info *sta) { - struct ieee80211_mgmt *assoc; - u16 seq; + u16 seq = 0; /* TODO */ if (iapp == NULL) return; - assoc = sta->last_assoc_req; - seq = assoc ? WLAN_GET_SEQ_SEQ(le_to_host16(assoc->seq_ctrl)) : 0; - /* IAPP-ADD.request(MAC Address, Sequence Number, Timeout) */ hostapd_logger(iapp->hapd, sta->addr, HOSTAPD_MODULE_IAPP, HOSTAPD_LEVEL_DEBUG, "IAPP-ADD.request(seq=%d)", seq); iapp_send_layer2_update(iapp, sta->addr); iapp_send_add(iapp, sta->addr, seq); - if (assoc && WLAN_FC_GET_STYPE(le_to_host16(assoc->frame_control)) == - WLAN_FC_STYPE_REASSOC_REQ) { - /* IAPP-MOVE.request(MAC Address, Sequence Number, Old AP, - * Context Block, Timeout) - */ - /* TODO: Send IAPP-MOVE to the old AP; Map Old AP BSSID to - * IP address */ - } + /* TODO: If this was reassociation: + * IAPP-MOVE.request(MAC Address, Sequence Number, Old AP, + * Context Block, Timeout) + * TODO: Send IAPP-MOVE to the old AP; Map Old AP BSSID to + * IP address */ } diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index dee3c7a..ca8db8f 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -38,6 +38,7 @@ #include "ap_drv_ops.h" #include "wnm_ap.h" #include "ieee802_11.h" +#include "dfs.h" u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid) @@ -61,7 +62,6 @@ u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid) } *pos++ = num; - count = 0; for (i = 0, count = 0; i < hapd->iface->num_rates && count < num; i++) { count++; @@ -104,7 +104,6 @@ u8 * hostapd_eid_ext_supp_rates(struct hostapd_data *hapd, u8 *eid) *pos++ = WLAN_EID_EXT_SUPP_RATES; *pos++ = num; - count = 0; for (i = 0, count = 0; i < hapd->iface->num_rates && count < num + 8; i++) { count++; @@ -137,6 +136,15 @@ u16 hostapd_own_capab_info(struct hostapd_data *hapd, struct sta_info *sta, { int capab = WLAN_CAPABILITY_ESS; int privacy; + int dfs; + + /* 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) @@ -152,6 +160,11 @@ u16 hostapd_own_capab_info(struct hostapd_data *hapd, struct sta_info *sta, if (hapd->conf->wpa) privacy = 1; +#ifdef CONFIG_HS20 + if (hapd->conf->osen) + privacy = 1; +#endif /* CONFIG_HS20 */ + if (sta) { int policy, def_klen; if (probe && sta->ssid_probe) { @@ -174,22 +187,18 @@ u16 hostapd_own_capab_info(struct hostapd_data *hapd, struct sta_info *sta, hapd->iface->num_sta_no_short_slot_time == 0) capab |= WLAN_CAPABILITY_SHORT_SLOT_TIME; - return capab; -} - + /* + * 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; -void ieee802_11_print_ssid(char *buf, const u8 *ssid, u8 len) -{ - int i; - if (len > HOSTAPD_MAX_SSID_LEN) - len = HOSTAPD_MAX_SSID_LEN; - for (i = 0; i < len; i++) { - if (ssid[i] >= 32 && ssid[i] < 127) - buf[i] = ssid[i]; - else - buf[i] = '.'; - } - buf[len] = '\0'; + return capab; } @@ -486,6 +495,7 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta, HOSTAPD_LEVEL_DEBUG, "SAE confirm before commit"); resp = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION; + goto failed; } hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_DEBUG, @@ -517,6 +527,7 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta, resp = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION; } +failed: sta->auth_alg = WLAN_AUTH_SAE; send_auth_reply(hapd, mgmt->sa, mgmt->bssid, WLAN_AUTH_SAE, @@ -552,7 +563,7 @@ static void handle_auth(struct hostapd_data *hapd, } #ifdef CONFIG_TESTING_OPTIONS - if (hapd->iconf->ignore_auth_probability > 0.0d && + if (hapd->iconf->ignore_auth_probability > 0.0 && drand48() < hapd->iconf->ignore_auth_probability) { wpa_printf(MSG_INFO, "TESTING: ignoring auth frame from " MACSTR, @@ -781,12 +792,10 @@ static u16 check_ssid(struct hostapd_data *hapd, struct sta_info *sta, if (ssid_ie_len != hapd->conf->ssid.ssid_len || os_memcmp(ssid_ie, hapd->conf->ssid.ssid, ssid_ie_len) != 0) { - char ssid_txt[33]; - ieee802_11_print_ssid(ssid_txt, ssid_ie, ssid_ie_len); hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_INFO, "Station tried to associate with unknown SSID " - "'%s'", ssid_txt); + "'%s'", wpa_ssid_txt(ssid_ie, ssid_ie_len)); return WLAN_STATUS_UNSPECIFIED_FAILURE; } @@ -910,6 +919,11 @@ static u16 check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta, elems.vht_capabilities_len); if (resp != WLAN_STATUS_SUCCESS) return resp; + + resp = set_sta_vht_opmode(hapd, sta, elems.vht_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, @@ -1078,6 +1092,29 @@ static u16 check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta, return WLAN_STATUS_CIPHER_REJECTED_PER_POLICY; } #endif /* CONFIG_IEEE80211N */ +#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 */ } else wpa_auth_sta_no_wpa(sta->wpa_sm); @@ -1144,8 +1181,7 @@ static void send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta, reply->u.assoc_resp.capab_info = host_to_le16(hostapd_own_capab_info(hapd, sta, 0)); 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)); + reply->u.assoc_resp.aid = host_to_le16(sta->aid | BIT(14) | BIT(15)); /* Supported rates */ p = hostapd_eid_supp_rates(hapd, reply->u.assoc_resp.variable); /* Extended supported rates */ @@ -1253,7 +1289,7 @@ static void handle_assoc(struct hostapd_data *hapd, #ifdef CONFIG_TESTING_OPTIONS if (reassoc) { - if (hapd->iconf->ignore_reassoc_probability > 0.0d && + if (hapd->iconf->ignore_reassoc_probability > 0.0 && drand48() < hapd->iconf->ignore_reassoc_probability) { wpa_printf(MSG_INFO, "TESTING: ignoring reassoc request from " @@ -1261,7 +1297,7 @@ static void handle_assoc(struct hostapd_data *hapd, return; } } else { - if (hapd->iconf->ignore_assoc_probability > 0.0d && + if (hapd->iconf->ignore_assoc_probability > 0.0 && drand48() < hapd->iconf->ignore_assoc_probability) { wpa_printf(MSG_INFO, "TESTING: ignoring assoc request from " @@ -1407,17 +1443,6 @@ static void handle_assoc(struct hostapd_data *hapd, } #endif /* CONFIG_IEEE80211W */ - if (reassoc) { - os_memcpy(sta->previous_ap, mgmt->u.reassoc_req.current_ap, - ETH_ALEN); - } - - if (sta->last_assoc_req) - os_free(sta->last_assoc_req); - sta->last_assoc_req = os_malloc(len); - if (sta->last_assoc_req) - os_memcpy(sta->last_assoc_req, mgmt, len); - /* Make sure that the previously registered inactivity timer will not * remove the STA immediately. */ sta->timeout_next = STA_NULLFUNC; @@ -1601,7 +1626,8 @@ static int handle_action(struct hostapd_data *hapd, switch (mgmt->u.action.category) { #ifdef CONFIG_IEEE80211R case WLAN_ACTION_FT: - if (wpa_ft_action_rx(sta->wpa_sm, (u8 *) &mgmt->u.action, + if (!sta || + wpa_ft_action_rx(sta->wpa_sm, (u8 *) &mgmt->u.action, len - IEEE80211_HDRLEN)) break; return 1; @@ -1620,6 +1646,15 @@ static int handle_action(struct hostapd_data *hapd, #endif /* CONFIG_WNM */ case WLAN_ACTION_PUBLIC: case WLAN_ACTION_PROTECTED_DUAL: +#ifdef CONFIG_IEEE80211N + if (mgmt->u.action.u.public_action.action == + WLAN_PA_20_40_BSS_COEX) { + wpa_printf(MSG_DEBUG, + "HT20/40 coex mgmt frame received from STA " + MACSTR, MAC2STR(mgmt->sa)); + hostapd_2040_coex_action(hapd, mgmt, len); + } +#endif /* CONFIG_IEEE80211N */ if (hapd->public_action_cb) { hapd->public_action_cb(hapd->public_action_cb_ctx, (u8 *) mgmt, len, @@ -1700,19 +1735,6 @@ int ieee802_11_mgmt(struct hostapd_data *hapd, const u8 *buf, size_t len, u16 fc, stype; int ret = 0; -#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-RX %s", hex); - os_free(hex); - } - return 1; - } -#endif /* CONFIG_TESTING_OPTIONS */ - if (len < 24) return 0; @@ -1899,7 +1921,7 @@ static void handle_assoc_cb(struct hostapd_data *hapd, status = le_to_host16(mgmt->u.assoc_resp.status_code); if (status != WLAN_STATUS_SUCCESS) - goto fail; + return; /* Stop previous accounting session, if one is started, and allocate * new session id for the new session. */ @@ -1914,7 +1936,7 @@ static void handle_assoc_cb(struct hostapd_data *hapd, new_assoc = 0; sta->flags |= WLAN_STA_ASSOC; sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE; - if ((!hapd->conf->ieee802_1x && !hapd->conf->wpa) || + if ((!hapd->conf->ieee802_1x && !hapd->conf->wpa && !hapd->conf->osen) || sta->auth_alg == WLAN_AUTH_FT) { /* * Open, static WEP, or FT protocol; no separate authorization @@ -1953,7 +1975,7 @@ static void handle_assoc_cb(struct hostapd_data *hapd, sta->listen_interval, sta->flags & WLAN_STA_HT ? &ht_cap : NULL, sta->flags & WLAN_STA_VHT ? &vht_cap : NULL, - sta->flags, sta->qosinfo)) { + sta->flags, sta->qosinfo, sta->vht_opmode)) { hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_NOTICE, "Could not add STA to kernel driver"); @@ -1961,7 +1983,7 @@ static void handle_assoc_cb(struct hostapd_data *hapd, ap_sta_disconnect(hapd, sta, sta->addr, WLAN_REASON_DISASSOC_AP_BUSY); - goto fail; + return; } if (sta->flags & WLAN_STA_WDS) { @@ -1981,11 +2003,11 @@ static void handle_assoc_cb(struct hostapd_data *hapd, * interface selection is not going to change anymore. */ if (ap_sta_bind_vlan(hapd, sta, 0) < 0) - goto fail; + return; } 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) < 0) - goto fail; + return; } hostapd_set_sta_flags(hapd, sta); @@ -1997,13 +2019,6 @@ static void handle_assoc_cb(struct hostapd_data *hapd, hapd->new_assoc_sta_cb(hapd, sta, !new_assoc); ieee802_1x_notify_port_enabled(sta->eapol_sm, 1); - - fail: - /* Copy of the association request is not needed anymore */ - if (sta->last_assoc_req) { - os_free(sta->last_assoc_req); - sta->last_assoc_req = NULL; - } } diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h index 5edeb71..cf0d3f2 100644 --- a/src/ap/ieee802_11.h +++ b/src/ap/ieee802_11.h @@ -14,12 +14,14 @@ struct hostapd_data; struct sta_info; struct hostapd_frame_info; struct ieee80211_ht_capabilities; +struct ieee80211_mgmt; 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 ieee802_11_print_ssid(char *buf, const u8 *ssid, u8 len); +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, @@ -40,6 +42,7 @@ static inline int ieee802_11_get_mib_sta(struct hostapd_data *hapd, #endif /* NEED_AP_MLME */ u16 hostapd_own_capab_info(struct hostapd_data *hapd, struct sta_info *sta, int probe); +void ap_ht2040_timeout(void *eloop_data, void *user_data); u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid); u8 * hostapd_eid_qos_map_set(struct hostapd_data *hapd, u8 *eid); u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid); @@ -60,8 +63,12 @@ void hostapd_get_vht_capab(struct hostapd_data *hapd, u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta, const u8 *ht_capab, size_t ht_capab_len); void 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, size_t vht_capab_len); +u16 set_sta_vht_opmode(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *vht_opmode); void hostapd_tx_status(struct hostapd_data *hapd, const u8 *addr, const u8 *buf, size_t len, int ack); void hostapd_eapol_tx_status(struct hostapd_data *hapd, const u8 *dst, diff --git a/src/ap/ieee802_11_ht.c b/src/ap/ieee802_11_ht.c index 31dc47e..c0a7cd4 100644 --- a/src/ap/ieee802_11_ht.c +++ b/src/ap/ieee802_11_ht.c @@ -10,12 +10,15 @@ #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) @@ -54,7 +57,20 @@ u8 * hostapd_eid_ht_capabilities(struct hostapd_data *hapd, u8 *eid) scan_params->width_trigger_scan_interval = host_to_le16(hapd->iconf->obss_interval); - /* TODO: Fill in more parameters (supplicant ignores them) */ + /* 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); } @@ -77,14 +93,14 @@ u8 * hostapd_eid_ht_operation(struct hostapd_data *hapd, u8 *eid) oper = (struct ieee80211_ht_operation *) pos; os_memset(oper, 0, sizeof(*oper)); - oper->control_chan = hapd->iconf->channel; + 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_REC_TRANS_CHNL_WIDTH; + 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_REC_TRANS_CHNL_WIDTH; + HT_INFO_HT_PARAM_STA_CHNL_WIDTH; pos += sizeof(*oper); @@ -114,44 +130,40 @@ int hostapd_ht_operation_update(struct hostapd_iface *iface) wpa_printf(MSG_DEBUG, "%s current operation mode=0x%X", __func__, iface->ht_op_mode); - if (!(iface->ht_op_mode & HT_INFO_OPERATION_MODE_NON_GF_DEVS_PRESENT) + 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_INFO_OPERATION_MODE_NON_GF_DEVS_PRESENT; + iface->ht_op_mode |= HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT; op_mode_changes++; } else if ((iface->ht_op_mode & - HT_INFO_OPERATION_MODE_NON_GF_DEVS_PRESENT) && + HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT) && iface->num_sta_ht_no_gf == 0) { - iface->ht_op_mode &= - ~HT_INFO_OPERATION_MODE_NON_GF_DEVS_PRESENT; + iface->ht_op_mode &= ~HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT; op_mode_changes++; } - if (!(iface->ht_op_mode & HT_INFO_OPERATION_MODE_NON_HT_STA_PRESENT) && + 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_INFO_OPERATION_MODE_NON_HT_STA_PRESENT; + iface->ht_op_mode |= HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT; op_mode_changes++; } else if ((iface->ht_op_mode & - HT_INFO_OPERATION_MODE_NON_HT_STA_PRESENT) && + HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT) && (iface->num_sta_no_ht == 0 && !iface->olbc_ht)) { - iface->ht_op_mode &= - ~HT_INFO_OPERATION_MODE_NON_HT_STA_PRESENT; + iface->ht_op_mode &= ~HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT; op_mode_changes++; } - new_op_mode = 0; if (iface->num_sta_no_ht) - new_op_mode = OP_MODE_MIXED; + new_op_mode = HT_PROT_NON_HT_MIXED; else if (iface->conf->secondary_channel && iface->num_sta_ht_20mhz) - new_op_mode = OP_MODE_20MHZ_HT_STA_ASSOCED; + new_op_mode = HT_PROT_20MHZ_PROTECTION; else if (iface->olbc_ht) - new_op_mode = OP_MODE_MAY_BE_LEGACY_STAS; + new_op_mode = HT_PROT_NONMEMBER_PROTECTION; else - new_op_mode = OP_MODE_PURE; + new_op_mode = HT_PROT_NO_PROTECTION; - cur_op_mode = iface->ht_op_mode & HT_INFO_OPERATION_MODE_OP_MODE_MASK; + 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_INFO_OPERATION_MODE_OP_MODE_MASK; + iface->ht_op_mode &= ~HT_OPER_OP_MODE_HT_PROT_MASK; iface->ht_op_mode |= new_op_mode; op_mode_changes++; } @@ -163,6 +175,117 @@ int hostapd_ht_operation_update(struct hostapd_iface *iface) } +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_ht_allowed = 1; + int i; + const u8 *data = (const u8 *) &mgmt->u.action.u.public_action.action; + size_t hdr_len; + + 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)) + return; + + hdr_len = data - (u8 *) mgmt; + if (hdr_len > len) + return; + data++; + + bc_ie = (struct ieee80211_2040_bss_coex_ie *) &data[0]; + ic_report = (struct ieee80211_2040_intol_chan_report *) + (&data[0] + sizeof(*bc_ie)); + + if (bc_ie->coex_param & WLAN_20_40_BSS_COEX_20MHZ_WIDTH_REQ) { + 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_ht_allowed = 0; + } + + if (bc_ie->coex_param & WLAN_20_40_BSS_COEX_40MHZ_INTOL) { + hostapd_logger(hapd, mgmt->sa, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "40 MHz intolerant bit is set in BSS coexistence information field"); + is_ht_allowed = 0; + } + + if (ic_report && + (ic_report->element_id == WLAN_EID_20_40_BSS_INTOLERANT)) { + /* Go through the channel report to find any BSS there in the + * affected channel range */ + for (i = 0; i < ic_report->length - 1; i++) { + if (is_40_allowed(iface, ic_report->variable[i])) + continue; + hostapd_logger(hapd, mgmt->sa, + HOSTAPD_MODULE_IEEE80211, + HOSTAPD_LEVEL_DEBUG, + "20_40_INTOLERANT channel %d reported", + ic_report->variable[i]); + is_ht_allowed = 0; + break; + } + } + + if (!is_ht_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) { + 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); + } + } +} + + u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta, const u8 *ht_capab, size_t ht_capab_len) { @@ -191,6 +314,52 @@ u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta, } +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; @@ -218,6 +387,9 @@ static void update_sta_ht(struct hostapd_data *hapd, struct sta_info *sta) __func__, MAC2STR(sta->addr), hapd->iface->num_sta_ht_20mhz); } + + if (ht_capab & HT_CAP_INFO_40MHZ_INTOLERANT) + ht40_intolerant_add(hapd->iface, sta); } @@ -279,3 +451,14 @@ void hostapd_get_ht_capab(struct hostapd_data *hapd, 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 index eadaa4d..12403f9 100644 --- a/src/ap/ieee802_11_shared.c +++ b/src/ap/ieee802_11_shared.c @@ -170,6 +170,8 @@ static void hostapd_ext_capab_byte(struct hostapd_data *hapd, u8 *pos, int idx) switch (idx) { case 0: /* Bits 0-7 */ + if (hapd->iconf->obss_interval) + *pos |= 0x01; /* Bit 0 - Coexistence management */ break; case 1: /* Bits 8-15 */ break; @@ -199,6 +201,10 @@ static void hostapd_ext_capab_byte(struct hostapd_data *hapd, u8 *pos, int idx) } break; case 5: /* Bits 40-47 */ +#ifdef CONFIG_HS20 + if (hapd->conf->hs20) + *pos |= 0x40; /* Bit 46 - WNM-Notification */ +#endif /* CONFIG_HS20 */ break; case 6: /* Bits 48-55 */ if (hapd->conf->ssid.utf8_ssid) @@ -219,12 +225,18 @@ u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid) len = 4; if (len < 3 && hapd->conf->wnm_sleep_mode) len = 3; + if (len < 1 && hapd->iconf->obss_interval) + len = 1; if (len < 7 && hapd->conf->ssid.utf8_ssid) len = 7; #ifdef CONFIG_WNM if (len < 4) len = 4; #endif /* CONFIG_WNM */ +#ifdef CONFIG_HS20 + if (hapd->conf->hs20 && len < 6) + len = 6; +#endif /* CONFIG_HS20 */ if (len < hapd->iface->extended_capa_len) len = hapd->iface->extended_capa_len; if (len == 0) diff --git a/src/ap/ieee802_11_vht.c b/src/ap/ieee802_11_vht.c index f2ab182..221d9c2 100644 --- a/src/ap/ieee802_11_vht.c +++ b/src/ap/ieee802_11_vht.c @@ -108,6 +108,35 @@ u16 copy_sta_vht_capab(struct hostapd_data *hapd, struct sta_info *sta, return WLAN_STATUS_SUCCESS; } + +u16 set_sta_vht_opmode(struct hostapd_data *hapd, struct sta_info *sta, + const u8 *vht_oper_notif) +{ + u8 channel_width; + + if (!vht_oper_notif) { + sta->flags &= ~WLAN_STA_VHT_OPMODE_ENABLED; + return WLAN_STATUS_SUCCESS; + } + + channel_width = *vht_oper_notif & VHT_OPMODE_CHANNEL_WIDTH_MASK; + + if (channel_width != VHT_CHANWIDTH_USE_HT && + channel_width != VHT_CHANWIDTH_80MHZ && + channel_width != VHT_CHANWIDTH_160MHZ && + channel_width != VHT_CHANWIDTH_80P80MHZ && + ((*vht_oper_notif & VHT_OPMODE_CHANNEL_RxNSS_MASK) >> + VHT_OPMODE_NOTIF_RX_NSS_SHIFT) > VHT_RX_NSS_MAX_STREAMS - 1) { + sta->flags &= ~WLAN_STA_VHT_OPMODE_ENABLED; + return WLAN_STATUS_UNSPECIFIED_FAILURE; + } + + 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) diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c index 49b30e4..035415f 100644 --- a/src/ap/ieee802_1x.c +++ b/src/ap/ieee802_1x.c @@ -30,11 +30,13 @@ #include "ap_config.h" #include "ap_drv_ops.h" #include "wps_hostapd.h" +#include "hs20.h" #include "ieee802_1x.h" static void ieee802_1x_finished(struct hostapd_data *hapd, - struct sta_info *sta, int success); + struct sta_info *sta, int success, + int remediation); static void ieee802_1x_send(struct hostapd_data *hapd, struct sta_info *sta, @@ -521,6 +523,41 @@ static void ieee802_1x_encapsulate_radius(struct hostapd_data *hapd, } } +#ifdef CONFIG_HS20 + if (hapd->conf->hs20) { + u8 ver = 1; /* Release 2 */ + 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; + } + } + } +#endif /* CONFIG_HS20 */ + if (radius_client_send(hapd->radius, msg, RADIUS_AUTH, sta->addr) < 0) goto fail; @@ -650,7 +687,7 @@ void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, struct rsn_pmksa_cache_entry *pmksa; int key_mgmt; - if (!hapd->conf->ieee802_1x && !hapd->conf->wpa && + if (!hapd->conf->ieee802_1x && !hapd->conf->wpa && !hapd->conf->osen && !hapd->conf->wps_state) return; @@ -701,7 +738,7 @@ void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, return; } - if (!hapd->conf->ieee802_1x && + 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"); @@ -721,7 +758,7 @@ void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf, return; #ifdef CONFIG_WPS - if (!hapd->conf->ieee802_1x) { + if (!hapd->conf->ieee802_1x && hapd->conf->wps_state) { u32 wflags = sta->flags & (WLAN_STA_WPS | WLAN_STA_WPS2 | WLAN_STA_MAYBE_WPS); @@ -839,7 +876,7 @@ void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta) } #endif /* CONFIG_WPS */ - if (!force_1x && !hapd->conf->ieee802_1x) { + 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"); /* @@ -877,7 +914,8 @@ void ieee802_1x_new_station(struct hostapd_data *hapd, struct sta_info *sta) #ifdef CONFIG_WPS sta->eapol_sm->flags &= ~EAPOL_SM_WAIT_START; - if (!hapd->conf->ieee802_1x && !(sta->flags & WLAN_STA_WPS2)) { + 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 @@ -1203,6 +1241,147 @@ static void ieee802_1x_update_sta_cui(struct hostapd_data *hapd, } +#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, u8 *pos, + size_t len) +{ + if (len < 3) + return; /* Malformed information */ + sta->hs20_deauth_requested = 1; + wpa_printf(MSG_DEBUG, "HS 2.0: Deauthentication request - Code %u " + "Re-auth Delay %u", + *pos, WPA_GET_LE16(pos + 1)); + 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, len - 3); + wpabuf_put_data(sta->hs20_deauth_req, pos + 3, len - 3); + } + ap_sta_session_timeout(hapd, sta, hapd->conf->hs20_deauth_req_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 == NULL) + 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); +} + +#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; + + 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; + } + } +#endif /* CONFIG_HS20 */ +} + + struct sta_id_search { u8 identifier; struct eapol_state_machine *sm; @@ -1361,7 +1540,11 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req, ieee802_1x_store_radius_class(hapd, sta, msg); ieee802_1x_update_sta_identity(hapd, sta, msg); ieee802_1x_update_sta_cui(hapd, sta, msg); - if (sm->eap_if->eapKeyAvailable && + ieee802_1x_check_hs20(hapd, sta, msg, + session_timeout_set ? + (int) session_timeout : -1); + if (sm->eap_if->eapKeyAvailable && !sta->remediation && + !sta->hs20_deauth_requested && wpa_auth_pmksa_add(sta->wpa_sm, sm->eapol_key_crypt, session_timeout_set ? (int) session_timeout : -1, sm) == 0) { @@ -1564,14 +1747,14 @@ static void ieee802_1x_aaa_send(void *ctx, void *sta_ctx, static void _ieee802_1x_finished(void *ctx, void *sta_ctx, int success, - int preauth) + int preauth, int remediation) { struct hostapd_data *hapd = ctx; struct sta_info *sta = sta_ctx; if (preauth) rsn_preauth_finished(hapd, sta, success); else - ieee802_1x_finished(hapd, sta, success); + ieee802_1x_finished(hapd, sta, success, remediation); } @@ -1604,7 +1787,9 @@ static int ieee802_1x_get_eap_user(void *ctx, const u8 *identity, user->password_hash = eap_user->password_hash; } user->force_version = eap_user->force_version; + user->macacl = eap_user->macacl; user->ttls_auth = eap_user->ttls_auth; + user->remediation = eap_user->remediation; return 0; } @@ -1953,6 +2138,8 @@ int ieee802_1x_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, int len = 0, ret; struct eapol_state_machine *sm = sta->eapol_sm; struct os_reltime diff; + const char *name1; + const char *name2; if (sm == NULL) return 0; @@ -2088,13 +2275,15 @@ int ieee802_1x_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, 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, - eap_server_get_name(0, sm->eap_type_authsrv), + name1 ? name1 : "", sm->eap_type_supp, - eap_server_get_name(0, sm->eap_type_supp)); + name2 ? name2 : ""); if (ret < 0 || (size_t) ret >= buflen - len) return len; len += ret; @@ -2104,15 +2293,49 @@ int ieee802_1x_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta, static void ieee802_1x_finished(struct hostapd_data *hapd, - struct sta_info *sta, int success) + struct sta_info *sta, int success, + int remediation) { const u8 *key; size_t len; /* TODO: get PMKLifetime from WPA parameters */ static const int dot11RSNAConfigPMKLifetime = 43200; +#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) { + 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); + } + } +#endif /* CONFIG_HS20 */ + key = ieee802_1x_get_key(sta->eapol_sm, &len); - if (success && key && len >= PMK_LEN && + if (success && key && len >= PMK_LEN && !sta->remediation && + !sta->hs20_deauth_requested && wpa_auth_pmksa_add(sta->wpa_sm, key, dot11RSNAConfigPMKLifetime, sta->eapol_sm) == 0) { hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_WPA, diff --git a/src/ap/p2p_hostapd.c b/src/ap/p2p_hostapd.c index 795d313..9be640c 100644 --- a/src/ap/p2p_hostapd.c +++ b/src/ap/p2p_hostapd.c @@ -96,9 +96,8 @@ u8 * hostapd_eid_p2p_manage(struct hostapd_data *hapd, u8 *eid) u8 bitmap; *eid++ = WLAN_EID_VENDOR_SPECIFIC; *eid++ = 4 + 3 + 1; - WPA_PUT_BE24(eid, OUI_WFA); - eid += 3; - *eid++ = P2P_OUI_TYPE; + WPA_PUT_BE32(eid, P2P_IE_VENDOR_TYPE); + eid += 4; *eid++ = P2P_ATTR_MANAGEABILITY; WPA_PUT_LE16(eid, 1); diff --git a/src/ap/peerkey_auth.c b/src/ap/peerkey_auth.c index ba5c606..612babc 100644 --- a/src/ap/peerkey_auth.c +++ b/src/ap/peerkey_auth.c @@ -221,8 +221,8 @@ static void wpa_send_smk_m5(struct wpa_authenticator *wpa_auth, return; /* Peer RSN IE */ - os_memcpy(buf, kde->rsn_ie, kde->rsn_ie_len); - pos = buf + kde->rsn_ie_len; + os_memcpy(pos, kde->rsn_ie, kde->rsn_ie_len); + pos += kde->rsn_ie_len; /* Peer MAC Address */ pos = wpa_add_kde(pos, RSN_KEY_DATA_MAC_ADDR, peer, ETH_ALEN, NULL, 0); diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index 24e764d..60f0768 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -30,11 +30,13 @@ #include "p2p_hostapd.h" #include "ap_drv_ops.h" #include "gas_serv.h" +#include "wnm_ap.h" #include "sta_info.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); #ifdef CONFIG_IEEE80211W @@ -154,7 +156,8 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) if (sta->flags & WLAN_STA_WDS) hostapd_set_wds_sta(hapd, NULL, sta->addr, sta->aid, 0); - if (!(sta->flags & WLAN_STA_PREAUTH)) + if (!hapd->iface->driver_ap_teardown && + !(sta->flags & WLAN_STA_PREAUTH)) hostapd_drv_sta_remove(hapd, sta->addr); ap_sta_hash_del(hapd, sta); @@ -203,6 +206,10 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) hapd->iface->num_sta_ht_20mhz--; } +#ifdef CONFIG_IEEE80211N + ht40_intolerant_remove(hapd->iface, sta); +#endif /* CONFIG_IEEE80211N */ + #ifdef CONFIG_P2P if (sta->no_p2p_set) { sta->no_p2p_set = 0; @@ -224,6 +231,7 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) __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); eloop_cancel_timeout(ap_sta_deauth_cb_timeout, hapd, sta); eloop_cancel_timeout(ap_sta_disassoc_cb_timeout, hapd, sta); @@ -235,7 +243,6 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) radius_client_flush_auth(hapd->radius, sta->addr); #endif /* CONFIG_NO_RADIUS */ - os_free(sta->last_assoc_req); os_free(sta->challenge); #ifdef CONFIG_IEEE80211W @@ -265,6 +272,9 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta) hostapd_free_psk_list(sta->psk); os_free(sta->identity); os_free(sta->radius_cui); + os_free(sta->remediation_url); + wpabuf_free(sta->hs20_deauth_req); + os_free(sta->hs20_session_info_url); #ifdef CONFIG_SAE sae_clear_data(sta->sae); @@ -466,7 +476,6 @@ static void ap_handle_session_timer(void *eloop_ctx, void *timeout_ctx) { struct hostapd_data *hapd = eloop_ctx; struct sta_info *sta = timeout_ctx; - u8 addr[ETH_ALEN]; if (!(sta->flags & WLAN_STA_AUTH)) { if (sta->flags & WLAN_STA_GAS) { @@ -477,6 +486,8 @@ static void ap_handle_session_timer(void *eloop_ctx, void *timeout_ctx) 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, @@ -484,9 +495,7 @@ static void ap_handle_session_timer(void *eloop_ctx, void *timeout_ctx) "session timeout"); sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_SESSION_TIMEOUT; - os_memcpy(addr, sta->addr, ETH_ALEN); ap_free_sta(hapd, sta); - hostapd_drv_sta_deauth(hapd, addr, WLAN_REASON_PREV_AUTH_NOT_VALID); } @@ -520,6 +529,32 @@ void ap_sta_no_session_timeout(struct hostapd_data *hapd, struct sta_info *sta) } +static void ap_handle_session_warning_timer(void *eloop_ctx, void *timeout_ctx) +{ +#ifdef CONFIG_WNM + struct hostapd_data *hapd = eloop_ctx; + struct sta_info *sta = timeout_ctx; + + wpa_printf(MSG_DEBUG, "WNM: Session warning time reached for " MACSTR, + 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 */ +} + + +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; diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h index 9b77e06..03db98f 100644 --- a/src/ap/sta_info.h +++ b/src/ap/sta_info.h @@ -27,6 +27,7 @@ #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_PENDING_DISASSOC_CB BIT(29) #define WLAN_STA_PENDING_DEAUTH_CB BIT(30) #define WLAN_STA_NONERP BIT(31) @@ -53,12 +54,14 @@ struct sta_info { 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; u16 auth_alg; - u8 previous_ap[6]; enum { STA_NULLFUNC = 0, STA_DISASSOC, STA_DEAUTH, STA_REMOVE, @@ -71,9 +74,6 @@ struct sta_info { /* IEEE 802.1X related data */ struct eapol_state_machine *eapol_sm; - /* IEEE 802.11f (IAPP) related data */ - struct ieee80211_mgmt *last_assoc_req; - u32 acct_session_id_hi; u32 acct_session_id_lo; struct os_reltime acct_session_start; @@ -103,6 +103,7 @@ struct sta_info { struct ieee80211_ht_capabilities *ht_capabilities; struct ieee80211_vht_capabilities *vht_capabilities; + u8 vht_opmode; #ifdef CONFIG_IEEE80211W int sa_query_count; /* number of pending SA Query requests; @@ -123,6 +124,11 @@ struct sta_info { 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 */ + u8 remediation_method; + char *remediation_url; /* HS 2.0 Subscription Remediation Server URL */ + struct wpabuf *hs20_deauth_req; + char *hs20_session_info_url; + int hs20_disassoc_timer; struct os_reltime connected_time; @@ -166,6 +172,8 @@ 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); diff --git a/src/ap/vlan_init.c b/src/ap/vlan_init.c index 509e557..4e4a352 100644 --- a/src/ap/vlan_init.c +++ b/src/ap/vlan_init.c @@ -474,123 +474,6 @@ static int vlan_set_name_type(unsigned int name_type) #endif /* CONFIG_VLAN_NETLINK */ -/** - * Increase the usage counter for given parent/ifname combination. - * If create is set, then this iface is added to the global list. - * Returns - * -1 on error - * 0 if iface is not in list - * 1 if iface is in list (was there or has been added) - */ -static int hapd_get_dynamic_iface(const char *parent, const char *ifname, - int create, struct hostapd_data *hapd) -{ - size_t i; - struct hostapd_dynamic_iface *j = NULL, **tmp; - struct hapd_interfaces *hapd_global = hapd->iface->interfaces; - - if (!parent) - parent = ""; - - for (i = 0; i < hapd_global->count_dynamic; i++) { - j = hapd_global->dynamic_iface[i]; - if (os_strncmp(j->iface, ifname, sizeof(j->iface)) == 0 && - os_strncmp(j->parent, parent, sizeof(j->parent)) == 0) - break; - } - if (i < hapd_global->count_dynamic) { - j->usage++; - return 1; - } - - /* new entry required */ - if (!create) - return 0; - - j = os_zalloc(sizeof(*j)); - if (!j) - return -1; - os_strlcpy(j->iface, ifname, sizeof(j->iface)); - os_strlcpy(j->parent, parent, sizeof(j->parent)); - - tmp = os_realloc_array(hapd_global->dynamic_iface, i + 1, - sizeof(*hapd_global->dynamic_iface)); - if (!tmp) { - wpa_printf(MSG_ERROR, "VLAN: Failed to allocate memory in %s", - __func__); - return -1; - } - hapd_global->count_dynamic++; - hapd_global->dynamic_iface = tmp; - hapd_global->dynamic_iface[i] = j; - - return 1; -} - - -/** - * Decrease the usage counter for given ifname. - * Returns - * -1 on error or if iface was not found - * 0 if iface was found and is still present - * 1 if iface was removed from global list - */ -static int hapd_put_dynamic_iface(const char *parent, const char *ifname, - struct hostapd_data *hapd) -{ - size_t i; - struct hostapd_dynamic_iface *j = NULL, **tmp; - struct hapd_interfaces *hapd_glob = hapd->iface->interfaces; - - if (!parent) - parent = ""; - - for (i = 0; i < hapd_glob->count_dynamic; i++) { - j = hapd_glob->dynamic_iface[i]; - if (os_strncmp(j->iface, ifname, sizeof(j->iface)) == 0 && - os_strncmp(j->parent, parent, sizeof(j->parent)) == 0) - break; - } - - if (i == hapd_glob->count_dynamic) { - /* - * Interface not in global list. This can happen if alloc in - * _get_ failed. - */ - return -1; - } - - if (j->usage > 0) { - j->usage--; - return 0; - } - - os_free(j); - for (; i < hapd_glob->count_dynamic - 1; i++) - hapd_glob->dynamic_iface[i] = hapd_glob->dynamic_iface[i + 1]; - hapd_glob->dynamic_iface[hapd_glob->count_dynamic - 1] = NULL; - hapd_glob->count_dynamic--; - - if (hapd_glob->count_dynamic == 0) { - os_free(hapd_glob->dynamic_iface); - hapd_glob->dynamic_iface = NULL; - return 1; - } - - tmp = os_realloc_array(hapd_glob->dynamic_iface, - hapd_glob->count_dynamic, - sizeof(*hapd_glob->dynamic_iface)); - if (!tmp) { - wpa_printf(MSG_ERROR, "VLAN: Failed to release memory in %s", - __func__); - return -1; - } - hapd_glob->dynamic_iface = tmp; - - return 1; -} - - static void vlan_newlink(char *ifname, struct hostapd_data *hapd) { char vlan_ifname[IFNAMSIZ]; @@ -598,7 +481,6 @@ static void vlan_newlink(char *ifname, struct hostapd_data *hapd) struct hostapd_vlan *vlan = hapd->conf->vlan; char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface; int vlan_naming = hapd->conf->ssid.vlan_naming; - int ret; wpa_printf(MSG_DEBUG, "VLAN: vlan_newlink(%s)", ifname); @@ -618,9 +500,7 @@ static void vlan_newlink(char *ifname, struct hostapd_data *hapd) "brvlan%d", vlan->vlan_id); } - ret = br_addbr(br_name); - if (hapd_get_dynamic_iface(NULL, br_name, ret == 0, - hapd)) + if (!br_addbr(br_name)) vlan->clean |= DVLAN_CLEAN_BR; ifconfig_up(br_name); @@ -638,24 +518,17 @@ static void vlan_newlink(char *ifname, struct hostapd_data *hapd) "vlan%d", vlan->vlan_id); ifconfig_up(tagged_interface); - ret = vlan_add(tagged_interface, vlan->vlan_id, - vlan_ifname); - if (hapd_get_dynamic_iface(NULL, vlan_ifname, - ret == 0, hapd)) + if (!vlan_add(tagged_interface, vlan->vlan_id, + vlan_ifname)) vlan->clean |= DVLAN_CLEAN_VLAN; - ret = br_addif(br_name, vlan_ifname); - if (hapd_get_dynamic_iface(br_name, - vlan_ifname, - ret == 0, hapd)) + if (!br_addif(br_name, vlan_ifname)) vlan->clean |= DVLAN_CLEAN_VLAN_PORT; ifconfig_up(vlan_ifname); } - ret = br_addif(br_name, ifname); - if (hapd_get_dynamic_iface(br_name, ifname, ret == 0, - hapd)) + if (!br_addif(br_name, ifname)) vlan->clean |= DVLAN_CLEAN_WLAN_PORT; ifconfig_up(ifname); @@ -694,8 +567,7 @@ static void vlan_dellink(char *ifname, struct hostapd_data *hapd) "brvlan%d", vlan->vlan_id); } - if ((vlan->clean & DVLAN_CLEAN_WLAN_PORT) && - hapd_put_dynamic_iface(br_name, vlan->ifname, hapd)) + if (vlan->clean & DVLAN_CLEAN_WLAN_PORT) br_delif(br_name, vlan->ifname); if (tagged_interface) { @@ -709,20 +581,15 @@ static void vlan_dellink(char *ifname, struct hostapd_data *hapd) os_snprintf(vlan_ifname, sizeof(vlan_ifname), "vlan%d", vlan->vlan_id); - if ((vlan->clean & DVLAN_CLEAN_VLAN_PORT) && - hapd_put_dynamic_iface(br_name, vlan_ifname, - hapd)) + if (vlan->clean & DVLAN_CLEAN_VLAN_PORT) br_delif(br_name, vlan_ifname); ifconfig_down(vlan_ifname); - if ((vlan->clean & DVLAN_CLEAN_VLAN) && - hapd_put_dynamic_iface(NULL, vlan_ifname, - hapd)) + if (vlan->clean & DVLAN_CLEAN_VLAN) vlan_rem(vlan_ifname); } if ((vlan->clean & DVLAN_CLEAN_BR) && - hapd_put_dynamic_iface(NULL, br_name, hapd) && br_getnumports(br_name) == 0) { ifconfig_down(br_name); br_delbr(br_name); diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c index 707a63f..a9cd6f6 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -211,6 +211,8 @@ static int wpa_use_aes_cmac(struct wpa_state_machine *sm) if (wpa_key_mgmt_sha256(sm->wpa_key_mgmt)) ret = 1; #endif /* CONFIG_IEEE80211W */ + if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) + ret = 1; return ret; } @@ -563,6 +565,8 @@ int wpa_auth_sta_associated(struct wpa_authenticator *wpa_auth, wpa_auth_logger(wpa_auth, sm->addr, 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; return 0; } #endif /* CONFIG_IEEE80211R */ @@ -619,6 +623,7 @@ static void wpa_free_sta_sm(struct wpa_state_machine *sm) } #ifdef CONFIG_IEEE80211R os_free(sm->assoc_resp_ftie); + wpabuf_free(sm->ft_pending_req_ies); #endif /* CONFIG_IEEE80211R */ os_free(sm->last_rx_eapol_key); os_free(sm->wpa_ie); @@ -878,6 +883,7 @@ void wpa_receive(struct wpa_authenticator *wpa_auth, if (sm->pairwise == WPA_CIPHER_CCMP || sm->pairwise == WPA_CIPHER_GCMP) { if (wpa_use_aes_cmac(sm) && + sm->wpa_key_mgmt != WPA_KEY_MGMT_OSEN && ver != WPA_KEY_INFO_TYPE_AES_128_CMAC) { wpa_auth_logger(wpa_auth, sm->addr, LOGGER_WARNING, @@ -1001,6 +1007,9 @@ continue_processing: 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; @@ -1286,6 +1295,8 @@ void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, if (force_version) version = force_version; + else if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) + version = WPA_KEY_INFO_TYPE_AKM_DEFINED; else if (wpa_use_aes_cmac(sm)) version = WPA_KEY_INFO_TYPE_AES_128_CMAC; else if (sm->pairwise != WPA_CIPHER_TKIP) @@ -1308,6 +1319,7 @@ void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, key_data_len = kde_len; if ((version == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN || version == WPA_KEY_INFO_TYPE_AES_128_CMAC) && encr) { pad_len = key_data_len % 8; if (pad_len) @@ -1376,6 +1388,7 @@ void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, wpa_hexdump_key(MSG_DEBUG, "Plaintext EAPOL-Key Key Data", buf, key_data_len); if (version == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || + sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN || version == WPA_KEY_INFO_TYPE_AES_128_CMAC) { if (aes_wrap(sm->PTK.kek, (key_data_len - 8) / 8, buf, (u8 *) (key + 1))) { @@ -1410,7 +1423,7 @@ void __wpa_send_eapol(struct wpa_authenticator *wpa_auth, key->key_mic); #ifdef CONFIG_TESTING_OPTIONS if (!pairwise && - wpa_auth->conf.corrupt_gtk_rekey_mic_probability > 0.0d && + wpa_auth->conf.corrupt_gtk_rekey_mic_probability > 0.0 && drand48() < wpa_auth->conf.corrupt_gtk_rekey_mic_probability) { wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO, @@ -1774,7 +1787,8 @@ SM_STATE(WPA_PTK, PTKSTART) * one possible PSK for this STA. */ if (sm->wpa == WPA_VERSION_WPA2 && - wpa_key_mgmt_wpa_ieee8021x(sm->wpa_key_mgmt)) { + wpa_key_mgmt_wpa_ieee8021x(sm->wpa_key_mgmt) && + sm->wpa_key_mgmt != WPA_KEY_MGMT_OSEN) { pmkid = buf; pmkid_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN; pmkid[0] = WLAN_EID_VENDOR_SPECIFIC; @@ -1802,7 +1816,7 @@ SM_STATE(WPA_PTK, PTKSTART) static int wpa_derive_ptk(struct wpa_state_machine *sm, const u8 *pmk, struct wpa_ptk *ptk) { - size_t ptk_len = sm->pairwise != WPA_CIPHER_TKIP ? 48 : 64; + size_t ptk_len = wpa_cipher_key_len(sm->pairwise) + 32; #ifdef CONFIG_IEEE80211R if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) return wpa_auth_derive_ptk_ft(sm, pmk, ptk, ptk_len); @@ -1907,7 +1921,9 @@ SM_STATE(WPA_PTK, PTKCALCNEGOTIATING2) static int ieee80211w_kde_len(struct wpa_state_machine *sm) { if (sm->mgmt_frame_prot) { - return 2 + RSN_SELECTOR_LEN + sizeof(struct wpa_igtk_kde); + size_t len; + len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher); + return 2 + RSN_SELECTOR_LEN + WPA_IGTK_KDE_PREFIX_LEN + len; } return 0; @@ -1919,6 +1935,7 @@ static u8 * ieee80211w_kde_add(struct wpa_state_machine *sm, u8 *pos) struct wpa_igtk_kde igtk; struct wpa_group *gsm = sm->group; u8 rsc[WPA_KEY_RSC_LEN]; + size_t len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher); if (!sm->mgmt_frame_prot) return pos; @@ -1930,17 +1947,18 @@ static u8 * ieee80211w_kde_add(struct wpa_state_machine *sm, u8 *pos) 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], WPA_IGTK_LEN); + os_memcpy(igtk.igtk, gsm->IGTK[gsm->GN_igtk - 4], len); if (sm->wpa_auth->conf.disable_gtk) { /* * Provide unique random IGTK to each STA to prevent use of * IGTK in the BSS. */ - if (random_get_bytes(igtk.igtk, WPA_IGTK_LEN) < 0) + if (random_get_bytes(igtk.igtk, len) < 0) return pos; } pos = wpa_add_kde(pos, RSN_KEY_DATA_IGTK, - (const u8 *) &igtk, sizeof(igtk), NULL, 0); + (const u8 *) &igtk, WPA_IGTK_KDE_PREFIX_LEN + len, + NULL, 0); return pos; } @@ -2446,15 +2464,16 @@ static int wpa_gtk_update(struct wpa_authenticator *wpa_auth, #ifdef CONFIG_IEEE80211W if (wpa_auth->conf.ieee80211w != NO_MGMT_FRAME_PROTECTION) { + size_t len; + len = wpa_cipher_key_len(wpa_auth->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], - WPA_IGTK_LEN) < 0) + group->IGTK[group->GN_igtk - 4], len) < 0) ret = -1; wpa_hexdump_key(MSG_DEBUG, "IGTK", - group->IGTK[group->GN_igtk - 4], WPA_IGTK_LEN); + group->IGTK[group->GN_igtk - 4], len); } #endif /* CONFIG_IEEE80211W */ @@ -2571,26 +2590,27 @@ int wpa_wnmsleep_igtk_subelem(struct wpa_state_machine *sm, u8 *pos) { 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 + WPA_IGTK_LEN; + *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], WPA_IGTK_LEN); - pos += WPA_IGTK_LEN; + os_memcpy(pos, gsm->IGTK[gsm->GN_igtk - 4], len); + 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], WPA_IGTK_LEN); + gsm->IGTK[gsm->GN_igtk - 4], len); return pos - start; } @@ -2645,12 +2665,19 @@ static int wpa_group_config_group_keys(struct wpa_authenticator *wpa_auth, ret = -1; #ifdef CONFIG_IEEE80211W - if (wpa_auth->conf.ieee80211w != NO_MGMT_FRAME_PROTECTION && - wpa_auth_set_key(wpa_auth, group->vlan_id, WPA_ALG_IGTK, - broadcast_ether_addr, group->GN_igtk, - group->IGTK[group->GN_igtk - 4], - WPA_IGTK_LEN) < 0) - ret = -1; + if (wpa_auth->conf.ieee80211w != NO_MGMT_FRAME_PROTECTION) { + enum wpa_alg alg; + size_t len; + + alg = wpa_cipher_to_alg(wpa_auth->conf.group_mgmt_cipher); + len = wpa_cipher_key_len(wpa_auth->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) < 0) + ret = -1; + } #endif /* CONFIG_IEEE80211W */ return ret; diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index bc3dec4..929a253 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -42,6 +42,7 @@ struct ft_rrb_frame { #define FT_R0KH_R1KH_PULL_DATA_LEN 44 #define FT_R0KH_R1KH_RESP_DATA_LEN 76 #define FT_R0KH_R1KH_PUSH_DATA_LEN 88 +#define FT_R0KH_R1KH_PULL_NONCE_LEN 16 struct ft_r0kh_r1kh_pull_frame { u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */ @@ -49,7 +50,7 @@ struct ft_r0kh_r1kh_pull_frame { le16 data_length; /* little endian length of data (44) */ u8 ap_address[ETH_ALEN]; - u8 nonce[16]; + u8 nonce[FT_R0KH_R1KH_PULL_NONCE_LEN]; u8 pmk_r0_name[WPA_PMK_NAME_LEN]; u8 r1kh_id[FT_R1KH_ID_LEN]; u8 s1kh_id[ETH_ALEN]; @@ -63,7 +64,7 @@ struct ft_r0kh_r1kh_resp_frame { le16 data_length; /* little endian length of data (76) */ u8 ap_address[ETH_ALEN]; - u8 nonce[16]; /* copied from pull */ + u8 nonce[FT_R0KH_R1KH_PULL_NONCE_LEN]; /* copied from pull */ u8 r1kh_id[FT_R1KH_ID_LEN]; /* copied from pull */ u8 s1kh_id[ETH_ALEN]; /* copied from pull */ u8 pmk_r1[PMK_LEN]; @@ -142,6 +143,7 @@ struct wpa_auth_config { int tx_status; #ifdef CONFIG_IEEE80211W enum mfp_options ieee80211w; + int group_mgmt_cipher; #endif /* CONFIG_IEEE80211W */ #ifdef CONFIG_IEEE80211R #define SSID_LEN 32 @@ -232,6 +234,9 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, struct wpa_state_machine *sm, const u8 *wpa_ie, size_t wpa_ie_len, const u8 *mdie, size_t mdie_len); +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); struct wpa_state_machine * wpa_auth_sta_init(struct wpa_authenticator *wpa_auth, const u8 *addr, diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c index c22c4cc..a80bbb7 100644 --- a/src/ap/wpa_auth_ft.c +++ b/src/ap/wpa_auth_ft.c @@ -1,6 +1,6 @@ /* * hostapd - IEEE 802.11r - Fast BSS Transition - * Copyright (c) 2004-2009, Jouni Malinen <j@w1.fi> + * Copyright (c) 2004-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -9,6 +9,7 @@ #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 "crypto/aes_wrap.h" @@ -22,6 +23,12 @@ #ifdef CONFIG_IEEE80211R +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 int wpa_ft_rrb_send(struct wpa_authenticator *wpa_auth, const u8 *dst, const u8 *data, size_t data_len) { @@ -293,22 +300,25 @@ static int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth, } -static int wpa_ft_pull_pmk_r1(struct wpa_authenticator *wpa_auth, - const u8 *s1kh_id, const u8 *r0kh_id, - size_t r0kh_id_len, const u8 *pmk_r0_name) +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; struct ft_r0kh_r1kh_pull_frame frame, f; - r0kh = wpa_auth->conf.r0kh_list; + r0kh = sm->wpa_auth->conf.r0kh_list; while (r0kh) { - if (r0kh->id_len == r0kh_id_len && - os_memcmp(r0kh->id, r0kh_id, r0kh_id_len) == 0) + if (r0kh->id_len == sm->r0kh_id_len && + os_memcmp(r0kh->id, sm->r0kh_id, sm->r0kh_id_len) == 0) break; r0kh = r0kh->next; } - if (r0kh == NULL) + if (r0kh == NULL) { + wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID", + sm->r0kh_id, sm->r0kh_id_len); return -1; + } wpa_printf(MSG_DEBUG, "FT: Send PMK-R1 pull request to remote R0KH " "address " MACSTR, MAC2STR(r0kh->addr)); @@ -317,25 +327,32 @@ static int wpa_ft_pull_pmk_r1(struct wpa_authenticator *wpa_auth, frame.frame_type = RSN_REMOTE_FRAME_TYPE_FT_RRB; frame.packet_type = FT_PACKET_R0KH_R1KH_PULL; frame.data_length = host_to_le16(FT_R0KH_R1KH_PULL_DATA_LEN); - os_memcpy(frame.ap_address, wpa_auth->addr, ETH_ALEN); + os_memcpy(frame.ap_address, sm->wpa_auth->addr, ETH_ALEN); /* aes_wrap() does not support inplace encryption, so use a temporary * buffer for the data. */ - if (random_get_bytes(f.nonce, sizeof(f.nonce))) { + if (random_get_bytes(f.nonce, FT_R0KH_R1KH_PULL_NONCE_LEN)) { wpa_printf(MSG_DEBUG, "FT: Failed to get random data for " "nonce"); return -1; } + os_memcpy(sm->ft_pending_pull_nonce, f.nonce, + FT_R0KH_R1KH_PULL_NONCE_LEN); os_memcpy(f.pmk_r0_name, pmk_r0_name, WPA_PMK_NAME_LEN); - os_memcpy(f.r1kh_id, wpa_auth->conf.r1_key_holder, FT_R1KH_ID_LEN); - os_memcpy(f.s1kh_id, s1kh_id, ETH_ALEN); + os_memcpy(f.r1kh_id, sm->wpa_auth->conf.r1_key_holder, FT_R1KH_ID_LEN); + os_memcpy(f.s1kh_id, sm->addr, ETH_ALEN); os_memset(f.pad, 0, sizeof(f.pad)); if (aes_wrap(r0kh->key, (FT_R0KH_R1KH_PULL_DATA_LEN + 7) / 8, f.nonce, frame.nonce) < 0) return -1; - wpa_ft_rrb_send(wpa_auth, r0kh->addr, (u8 *) &frame, sizeof(frame)); + wpabuf_free(sm->ft_pending_req_ies); + sm->ft_pending_req_ies = wpabuf_alloc_copy(ies, ies_len); + if (sm->ft_pending_req_ies == NULL) + return -1; + + wpa_ft_rrb_send(sm->wpa_auth, r0kh->addr, (u8 *) &frame, sizeof(frame)); return 0; } @@ -777,7 +794,7 @@ void wpa_ft_install_ptk(struct wpa_state_machine *sm) } -static u16 wpa_ft_process_auth_req(struct wpa_state_machine *sm, +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) { @@ -848,19 +865,13 @@ static u16 wpa_ft_process_auth_req(struct wpa_state_machine *sm, if (wpa_ft_fetch_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1_name, pmk_r1, &pairwise) < 0) { - if (wpa_ft_pull_pmk_r1(sm->wpa_auth, sm->addr, sm->r0kh_id, - sm->r0kh_id_len, parse.rsn_pmkid) < 0) { + 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 unknown R0KH-ID"); return WLAN_STATUS_INVALID_PMKID; } - /* - * TODO: Should return "status pending" (and the caller should - * not send out response now). The real response will be sent - * once the response from R0KH is received. - */ - return WLAN_STATUS_INVALID_PMKID; + return -1; /* Status pending */ } wpa_hexdump_key(MSG_DEBUG, "FT: Selected PMK-R1", pmk_r1, PMK_LEN); @@ -887,6 +898,7 @@ static u16 wpa_ft_process_auth_req(struct wpa_state_machine *sm, wpa_hexdump(MSG_DEBUG, "FT: PTKName", ptk_name, WPA_PMK_NAME_LEN); sm->pairwise = pairwise; + sm->PTK_valid = TRUE; wpa_ft_install_ptk(sm); buflen = 2 + sizeof(struct rsn_mdie) + 2 + sizeof(struct rsn_ftie) + @@ -940,6 +952,7 @@ void wpa_ft_process_auth(struct wpa_state_machine *sm, const u8 *bssid, u16 status; u8 *resp_ies; size_t resp_ies_len; + int res; if (sm == NULL) { wpa_printf(MSG_DEBUG, "FT: Received authentication frame, but " @@ -950,8 +963,16 @@ void wpa_ft_process_auth(struct wpa_state_machine *sm, const u8 *bssid, wpa_printf(MSG_DEBUG, "FT: Received authentication frame: STA=" MACSTR " BSSID=" MACSTR " transaction=%d", MAC2STR(sm->addr), MAC2STR(bssid), auth_transaction); - status = wpa_ft_process_auth_req(sm, ies, ies_len, &resp_ies, - &resp_ies_len); + sm->ft_pending_cb = cb; + sm->ft_pending_cb_ctx = ctx; + sm->ft_pending_auth_transaction = auth_transaction; + 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=%d", @@ -1182,15 +1203,27 @@ int wpa_ft_action_rx(struct wpa_state_machine *sm, const u8 *data, size_t len) } +static void wpa_ft_rrb_rx_request_cb(void *ctx, const u8 *dst, const u8 *bssid, + 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, *pos; - size_t resp_ies_len, rlen; - struct ft_rrb_frame *frame; + u8 *resp_ies; + size_t resp_ies_len; + int res; sm = wpa_ft_add_sta(wpa_auth, sta_addr); if (sm == NULL) { @@ -1201,8 +1234,33 @@ static int wpa_ft_rrb_rx_request(struct wpa_authenticator *wpa_auth, wpa_hexdump(MSG_MSGDUMP, "FT: RRB Request Frame body", body, len); - status = wpa_ft_process_auth_req(sm, body, len, &resp_ies, - &resp_ies_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); + 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=%d", @@ -1218,10 +1276,8 @@ static int wpa_ft_rrb_rx_request(struct wpa_authenticator *wpa_auth, rlen = 2 + 2 * ETH_ALEN + 2 + resp_ies_len; frame = os_malloc(sizeof(*frame) + rlen); - if (frame == NULL) { - os_free(resp_ies); + 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); @@ -1235,10 +1291,8 @@ static int wpa_ft_rrb_rx_request(struct wpa_authenticator *wpa_auth, pos += ETH_ALEN; WPA_PUT_LE16(pos, status); pos += 2; - if (resp_ies) { + if (resp_ies) os_memcpy(pos, resp_ies, resp_ies_len); - os_free(resp_ies); - } wpa_ft_rrb_send(wpa_auth, current_ap, (u8 *) frame, sizeof(*frame) + rlen); @@ -1290,7 +1344,7 @@ static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth, f.nonce, sizeof(f.nonce)); wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - PMKR0Name", f.pmk_r0_name, WPA_PMK_NAME_LEN); - wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull - R1KH-ID=" MACSTR "S1KH-ID=" + wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull - R1KH-ID=" MACSTR " S1KH-ID=" MACSTR, MAC2STR(f.r1kh_id), MAC2STR(f.s1kh_id)); os_memset(&resp, 0, sizeof(resp)); @@ -1333,13 +1387,58 @@ static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth, } +static void ft_pull_resp_cb_finish(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_state_machine *sm = eloop_ctx; + int res; + u8 *resp_ies; + size_t resp_ies_len; + u16 status; + + 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); + wpabuf_free(sm->ft_pending_req_ies); + sm->ft_pending_req_ies = NULL; + if (res < 0) + res = WLAN_STATUS_UNSPECIFIED_FAILURE; + 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->wpa_auth->addr, + sm->ft_pending_auth_transaction + 1, status, + resp_ies, resp_ies_len); + os_free(resp_ies); +} + + +static int ft_pull_resp_cb(struct wpa_state_machine *sm, void *ctx) +{ + struct ft_r0kh_r1kh_resp_frame *frame = ctx; + + if (os_memcmp(frame->s1kh_id, sm->addr, ETH_ALEN) != 0) + return 0; + if (os_memcmp(frame->nonce, sm->ft_pending_pull_nonce, + FT_R0KH_R1KH_PULL_NONCE_LEN) != 0) + return 0; + if (sm->ft_pending_cb == NULL || sm->ft_pending_req_ies == NULL) + return 0; + + wpa_printf(MSG_DEBUG, "FT: Response to a pending pull request for " + MACSTR " - process from timeout", MAC2STR(sm->addr)); + eloop_register_timeout(0, 0, ft_pull_resp_cb_finish, sm, NULL); + return 1; +} + + static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth, const u8 *src_addr, const u8 *data, size_t data_len) { struct ft_r0kh_r1kh_resp_frame *frame, f; struct ft_remote_r0kh *r0kh; - int pairwise; + int pairwise, res; wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull response"); @@ -1376,14 +1475,10 @@ static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth, return -1; } - /* TODO: verify that <nonce,s1kh_id> matches with a pending request - * and call this requests callback function to finish request - * processing */ - pairwise = le_to_host16(f.pairwise); wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - nonce", f.nonce, sizeof(f.nonce)); - wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull - R1KH-ID=" MACSTR "S1KH-ID=" + wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull - R1KH-ID=" MACSTR " S1KH-ID=" MACSTR " pairwise=0x%x", MAC2STR(f.r1kh_id), MAC2STR(f.s1kh_id), pairwise); wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R1 pull - PMK-R1", @@ -1391,11 +1486,13 @@ static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth, wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - PMKR1Name", f.pmk_r1_name, WPA_PMK_NAME_LEN); - wpa_ft_store_pmk_r1(wpa_auth, f.s1kh_id, f.pmk_r1, f.pmk_r1_name, - pairwise); + res = wpa_ft_store_pmk_r1(wpa_auth, f.s1kh_id, f.pmk_r1, f.pmk_r1_name, + pairwise); + wpa_printf(MSG_DEBUG, "FT: Look for pending pull request"); + wpa_auth_for_each_sta(wpa_auth, ft_pull_resp_cb, &f); os_memset(f.pmk_r1, 0, PMK_LEN); - return 0; + return res ? 0 : -1; } diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c index 5af1495..6ee9a4f 100644 --- a/src/ap/wpa_auth_glue.c +++ b/src/ap/wpa_auth_glue.c @@ -49,6 +49,7 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf, wconf->okc = conf->okc; #ifdef CONFIG_IEEE80211W wconf->ieee80211w = conf->ieee80211w; + wconf->group_mgmt_cipher = conf->group_mgmt_cipher; #endif /* CONFIG_IEEE80211W */ #ifdef CONFIG_IEEE80211R wconf->ssid_len = conf->ssid.ssid_len; @@ -73,6 +74,19 @@ static void hostapd_wpa_auth_conf(struct hostapd_bss_config *conf, #endif /* CONFIG_IEEE80211R */ #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; +#ifdef CONFIG_IEEE80211W + wconf->ieee80211w = 1; +#endif /* CONFIG_IEEE80211W */ + } #endif /* CONFIG_HS20 */ #ifdef CONFIG_TESTING_OPTIONS wconf->corrupt_gtk_rekey_mic_probability = diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h index fcd5878..6960ff3 100644 --- a/src/ap/wpa_auth_i.h +++ b/src/ap/wpa_auth_i.h @@ -118,6 +118,15 @@ struct wpa_state_machine { u8 sup_pmk_r1_name[WPA_PMK_NAME_LEN]; /* PMKR1Name from EAPOL-Key * message 2/4 */ u8 *assoc_resp_ftie; + + void (*ft_pending_cb)(void *ctx, const u8 *dst, const u8 *bssid, + 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_R0KH_R1KH_PULL_NONCE_LEN]; + u8 ft_pending_auth_transaction; + u8 ft_pending_current_ap[ETH_ALEN]; #endif /* CONFIG_IEEE80211R */ int pending_1_of_4_timeout; @@ -154,7 +163,7 @@ struct wpa_group { Boolean first_sta_seen; Boolean reject_4way_hs_for_entropy; #ifdef CONFIG_IEEE80211W - u8 IGTK[2][WPA_IGTK_LEN]; + u8 IGTK[2][WPA_IGTK_MAX_LEN]; int GN_igtk, GM_igtk; #endif /* CONFIG_IEEE80211W */ }; diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c index 274f4d6..1e4defc 100644 --- a/src/ap/wpa_auth_ie.c +++ b/src/ap/wpa_auth_ie.c @@ -261,7 +261,25 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len, } /* Management Group Cipher Suite */ - RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_AES_128_CMAC); + 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; } #endif /* CONFIG_IEEE80211W */ @@ -295,6 +313,55 @@ int wpa_write_rsn_ie(struct wpa_auth_config *conf, u8 *buf, size_t len, } +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); + } +#ifdef CONFIG_IEEE80211W + if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) { + capab |= WPA_CAPABILITY_MFPC; + if (conf->ieee80211w == MGMT_FRAME_PROTECTION_REQUIRED) + capab |= WPA_CAPABILITY_MFPR; + } +#endif /* CONFIG_IEEE80211W */ + 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]; @@ -302,6 +369,9 @@ int wpa_auth_gen_wpa_ie(struct wpa_authenticator *wpa_auth) 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); @@ -534,7 +604,8 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, return WPA_MGMT_FRAME_PROTECTION_VIOLATION; } - if (data.mgmt_group_cipher != WPA_CIPHER_AES_128_CMAC) { + 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; @@ -604,7 +675,7 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, break; } } - if (sm->pmksa) { + if (sm->pmksa && pmkid) { wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG, "PMKID found from PMKSA cache " "eap_type=%d vlan_id=%d", @@ -626,6 +697,36 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth, } +#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 */ + + /** * wpa_parse_generic - Parse EAPOL-Key Key Data Generic IEs * @pos: Pointer to the IE header @@ -648,6 +749,12 @@ static int wpa_parse_generic(const u8 *pos, const u8 *end, return 0; } + if (pos[1] >= 4 && WPA_GET_BE32(pos + 2) == OSEN_IE_VENDOR_TYPE) { + ie->osen = pos; + ie->osen_len = pos[1] + 2; + return 0; + } + if (pos + 1 + RSN_SELECTOR_LEN < end && pos[1] >= RSN_SELECTOR_LEN + PMKID_LEN && RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_PMKID) { diff --git a/src/ap/wpa_auth_ie.h b/src/ap/wpa_auth_ie.h index f945882..d2067ba 100644 --- a/src/ap/wpa_auth_ie.h +++ b/src/ap/wpa_auth_ie.h @@ -43,6 +43,9 @@ struct wpa_eapol_ie_parse { const u8 *ip_addr_req; const u8 *ip_addr_alloc; #endif /* CONFIG_P2P */ + + const u8 *osen; + size_t osen_len; }; int wpa_parse_kde_ies(const u8 *buf, size_t len, diff --git a/src/ap/wps_hostapd.c b/src/ap/wps_hostapd.c index 1b1dce4..6f16f50 100644 --- a/src/ap/wps_hostapd.c +++ b/src/ap/wps_hostapd.c @@ -40,6 +40,7 @@ static int hostapd_wps_probe_req_rx(void *ctx, const u8 *addr, const u8 *da, 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 { @@ -377,40 +378,11 @@ static int hapd_wps_reconfig_in_memory(struct hostapd_data *hapd, } bss->auth_algs = 1; } else { - if ((cred->auth_type & WPS_AUTH_OPEN) && - (cred->auth_type & WPS_AUTH_SHARED)) - bss->auth_algs = 3; - else if (cred->auth_type & WPS_AUTH_SHARED) - bss->auth_algs = 2; - else - bss->auth_algs = 1; - if (cred->encr_type & WPS_ENCR_WEP && cred->key_idx > 0 && - cred->key_idx <= 4) { - struct hostapd_wep_keys *wep = &bss->ssid.wep; - int idx = cred->key_idx; - if (idx) - idx--; - wep->idx = idx; - if (cred->key_len == 10 || cred->key_len == 26) { - os_free(wep->key[idx]); - wep->key[idx] = os_malloc(cred->key_len / 2); - if (wep->key[idx] == NULL || - hexstr2bin((const char *) cred->key, - wep->key[idx], - cred->key_len / 2)) - return -1; - wep->len[idx] = cred->key_len / 2; - } else { - os_free(wep->key[idx]); - wep->key[idx] = os_malloc(cred->key_len); - if (wep->key[idx] == NULL) - return -1; - os_memcpy(wep->key[idx], cred->key, - cred->key_len); - wep->len[idx] = cred->key_len; - } - wep->keys_set = 1; - } + /* + * 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 @@ -471,6 +443,8 @@ static int hapd_wps_cred_cb(struct hostapd_data *hapd, void *ctx) hapd->wps->ssid_len = cred->ssid_len; hapd->wps->encr_types = 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; @@ -583,31 +557,11 @@ static int hapd_wps_cred_cb(struct hostapd_data *hapd, void *ctx) fprintf(nconf, "auth_algs=1\n"); } else { - if ((cred->auth_type & WPS_AUTH_OPEN) && - (cred->auth_type & WPS_AUTH_SHARED)) - fprintf(nconf, "auth_algs=3\n"); - else if (cred->auth_type & WPS_AUTH_SHARED) - fprintf(nconf, "auth_algs=2\n"); - else - fprintf(nconf, "auth_algs=1\n"); - - if (cred->encr_type & WPS_ENCR_WEP && cred->key_idx <= 4) { - int key_idx = cred->key_idx; - if (key_idx) - key_idx--; - fprintf(nconf, "wep_default_key=%d\n", key_idx); - fprintf(nconf, "wep_key%d=", key_idx); - if (cred->key_len == 10 || cred->key_len == 26) { - /* WEP key as a hex string */ - for (i = 0; i < cred->key_len; i++) - fputc(cred->key[i], nconf); - } else { - /* Raw WEP key; convert to hex */ - for (i = 0; i < cred->key_len; i++) - fprintf(nconf, "%02x", cred->key[i]); - } - fprintf(nconf, "\n"); - } + /* + * 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"); @@ -895,7 +849,7 @@ static int hostapd_wps_rf_band_cb(void *ctx) } -static void hostapd_wps_clear_ies(struct hostapd_data *hapd) +static void hostapd_wps_clear_ies(struct hostapd_data *hapd, int deinit_only) { wpabuf_free(hapd->wps_beacon_ie); hapd->wps_beacon_ie = NULL; @@ -903,6 +857,9 @@ static void hostapd_wps_clear_ies(struct hostapd_data *hapd) wpabuf_free(hapd->wps_probe_resp_ie); hapd->wps_probe_resp_ie = NULL; + if (deinit_only) + return; + hostapd_set_ap_wps_ie(hapd); } @@ -985,6 +942,21 @@ static int hostapd_wps_set_vendor_ext(struct hostapd_data *hapd, } +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); + os_free(wps->network_key); + hostapd_wps_nfc_clear(wps); + wpabuf_free(wps->dh_pubkey); + wpabuf_free(wps->dh_privkey); + os_free(wps); +} + + int hostapd_init_wps(struct hostapd_data *hapd, struct hostapd_bss_config *conf) { @@ -992,7 +964,7 @@ int hostapd_init_wps(struct hostapd_data *hapd, struct wps_registrar_config cfg; if (conf->wps_state == 0) { - hostapd_wps_clear_ies(hapd); + hostapd_wps_clear_ies(hapd, 0); return 0; } @@ -1041,7 +1013,6 @@ int hostapd_init_wps(struct hostapd_data *hapd, os_strdup(hapd->conf->serial_number) : NULL; wps->config_methods = wps_config_methods_str2bin(hapd->conf->config_methods); -#ifdef CONFIG_WPS2 if ((wps->config_methods & (WPS_CONFIG_DISPLAY | WPS_CONFIG_VIRT_DISPLAY | WPS_CONFIG_PHY_DISPLAY)) == WPS_CONFIG_DISPLAY) { @@ -1056,14 +1027,11 @@ int hostapd_init_wps(struct hostapd_data *hapd, "virtual_push_button for WPS 2.0 compliance"); wps->config_methods |= WPS_CONFIG_VIRT_PUSHBUTTON; } -#endif /* CONFIG_WPS2 */ os_memcpy(wps->dev.pri_dev_type, hapd->conf->device_type, WPS_DEV_TYPE_LEN); - if (hostapd_wps_set_vendor_ext(hapd, wps) < 0) { - os_free(wps); - return -1; - } + if (hostapd_wps_set_vendor_ext(hapd, wps) < 0) + goto fail; wps->dev.os_version = WPA_GET_BE32(hapd->conf->os_version); @@ -1102,18 +1070,6 @@ int hostapd_init_wps(struct hostapd_data *hapd, if (conf->ssid.security_policy == SECURITY_PLAINTEXT) { wps->encr_types |= WPS_ENCR_NONE; wps->auth_types |= WPS_AUTH_OPEN; - } else if (conf->ssid.security_policy == SECURITY_STATIC_WEP) { - wps->encr_types |= WPS_ENCR_WEP; - if (conf->auth_algs & WPA_AUTH_ALG_OPEN) - wps->auth_types |= WPS_AUTH_OPEN; - if (conf->auth_algs & WPA_AUTH_ALG_SHARED) - wps->auth_types |= WPS_AUTH_SHARED; - } else if (conf->ssid.security_policy == SECURITY_IEEE_802_1X) { - wps->auth_types |= WPS_AUTH_OPEN; - if (conf->default_wep_key_len) - wps->encr_types |= WPS_ENCR_WEP; - else - wps->encr_types |= WPS_ENCR_NONE; } if (conf->ssid.wpa_psk_file) { @@ -1123,19 +1079,15 @@ int hostapd_init_wps(struct hostapd_data *hapd, 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) { - os_free(wps); - return -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; } 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) { - os_free(wps); - return -1; - } + 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]; @@ -1146,6 +1098,8 @@ int hostapd_init_wps(struct hostapd_data *hapd, 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 */ wps->auth_types = WPS_AUTH_WPA2PSK | WPS_AUTH_WPAPSK; @@ -1179,9 +1133,7 @@ int hostapd_init_wps(struct hostapd_data *hapd, wps->registrar = wps_registrar_init(wps, &cfg); if (wps->registrar == NULL) { wpa_printf(MSG_ERROR, "Failed to initialize WPS Registrar"); - os_free(wps->network_key); - os_free(wps); - return -1; + goto fail; } #ifdef CONFIG_WPS_UPNP @@ -1197,6 +1149,10 @@ int hostapd_init_wps(struct hostapd_data *hapd, hapd->wps = wps; return 0; + +fail: + hostapd_free_wps(wps); + return -1; } @@ -1211,8 +1167,7 @@ int hostapd_init_wps_complete(struct hostapd_data *hapd) if (hostapd_wps_upnp_init(hapd, wps) < 0) { wpa_printf(MSG_ERROR, "Failed to initialize WPS UPnP"); wps_registrar_deinit(wps->registrar); - os_free(wps->network_key); - os_free(wps); + hostapd_free_wps(wps); hapd->wps = NULL; return -1; } @@ -1242,21 +1197,18 @@ 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) + 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); - os_free(hapd->wps->network_key); - wps_device_data_free(&hapd->wps->dev); - wpabuf_free(hapd->wps->dh_pubkey); - wpabuf_free(hapd->wps->dh_privkey); wps_free_pending_msgs(hapd->wps->upnp_msgs); - hostapd_wps_nfc_clear(hapd->wps); - os_free(hapd->wps); + hostapd_free_wps(hapd->wps); hapd->wps = NULL; - hostapd_wps_clear_ies(hapd); + hostapd_wps_clear_ies(hapd, 1); } @@ -1338,7 +1290,7 @@ static int wps_button_pushed(struct hostapd_data *hapd, void *ctx) { const u8 *p2p_dev_addr = ctx; if (hapd->wps == NULL) - return 0; + return -1; return wps_registrar_button_pushed(hapd->wps->registrar, p2p_dev_addr); } @@ -1354,7 +1306,7 @@ int hostapd_wps_button_pushed(struct hostapd_data *hapd, static int wps_cancel(struct hostapd_data *hapd, void *ctx) { if (hapd->wps == NULL) - return 0; + return -1; wps_registrar_wps_cancel(hapd->wps->registrar); ap_for_each_sta(hapd, ap_sta_wps_cancel, NULL); @@ -1475,6 +1427,16 @@ static int hostapd_rx_req_put_wlan_response( 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; @@ -1668,8 +1630,6 @@ int hostapd_wps_config_ap(struct hostapd_data *hapd, const char *ssid, if (encr) { if (os_strncmp(encr, "NONE", 4) == 0) cred.encr_type = WPS_ENCR_NONE; - else if (os_strncmp(encr, "WEP", 3) == 0) - cred.encr_type = WPS_ENCR_WEP; else if (os_strncmp(encr, "TKIP", 4) == 0) cred.encr_type = WPS_ENCR_TKIP; else if (os_strncmp(encr, "CCMP", 4) == 0) diff --git a/src/common/common_module_tests.c b/src/common/common_module_tests.c new file mode 100644 index 0000000..56b1122 --- /dev/null +++ b/src/common/common_module_tests.c @@ -0,0 +1,172 @@ +/* + * common module tests + * Copyright (c) 2014, Jouni Malinen <j@w1.fi> + * + * 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 "ieee802_11_common.h" +#include "wpa_common.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 }, + { NULL, 0, ParseOK, 0 } +}; + +static int ieee802_11_parse_tests(void) +{ + int i, ret = 0; + + 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; + } + + 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; +} + + +int common_module_tests(void) +{ + int ret = 0; + + wpa_printf(MSG_INFO, "common module tests"); + + if (ieee802_11_parse_tests() < 0 || + rsn_ie_parse_tests() < 0) + ret = -1; + + return ret; +} diff --git a/src/common/defs.h b/src/common/defs.h index 4811e8e..d4091e3 100644 --- a/src/common/defs.h +++ b/src/common/defs.h @@ -48,12 +48,14 @@ typedef enum { FALSE = 0, TRUE = 1 } Boolean; #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) static inline int wpa_key_mgmt_wpa_ieee8021x(int akm) { return !!(akm & (WPA_KEY_MGMT_IEEE8021X | WPA_KEY_MGMT_FT_IEEE8021X | WPA_KEY_MGMT_CCKM | + WPA_KEY_MGMT_OSEN | WPA_KEY_MGMT_IEEE8021X_SHA256)); } @@ -82,7 +84,8 @@ static inline int wpa_key_mgmt_sae(int akm) static inline int wpa_key_mgmt_sha256(int akm) { return !!(akm & (WPA_KEY_MGMT_PSK_SHA256 | - WPA_KEY_MGMT_IEEE8021X_SHA256)); + WPA_KEY_MGMT_IEEE8021X_SHA256 | + WPA_KEY_MGMT_OSEN)); } static inline int wpa_key_mgmt_wpa(int akm) @@ -106,6 +109,7 @@ static inline int wpa_key_mgmt_cckm(int akm) #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) diff --git a/src/common/eapol_common.h b/src/common/eapol_common.h index 4811f38..6958661 100644 --- a/src/common/eapol_common.h +++ b/src/common/eapol_common.h @@ -22,17 +22,28 @@ struct ieee802_1x_hdr { /* followed by length octets of data */ } STRUCT_PACKED; +struct ieee8023_hdr { + u8 dest[ETH_ALEN]; + u8 src[ETH_ALEN]; + u16 ethertype; +} STRUCT_PACKED; + #ifdef _MSC_VER #pragma pack(pop) #endif /* _MSC_VER */ +#ifdef CONFIG_MACSEC +#define EAPOL_VERSION 3 +#else /* CONFIG_MACSEC */ #define EAPOL_VERSION 2 +#endif /* CONFIG_MACSEC */ enum { IEEE802_1X_TYPE_EAP_PACKET = 0, IEEE802_1X_TYPE_EAPOL_START = 1, IEEE802_1X_TYPE_EAPOL_LOGOFF = 2, IEEE802_1X_TYPE_EAPOL_KEY = 3, - IEEE802_1X_TYPE_EAPOL_ENCAPSULATED_ASF_ALERT = 4 + IEEE802_1X_TYPE_EAPOL_ENCAPSULATED_ASF_ALERT = 4, + IEEE802_1X_TYPE_EAPOL_MKA = 5, }; enum { EAPOL_KEY_TYPE_RC4 = 1, EAPOL_KEY_TYPE_RSN = 2, diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c index 809089f..faa6a39 100644 --- a/src/common/ieee802_11_common.c +++ b/src/common/ieee802_11_common.c @@ -108,10 +108,15 @@ static int ieee802_11_parse_vendor_specific(const u8 *pos, size_t elen, elems->hs20 = pos; elems->hs20_len = elen; break; + case HS20_OSEN_OUI_TYPE: + /* Hotspot 2.0 OSEN */ + elems->osen = pos; + elems->osen_len = elen; + break; default: wpa_printf(MSG_MSGDUMP, "Unknown WFA " "information element ignored " - "(type=%d len=%lu)\n", + "(type=%d len=%lu)", pos[3], (unsigned long) elen); return -1; } @@ -252,6 +257,11 @@ ParseRes ieee802_11_parse_elems(const u8 *start, size_t len, elems->vht_operation = pos; elems->vht_operation_len = elen; break; + case WLAN_EID_VHT_OPERATING_MODE_NOTIFICATION: + if (elen != 1) + break; + elems->vht_opmode_notif = pos; + break; case WLAN_EID_LINK_ID: if (elen < 18) break; diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h index b84dd9e..9b8bbd1 100644 --- a/src/common/ieee802_11_common.h +++ b/src/common/ieee802_11_common.h @@ -30,6 +30,7 @@ struct ieee802_11_elems { const u8 *ht_operation; const u8 *vht_capabilities; const u8 *vht_operation; + const u8 *vht_opmode_notif; const u8 *vendor_ht_cap; const u8 *p2p; const u8 *wfd; @@ -40,6 +41,7 @@ struct ieee802_11_elems { const u8 *ext_capab; const u8 *bss_max_idle_period; const u8 *ssid_list; + const u8 *osen; u8 ssid_len; u8 supp_rates_len; @@ -68,6 +70,7 @@ struct ieee802_11_elems { u8 hs20_len; u8 ext_capab_len; u8 ssid_list_len; + u8 osen_len; }; typedef enum { ParseOK = 0, ParseUnknown = 1, ParseFailed = -1 } ParseRes; diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index 6f7f777..b8e9254 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -161,6 +161,7 @@ #define WLAN_STATUS_ANTI_CLOGGING_TOKEN_REQ 76 #define WLAN_STATUS_FINITE_CYCLIC_GROUP_NOT_SUPPORTED 77 #define WLAN_STATUS_TRANSMISSION_FAILURE 79 +#define WLAN_STATUS_QUERY_RESP_OUTSTANDING 95 #define WLAN_STATUS_ASSOC_DENIED_NO_VHT 104 /* Reason codes (IEEE 802.11-2007, 7.3.1.7, Table 7-22) */ @@ -223,6 +224,7 @@ #define WLAN_EID_QOS 46 #define WLAN_EID_RSN 48 #define WLAN_EID_EXT_SUPP_RATES 50 +#define WLAN_EID_NEIGHBOR_REPORT 52 #define WLAN_EID_MOBILITY_DOMAIN 54 #define WLAN_EID_FAST_BSS_TRANSITION 55 #define WLAN_EID_TIMEOUT_INTERVAL 56 @@ -584,9 +586,12 @@ struct ieee80211_mgmt { /* Rx MCS bitmask is in the first 77 bits of supported_mcs_set */ #define IEEE80211_HT_MCS_MASK_LEN 10 +/* HT Capabilities element */ struct ieee80211_ht_capabilities { le16 ht_capabilities_info; - u8 a_mpdu_params; + u8 a_mpdu_params; /* Maximum A-MPDU Length Exponent B0..B1 + * Minimum MPDU Start Spacing B2..B4 + * Reserved B5..B7 */ u8 supported_mcs_set[16]; le16 ht_extended_capabilities; le32 tx_bf_capability_info; @@ -594,12 +599,14 @@ struct ieee80211_ht_capabilities { } STRUCT_PACKED; +/* HT Operation element */ struct ieee80211_ht_operation { - u8 control_chan; - u8 ht_param; - le16 operation_mode; - le16 stbc_param; - u8 basic_set[16]; + u8 primary_chan; + /* Five octets of HT Operation Information */ + u8 ht_param; /* B0..B7 */ + le16 operation_mode; /* B8..B23 */ + le16 param; /* B24..B39 */ + u8 basic_mcs_set[16]; } STRUCT_PACKED; @@ -639,7 +646,9 @@ struct ieee80211_vht_operation { #define ERP_INFO_USE_PROTECTION BIT(1) #define ERP_INFO_BARKER_PREAMBLE_MODE BIT(2) +#define OVERLAPPING_BSS_TRANS_DELAY_FACTOR 5 +/* HT Capabilities Info field within HT Capabilities element */ #define HT_CAP_INFO_LDPC_CODING_CAP ((u16) BIT(0)) #define HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET ((u16) BIT(1)) #define HT_CAP_INFO_SMPS_MASK ((u16) (BIT(2) | BIT(3))) @@ -657,73 +666,86 @@ struct ieee80211_vht_operation { #define HT_CAP_INFO_DELAYED_BA ((u16) BIT(10)) #define HT_CAP_INFO_MAX_AMSDU_SIZE ((u16) BIT(11)) #define HT_CAP_INFO_DSSS_CCK40MHZ ((u16) BIT(12)) -#define HT_CAP_INFO_PSMP_SUPP ((u16) BIT(13)) +/* B13 - Reserved (was PSMP support during P802.11n development) */ #define HT_CAP_INFO_40MHZ_INTOLERANT ((u16) BIT(14)) #define HT_CAP_INFO_LSIG_TXOP_PROTECT_SUPPORT ((u16) BIT(15)) - +/* HT Extended Capabilities field within HT Capabilities element */ #define EXT_HT_CAP_INFO_PCO ((u16) BIT(0)) +#define EXT_HT_CAP_INFO_PCO_TRANS_TIME_MASK ((u16) (BIT(1) | BIT(2))) #define EXT_HT_CAP_INFO_TRANS_TIME_OFFSET 1 +/* B3..B7 - Reserved */ +#define EXT_HT_CAP_INFO_MCS_FEEDBACK_MASK ((u16) (BIT(8) | BIT(9))) #define EXT_HT_CAP_INFO_MCS_FEEDBACK_OFFSET 8 -#define EXT_HT_CAP_INFO_HTC_SUPPORTED ((u16) BIT(10)) +#define EXT_HT_CAP_INFO_HTC_SUPPORT ((u16) BIT(10)) #define EXT_HT_CAP_INFO_RD_RESPONDER ((u16) BIT(11)) - - -#define TX_BEAMFORM_CAP_TXBF_CAP ((u32) BIT(0)) -#define TX_BEAMFORM_CAP_RX_STAGGERED_SOUNDING_CAP ((u32) BIT(1)) -#define TX_BEAMFORM_CAP_TX_STAGGERED_SOUNDING_CAP ((u32) BIT(2)) -#define TX_BEAMFORM_CAP_RX_ZLF_CAP ((u32) BIT(3)) -#define TX_BEAMFORM_CAP_TX_ZLF_CAP ((u32) BIT(4)) -#define TX_BEAMFORM_CAP_IMPLICIT_ZLF_CAP ((u32) BIT(5)) -#define TX_BEAMFORM_CAP_CALIB_OFFSET 6 -#define TX_BEAMFORM_CAP_EXPLICIT_CSI_TXBF_CAP ((u32) BIT(8)) -#define TX_BEAMFORM_CAP_EXPLICIT_UNCOMPR_STEERING_MATRIX_CAP ((u32) BIT(9)) -#define TX_BEAMFORM_CAP_EXPLICIT_BF_CSI_FEEDBACK_CAP ((u32) BIT(10)) -#define TX_BEAMFORM_CAP_EXPLICIT_BF_CSI_FEEDBACK_OFFSET 11 -#define TX_BEAMFORM_CAP_EXPLICIT_UNCOMPR_STEERING_MATRIX_FEEDBACK_OFFSET 13 -#define TX_BEAMFORM_CAP_EXPLICIT_COMPRESSED_STEERING_MATRIX_FEEDBACK_OFFSET 15 -#define TX_BEAMFORM_CAP_MINIMAL_GROUPING_OFFSET 17 -#define TX_BEAMFORM_CAP_CSI_NUM_BEAMFORMER_ANT_OFFSET 19 -#define TX_BEAMFORM_CAP_UNCOMPRESSED_STEERING_MATRIX_BEAMFORMER_ANT_OFFSET 21 -#define TX_BEAMFORM_CAP_COMPRESSED_STEERING_MATRIX_BEAMFORMER_ANT_OFFSET 23 -#define TX_BEAMFORM_CAP_SCI_MAX_OF_ROWS_BEANFORMER_SUPPORTED_OFFSET 25 - - -#define ASEL_CAPABILITY_ASEL_CAPABLE ((u8) BIT(0)) -#define ASEL_CAPABILITY_EXPLICIT_CSI_FEEDBACK_BASED_TX_AS_CAP ((u8) BIT(1)) -#define ASEL_CAPABILITY_ANT_INDICES_FEEDBACK_BASED_TX_AS_CAP ((u8) BIT(2)) -#define ASEL_CAPABILITY_EXPLICIT_CSI_FEEDBACK_CAP ((u8) BIT(3)) -#define ASEL_CAPABILITY_ANT_INDICES_FEEDBACK_CAP ((u8) BIT(4)) -#define ASEL_CAPABILITY_RX_AS_CAP ((u8) BIT(5)) -#define ASEL_CAPABILITY_TX_SOUND_PPDUS_CAP ((u8) BIT(6)) - +/* B12..B15 - Reserved */ + +/* Transmit Beanforming Capabilities within HT Capabilities element */ +#define TX_BF_CAP_IMPLICIT_TXBF_RX_CAP ((u32) BIT(0)) +#define TX_BF_CAP_RX_STAGGERED_SOUNDING_CAP ((u32) BIT(1)) +#define TX_BF_CAP_TX_STAGGERED_SOUNDING_CAP ((u32) BIT(2)) +#define TX_BF_CAP_RX_NDP_CAP ((u32) BIT(3)) +#define TX_BF_CAP_TX_NDP_CAP ((u32) BIT(4)) +#define TX_BF_CAP_IMPLICIT_TX_BF_CAP ((u32) BIT(5)) +#define TX_BF_CAP_CALIBRATION_MASK ((u32) (BIT(6) | BIT(7)) +#define TX_BF_CAP_CALIB_OFFSET 6 +#define TX_BF_CAP_EXPLICIT_CSI_TXBF_CAP ((u32) BIT(8)) +#define TX_BF_CAP_EXPLICIT_NONCOMPR_STEERING_CAP ((u32) BIT(9)) +#define TX_BF_CAP_EXPLICIT_COMPR_STEERING_CAP ((u32) BIT(10)) +#define TX_BF_CAP_EXPLICIT_TX_BF_CSI_FEEDBACK_MASK ((u32) (BIT(10) | BIT(11))) +#define TX_BF_CAP_EXPLICIT_BF_CSI_FEEDBACK_OFFSET 11 +#define TX_BF_CAP_EXPLICIT_UNCOMPR_STEERING_MATRIX_FEEDBACK_OFFSET 13 +#define TX_BF_CAP_EXPLICIT_COMPRESSED_STEERING_MATRIX_FEEDBACK_OFFSET 15 +#define TX_BF_CAP_MINIMAL_GROUPING_OFFSET 17 +#define TX_BF_CAP_CSI_NUM_BEAMFORMER_ANT_OFFSET 19 +#define TX_BF_CAP_UNCOMPRESSED_STEERING_MATRIX_BEAMFORMER_ANT_OFFSET 21 +#define TX_BF_CAP_COMPRESSED_STEERING_MATRIX_BEAMFORMER_ANT_OFFSET 23 +#define TX_BF_CAP_SCI_MAX_OF_ROWS_BEANFORMER_SUPPORTED_OFFSET 25 +#define TX_BF_CAP_CHANNEL_ESTIMATION_CAP_MASK ((u32) (BIT(27) | BIT(28))) +#define TX_BF_CAP_CHANNEL_ESTIMATION_CAP_OFFSET 27 +/* B29..B31 - Reserved */ + +/* ASEL Capability field within HT Capabilities element */ +#define ASEL_CAP_ASEL_CAPABLE ((u8) BIT(0)) +#define ASEL_CAP_EXPLICIT_CSI_FEEDBACK_BASED_TX_AS_CAP ((u8) BIT(1)) +#define ASEL_CAP_ANT_INDICES_FEEDBACK_BASED_TX_AS_CAP ((u8) BIT(2)) +#define ASEL_CAP_EXPLICIT_CSI_FEEDBACK_CAP ((u8) BIT(3)) +#define ASEL_CAP_ANT_INDICES_FEEDBACK_CAP ((u8) BIT(4)) +#define ASEL_CAP_RX_AS_CAP ((u8) BIT(5)) +#define ASEL_CAP_TX_SOUNDING_PPDUS_CAP ((u8) BIT(6)) +/* B7 - Reserved */ + +/* First octet of HT Operation Information within HT Operation element */ #define HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK ((u8) BIT(0) | BIT(1)) #define HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE ((u8) BIT(0)) #define HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW ((u8) BIT(0) | BIT(1)) -#define HT_INFO_HT_PARAM_REC_TRANS_CHNL_WIDTH ((u8) BIT(2)) +#define HT_INFO_HT_PARAM_STA_CHNL_WIDTH ((u8) BIT(2)) #define HT_INFO_HT_PARAM_RIFS_MODE ((u8) BIT(3)) -#define HT_INFO_HT_PARAM_CTRL_ACCESS_ONLY ((u8) BIT(4)) -#define HT_INFO_HT_PARAM_SRV_INTERVAL_GRANULARITY ((u8) BIT(5)) - - -#define OP_MODE_PURE 0 -#define OP_MODE_MAY_BE_LEGACY_STAS 1 -#define OP_MODE_20MHZ_HT_STA_ASSOCED 2 -#define OP_MODE_MIXED 3 - -#define HT_INFO_OPERATION_MODE_OP_MODE_MASK \ - (0x0001 | 0x0002) -#define HT_INFO_OPERATION_MODE_OP_MODE_OFFSET 0 -#define HT_INFO_OPERATION_MODE_NON_GF_DEVS_PRESENT ((u8) BIT(2)) -#define HT_INFO_OPERATION_MODE_TRANSMIT_BURST_LIMIT ((u8) BIT(3)) -#define HT_INFO_OPERATION_MODE_NON_HT_STA_PRESENT ((u8) BIT(4)) - -#define HT_INFO_STBC_PARAM_DUAL_BEACON ((u16) BIT(6)) -#define HT_INFO_STBC_PARAM_DUAL_STBC_PROTECT ((u16) BIT(7)) -#define HT_INFO_STBC_PARAM_SECONDARY_BCN ((u16) BIT(8)) -#define HT_INFO_STBC_PARAM_LSIG_TXOP_PROTECT_ALLOWED ((u16) BIT(9)) -#define HT_INFO_STBC_PARAM_PCO_ACTIVE ((u16) BIT(10)) -#define HT_INFO_STBC_PARAM_PCO_PHASE ((u16) BIT(11)) +/* B4..B7 - Reserved */ + +/* HT Protection (B8..B9 of HT Operation Information) */ +#define HT_PROT_NO_PROTECTION 0 +#define HT_PROT_NONMEMBER_PROTECTION 1 +#define HT_PROT_20MHZ_PROTECTION 2 +#define HT_PROT_NON_HT_MIXED 3 +/* Bits within ieee80211_ht_operation::operation_mode (BIT(0) maps to B8 in + * HT Operation Information) */ +#define HT_OPER_OP_MODE_HT_PROT_MASK ((u16) (BIT(0) | BIT(1))) /* B8..B9 */ +#define HT_OPER_OP_MODE_NON_GF_HT_STAS_PRESENT ((u16) BIT(2)) /* B10 */ +/* BIT(3), i.e., B11 in HT Operation Information field - Reserved */ +#define HT_OPER_OP_MODE_OBSS_NON_HT_STAS_PRESENT ((u16) BIT(4)) /* B12 */ +/* BIT(5)..BIT(15), i.e., B13..B23 - Reserved */ + +/* Last two octets of HT Operation Information (BIT(0) = B24) */ +/* B24..B29 - Reserved */ +#define HT_OPER_PARAM_DUAL_BEACON ((u16) BIT(6)) +#define HT_OPER_PARAM_DUAL_CTS_PROTECTION ((u16) BIT(7)) +#define HT_OPER_PARAM_STBC_BEACON ((u16) BIT(8)) +#define HT_OPER_PARAM_LSIG_TXOP_PROT_FULL_SUPP ((u16) BIT(9)) +#define HT_OPER_PARAM_PCO_ACTIVE ((u16) BIT(10)) +#define HT_OPER_PARAM_PCO_PHASE ((u16) BIT(11)) +/* B36..B39 - Reserved */ #define BSS_MEMBERSHIP_SELECTOR_VHT_PHY 126 #define BSS_MEMBERSHIP_SELECTOR_HT_PHY 127 @@ -757,13 +779,27 @@ struct ieee80211_vht_operation { #define VHT_CAP_MU_BEAMFORMEE_CAPABLE ((u32) BIT(20)) #define VHT_CAP_VHT_TXOP_PS ((u32) BIT(21)) #define VHT_CAP_HTC_VHT ((u32) BIT(22)) -#define VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT ((u32) BIT(23) | \ + +#define VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_1 ((u32) BIT(23)) +#define VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_2 ((u32) BIT(24)) +#define VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_3 ((u32) BIT(23) | BIT(24)) +#define VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_4 ((u32) BIT(25)) +#define VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_5 ((u32) BIT(23) | BIT(25)) +#define VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_6 ((u32) BIT(24) | BIT(25)) +#define VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX ((u32) BIT(23) | \ BIT(24) | BIT(25)) #define VHT_CAP_VHT_LINK_ADAPTATION_VHT_UNSOL_MFB ((u32) BIT(27)) #define VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB ((u32) BIT(26) | BIT(27)) #define VHT_CAP_RX_ANTENNA_PATTERN ((u32) BIT(28)) #define VHT_CAP_TX_ANTENNA_PATTERN ((u32) BIT(29)) +#define VHT_OPMODE_CHANNEL_WIDTH_MASK ((u8) BIT(0) | BIT(1)) +#define VHT_OPMODE_CHANNEL_RxNSS_MASK ((u8) BIT(4) | BIT(5) | \ + BIT(6)) +#define VHT_OPMODE_NOTIF_RX_NSS_SHIFT 4 + +#define VHT_RX_NSS_MAX_STREAMS 8 + /* VHT channel widths */ #define VHT_CHANWIDTH_USE_HT 0 #define VHT_CHANWIDTH_80MHZ 1 @@ -779,6 +815,7 @@ struct ieee80211_vht_operation { #define WFD_IE_VENDOR_TYPE 0x506f9a0a #define WFD_OUI_TYPE 10 #define HS20_IE_VENDOR_TYPE 0x506f9a10 +#define OSEN_IE_VENDOR_TYPE 0x506f9a12 #define WMM_OUI_TYPE 2 #define WMM_OUI_SUBTYPE_INFORMATION_ELEMENT 0 @@ -894,6 +931,7 @@ enum { #define HS20_INDICATION_OUI_TYPE 16 #define HS20_ANQP_OUI_TYPE 17 +#define HS20_OSEN_OUI_TYPE 18 #define HS20_STYPE_QUERY_LIST 1 #define HS20_STYPE_CAPABILITY_LIST 2 #define HS20_STYPE_OPERATOR_FRIENDLY_NAME 3 @@ -901,6 +939,21 @@ enum { #define HS20_STYPE_CONNECTION_CAPABILITY 5 #define HS20_STYPE_NAI_HOME_REALM_QUERY 6 #define HS20_STYPE_OPERATING_CLASS 7 +#define HS20_STYPE_OSU_PROVIDERS_LIST 8 +#define HS20_STYPE_ICON_REQUEST 10 +#define HS20_STYPE_ICON_BINARY_FILE 11 + +#define HS20_DGAF_DISABLED 0x01 +#define HS20_PPS_MO_ID_PRESENT 0x02 +#define HS20_ANQP_DOMAIN_ID_PRESENT 0x04 +#define HS20_VERSION 0x10 /* Release 2 */ + +/* WNM-Notification WFA vendors specific subtypes */ +#define HS20_WNM_SUB_REM_NEEDED 0 +#define HS20_WNM_DEAUTH_IMMINENT_NOTICE 1 + +#define HS20_DEAUTH_REASON_CODE_BSS 0 +#define HS20_DEAUTH_REASON_CODE_ESS 1 /* Wi-Fi Direct (P2P) */ @@ -1066,7 +1119,10 @@ enum wifi_display_subelem { #define WLAN_AKM_SUITE_PSK 0x000FAC02 #define WLAN_AKM_SUITE_FT_8021X 0x000FAC03 #define WLAN_AKM_SUITE_FT_PSK 0x000FAC04 +#define WLAN_AKM_SUITE_8021X_SHA256 0x000FAC05 +#define WLAN_AKM_SUITE_PSK_SHA256 0x000FAC06 #define WLAN_AKM_SUITE_CCKM 0x00409600 +#define WLAN_AKM_SUITE_OSEN 0x506f9a01 /* IEEE 802.11v - WNM Action field values */ diff --git a/src/common/ieee802_1x_defs.h b/src/common/ieee802_1x_defs.h new file mode 100644 index 0000000..cc88caa --- /dev/null +++ b/src/common/ieee802_1x_defs.h @@ -0,0 +1,78 @@ +/* + * IEEE Std 802.1X-2010 definitions + * 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 IEEE802_1X_DEFS_H +#define IEEE802_1X_DEFS_H + +#define CS_ID_LEN 8 +#define CS_ID_GCM_AES_128 {0x00, 0x80, 0x02, 0x00, 0x01, 0x00, 0x00, 0x01} +#define CS_NAME_GCM_AES_128 "GCM-AES-128" + +enum macsec_policy { + /** + * Should secure sessions. + * This accepts key server's advice to determine whether to secure the + * session or not. + */ + SHOULD_SECURE, + + /** + * Disabled MACsec - do not secure sessions. + */ + DO_NOT_SECURE, +}; + + +/* IEEE Std 802.1X-2010 - Table 11-6 - MACsec Capability */ +enum macsec_cap { + /** + * MACsec is not implemented + */ + MACSEC_CAP_NOT_IMPLEMENTED, + + /** + * 'Integrity without confidentiality' + */ + MACSEC_CAP_INTEGRITY, + + /** + * 'Integrity without confidentiality' and + * 'Integrity and confidentiality' with a confidentiality offset of 0 + */ + MACSEC_CAP_INTEG_AND_CONF, + + /** + * 'Integrity without confidentiality' and + * 'Integrity and confidentiality' with a confidentiality offset of 0, + * 30, 50 + */ + MACSEC_CAP_INTEG_AND_CONF_0_30_50, +}; + +enum validate_frames { + Disabled, + Checked, + Strict, +}; + +/* IEEE Std 802.1X-2010 - Table 11-6 - Confidentiality Offset */ +enum confidentiality_offset { + CONFIDENTIALITY_NONE = 0, + CONFIDENTIALITY_OFFSET_0 = 1, + CONFIDENTIALITY_OFFSET_30 = 2, + CONFIDENTIALITY_OFFSET_50 = 3, +}; + +/* IEEE Std 802.1X-2010 - Table 9-2 */ +#define DEFAULT_PRIO_INFRA_PORT 0x10 +#define DEFAULT_PRIO_PRIMRAY_AP 0x30 +#define DEFAULT_PRIO_SECONDARY_AP 0x50 +#define DEFAULT_PRIO_GROUP_CA_MEMBER 0x70 +#define DEFAULT_PRIO_NOT_KEY_SERVER 0xFF + +#endif /* IEEE802_1X_DEFS_H */ diff --git a/src/common/qca-vendor-attr.h b/src/common/qca-vendor-attr.h new file mode 100644 index 0000000..6f51803 --- /dev/null +++ b/src/common/qca-vendor-attr.h @@ -0,0 +1,28 @@ +/* + * Qualcomm Atheros vendor specific attribute definitions + * Copyright (c) 2014, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef QCA_VENDOR_ATTR_H +#define QCA_VENDOR_ATTR_H + +/* + * This file defines some of the attributes used with Qualcomm Atheros OUI + * 00:13:74 in a way that is not suitable for qca-vendor.h, e.g., due to + * compiler dependencies. + */ + +struct qca_avoid_freq_range { + u32 start_freq; + u32 end_freq; +} __attribute__ ((packed)); + +struct qca_avoid_freq_list { + u32 count; + struct qca_avoid_freq_range range[0]; +} __attribute__ ((packed)); + +#endif /* QCA_VENDOR_ATTR_H */ diff --git a/src/common/qca-vendor.h b/src/common/qca-vendor.h index 0d83920..a56b188 100644 --- a/src/common/qca-vendor.h +++ b/src/common/qca-vendor.h @@ -19,6 +19,13 @@ #define OUI_QCA 0x001374 /** + * enum qca_radiotap_vendor_ids - QCA radiotap vendor namespace IDs + */ +enum qca_radiotap_vendor_ids { + QCA_RADIOTAP_VID_WLANTEST = 0, +}; + +/** * enum qca_nl80211_vendor_subcmds - QCA nl80211 vendor command identifiers * * @QCA_NL80211_VENDOR_SUBCMD_UNSPEC: Reserved value 0 @@ -29,23 +36,37 @@ * ranges to avoid to reduce issues due to interference or internal * co-existence information in the driver. The event data structure is * defined in struct qca_avoid_freq_list. + * + * @QCA_NL80211_VENDOR_SUBCMD_DFS_CAPABILITY: Command to check driver support + * for DFS offloading. + * + * @QCA_NL80211_VENDOR_SUBCMD_NAN: NAN command/event which is used to pass + * NAN Request/Response and NAN Indication messages. These messages are + * interpreted between the framework and the firmware component. */ enum qca_nl80211_vendor_subcmds { QCA_NL80211_VENDOR_SUBCMD_UNSPEC = 0, QCA_NL80211_VENDOR_SUBCMD_TEST = 1, /* subcmds 2..9 not yet allocated */ QCA_NL80211_VENDOR_SUBCMD_AVOID_FREQUENCY = 10, + QCA_NL80211_VENDOR_SUBCMD_DFS_CAPABILITY = 11, + QCA_NL80211_VENDOR_SUBCMD_NAN = 12, + QCA_NL80211_VENDOR_SUBMCD_STATS_EXT = 13, + /* 14..19 - reserved for QCA */ }; -struct qca_avoid_freq_range { - u32 start_freq; - u32 end_freq; -} STRUCT_PACKED; - -struct qca_avoid_freq_list { - u32 count; - struct qca_avoid_freq_range range[0]; -} STRUCT_PACKED; +enum qca_wlan_vendor_attr { + QCA_WLAN_VENDOR_ATTR_INVALID = 0, + /* used by QCA_NL80211_VENDOR_SUBCMD_DFS_CAPABILITY */ + QCA_WLAN_VENDOR_ATTR_DFS = 1, + /* used by QCA_NL80211_VENDOR_SUBCMD_NAN */ + QCA_WLAN_VENDOR_ATTR_NAN = 2, + /* used by QCA_NL80211_VENDOR_SUBCMD_STATS_EXT */ + QCA_WLAN_VENDOR_ATTR_STATS_EXT = 3, + /* keep last */ + QCA_WLAN_VENDOR_ATTR_AFTER_LAST, + QCA_WLAN_VENDOR_ATTR_MAX = QCA_WLAN_VENDOR_ATTR_AFTER_LAST - 1, +}; #endif /* QCA_VENDOR_H */ diff --git a/src/common/sae.c b/src/common/sae.c index 08bf054..c1b488e 100644 --- a/src/common/sae.c +++ b/src/common/sae.c @@ -134,8 +134,10 @@ static struct crypto_bignum * sae_get_rand(struct sae_data *sae) return NULL; if (crypto_bignum_is_zero(bn) || crypto_bignum_is_one(bn) || - crypto_bignum_cmp(bn, sae->tmp->order) >= 0) + crypto_bignum_cmp(bn, sae->tmp->order) >= 0) { + crypto_bignum_deinit(bn, 0); continue; + } break; } @@ -503,6 +505,8 @@ int sae_prepare_commit(const u8 *addr1, const u8 *addr2, const u8 *password, size_t password_len, struct sae_data *sae) { + if (sae->tmp == NULL) + return -1; if (sae->tmp->ec && sae_derive_pwe_ecc(sae, addr1, addr2, password, password_len) < 0) return -1; @@ -634,7 +638,8 @@ fail: int sae_process_commit(struct sae_data *sae) { u8 k[SAE_MAX_PRIME_LEN]; - if ((sae->tmp->ec && sae_derive_k_ecc(sae, k) < 0) || + if (sae->tmp == NULL || + (sae->tmp->ec && sae_derive_k_ecc(sae, k) < 0) || (sae->tmp->dh && sae_derive_k_ffc(sae, k) < 0) || sae_derive_keys(sae, k) < 0) return -1; @@ -646,6 +651,10 @@ void sae_write_commit(struct sae_data *sae, struct wpabuf *buf, const struct wpabuf *token) { u8 *pos; + + if (sae->tmp == NULL) + return; + wpabuf_put_le16(buf, sae->group); /* Finite Cyclic Group */ if (token) wpabuf_put_buf(buf, token); @@ -990,6 +999,9 @@ void sae_write_confirm(struct sae_data *sae, struct wpabuf *buf) { const u8 *sc; + if (sae->tmp == NULL) + return; + /* Send-Confirm */ sc = wpabuf_put(buf, 0); wpabuf_put_le16(buf, sae->send_confirm); @@ -1021,6 +1033,11 @@ int sae_check_confirm(struct sae_data *sae, const u8 *data, size_t len) wpa_printf(MSG_DEBUG, "SAE: peer-send-confirm %u", WPA_GET_LE16(data)); + if (sae->tmp == NULL) { + wpa_printf(MSG_DEBUG, "SAE: Temporary data not yet available"); + return -1; + } + if (sae->tmp->ec) sae_cn_confirm_ecc(sae, data, sae->peer_commit_scalar, sae->tmp->peer_commit_element_ecc, diff --git a/src/common/tnc.h b/src/common/tnc.h new file mode 100644 index 0000000..108acf9 --- /dev/null +++ b/src/common/tnc.h @@ -0,0 +1,121 @@ +/* + * TNC - Common defines + * Copyright (c) 2007-2014, Jouni Malinen <j@w1.fi> + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef TNC_H +#define TNC_H + +typedef unsigned long TNC_UInt32; +typedef unsigned char *TNC_BufferReference; + +typedef TNC_UInt32 TNC_IMVID; +typedef TNC_UInt32 TNC_IMCID; +typedef TNC_UInt32 TNC_ConnectionID; +typedef TNC_UInt32 TNC_ConnectionState; +typedef TNC_UInt32 TNC_RetryReason; +typedef TNC_UInt32 TNC_IMV_Action_Recommendation; +typedef TNC_UInt32 TNC_IMV_Evaluation_Result; +typedef TNC_UInt32 TNC_MessageType; +typedef TNC_MessageType *TNC_MessageTypeList; +typedef TNC_UInt32 TNC_VendorID; +typedef TNC_UInt32 TNC_Subtype; +typedef TNC_UInt32 TNC_MessageSubtype; +typedef TNC_UInt32 TNC_Version; +typedef TNC_UInt32 TNC_Result; +typedef TNC_UInt32 TNC_AttributeID; + +typedef TNC_Result (*TNC_TNCS_BindFunctionPointer)( + TNC_IMVID imvID, + char *functionName, + void **pOutfunctionPointer); +typedef TNC_Result (*TNC_TNCS_ReportMessageTypesPointer)( + TNC_IMVID imvID, + TNC_MessageTypeList supportedTypes, + TNC_UInt32 typeCount); +typedef TNC_Result (*TNC_TNCS_SendMessagePointer)( + TNC_IMVID imvID, + TNC_ConnectionID connectionID, + TNC_BufferReference message, + TNC_UInt32 messageLength, + TNC_MessageType messageType); +typedef TNC_Result (*TNC_TNCS_RequestHandshakeRetryPointer)( + TNC_IMVID imvID, + TNC_ConnectionID connectionID, + TNC_RetryReason reason); +typedef TNC_Result (*TNC_TNCS_ProvideRecommendationPointer)( + TNC_IMVID imvID, + TNC_ConnectionID connectionID, + TNC_IMV_Action_Recommendation recommendation, + TNC_IMV_Evaluation_Result evaluation); +typedef TNC_Result (*TNC_TNCC_BindFunctionPointer)( + TNC_IMCID imcID, + char *functionName, + void **pOutfunctionPointer); +typedef TNC_Result (*TNC_TNCC_SendMessagePointer)( + TNC_IMCID imcID, + TNC_ConnectionID connectionID, + TNC_BufferReference message, + TNC_UInt32 messageLength, + TNC_MessageType messageType); +typedef TNC_Result (*TNC_TNCC_ReportMessageTypesPointer)( + TNC_IMCID imcID, + TNC_MessageTypeList supportedTypes, + TNC_UInt32 typeCount); +typedef TNC_Result (*TNC_TNCC_RequestHandshakeRetryPointer)( + TNC_IMCID imcID, + TNC_ConnectionID connectionID, + TNC_RetryReason reason); + +#define TNC_IFIMV_VERSION_1 1 +#define TNC_IFIMC_VERSION_1 1 + +#define TNC_RESULT_SUCCESS 0 +#define TNC_RESULT_NOT_INITIALIZED 1 +#define TNC_RESULT_ALREADY_INITIALIZED 2 +#define TNC_RESULT_NO_COMMON_VERSION 3 +#define TNC_RESULT_CANT_RETRY 4 +#define TNC_RESULT_WONT_RETRY 5 +#define TNC_RESULT_INVALID_PARAMETER 6 +#define TNC_RESULT_CANT_RESPOND 7 +#define TNC_RESULT_ILLEGAL_OPERATION 8 +#define TNC_RESULT_OTHER 9 +#define TNC_RESULT_FATAL 10 + +#define TNC_CONNECTION_STATE_CREATE 0 +#define TNC_CONNECTION_STATE_HANDSHAKE 1 +#define TNC_CONNECTION_STATE_ACCESS_ALLOWED 2 +#define TNC_CONNECTION_STATE_ACCESS_ISOLATED 3 +#define TNC_CONNECTION_STATE_ACCESS_NONE 4 +#define TNC_CONNECTION_STATE_DELETE 5 + +#define TNC_VENDORID_ANY ((TNC_VendorID) 0xffffff) +#define TNC_SUBTYPE_ANY ((TNC_Subtype) 0xff) + +/* TNCC-TNCS Message Types */ +#define TNC_TNCCS_RECOMMENDATION 0x00000001 +#define TNC_TNCCS_ERROR 0x00000002 +#define TNC_TNCCS_PREFERREDLANGUAGE 0x00000003 +#define TNC_TNCCS_REASONSTRINGS 0x00000004 + +/* Possible TNC_IMV_Action_Recommendation values: */ +enum IMV_Action_Recommendation { + TNC_IMV_ACTION_RECOMMENDATION_ALLOW, + TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS, + TNC_IMV_ACTION_RECOMMENDATION_ISOLATE, + TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION +}; + +/* Possible TNC_IMV_Evaluation_Result values: */ +enum IMV_Evaluation_Result { + TNC_IMV_EVALUATION_RESULT_COMPLIANT, + TNC_IMV_EVALUATION_RESULT_NONCOMPLIANT_MINOR, + TNC_IMV_EVALUATION_RESULT_NONCOMPLIANT_MAJOR, + TNC_IMV_EVALUATION_RESULT_ERROR, + TNC_IMV_EVALUATION_RESULT_DONT_KNOW +}; + +#endif /* TNC_H */ diff --git a/src/common/version.h b/src/common/version.h index 0edf11c..340afc7 100644 --- a/src/common/version.h +++ b/src/common/version.h @@ -5,6 +5,6 @@ #define VERSION_STR_POSTFIX "" #endif /* VERSION_STR_POSTFIX */ -#define VERSION_STR "2.1" VERSION_STR_POSTFIX +#define VERSION_STR "2.2" VERSION_STR_POSTFIX #endif /* VERSION_H */ diff --git a/src/common/wpa_common.c b/src/common/wpa_common.c index 37b265d..adb22c7 100644 --- a/src/common/wpa_common.c +++ b/src/common/wpa_common.c @@ -56,6 +56,11 @@ int wpa_eapol_key_mic(const u8 *key, int ver, const u8 *buf, size_t len, case WPA_KEY_INFO_TYPE_AES_128_CMAC: return omac1_aes_128(key, buf, len, mic); #endif /* CONFIG_IEEE80211R || CONFIG_IEEE80211W */ +#ifdef CONFIG_HS20 + case WPA_KEY_INFO_TYPE_AKM_DEFINED: + /* FIX: This should be based on negotiated AKM */ + return omac1_aes_128(key, buf, len, mic); +#endif /* CONFIG_HS20 */ default: return -1; } @@ -363,6 +368,8 @@ static int rsn_selector_to_bitfield(const u8 *s) return WPA_CIPHER_BIP_GMAC_256; if (RSN_SELECTOR_GET(s) == RSN_CIPHER_SUITE_BIP_CMAC_256) return WPA_CIPHER_BIP_CMAC_256; + if (RSN_SELECTOR_GET(s) == RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED) + return WPA_CIPHER_GTK_NOT_USED; return 0; } @@ -395,6 +402,26 @@ static int rsn_key_mgmt_to_bitfield(const u8 *s) } +static int wpa_cipher_valid_group(int cipher) +{ + return wpa_cipher_valid_pairwise(cipher) || + cipher == WPA_CIPHER_WEP104 || + cipher == WPA_CIPHER_WEP40 || + cipher == WPA_CIPHER_GTK_NOT_USED; +} + + +#ifdef CONFIG_IEEE80211W +int wpa_cipher_valid_mgmt_group(int cipher) +{ + return cipher == WPA_CIPHER_AES_128_CMAC || + cipher == WPA_CIPHER_BIP_GMAC_128 || + cipher == WPA_CIPHER_BIP_GMAC_256 || + cipher == WPA_CIPHER_BIP_CMAC_256; +} +#endif /* CONFIG_IEEE80211W */ + + /** * wpa_parse_wpa_ie_rsn - Parse RSN IE * @rsn_ie: Buffer containing RSN IE @@ -450,13 +477,11 @@ int wpa_parse_wpa_ie_rsn(const u8 *rsn_ie, size_t rsn_ie_len, if (left >= RSN_SELECTOR_LEN) { data->group_cipher = rsn_selector_to_bitfield(pos); -#ifdef CONFIG_IEEE80211W - if (data->group_cipher == WPA_CIPHER_AES_128_CMAC) { - wpa_printf(MSG_DEBUG, "%s: AES-128-CMAC used as group " - "cipher", __func__); + if (!wpa_cipher_valid_group(data->group_cipher)) { + wpa_printf(MSG_DEBUG, "%s: invalid group cipher 0x%x", + __func__, data->group_cipher); return -1; } -#endif /* CONFIG_IEEE80211W */ pos += RSN_SELECTOR_LEN; left -= RSN_SELECTOR_LEN; } else if (left > 0) { @@ -541,7 +566,7 @@ int wpa_parse_wpa_ie_rsn(const u8 *rsn_ie, size_t rsn_ie_len, #ifdef CONFIG_IEEE80211W if (left >= 4) { data->mgmt_group_cipher = rsn_selector_to_bitfield(pos); - if (data->mgmt_group_cipher != WPA_CIPHER_AES_128_CMAC) { + if (!wpa_cipher_valid_mgmt_group(data->mgmt_group_cipher)) { wpa_printf(MSG_DEBUG, "%s: Unsupported management " "group cipher 0x%x", __func__, data->mgmt_group_cipher); @@ -553,8 +578,9 @@ int wpa_parse_wpa_ie_rsn(const u8 *rsn_ie, size_t rsn_ie_len, #endif /* CONFIG_IEEE80211W */ if (left > 0) { - wpa_printf(MSG_DEBUG, "%s: ie has %u trailing bytes - ignored", - __func__, left); + wpa_hexdump(MSG_DEBUG, + "wpa_parse_wpa_ie_rsn: ignore trailing bytes", + pos, left); } return 0; @@ -691,8 +717,9 @@ int wpa_parse_wpa_ie_wpa(const u8 *wpa_ie, size_t wpa_ie_len, } if (left > 0) { - wpa_printf(MSG_DEBUG, "%s: ie has %u trailing bytes - ignored", - __func__, left); + wpa_hexdump(MSG_DEBUG, + "wpa_parse_wpa_ie_wpa: ignore trailing bytes", + pos, left); } return 0; @@ -1096,9 +1123,13 @@ int wpa_cipher_key_len(int cipher) switch (cipher) { case WPA_CIPHER_CCMP_256: case WPA_CIPHER_GCMP_256: + case WPA_CIPHER_BIP_GMAC_256: + case WPA_CIPHER_BIP_CMAC_256: return 32; case WPA_CIPHER_CCMP: case WPA_CIPHER_GCMP: + case WPA_CIPHER_AES_128_CMAC: + case WPA_CIPHER_BIP_GMAC_128: return 16; case WPA_CIPHER_TKIP: return 32; @@ -1146,6 +1177,14 @@ int wpa_cipher_to_alg(int cipher) case WPA_CIPHER_WEP104: case WPA_CIPHER_WEP40: return WPA_ALG_WEP; + case WPA_CIPHER_AES_128_CMAC: + return WPA_ALG_IGTK; + case WPA_CIPHER_BIP_GMAC_128: + return WPA_ALG_BIP_GMAC_128; + case WPA_CIPHER_BIP_GMAC_256: + return WPA_ALG_BIP_GMAC_256; + case WPA_CIPHER_BIP_CMAC_256: + return WPA_ALG_BIP_CMAC_256; } return WPA_ALG_NONE; } @@ -1186,70 +1225,69 @@ u32 wpa_cipher_to_suite(int proto, int cipher) RSN_CIPHER_SUITE_NONE : WPA_CIPHER_SUITE_NONE); if (cipher & WPA_CIPHER_GTK_NOT_USED) return RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED; + if (cipher & WPA_CIPHER_AES_128_CMAC) + return RSN_CIPHER_SUITE_AES_128_CMAC; + if (cipher & WPA_CIPHER_BIP_GMAC_128) + return RSN_CIPHER_SUITE_BIP_GMAC_128; + if (cipher & WPA_CIPHER_BIP_GMAC_256) + return RSN_CIPHER_SUITE_BIP_GMAC_256; + if (cipher & WPA_CIPHER_BIP_CMAC_256) + return RSN_CIPHER_SUITE_BIP_CMAC_256; return 0; } -int rsn_cipher_put_suites(u8 *pos, int ciphers) +int rsn_cipher_put_suites(u8 *start, int ciphers) { - int num_suites = 0; + u8 *pos = start; if (ciphers & WPA_CIPHER_CCMP_256) { RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_CCMP_256); pos += RSN_SELECTOR_LEN; - num_suites++; } if (ciphers & WPA_CIPHER_GCMP_256) { RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_GCMP_256); pos += RSN_SELECTOR_LEN; - num_suites++; } if (ciphers & WPA_CIPHER_CCMP) { RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_CCMP); pos += RSN_SELECTOR_LEN; - num_suites++; } if (ciphers & WPA_CIPHER_GCMP) { RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_GCMP); pos += RSN_SELECTOR_LEN; - num_suites++; } if (ciphers & WPA_CIPHER_TKIP) { RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_TKIP); pos += RSN_SELECTOR_LEN; - num_suites++; } if (ciphers & WPA_CIPHER_NONE) { RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_NONE); pos += RSN_SELECTOR_LEN; - num_suites++; } - return num_suites; + return (pos - start) / RSN_SELECTOR_LEN; } -int wpa_cipher_put_suites(u8 *pos, int ciphers) +int wpa_cipher_put_suites(u8 *start, int ciphers) { - int num_suites = 0; + u8 *pos = start; if (ciphers & WPA_CIPHER_CCMP) { RSN_SELECTOR_PUT(pos, WPA_CIPHER_SUITE_CCMP); pos += WPA_SELECTOR_LEN; - num_suites++; } if (ciphers & WPA_CIPHER_TKIP) { RSN_SELECTOR_PUT(pos, WPA_CIPHER_SUITE_TKIP); pos += WPA_SELECTOR_LEN; - num_suites++; } if (ciphers & WPA_CIPHER_NONE) { RSN_SELECTOR_PUT(pos, WPA_CIPHER_SUITE_NONE); pos += WPA_SELECTOR_LEN; - num_suites++; } - return num_suites; + return (pos - start) / RSN_SELECTOR_LEN; } diff --git a/src/common/wpa_common.h b/src/common/wpa_common.h index dcc035c..c0b2caa 100644 --- a/src/common/wpa_common.h +++ b/src/common/wpa_common.h @@ -67,6 +67,7 @@ WPA_CIPHER_GTK_NOT_USED) #define RSN_AUTH_KEY_MGMT_FT_802_1X_SUITE_B_384 \ RSN_SELECTOR(0x00, 0x0f, 0xac, 13) #define RSN_AUTH_KEY_MGMT_CCKM RSN_SELECTOR(0x00, 0x40, 0x96, 0x00) +#define RSN_AUTH_KEY_MGMT_OSEN RSN_SELECTOR(0x50, 0x6f, 0x9a, 0x01) #define RSN_CIPHER_SUITE_NONE RSN_SELECTOR(0x00, 0x0f, 0xac, 0) #define RSN_CIPHER_SUITE_WEP40 RSN_SELECTOR(0x00, 0x0f, 0xac, 1) @@ -76,9 +77,7 @@ RSN_SELECTOR(0x00, 0x0f, 0xac, 13) #endif #define RSN_CIPHER_SUITE_CCMP RSN_SELECTOR(0x00, 0x0f, 0xac, 4) #define RSN_CIPHER_SUITE_WEP104 RSN_SELECTOR(0x00, 0x0f, 0xac, 5) -#ifdef CONFIG_IEEE80211W #define RSN_CIPHER_SUITE_AES_128_CMAC RSN_SELECTOR(0x00, 0x0f, 0xac, 6) -#endif /* CONFIG_IEEE80211W */ #define RSN_CIPHER_SUITE_NO_GROUP_ADDRESSED RSN_SELECTOR(0x00, 0x0f, 0xac, 7) #define RSN_CIPHER_SUITE_GCMP RSN_SELECTOR(0x00, 0x0f, 0xac, 8) #define RSN_CIPHER_SUITE_GCMP_256 RSN_SELECTOR(0x00, 0x0f, 0xac, 9) @@ -129,6 +128,7 @@ RSN_SELECTOR(0x00, 0x0f, 0xac, 13) #ifdef CONFIG_IEEE80211W #define WPA_IGTK_LEN 16 +#define WPA_IGTK_MAX_LEN 32 #endif /* CONFIG_IEEE80211W */ @@ -157,6 +157,7 @@ RSN_SELECTOR(0x00, 0x0f, 0xac, 13) /* IEEE 802.11, 8.5.2 EAPOL-Key frames */ #define WPA_KEY_INFO_TYPE_MASK ((u16) (BIT(0) | BIT(1) | BIT(2))) +#define WPA_KEY_INFO_TYPE_AKM_DEFINED 0 #define WPA_KEY_INFO_TYPE_HMAC_MD5_RC4 BIT(0) #define WPA_KEY_INFO_TYPE_HMAC_SHA1_AES BIT(1) #define WPA_KEY_INFO_TYPE_AES_128_CMAC 3 @@ -283,10 +284,11 @@ struct rsn_error_kde { } STRUCT_PACKED; #ifdef CONFIG_IEEE80211W +#define WPA_IGTK_KDE_PREFIX_LEN (2 + 6) struct wpa_igtk_kde { u8 keyid[2]; u8 pn[6]; - u8 igtk[WPA_IGTK_LEN]; + u8 igtk[WPA_IGTK_MAX_LEN]; } STRUCT_PACKED; #endif /* CONFIG_IEEE80211W */ @@ -407,6 +409,7 @@ int wpa_cipher_key_len(int cipher); int wpa_cipher_rsc_len(int cipher); int wpa_cipher_to_alg(int cipher); int wpa_cipher_valid_pairwise(int cipher); +int wpa_cipher_valid_mgmt_group(int cipher); u32 wpa_cipher_to_suite(int proto, int cipher); int rsn_cipher_put_suites(u8 *pos, int ciphers); int wpa_cipher_put_suites(u8 *pos, int ciphers); diff --git a/src/common/wpa_ctrl.c b/src/common/wpa_ctrl.c index f4af94a..5820a13 100644 --- a/src/common/wpa_ctrl.c +++ b/src/common/wpa_ctrl.c @@ -25,6 +25,10 @@ #include "private/android_filesystem_config.h" #endif /* ANDROID */ +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 +#include <net/if.h> +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ + #include "wpa_ctrl.h" #include "common.h" @@ -46,8 +50,13 @@ struct wpa_ctrl { #ifdef CONFIG_CTRL_IFACE_UDP int s; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 local; + struct sockaddr_in6 dest; +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in local; struct sockaddr_in dest; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ char *cookie; char *remote_ifname; char *remote_ip; @@ -279,19 +288,33 @@ struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path) return NULL; os_memset(ctrl, 0, sizeof(*ctrl)); +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + ctrl->s = socket(PF_INET6, SOCK_DGRAM, 0); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ ctrl->s = socket(PF_INET, SOCK_DGRAM, 0); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (ctrl->s < 0) { perror("socket"); os_free(ctrl); return NULL; } +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + ctrl->local.sin6_family = AF_INET6; +#ifdef CONFIG_CTRL_IFACE_UDP_REMOTE + ctrl->local.sin6_addr = in6addr_any; +#else /* CONFIG_CTRL_IFACE_UDP_REMOTE */ + inet_pton(AF_INET6, "::1", &ctrl->local.sin6_addr); +#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ ctrl->local.sin_family = AF_INET; #ifdef CONFIG_CTRL_IFACE_UDP_REMOTE ctrl->local.sin_addr.s_addr = INADDR_ANY; #else /* CONFIG_CTRL_IFACE_UDP_REMOTE */ ctrl->local.sin_addr.s_addr = htonl((127 << 24) | 1); #endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ + if (bind(ctrl->s, (struct sockaddr *) &ctrl->local, sizeof(ctrl->local)) < 0) { close(ctrl->s); @@ -299,14 +322,24 @@ struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path) return NULL; } +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + ctrl->dest.sin6_family = AF_INET6; + inet_pton(AF_INET6, "::1", &ctrl->dest.sin6_addr); + ctrl->dest.sin6_port = htons(WPA_CTRL_IFACE_PORT); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ ctrl->dest.sin_family = AF_INET; ctrl->dest.sin_addr.s_addr = htonl((127 << 24) | 1); ctrl->dest.sin_port = htons(WPA_CTRL_IFACE_PORT); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ #ifdef CONFIG_CTRL_IFACE_UDP_REMOTE if (ctrl_path) { char *port, *name; int port_id; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char *scope; + int scope_id = 0; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ name = os_strdup(ctrl_path); if (name == NULL) { @@ -314,7 +347,11 @@ struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path) os_free(ctrl); return NULL; } +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + port = os_strchr(name, ','); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ port = os_strchr(name, ':'); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (port) { port_id = atoi(&port[1]); @@ -322,7 +359,16 @@ struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path) } else port_id = WPA_CTRL_IFACE_PORT; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + scope = os_strchr(name, '%'); + if (scope) { + scope_id = if_nametoindex(&scope[1]); + scope[0] = '\0'; + } + h = gethostbyname2(name, AF_INET6); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ h = gethostbyname(name); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ ctrl->remote_ip = os_strdup(name); os_free(name); if (h == NULL) { @@ -332,16 +378,33 @@ struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path) os_free(ctrl); return NULL; } +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + ctrl->dest.sin6_scope_id = scope_id; + ctrl->dest.sin6_port = htons(port_id); + os_memcpy(&ctrl->dest.sin6_addr, h->h_addr, h->h_length); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ ctrl->dest.sin_port = htons(port_id); - os_memcpy(h->h_addr, (char *) &ctrl->dest.sin_addr.s_addr, - h->h_length); + os_memcpy(&ctrl->dest.sin_addr.s_addr, h->h_addr, h->h_length); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ } else ctrl->remote_ip = os_strdup("localhost"); #endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ if (connect(ctrl->s, (struct sockaddr *) &ctrl->dest, sizeof(ctrl->dest)) < 0) { - perror("connect"); +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char addr[INET6_ADDRSTRLEN]; + wpa_printf(MSG_ERROR, "connect(%s:%d) failed: %s", + inet_ntop(AF_INET6, &ctrl->dest.sin6_addr, addr, + sizeof(ctrl->dest)), + ntohs(ctrl->dest.sin6_port), + strerror(errno)); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ + wpa_printf(MSG_ERROR, "connect(%s:%d) failed: %s", + inet_ntoa(ctrl->dest.sin_addr), + ntohs(ctrl->dest.sin_port), + strerror(errno)); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ close(ctrl->s); os_free(ctrl->remote_ip); os_free(ctrl); diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h index 759cee4..534bc99 100644 --- a/src/common/wpa_ctrl.h +++ b/src/common/wpa_ctrl.h @@ -64,6 +64,10 @@ extern "C" { #define WPA_EVENT_BSS_ADDED "CTRL-EVENT-BSS-ADDED " /** A BSS entry was removed (followed by BSS entry id and BSSID) */ #define WPA_EVENT_BSS_REMOVED "CTRL-EVENT-BSS-REMOVED " +/** Change in the signal level was reported by the driver */ +#define WPA_EVENT_SIGNAL_CHANGE "CTRL-EVENT-SIGNAL-CHANGE " +/** Regulatory domain channel */ +#define WPA_EVENT_REGDOM_CHANGE "CTRL-EVENT-REGDOM-CHANGE " /** RSN IBSS 4-way handshakes completed with specified peer */ #define IBSS_RSN_COMPLETED "IBSS-RSN-COMPLETED " @@ -159,8 +163,17 @@ extern "C" { #define P2P_EVENT_REMOVE_AND_REFORM_GROUP "P2P-REMOVE-AND-REFORM-GROUP " #define INTERWORKING_AP "INTERWORKING-AP " +#define INTERWORKING_BLACKLISTED "INTERWORKING-BLACKLISTED " #define INTERWORKING_NO_MATCH "INTERWORKING-NO-MATCH " #define INTERWORKING_ALREADY_CONNECTED "INTERWORKING-ALREADY-CONNECTED " +#define INTERWORKING_SELECTED "INTERWORKING-SELECTED " + +/* Credential block added; parameters: <id> */ +#define CRED_ADDED "CRED-ADDED " +/* Credential block modified; parameters: <id> <field> */ +#define CRED_MODIFIED "CRED-MODIFIED " +/* Credential block removed; parameters: <id> */ +#define CRED_REMOVED "CRED-REMOVED " #define GAS_RESPONSE_INFO "GAS-RESPONSE-INFO " /* parameters: <addr> <dialog_token> <freq> */ @@ -168,6 +181,9 @@ extern "C" { /* parameters: <addr> <dialog_token> <freq> <status_code> <result> */ #define GAS_QUERY_DONE "GAS-QUERY-DONE " +#define HS20_SUBSCRIPTION_REMEDIATION "HS20-SUBSCRIPTION-REMEDIATION " +#define HS20_DEAUTH_IMMINENT_NOTICE "HS20-DEAUTH-IMMINENT-NOTICE " + #define EXT_RADIO_WORK_START "EXT-RADIO-WORK-START " #define EXT_RADIO_WORK_TIMEOUT "EXT-RADIO-WORK-TIMEOUT " diff --git a/src/common/wpa_helpers.c b/src/common/wpa_helpers.c new file mode 100644 index 0000000..28913b9 --- /dev/null +++ b/src/common/wpa_helpers.c @@ -0,0 +1,292 @@ +/* + * wpa_supplicant ctrl_iface helpers + * Copyright (c) 2010-2011, Atheros Communications, Inc. + * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" +#include <time.h> + +#include "common.h" +#include "wpa_ctrl.h" +#include "wpa_helpers.h" + + +char *wpas_ctrl_path = "/var/run/wpa_supplicant/"; +static int default_timeout = 60; + + +static struct wpa_ctrl * wpa_open_ctrl(const char *ifname) +{ + char buf[128]; + struct wpa_ctrl *ctrl; + + os_snprintf(buf, sizeof(buf), "%s%s", wpas_ctrl_path, ifname); + ctrl = wpa_ctrl_open(buf); + if (ctrl == NULL) + printf("wpa_command: wpa_ctrl_open(%s) failed\n", buf); + return ctrl; +} + + +int wpa_command(const char *ifname, const char *cmd) +{ + struct wpa_ctrl *ctrl; + char buf[128]; + size_t len; + + printf("wpa_command(ifname='%s', cmd='%s')\n", ifname, cmd); + ctrl = wpa_open_ctrl(ifname); + if (ctrl == NULL) + return -1; + len = sizeof(buf); + if (wpa_ctrl_request(ctrl, cmd, strlen(cmd), buf, &len, NULL) < 0) { + printf("wpa_command: wpa_ctrl_request failed\n"); + wpa_ctrl_close(ctrl); + return -1; + } + wpa_ctrl_close(ctrl); + buf[len] = '\0'; + if (strncmp(buf, "FAIL", 4) == 0) { + printf("wpa_command: Command failed (FAIL received)\n"); + return -1; + } + return 0; +} + + +int wpa_command_resp(const char *ifname, const char *cmd, + char *resp, size_t resp_size) +{ + struct wpa_ctrl *ctrl; + size_t len; + + printf("wpa_command(ifname='%s', cmd='%s')\n", ifname, cmd); + ctrl = wpa_open_ctrl(ifname); + if (ctrl == NULL) + return -1; + len = resp_size; + if (wpa_ctrl_request(ctrl, cmd, strlen(cmd), resp, &len, NULL) < 0) { + printf("wpa_command: wpa_ctrl_request failed\n"); + wpa_ctrl_close(ctrl); + return -1; + } + wpa_ctrl_close(ctrl); + resp[len] = '\0'; + return 0; +} + + +struct wpa_ctrl * open_wpa_mon(const char *ifname) +{ + struct wpa_ctrl *ctrl; + + ctrl = wpa_open_ctrl(ifname); + if (ctrl == NULL) + return NULL; + if (wpa_ctrl_attach(ctrl) < 0) { + wpa_ctrl_close(ctrl); + return NULL; + } + + return ctrl; +} + + +int get_wpa_cli_event2(struct wpa_ctrl *mon, + const char *event, const char *event2, + char *buf, size_t buf_size) +{ + int fd, ret; + fd_set rfd; + char *pos; + struct timeval tv; + time_t start, now; + + printf("Waiting for wpa_cli event %s\n", event); + fd = wpa_ctrl_get_fd(mon); + if (fd < 0) + return -1; + + time(&start); + while (1) { + size_t len; + + FD_ZERO(&rfd); + FD_SET(fd, &rfd); + tv.tv_sec = default_timeout; + tv.tv_usec = 0; + ret = select(fd + 1, &rfd, NULL, NULL, &tv); + if (ret == 0) { + printf("Timeout on waiting for event %s\n", event); + return -1; + } + if (ret < 0) { + printf("select: %s\n", strerror(errno)); + return -1; + } + len = buf_size; + if (wpa_ctrl_recv(mon, buf, &len) < 0) { + printf("Failure while waiting for event %s\n", event); + return -1; + } + if (len == buf_size) + len--; + buf[len] = '\0'; + + pos = strchr(buf, '>'); + if (pos && + (strncmp(pos + 1, event, strlen(event)) == 0 || + (event2 && + strncmp(pos + 1, event2, strlen(event2)) == 0))) + return 0; /* Event found */ + + time(&now); + if ((int) (now - start) > default_timeout) { + printf("Timeout on waiting for event %s\n", event); + return -1; + } + } +} + + +int get_wpa_cli_event(struct wpa_ctrl *mon, + const char *event, char *buf, size_t buf_size) +{ + return get_wpa_cli_event2(mon, event, NULL, buf, buf_size); +} + + +int get_wpa_status(const char *ifname, const char *field, char *obuf, + size_t obuf_size) +{ + struct wpa_ctrl *ctrl; + char buf[4096]; + char *pos, *end; + size_t len, flen; + + ctrl = wpa_open_ctrl(ifname); + if (ctrl == NULL) + return -1; + len = sizeof(buf); + if (wpa_ctrl_request(ctrl, "STATUS", 6, buf, &len, NULL) < 0) { + wpa_ctrl_close(ctrl); + return -1; + } + wpa_ctrl_close(ctrl); + buf[len] = '\0'; + + flen = strlen(field); + pos = buf; + while (pos + flen < buf + len) { + if (pos > buf) { + if (*pos != '\n') { + pos++; + continue; + } + pos++; + } + if (strncmp(pos, field, flen) != 0 || pos[flen] != '=') { + pos++; + continue; + } + pos += flen + 1; + end = strchr(pos, '\n'); + if (end == NULL) + return -1; + *end++ = '\0'; + if (end - pos > (int) obuf_size) + return -1; + memcpy(obuf, pos, end - pos); + return 0; + } + + return -1; +} + + +int wait_ip_addr(const char *ifname, int timeout) +{ + char ip[30]; + int count = timeout; + struct wpa_ctrl *ctrl; + + while (count > 0) { + printf("%s: ifname='%s' - %d seconds remaining\n", + __func__, ifname, count); + count--; + if (get_wpa_status(ifname, "ip_address", ip, sizeof(ip)) == 0 + && strlen(ip) > 0) { + printf("IP address found: '%s'\n", ip); + return 0; + } + ctrl = wpa_open_ctrl(ifname); + if (ctrl == NULL) + return -1; + wpa_ctrl_close(ctrl); + sleep(1); + } + printf("%s: Could not get IP address for ifname='%s'", __func__, + ifname); + return -1; +} + + +int add_network(const char *ifname) +{ + char res[30]; + + if (wpa_command_resp(ifname, "ADD_NETWORK", res, sizeof(res)) < 0) + return -1; + return atoi(res); +} + + +int set_network(const char *ifname, int id, const char *field, + const char *value) +{ + char buf[200]; + snprintf(buf, sizeof(buf), "SET_NETWORK %d %s %s", id, field, value); + return wpa_command(ifname, buf); +} + + +int set_network_quoted(const char *ifname, int id, const char *field, + const char *value) +{ + char buf[200]; + snprintf(buf, sizeof(buf), "SET_NETWORK %d %s \"%s\"", + id, field, value); + return wpa_command(ifname, buf); +} + + +int add_cred(const char *ifname) +{ + char res[30]; + + if (wpa_command_resp(ifname, "ADD_CRED", res, sizeof(res)) < 0) + return -1; + return atoi(res); +} + + +int set_cred(const char *ifname, int id, const char *field, const char *value) +{ + char buf[200]; + snprintf(buf, sizeof(buf), "SET_CRED %d %s %s", id, field, value); + return wpa_command(ifname, buf); +} + + +int set_cred_quoted(const char *ifname, int id, const char *field, + const char *value) +{ + char buf[200]; + snprintf(buf, sizeof(buf), "SET_CRED %d %s \"%s\"", + id, field, value); + return wpa_command(ifname, buf); +} diff --git a/src/common/wpa_helpers.h b/src/common/wpa_helpers.h new file mode 100644 index 0000000..54c2872 --- /dev/null +++ b/src/common/wpa_helpers.h @@ -0,0 +1,37 @@ +/* + * wpa_supplicant ctrl_iface helpers + * Copyright (c) 2010-2011, Atheros Communications, Inc. + * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPA_HELPERS_H +#define WPA_HELPERS_H + +int wpa_command(const char *ifname, const char *cmd); +int wpa_command_resp(const char *ifname, const char *cmd, + char *resp, size_t resp_size); +int get_wpa_status(const char *ifname, const char *field, char *obuf, + size_t obuf_size); + +struct wpa_ctrl * open_wpa_mon(const char *ifname); +int wait_ip_addr(const char *ifname, int timeout); +int get_wpa_cli_event(struct wpa_ctrl *mon, + const char *event, char *buf, size_t buf_size); +int get_wpa_cli_event2(struct wpa_ctrl *mon, + const char *event, const char *event2, + char *buf, size_t buf_size); + +int add_network(const char *ifname); +int set_network(const char *ifname, int id, const char *field, + const char *value); +int set_network_quoted(const char *ifname, int id, const char *field, + const char *value); +int add_cred(const char *ifname); +int set_cred(const char *ifname, int id, const char *field, const char *value); +int set_cred_quoted(const char *ifname, int id, const char *field, + const char *value); + +#endif /* WPA_HELPERS_H */ diff --git a/src/crypto/Makefile b/src/crypto/Makefile index fcf9586..2a92109 100644 --- a/src/crypto/Makefile +++ b/src/crypto/Makefile @@ -9,6 +9,7 @@ install: include ../lib.rules +CFLAGS += -DCONFIG_CRYPTO_INTERNAL CFLAGS += -DCONFIG_TLS_INTERNAL_CLIENT CFLAGS += -DCONFIG_TLS_INTERNAL_SERVER #CFLAGS += -DALL_DH_GROUPS diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 9bccaaa..f2d5662 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -271,6 +271,10 @@ struct crypto_private_key; */ struct crypto_public_key * crypto_public_key_import(const u8 *key, size_t len); +struct crypto_public_key * +crypto_public_key_import_parts(const u8 *n, size_t n_len, + const u8 *e, size_t e_len); + /** * crypto_private_key_import - Import an RSA private key * @key: Key buffer (DER encoded RSA private key) @@ -534,16 +538,6 @@ int crypto_bignum_exptmod(const struct crypto_bignum *a, struct crypto_bignum *d); /** - * crypto_bignum_rshift - b = a >> n - * @a: Bignum - * @n: Number of bits to shift - * @b: Bignum; used to store the result of a >> n - * Returns: 0 on success, -1 on failure - */ -int crypto_bignum_rshift(const struct crypto_bignum *a, int n, - struct crypto_bignum *b); - -/** * crypto_bignum_inverse - Inverse a bignum so that a * c = 1 (mod b) * @a: Bignum * @b: Bignum diff --git a/src/crypto/crypto_internal-rsa.c b/src/crypto/crypto_internal-rsa.c index 54209fa..dc7f350 100644 --- a/src/crypto/crypto_internal-rsa.c +++ b/src/crypto/crypto_internal-rsa.c @@ -26,6 +26,15 @@ struct crypto_public_key * crypto_public_key_import(const u8 *key, size_t len) } +struct crypto_public_key * +crypto_public_key_import_parts(const u8 *n, size_t n_len, + const u8 *e, size_t e_len) +{ + return (struct crypto_public_key *) + crypto_rsa_import_public_key_parts(n, n_len, e, e_len); +} + + struct crypto_private_key * crypto_private_key_import(const u8 *key, size_t len, const char *passwd) diff --git a/src/crypto/crypto_openssl.c b/src/crypto/crypto_openssl.c index 1da2b9f..817ee2d 100644 --- a/src/crypto/crypto_openssl.c +++ b/src/crypto/crypto_openssl.c @@ -916,13 +916,6 @@ int crypto_bignum_exptmod(const struct crypto_bignum *a, } -int crypto_bignum_rshift(const struct crypto_bignum *a, int n, - struct crypto_bignum *b) -{ - return BN_rshift((BIGNUM *) b, (const BIGNUM *) a, n) ? 0 : -1; -} - - int crypto_bignum_inverse(const struct crypto_bignum *a, const struct crypto_bignum *b, struct crypto_bignum *c) diff --git a/src/crypto/fips_prf_cryptoapi.c b/src/crypto/fips_prf_cryptoapi.c deleted file mode 100644 index dca93a3..0000000 --- a/src/crypto/fips_prf_cryptoapi.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * FIPS 186-2 PRF for Microsoft CryptoAPI - * Copyright (c) 2009, Jouni Malinen <j@w1.fi> - * - * This software may be distributed under the terms of the BSD license. - * See README for more details. - */ - -#include "includes.h" - -#include "common.h" -#include "crypto.h" - - -int fips186_2_prf(const u8 *seed, size_t seed_len, u8 *x, size_t xlen) -{ - /* FIX: how to do this with CryptoAPI? */ - return -1; -} diff --git a/src/crypto/fips_prf_gnutls.c b/src/crypto/fips_prf_gnutls.c deleted file mode 100644 index 947e6f6..0000000 --- a/src/crypto/fips_prf_gnutls.c +++ /dev/null @@ -1,20 +0,0 @@ -/* - * FIPS 186-2 PRF for libgcrypt - * Copyright (c) 2004-2009, Jouni Malinen <j@w1.fi> - * - * This software may be distributed under the terms of the BSD license. - * See README for more details. - */ - -#include "includes.h" -#include <gcrypt.h> - -#include "common.h" -#include "crypto.h" - - -int fips186_2_prf(const u8 *seed, size_t seed_len, u8 *x, size_t xlen) -{ - /* FIX: how to do this with libgcrypt? */ - return -1; -} diff --git a/src/crypto/fips_prf_nss.c b/src/crypto/fips_prf_nss.c deleted file mode 100644 index 2c962f4..0000000 --- a/src/crypto/fips_prf_nss.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * FIPS 186-2 PRF for NSS - * Copyright (c) 2009, Jouni Malinen <j@w1.fi> - * - * This software may be distributed under the terms of the BSD license. - * See README for more details. - */ - -#include "includes.h" -#include <openssl/sha.h> - -#include "common.h" -#include "crypto.h" - - -int fips186_2_prf(const u8 *seed, size_t seed_len, u8 *x, size_t xlen) -{ - return -1; -} diff --git a/src/crypto/ms_funcs.c b/src/crypto/ms_funcs.c index b2bbab2..49a5c1c 100644 --- a/src/crypto/ms_funcs.c +++ b/src/crypto/ms_funcs.c @@ -58,6 +58,7 @@ static int utf8_to_ucs2(const u8 *utf8_string, size_t utf8_string_len, WPA_PUT_LE16(ucs2_buffer + j, ((c & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F)); + j += 2; } } } diff --git a/src/crypto/sha1-internal.c b/src/crypto/sha1-internal.c index 10bf153..24bc3ff 100644 --- a/src/crypto/sha1-internal.c +++ b/src/crypto/sha1-internal.c @@ -19,6 +19,7 @@ typedef struct SHA1Context SHA1_CTX; void SHA1Transform(u32 state[5], const unsigned char buffer[64]); +#ifdef CONFIG_CRYPTO_INTERNAL /** * sha1_vector - SHA-1 hash for data vector * @num_elem: Number of elements in the data vector @@ -38,6 +39,7 @@ int sha1_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac) SHA1Final(mac, &ctx); return 0; } +#endif /* CONFIG_CRYPTO_INTERNAL */ /* ===== start - public domain SHA1 implementation ===== */ diff --git a/src/crypto/tls.h b/src/crypto/tls.h index 287fd33..65e0f79 100644 --- a/src/crypto/tls.h +++ b/src/crypto/tls.h @@ -41,8 +41,7 @@ enum tls_fail_reason { TLS_FAIL_ALTSUBJECT_MISMATCH = 6, TLS_FAIL_BAD_CERTIFICATE = 7, TLS_FAIL_SERVER_CHAIN_PROBE = 8, - TLS_FAIL_DOMAIN_SUFFIX_MISMATCH = 9, - TLS_FAIL_SERVER_USED_CLIENT_CERT = 10 + TLS_FAIL_DOMAIN_SUFFIX_MISMATCH = 9 }; union tls_event_data { @@ -86,6 +85,8 @@ struct tls_config { #define TLS_CONN_DISABLE_SESSION_TICKET BIT(2) #define TLS_CONN_REQUEST_OCSP BIT(3) #define TLS_CONN_REQUIRE_OCSP BIT(4) +#define TLS_CONN_DISABLE_TLSv1_1 BIT(5) +#define TLS_CONN_DISABLE_TLSv1_2 BIT(6) /** * struct tls_connection_params - Parameters for TLS connection @@ -536,4 +537,19 @@ int __must_check tls_connection_set_session_ticket_cb( void *tls_ctx, struct tls_connection *conn, tls_session_ticket_cb cb, void *ctx); +void tls_connection_set_log_cb(struct tls_connection *conn, + void (*log_cb)(void *ctx, const char *msg), + void *ctx); + +#define TLS_BREAK_VERIFY_DATA BIT(0) +#define TLS_BREAK_SRV_KEY_X_HASH BIT(1) +#define TLS_BREAK_SRV_KEY_X_SIGNATURE BIT(2) +#define TLS_DHE_PRIME_511B BIT(3) +#define TLS_DHE_PRIME_767B BIT(4) +#define TLS_DHE_PRIME_15 BIT(5) +#define TLS_DHE_PRIME_58B BIT(6) +#define TLS_DHE_NON_PRIME BIT(7) + +void tls_connection_set_test_flags(struct tls_connection *conn, u32 flags); + #endif /* TLS_H */ diff --git a/src/crypto/tls_internal.c b/src/crypto/tls_internal.c index 91f0690..6563ed2 100644 --- a/src/crypto/tls_internal.c +++ b/src/crypto/tls_internal.c @@ -28,6 +28,7 @@ struct tls_global { struct tls_connection { struct tlsv1_client *client; struct tlsv1_server *server; + struct tls_global *global; }; @@ -85,6 +86,7 @@ struct tls_connection * tls_connection_init(void *tls_ctx) conn = os_zalloc(sizeof(*conn)); if (conn == NULL) return NULL; + conn->global = global; #ifdef CONFIG_TLS_INTERNAL_CLIENT if (!global->server) { @@ -109,6 +111,28 @@ struct tls_connection * tls_connection_init(void *tls_ctx) } +#ifdef CONFIG_TESTING_OPTIONS +#ifdef CONFIG_TLS_INTERNAL_SERVER +void tls_connection_set_test_flags(struct tls_connection *conn, u32 flags) +{ + if (conn->server) + tlsv1_server_set_test_flags(conn->server, flags); +} +#endif /* CONFIG_TLS_INTERNAL_SERVER */ +#endif /* CONFIG_TESTING_OPTIONS */ + + +void tls_connection_set_log_cb(struct tls_connection *conn, + void (*log_cb)(void *ctx, const char *msg), + void *ctx) +{ +#ifdef CONFIG_TLS_INTERNAL_SERVER + if (conn->server) + tlsv1_server_set_log_cb(conn->server, log_cb, ctx); +#endif /* CONFIG_TLS_INTERNAL_SERVER */ +} + + void tls_connection_deinit(void *tls_ctx, struct tls_connection *conn) { if (conn == NULL) diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c index d025ae0..d2d6600 100644 --- a/src/crypto/tls_openssl.c +++ b/src/crypto/tls_openssl.c @@ -105,7 +105,7 @@ struct tls_connection { unsigned int ca_cert_verify:1; unsigned int cert_probe:1; unsigned int server_cert_only:1; - unsigned int server:1; + unsigned int invalid_hb_used:1; u8 srv_cert_hash[32]; @@ -786,12 +786,13 @@ void * tls_init(const struct tls_config *conf) PKCS12_PBE_add(); #endif /* PKCS12_FUNCS */ } else { - context = tls_global; #ifdef OPENSSL_SUPPORTS_CTX_APP_DATA /* Newer OpenSSL can store app-data per-SSL */ context = tls_context_new(conf); if (context == NULL) return NULL; +#else /* OPENSSL_SUPPORTS_CTX_APP_DATA */ + context = tls_global; #endif /* OPENSSL_SUPPORTS_CTX_APP_DATA */ } tls_openssl_ref_count++; @@ -984,14 +985,35 @@ int tls_get_errors(void *ssl_ctx) return count; } + +static void tls_msg_cb(int write_p, int version, int content_type, + const void *buf, size_t len, SSL *ssl, void *arg) +{ + struct tls_connection *conn = arg; + const u8 *pos = buf; + + wpa_printf(MSG_DEBUG, "OpenSSL: %s ver=0x%x content_type=%d", + write_p ? "TX" : "RX", version, content_type); + wpa_hexdump_key(MSG_MSGDUMP, "OpenSSL: Message", buf, len); + if (content_type == 24 && len >= 3 && pos[0] == 1) { + size_t payload_len = WPA_GET_BE16(pos + 1); + if (payload_len + 3 > len) { + wpa_printf(MSG_ERROR, "OpenSSL: Heartbeat attack detected"); + conn->invalid_hb_used = 1; + } + } +} + + struct tls_connection * tls_connection_init(void *ssl_ctx) { SSL_CTX *ssl = ssl_ctx; struct tls_connection *conn; long options; - struct tls_context *context = tls_global; #ifdef OPENSSL_SUPPORTS_CTX_APP_DATA - context = SSL_CTX_get_app_data(ssl); + struct tls_context *context = SSL_CTX_get_app_data(ssl); +#else /* OPENSSL_SUPPORTS_CTX_APP_DATA */ + struct tls_context *context = tls_global; #endif /* OPENSSL_SUPPORTS_CTX_APP_DATA */ conn = os_zalloc(sizeof(*conn)); @@ -1007,6 +1029,8 @@ struct tls_connection * tls_connection_init(void *ssl_ctx) conn->context = context; SSL_set_app_data(conn->ssl, conn); + SSL_set_msg_callback(conn->ssl, tls_msg_cb); + SSL_set_msg_callback_arg(conn->ssl, conn); options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE; #ifdef SSL_OP_NO_COMPRESSION @@ -1368,6 +1392,9 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) const char *err_str; err_cert = X509_STORE_CTX_get_current_cert(x509_ctx); + if (!err_cert) + return 0; + err = X509_STORE_CTX_get_error(x509_ctx); depth = X509_STORE_CTX_get_error_depth(x509_ctx); ssl = X509_STORE_CTX_get_ex_data(x509_ctx, @@ -1477,16 +1504,6 @@ static int tls_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) TLS_FAIL_SERVER_CHAIN_PROBE); } - if (!conn->server && err_cert && preverify_ok && depth == 0 && - (err_cert->ex_flags & EXFLAG_XKUSAGE) && - (err_cert->ex_xkusage & XKU_SSL_CLIENT)) { - wpa_printf(MSG_WARNING, "TLS: Server used client certificate"); - openssl_tls_fail_event(conn, err_cert, err, depth, buf, - "Server used client certificate", - TLS_FAIL_SERVER_USED_CLIENT_CERT); - preverify_ok = 0; - } - if (preverify_ok && context->event_cb != NULL) context->event_cb(context->cb_ctx, TLS_CERT_CHAIN_SUCCESS, NULL); @@ -2538,8 +2555,6 @@ openssl_handshake(struct tls_connection *conn, const struct wpabuf *in_data, int res; struct wpabuf *out_data; - conn->server = !!server; - /* * Give TLS handshake data from the server (if available) to OpenSSL * for processing. @@ -2650,10 +2665,25 @@ openssl_connection_handshake(struct tls_connection *conn, out_data = openssl_handshake(conn, in_data, server); if (out_data == NULL) return NULL; + if (conn->invalid_hb_used) { + wpa_printf(MSG_INFO, "TLS: Heartbeat attack detected - do not send response"); + wpabuf_free(out_data); + return NULL; + } if (SSL_is_init_finished(conn->ssl) && appl_data && in_data) *appl_data = openssl_get_appl_data(conn, wpabuf_len(in_data)); + if (conn->invalid_hb_used) { + wpa_printf(MSG_INFO, "TLS: Heartbeat attack detected - do not send response"); + if (appl_data) { + wpabuf_free(*appl_data); + *appl_data = NULL; + } + wpabuf_free(out_data); + return NULL; + } + return out_data; } @@ -2755,6 +2785,12 @@ struct wpabuf * tls_connection_decrypt(void *tls_ctx, } wpabuf_put(buf, res); + if (conn->invalid_hb_used) { + wpa_printf(MSG_INFO, "TLS: Heartbeat attack detected - do not send response"); + wpabuf_free(buf); + return NULL; + } + return buf; } @@ -2932,6 +2968,41 @@ static void ocsp_debug_print_resp(OCSP_RESPONSE *rsp) } +static void debug_print_cert(X509 *cert, const char *title) +{ +#ifndef CONFIG_NO_STDOUT_DEBUG + BIO *out; + size_t rlen; + char *txt; + int res; + + if (wpa_debug_level > MSG_DEBUG) + return; + + out = BIO_new(BIO_s_mem()); + if (!out) + return; + + X509_print(out, cert); + rlen = BIO_ctrl_pending(out); + txt = os_malloc(rlen + 1); + if (!txt) { + BIO_free(out); + return; + } + + res = BIO_read(out, txt, rlen); + if (res > 0) { + txt[res] = '\0'; + wpa_printf(MSG_DEBUG, "OpenSSL: %s\n%s", title, txt); + } + os_free(txt); + + BIO_free(out); +#endif /* CONFIG_NO_STDOUT_DEBUG */ +} + + static int ocsp_resp_cb(SSL *s, void *arg) { struct tls_connection *conn = arg; @@ -2975,8 +3046,7 @@ static int ocsp_resp_cb(SSL *s, void *arg) store = SSL_CTX_get_cert_store(s->ctx); if (conn->peer_issuer) { - wpa_printf(MSG_DEBUG, "OpenSSL: Add issuer"); - X509_print_fp(stdout, conn->peer_issuer); + debug_print_cert(conn->peer_issuer, "Add OCSP issuer"); if (X509_STORE_add_cert(store, conn->peer_issuer) != 1) { tls_show_errors(MSG_INFO, __func__, @@ -3187,6 +3257,19 @@ int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, #endif /* SSL_clear_options */ #endif /* SSL_OP_NO_TICKET */ +#ifdef SSL_OP_NO_TLSv1_1 + if (params->flags & TLS_CONN_DISABLE_TLSv1_1) + SSL_set_options(conn->ssl, SSL_OP_NO_TLSv1_1); + else + SSL_clear_options(conn->ssl, SSL_OP_NO_TLSv1_1); +#endif /* SSL_OP_NO_TLSv1_1 */ +#ifdef SSL_OP_NO_TLSv1_2 + if (params->flags & TLS_CONN_DISABLE_TLSv1_2) + SSL_set_options(conn->ssl, SSL_OP_NO_TLSv1_2); + else + SSL_clear_options(conn->ssl, SSL_OP_NO_TLSv1_2); +#endif /* SSL_OP_NO_TLSv1_2 */ + #ifdef HAVE_OCSP if (params->flags & TLS_CONN_REQUEST_OCSP) { SSL_CTX *ssl_ctx = tls_ctx; diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 7ad8576..6e47b86 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -50,6 +50,14 @@ enum reg_change_initiator { REGDOM_BEACON_HINT, }; +enum reg_type { + REGDOM_TYPE_UNKNOWN, + REGDOM_TYPE_COUNTRY, + REGDOM_TYPE_WORLD, + REGDOM_TYPE_CUSTOM_WORLD, + REGDOM_TYPE_INTERSECTION, +}; + /** * struct hostapd_channel_data - Channel information */ @@ -93,6 +101,9 @@ struct hostapd_channel_data { */ long double interference_factor; #endif /* CONFIG_ACS */ + + /* DFS CAC time in milliseconds */ + unsigned int dfs_cac_ms; }; #define HOSTAPD_MODE_FLAG_HT_INFO_KNOWN BIT(0) @@ -404,6 +415,16 @@ struct wpa_driver_associate_params { const u8 *bssid; /** + * bssid_hint - BSSID of a proposed AP + * + * This indicates which BSS has been found a suitable candidate for + * initial association for drivers that use driver/firmwate-based BSS + * selection. Unlike the @bssid parameter, @bssid_hint does not limit + * the driver from selecting other BSSes in the ESS. + */ + const u8 *bssid_hint; + + /** * ssid - The selected SSID */ const u8 *ssid; @@ -421,6 +442,16 @@ struct wpa_driver_associate_params { int freq; /** + * freq_hint - Frequency of the channel the proposed AP is using + * + * This provides a channel on which a suitable BSS has been found as a + * hint for the driver. Unlike the @freq parameter, @freq_hint does not + * limit the driver from selecting other channels for + * driver/firmware-based BSS selection. + */ + int freq_hint; + + /** * bg_scan_period - Background scan period in seconds, 0 to disable * background scan, or -1 to indicate no change to default driver * configuration @@ -428,6 +459,11 @@ struct wpa_driver_associate_params { int bg_scan_period; /** + * beacon_int - Beacon interval for IBSS or 0 to use driver default + */ + int beacon_int; + + /** * wpa_ie - WPA information element for (Re)Association Request * WPA information element to be included in (Re)Association * Request (including information element id and length). Use @@ -646,6 +682,16 @@ enum hide_ssid { HIDDEN_SSID_ZERO_CONTENTS }; +struct wowlan_triggers { + u8 any; + u8 disconnect; + u8 magic_pkt; + u8 gtk_rekey_failure; + u8 eap_identity_req; + u8 four_way_handshake; + u8 rfkill_release; +}; + struct wpa_driver_ap_params { /** * head - Beacon head from IEEE 802.11 header to IEs before TIM IE @@ -827,6 +873,16 @@ struct wpa_driver_ap_params { * disable_dgaf - Whether group-addressed frames are disabled */ int disable_dgaf; + + /** + * osen - Whether OSEN security is enabled + */ + int osen; + + /** + * freq - Channel parameters for dynamic bandwidth changes + */ + struct hostapd_freq_params *freq; }; /** @@ -855,6 +911,7 @@ struct wpa_driver_capa { #define WPA_DRIVER_CAPA_ENC_BIP_GMAC_128 0x00000200 #define WPA_DRIVER_CAPA_ENC_BIP_GMAC_256 0x00000400 #define WPA_DRIVER_CAPA_ENC_BIP_CMAC_256 0x00000800 +#define WPA_DRIVER_CAPA_ENC_GTK_NOT_USED 0x00001000 unsigned int enc; #define WPA_DRIVER_AUTH_OPEN 0x00000001 @@ -866,7 +923,8 @@ struct wpa_driver_capa { #define WPA_DRIVER_FLAGS_DRIVER_IE 0x00000001 /* Driver needs static WEP key setup after association command */ #define WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC 0x00000002 -/* unused: 0x00000004 */ +/* Driver takes care of all DFS operations */ +#define WPA_DRIVER_FLAGS_DFS_OFFLOAD 0x00000004 /* Driver takes care of RSN 4-way handshake internally; PMK is configured with * struct wpa_driver_ops::set_key using alg = WPA_ALG_PMK */ #define WPA_DRIVER_FLAGS_4WAY_HANDSHAKE 0x00000008 @@ -878,7 +936,8 @@ struct wpa_driver_capa { #define WPA_DRIVER_FLAGS_AP 0x00000040 /* Driver needs static WEP key setup after association has been completed */ #define WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC_DONE 0x00000080 -/* unused: 0x00000100 */ +/* Driver supports dynamic HT 20/40 MHz channel changes during BSS lifetime */ +#define WPA_DRIVER_FLAGS_HT_2040_COEX 0x00000100 /* Driver supports concurrent P2P operations */ #define WPA_DRIVER_FLAGS_P2P_CONCURRENT 0x00000200 /* @@ -888,7 +947,8 @@ struct wpa_driver_capa { #define WPA_DRIVER_FLAGS_P2P_DEDICATED_INTERFACE 0x00000400 /* This interface is P2P capable (P2P GO or P2P Client) */ #define WPA_DRIVER_FLAGS_P2P_CAPABLE 0x00000800 -/* unused: 0x00001000 */ +/* Driver supports station and key removal when stopping an AP */ +#define WPA_DRIVER_FLAGS_AP_TEARDOWN_SUPPORT 0x00001000 /* * Driver uses the initial interface for P2P management interface and non-P2P * purposes (e.g., connect to infra AP), but this interface cannot be used for @@ -982,6 +1042,8 @@ struct wpa_driver_capa { */ const u8 *extended_capa, *extended_capa_mask; unsigned int extended_capa_len; + + struct wowlan_triggers wowlan_triggers; }; @@ -1008,6 +1070,8 @@ struct hostapd_sta_add_params { u16 listen_interval; const struct ieee80211_ht_capabilities *ht_capabilities; const struct ieee80211_vht_capabilities *vht_capabilities; + int vht_opmode_enabled; + u8 vht_opmode; u32 flags; /* bitmask of WPA_STA_* flags */ int set; /* Set STA parameters instead of add */ u8 qosinfo; @@ -1234,6 +1298,22 @@ struct csa_settings { u16 counter_offset_presp; }; +/* TDLS peer capabilities for send_tdls_mgmt() */ +enum tdls_peer_capability { + TDLS_PEER_HT = BIT(0), + TDLS_PEER_VHT = BIT(1), + TDLS_PEER_WMM = BIT(2), +}; + +#ifdef CONFIG_MACSEC +struct macsec_init_params { + Boolean always_include_sci; + Boolean use_es; + Boolean use_scb; +}; +#endif /* CONFIG_MACSEC */ + + /** * struct wpa_driver_ops - Driver interface API definition * @@ -2111,7 +2191,7 @@ struct wpa_driver_ops { * @session_timeout: Session timeout for the station * Returns: 0 on success, -1 on failure */ - int (*set_radius_acl_auth)(void *priv, const u8 *mac, int accepted, + int (*set_radius_acl_auth)(void *priv, const u8 *mac, int accepted, u32 session_timeout); /** @@ -2175,7 +2255,7 @@ struct wpa_driver_ops { * Returns: 0 on success, -1 on failure */ int (*set_wds_sta)(void *priv, const u8 *addr, int aid, int val, - const char *bridge_ifname, char *ifname_wds); + const char *bridge_ifname, char *ifname_wds); /** * send_action - Transmit an Action frame @@ -2414,6 +2494,7 @@ struct wpa_driver_ops { * @action_code: TDLS action code for the mssage * @dialog_token: Dialog Token to use in the message (if needed) * @status_code: Status Code or Reason Code to use (if needed) + * @peer_capab: TDLS peer capability (TDLS_PEER_* bitfield) * @buf: TDLS IEs to add to the message * @len: Length of buf in octets * Returns: 0 on success, negative (<0) on failure @@ -2422,7 +2503,7 @@ struct wpa_driver_ops { * responsible for receiving and sending all TDLS packets. */ int (*send_tdls_mgmt)(void *priv, const u8 *dst, u8 action_code, - u8 dialog_token, u16 status_code, + u8 dialog_token, u16 status_code, u32 peer_capab, const u8 *buf, size_t len); /** @@ -2459,10 +2540,17 @@ struct wpa_driver_ops { u8 qos_map_set_len); /** + * set_wowlan - Set wake-on-wireless triggers + * @priv: Private driver interface data + * @triggers: wowlan triggers + */ + int (*set_wowlan)(void *priv, const struct wowlan_triggers *triggers); + + /** * signal_poll - Get current connection information * @priv: Private driver interface data * @signal_info: Connection info structure - */ + */ int (*signal_poll)(void *priv, struct wpa_signal_info *signal_info); /** @@ -2492,6 +2580,30 @@ struct wpa_driver_ops { #endif /* ANDROID */ /** + * vendor_cmd - Execute vendor specific command + * @priv: Private driver interface data + * @vendor_id: Vendor id + * @subcmd: Vendor command id + * @data: Vendor command parameters (%NULL if no parameters) + * @data_len: Data length + * @buf: Return buffer (%NULL to ignore reply) + * Returns: 0 on success, negative (<0) on failure + * + * This function handles vendor specific commands that are passed to + * the driver/device. The command is identified by vendor id and + * command id. Parameters can be passed as argument to the command + * in the data buffer. Reply (if any) will be filled in the supplied + * return buffer. + * + * The exact driver behavior is driver interface and vendor specific. As + * an example, this will be converted to a vendor specific cfg80211 + * command in case of the nl80211 driver interface. + */ + int (*vendor_cmd)(void *priv, unsigned int vendor_id, + unsigned int subcmd, const u8 *data, size_t data_len, + struct wpabuf *buf); + + /** * set_rekey_info - Set rekey information * @priv: Private driver interface data * @kek: Current KEK @@ -2683,6 +2795,203 @@ struct wpa_driver_ops { * Returns: Length of written status information or -1 on failure */ int (*status)(void *priv, char *buf, size_t buflen); + +#ifdef CONFIG_MACSEC + int (*macsec_init)(void *priv, struct macsec_init_params *params); + + int (*macsec_deinit)(void *priv); + + /** + * enable_protect_frames - Set protect frames status + * @priv: Private driver interface data + * @enabled: TRUE = protect frames enabled + * FALSE = protect frames disabled + * Returns: 0 on success, -1 on failure (or if not supported) + */ + int (*enable_protect_frames)(void *priv, Boolean enabled); + + /** + * set_replay_protect - Set replay protect status and window size + * @priv: Private driver interface data + * @enabled: TRUE = replay protect enabled + * FALSE = replay protect disabled + * @window: replay window size, valid only when replay protect enabled + * Returns: 0 on success, -1 on failure (or if not supported) + */ + int (*set_replay_protect)(void *priv, Boolean enabled, u32 window); + + /** + * set_current_cipher_suite - Set current cipher suite + * @priv: Private driver interface data + * @cs: EUI64 identifier + * @cs_len: Length of the cs buffer in octets + * Returns: 0 on success, -1 on failure (or if not supported) + */ + int (*set_current_cipher_suite)(void *priv, const u8 *cs, + size_t cs_len); + + /** + * enable_controlled_port - Set controlled port status + * @priv: Private driver interface data + * @enabled: TRUE = controlled port enabled + * FALSE = controlled port disabled + * Returns: 0 on success, -1 on failure (or if not supported) + */ + int (*enable_controlled_port)(void *priv, Boolean enabled); + + /** + * get_receive_lowest_pn - Get receive lowest pn + * @priv: Private driver interface data + * @channel: secure channel + * @an: association number + * @lowest_pn: lowest accept pn + * Returns: 0 on success, -1 on failure (or if not supported) + */ + int (*get_receive_lowest_pn)(void *priv, u32 channel, u8 an, + u32 *lowest_pn); + + /** + * get_transmit_next_pn - Get transmit next pn + * @priv: Private driver interface data + * @channel: secure channel + * @an: association number + * @next_pn: next pn + * Returns: 0 on success, -1 on failure (or if not supported) + */ + int (*get_transmit_next_pn)(void *priv, u32 channel, u8 an, + u32 *next_pn); + + /** + * set_transmit_next_pn - Set transmit next pn + * @priv: Private driver interface data + * @channel: secure channel + * @an: association number + * @next_pn: next pn + * Returns: 0 on success, -1 on failure (or if not supported) + */ + int (*set_transmit_next_pn)(void *priv, u32 channel, u8 an, + u32 next_pn); + + /** + * get_available_receive_sc - get available receive channel + * @priv: Private driver interface data + * @channel: secure channel + * Returns: 0 on success, -1 on failure (or if not supported) + */ + int (*get_available_receive_sc)(void *priv, u32 *channel); + + /** + * create_receive_sc - create secure channel for receiving + * @priv: Private driver interface data + * @channel: secure channel + * @sci_addr: secure channel identifier - address + * @sci_port: secure channel identifier - port + * @conf_offset: confidentiality offset (0, 30, or 50) + * @validation: frame validation policy (0 = Disabled, 1 = Checked, + * 2 = Strict) + * Returns: 0 on success, -1 on failure (or if not supported) + */ + int (*create_receive_sc)(void *priv, u32 channel, const u8 *sci_addr, + u16 sci_port, unsigned int conf_offset, + int validation); + + /** + * delete_receive_sc - delete secure connection for receiving + * @priv: private driver interface data from init() + * @channel: secure channel + * Returns: 0 on success, -1 on failure + */ + int (*delete_receive_sc)(void *priv, u32 channel); + + /** + * create_receive_sa - create secure association for receive + * @priv: private driver interface data from init() + * @channel: secure channel + * @an: association number + * @lowest_pn: the lowest packet number can be received + * @sak: the secure association key + * Returns: 0 on success, -1 on failure + */ + int (*create_receive_sa)(void *priv, u32 channel, u8 an, + u32 lowest_pn, const u8 *sak); + + /** + * enable_receive_sa - enable the SA for receive + * @priv: private driver interface data from init() + * @channel: secure channel + * @an: association number + * Returns: 0 on success, -1 on failure + */ + int (*enable_receive_sa)(void *priv, u32 channel, u8 an); + + /** + * disable_receive_sa - disable SA for receive + * @priv: private driver interface data from init() + * @channel: secure channel index + * @an: association number + * Returns: 0 on success, -1 on failure + */ + int (*disable_receive_sa)(void *priv, u32 channel, u8 an); + + /** + * get_available_transmit_sc - get available transmit channel + * @priv: Private driver interface data + * @channel: secure channel + * Returns: 0 on success, -1 on failure (or if not supported) + */ + int (*get_available_transmit_sc)(void *priv, u32 *channel); + + /** + * create_transmit_sc - create secure connection for transmit + * @priv: private driver interface data from init() + * @channel: secure channel + * @sci_addr: secure channel identifier - address + * @sci_port: secure channel identifier - port + * Returns: 0 on success, -1 on failure + */ + int (*create_transmit_sc)(void *priv, u32 channel, const u8 *sci_addr, + u16 sci_port, unsigned int conf_offset); + + /** + * delete_transmit_sc - delete secure connection for transmit + * @priv: private driver interface data from init() + * @channel: secure channel + * Returns: 0 on success, -1 on failure + */ + int (*delete_transmit_sc)(void *priv, u32 channel); + + /** + * create_transmit_sa - create secure association for transmit + * @priv: private driver interface data from init() + * @channel: secure channel index + * @an: association number + * @next_pn: the packet number used as next transmit packet + * @confidentiality: True if the SA is to provide confidentiality + * as well as integrity + * @sak: the secure association key + * Returns: 0 on success, -1 on failure + */ + int (*create_transmit_sa)(void *priv, u32 channel, u8 an, u32 next_pn, + Boolean confidentiality, const u8 *sak); + + /** + * enable_transmit_sa - enable SA for transmit + * @priv: private driver interface data from init() + * @channel: secure channel + * @an: association number + * Returns: 0 on success, -1 on failure + */ + int (*enable_transmit_sa)(void *priv, u32 channel, u8 an); + + /** + * disable_transmit_sa - disable SA for transmit + * @priv: private driver interface data from init() + * @channel: secure channel + * @an: association number + * Returns: 0 on success, -1 on failure + */ + int (*disable_transmit_sa)(void *priv, u32 channel, u8 an); +#endif /* CONFIG_MACSEC */ }; @@ -3527,6 +3836,15 @@ union wpa_event_data { u32 datarate; /** + * drv_priv - Pointer to store driver private BSS information + * + * If not set to NULL, this is used for comparison with + * hostapd_data->drv_priv to determine which BSS should process + * the frame. + */ + void *drv_priv; + + /** * freq - Frequency (in MHz) on which the frame was received */ int freq; @@ -3772,9 +4090,13 @@ union wpa_event_data { /** * channel_list_changed - Data for EVENT_CHANNEL_LIST_CHANGED * @initiator: Initiator of the regulatory change + * @type: Regulatory change type + * @alpha2: Country code (or "" if not available) */ struct channel_list_changed { enum reg_change_initiator initiator; + enum reg_type type; + char alpha2[3]; } channel_list_changed; /** diff --git a/src/drivers/driver_atheros.c b/src/drivers/driver_atheros.c index 23a4e2b..c146cdc 100644 --- a/src/drivers/driver_atheros.c +++ b/src/drivers/driver_atheros.c @@ -1868,6 +1868,25 @@ static int atheros_set_ap(void *priv, struct wpa_driver_ap_params *params) wpa_hexdump_buf(MSG_DEBUG, "atheros: assocresp_ies", params->assocresp_ies); +#if defined(CONFIG_HS20) && defined(IEEE80211_PARAM_OSEN) + if (params->osen) { + struct wpa_bss_params bss_params; + + os_memset(&bss_params, 0, sizeof(struct wpa_bss_params)); + bss_params.enabled = 1; + bss_params.wpa = 2; + bss_params.wpa_pairwise = WPA_CIPHER_CCMP; + bss_params.wpa_group = WPA_CIPHER_CCMP; + bss_params.ieee802_1x = 1; + + if (atheros_set_privacy(priv, 1) || + set80211param(priv, IEEE80211_PARAM_OSEN, 1)) + return -1; + + return atheros_set_ieee8021x(priv, &bss_params); + } +#endif /* CONFIG_HS20 && IEEE80211_PARAM_OSEN */ + return 0; } diff --git a/src/drivers/driver_macsec_qca.c b/src/drivers/driver_macsec_qca.c new file mode 100644 index 0000000..cf24799 --- /dev/null +++ b/src/drivers/driver_macsec_qca.c @@ -0,0 +1,887 @@ +/* + * Wired Ethernet driver interface for QCA MACsec driver + * Copyright (c) 2005-2009, Jouni Malinen <j@w1.fi> + * Copyright (c) 2004, Gunter Burchardt <tira@isx.de> + * 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 <sys/ioctl.h> +#include <net/if.h> +#ifdef __linux__ +#include <netpacket/packet.h> +#include <net/if_arp.h> +#include <net/if.h> +#endif /* __linux__ */ +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) +#include <net/if_dl.h> +#include <net/if_media.h> +#endif /* defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) */ +#ifdef __sun__ +#include <sys/sockio.h> +#endif /* __sun__ */ + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/defs.h" +#include "common/ieee802_1x_defs.h" +#include "driver.h" + +#include "nss_macsec_secy.h" +#include "nss_macsec_secy_rx.h" +#include "nss_macsec_secy_tx.h" + +#define MAXSC 16 + +/* TCI field definition */ +#define TCI_ES 0x40 +#define TCI_SC 0x20 +#define TCI_SCB 0x10 +#define TCI_E 0x08 +#define TCI_C 0x04 + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif /* _MSC_VER */ + +#ifdef _MSC_VER +#pragma pack(pop) +#endif /* _MSC_VER */ + +static const u8 pae_group_addr[ETH_ALEN] = +{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }; + +struct macsec_qca_data { + char ifname[IFNAMSIZ + 1]; + u32 secy_id; + void *ctx; + + int sock; /* raw packet socket for driver access */ + int pf_sock; + int membership, multi, iff_allmulti, iff_up; + + /* shadow */ + Boolean always_include_sci; + Boolean use_es; + Boolean use_scb; + Boolean protect_frames; + Boolean replay_protect; + u32 replay_window; +}; + + +static int macsec_qca_multicast_membership(int sock, int ifindex, + const u8 *addr, int add) +{ +#ifdef __linux__ + struct packet_mreq mreq; + + if (sock < 0) + return -1; + + os_memset(&mreq, 0, sizeof(mreq)); + mreq.mr_ifindex = ifindex; + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = ETH_ALEN; + os_memcpy(mreq.mr_address, addr, ETH_ALEN); + + if (setsockopt(sock, SOL_PACKET, + add ? PACKET_ADD_MEMBERSHIP : PACKET_DROP_MEMBERSHIP, + &mreq, sizeof(mreq)) < 0) { + perror("setsockopt"); + return -1; + } + return 0; +#else /* __linux__ */ + return -1; +#endif /* __linux__ */ +} + + +static int macsec_qca_get_ssid(void *priv, u8 *ssid) +{ + ssid[0] = 0; + return 0; +} + + +static int macsec_qca_get_bssid(void *priv, u8 *bssid) +{ + /* Report PAE group address as the "BSSID" for macsec connection. */ + os_memcpy(bssid, pae_group_addr, ETH_ALEN); + return 0; +} + + +static int macsec_qca_get_capa(void *priv, struct wpa_driver_capa *capa) +{ + os_memset(capa, 0, sizeof(*capa)); + capa->flags = WPA_DRIVER_FLAGS_WIRED; + return 0; +} + + +static int macsec_qca_get_ifflags(const char *ifname, int *flags) +{ + struct ifreq ifr; + int s; + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket"); + return -1; + } + + os_memset(&ifr, 0, sizeof(ifr)); + os_strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); + if (ioctl(s, SIOCGIFFLAGS, (caddr_t) &ifr) < 0) { + perror("ioctl[SIOCGIFFLAGS]"); + close(s); + return -1; + } + close(s); + *flags = ifr.ifr_flags & 0xffff; + return 0; +} + + +static int macsec_qca_set_ifflags(const char *ifname, int flags) +{ + struct ifreq ifr; + int s; + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket"); + return -1; + } + + os_memset(&ifr, 0, sizeof(ifr)); + os_strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_flags = flags & 0xffff; + if (ioctl(s, SIOCSIFFLAGS, (caddr_t) &ifr) < 0) { + perror("ioctl[SIOCSIFFLAGS]"); + close(s); + return -1; + } + close(s); + return 0; +} + + +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) +static int macsec_qca_get_ifstatus(const char *ifname, int *status) +{ + struct ifmediareq ifmr; + int s; + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket"); + return -1; + } + + os_memset(&ifmr, 0, sizeof(ifmr)); + os_strlcpy(ifmr.ifm_name, ifname, IFNAMSIZ); + if (ioctl(s, SIOCGIFMEDIA, (caddr_t) &ifmr) < 0) { + perror("ioctl[SIOCGIFMEDIA]"); + close(s); + return -1; + } + close(s); + *status = (ifmr.ifm_status & (IFM_ACTIVE | IFM_AVALID)) == + (IFM_ACTIVE | IFM_AVALID); + + return 0; +} +#endif /* defined(__FreeBSD__) || defined(__DragonFly__) || defined(FreeBSD_kernel__) */ + + +static int macsec_qca_multi(const char *ifname, const u8 *addr, int add) +{ + struct ifreq ifr; + int s; + +#ifdef __sun__ + return -1; +#endif /* __sun__ */ + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket"); + return -1; + } + + os_memset(&ifr, 0, sizeof(ifr)); + os_strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); +#ifdef __linux__ + ifr.ifr_hwaddr.sa_family = AF_UNSPEC; + os_memcpy(ifr.ifr_hwaddr.sa_data, addr, ETH_ALEN); +#endif /* __linux__ */ +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) + { + struct sockaddr_dl *dlp; + dlp = (struct sockaddr_dl *) &ifr.ifr_addr; + dlp->sdl_len = sizeof(struct sockaddr_dl); + dlp->sdl_family = AF_LINK; + dlp->sdl_index = 0; + dlp->sdl_nlen = 0; + dlp->sdl_alen = ETH_ALEN; + dlp->sdl_slen = 0; + os_memcpy(LLADDR(dlp), addr, ETH_ALEN); + } +#endif /* defined(__FreeBSD__) || defined(__DragonFly__) || defined(FreeBSD_kernel__) */ +#if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__) + { + struct sockaddr *sap; + sap = (struct sockaddr *) &ifr.ifr_addr; + sap->sa_len = sizeof(struct sockaddr); + sap->sa_family = AF_UNSPEC; + os_memcpy(sap->sa_data, addr, ETH_ALEN); + } +#endif /* defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__) */ + + if (ioctl(s, add ? SIOCADDMULTI : SIOCDELMULTI, (caddr_t) &ifr) < 0) { + perror("ioctl[SIOC{ADD/DEL}MULTI]"); + close(s); + return -1; + } + close(s); + return 0; +} + + +static void __macsec_drv_init(struct macsec_qca_data *drv) +{ + int ret = 0; + fal_rx_ctl_filt_t rx_ctl_filt; + fal_tx_ctl_filt_t tx_ctl_filt; + + wpa_printf(MSG_INFO, "%s: secy_id=%d", __func__, drv->secy_id); + + /* Enable Secy and Let EAPoL bypass */ + ret = nss_macsec_secy_en_set(drv->secy_id, TRUE); + if (ret) + wpa_printf(MSG_ERROR, "nss_macsec_secy_en_set: FAIL"); + + ret = nss_macsec_secy_sc_sa_mapping_mode_set(drv->secy_id, + FAL_SC_SA_MAP_1_4); + if (ret) + wpa_printf(MSG_ERROR, + "nss_macsec_secy_sc_sa_mapping_mode_set: FAIL"); + + os_memset(&rx_ctl_filt, 0, sizeof(rx_ctl_filt)); + rx_ctl_filt.bypass = 1; + rx_ctl_filt.match_type = IG_CTL_COMPARE_ETHER_TYPE; + rx_ctl_filt.match_mask = 0xffff; + rx_ctl_filt.ether_type_da_range = 0x888e; + ret = nss_macsec_secy_rx_ctl_filt_set(drv->secy_id, 0, &rx_ctl_filt); + if (ret) + wpa_printf(MSG_ERROR, "nss_macsec_secy_rx_ctl_filt_set: FAIL"); + + os_memset(&tx_ctl_filt, 0, sizeof(tx_ctl_filt)); + tx_ctl_filt.bypass = 1; + tx_ctl_filt.match_type = EG_CTL_COMPARE_ETHER_TYPE; + tx_ctl_filt.match_mask = 0xffff; + tx_ctl_filt.ether_type_da_range = 0x888e; + ret = nss_macsec_secy_tx_ctl_filt_set(drv->secy_id, 0, &tx_ctl_filt); + if (ret) + wpa_printf(MSG_ERROR, "nss_macsec_secy_tx_ctl_filt_set: FAIL"); +} + + +static void __macsec_drv_deinit(struct macsec_qca_data *drv) +{ + nss_macsec_secy_en_set(drv->secy_id, FALSE); + nss_macsec_secy_rx_sc_del_all(drv->secy_id); + nss_macsec_secy_tx_sc_del_all(drv->secy_id); +} + + +static void * macsec_qca_init(void *ctx, const char *ifname) +{ + struct macsec_qca_data *drv; + int flags; + + drv = os_zalloc(sizeof(*drv)); + if (drv == NULL) + return NULL; + os_strlcpy(drv->ifname, ifname, sizeof(drv->ifname)); + drv->ctx = ctx; + + /* Board specific settings */ + if (os_memcmp("eth2", drv->ifname, 4) == 0) + drv->secy_id = 1; + else if (os_memcmp("eth3", drv->ifname, 4) == 0) + drv->secy_id = 2; + else + drv->secy_id = -1; + +#ifdef __linux__ + drv->pf_sock = socket(PF_PACKET, SOCK_DGRAM, 0); + if (drv->pf_sock < 0) + perror("socket(PF_PACKET)"); +#else /* __linux__ */ + drv->pf_sock = -1; +#endif /* __linux__ */ + + if (macsec_qca_get_ifflags(ifname, &flags) == 0 && + !(flags & IFF_UP) && + macsec_qca_set_ifflags(ifname, flags | IFF_UP) == 0) { + drv->iff_up = 1; + } + + if (macsec_qca_multicast_membership(drv->pf_sock, + if_nametoindex(drv->ifname), + pae_group_addr, 1) == 0) { + wpa_printf(MSG_DEBUG, + "%s: Added multicast membership with packet socket", + __func__); + drv->membership = 1; + } else if (macsec_qca_multi(ifname, pae_group_addr, 1) == 0) { + wpa_printf(MSG_DEBUG, + "%s: Added multicast membership with SIOCADDMULTI", + __func__); + drv->multi = 1; + } else if (macsec_qca_get_ifflags(ifname, &flags) < 0) { + wpa_printf(MSG_INFO, "%s: Could not get interface flags", + __func__); + os_free(drv); + return NULL; + } else if (flags & IFF_ALLMULTI) { + wpa_printf(MSG_DEBUG, + "%s: Interface is already configured for multicast", + __func__); + } else if (macsec_qca_set_ifflags(ifname, flags | IFF_ALLMULTI) < 0) { + wpa_printf(MSG_INFO, "%s: Failed to enable allmulti", + __func__); + os_free(drv); + return NULL; + } else { + wpa_printf(MSG_DEBUG, "%s: Enabled allmulti mode", __func__); + drv->iff_allmulti = 1; + } +#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__) + { + int status; + wpa_printf(MSG_DEBUG, "%s: waiting for link to become active", + __func__); + while (macsec_qca_get_ifstatus(ifname, &status) == 0 && + status == 0) + sleep(1); + } +#endif /* defined(__FreeBSD__) || defined(__DragonFly__) || defined(FreeBSD_kernel__) */ + + return drv; +} + + +static void macsec_qca_deinit(void *priv) +{ + struct macsec_qca_data *drv = priv; + int flags; + + if (drv->membership && + macsec_qca_multicast_membership(drv->pf_sock, + if_nametoindex(drv->ifname), + pae_group_addr, 0) < 0) { + wpa_printf(MSG_DEBUG, + "%s: Failed to remove PAE multicast group (PACKET)", + __func__); + } + + if (drv->multi && + macsec_qca_multi(drv->ifname, pae_group_addr, 0) < 0) { + wpa_printf(MSG_DEBUG, + "%s: Failed to remove PAE multicast group (SIOCDELMULTI)", + __func__); + } + + if (drv->iff_allmulti && + (macsec_qca_get_ifflags(drv->ifname, &flags) < 0 || + macsec_qca_set_ifflags(drv->ifname, flags & ~IFF_ALLMULTI) < 0)) { + wpa_printf(MSG_DEBUG, "%s: Failed to disable allmulti mode", + __func__); + } + + if (drv->iff_up && + macsec_qca_get_ifflags(drv->ifname, &flags) == 0 && + (flags & IFF_UP) && + macsec_qca_set_ifflags(drv->ifname, flags & ~IFF_UP) < 0) { + wpa_printf(MSG_DEBUG, "%s: Failed to set the interface down", + __func__); + } + + if (drv->pf_sock != -1) + close(drv->pf_sock); + + os_free(drv); +} + + +static int macsec_qca_macsec_init(void *priv, struct macsec_init_params *params) +{ + struct macsec_qca_data *drv = priv; + + drv->always_include_sci = params->always_include_sci; + drv->use_es = params->use_es; + drv->use_scb = params->use_scb; + + wpa_printf(MSG_DEBUG, "%s: es=%d, scb=%d, sci=%d", + __func__, drv->use_es, drv->use_scb, + drv->always_include_sci); + + __macsec_drv_init(drv); + + return 0; +} + + +static int macsec_qca_macsec_deinit(void *priv) +{ + struct macsec_qca_data *drv = priv; + + wpa_printf(MSG_DEBUG, "%s", __func__); + + __macsec_drv_deinit(drv); + + return 0; +} + + +static int macsec_qca_enable_protect_frames(void *priv, Boolean enabled) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + + wpa_printf(MSG_DEBUG, "%s: enabled=%d", __func__, enabled); + + drv->protect_frames = enabled; + + return ret; +} + + +static int macsec_qca_set_replay_protect(void *priv, Boolean enabled, + unsigned int window) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + + wpa_printf(MSG_DEBUG, "%s: enabled=%d, win=%u", + __func__, enabled, window); + + drv->replay_protect = enabled; + drv->replay_window = window; + + return ret; +} + + +static int macsec_qca_set_current_cipher_suite(void *priv, const u8 *cs, + size_t cs_len) +{ + u8 default_cs_id[] = CS_ID_GCM_AES_128; + + if (cs_len != CS_ID_LEN || + os_memcmp(cs, default_cs_id, cs_len) != 0) { + wpa_hexdump(MSG_ERROR, "macsec: NOT supported CipherSuite", + cs, cs_len); + return -1; + } + + /* Support default Cipher Suite 0080020001000001 (GCM-AES-128) */ + wpa_printf(MSG_DEBUG, "%s: default support aes-gcm-128", __func__); + + return 0; +} + + +static int macsec_qca_enable_controlled_port(void *priv, Boolean enabled) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + + wpa_printf(MSG_DEBUG, "%s: enable=%d", __func__, enabled); + + ret += nss_macsec_secy_controlled_port_en_set(drv->secy_id, enabled); + + return ret; +} + + +static int macsec_qca_get_receive_lowest_pn(void *priv, u32 channel, u8 an, + u32 *lowest_pn) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + u32 next_pn = 0; + bool enabled = FALSE; + u32 win; + + ret += nss_macsec_secy_rx_sa_next_pn_get(drv->secy_id, channel, an, + &next_pn); + ret += nss_macsec_secy_rx_sc_replay_protect_get(drv->secy_id, channel, + &enabled); + ret += nss_macsec_secy_rx_sc_anti_replay_window_get(drv->secy_id, + channel, &win); + + if (enabled) + *lowest_pn = (next_pn > win) ? (next_pn - win) : 1; + else + *lowest_pn = next_pn; + + wpa_printf(MSG_DEBUG, "%s: lpn=0x%x", __func__, *lowest_pn); + + return ret; +} + + +static int macsec_qca_get_transmit_next_pn(void *priv, u32 channel, u8 an, + u32 *next_pn) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + + ret += nss_macsec_secy_tx_sa_next_pn_get(drv->secy_id, channel, an, + next_pn); + + wpa_printf(MSG_DEBUG, "%s: npn=0x%x", __func__, *next_pn); + + return ret; +} + + +int macsec_qca_set_transmit_next_pn(void *priv, u32 channel, u8 an, u32 next_pn) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + + ret += nss_macsec_secy_tx_sa_next_pn_set(drv->secy_id, channel, an, + next_pn); + + wpa_printf(MSG_INFO, "%s: npn=0x%x", __func__, next_pn); + + return ret; +} + + +static int macsec_qca_get_available_receive_sc(void *priv, u32 *channel) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + u32 sc_ch = 0; + bool in_use = FALSE; + + for (sc_ch = 0; sc_ch < MAXSC; sc_ch++) { + ret = nss_macsec_secy_rx_sc_in_used_get(drv->secy_id, sc_ch, + &in_use); + if (ret) + continue; + + if (!in_use) { + *channel = sc_ch; + wpa_printf(MSG_DEBUG, "%s: channel=%d", + __func__, *channel); + return 0; + } + } + + wpa_printf(MSG_DEBUG, "%s: no available channel", __func__); + + return -1; +} + + +static int macsec_qca_create_receive_sc(void *priv, u32 channel, + const u8 *sci_addr, u16 sci_port, + unsigned int conf_offset, + int validation) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + fal_rx_prc_lut_t entry; + fal_rx_sc_validate_frame_e vf; + enum validate_frames validate_frames = validation; + + wpa_printf(MSG_DEBUG, "%s: channel=%d", __func__, channel); + + /* rx prc lut */ + os_memset(&entry, 0, sizeof(entry)); + + os_memcpy(entry.sci, sci_addr, ETH_ALEN); + entry.sci[6] = (sci_port >> 8) & 0xf; + entry.sci[7] = sci_port & 0xf; + entry.sci_mask = 0xf; + + entry.valid = 1; + entry.channel = channel; + entry.action = FAL_RX_PRC_ACTION_PROCESS; + entry.offset = conf_offset; + + /* rx validate frame */ + if (validate_frames == Strict) + vf = FAL_RX_SC_VALIDATE_FRAME_STRICT; + else if (validate_frames == Checked) + vf = FAL_RX_SC_VALIDATE_FRAME_CHECK; + else + vf = FAL_RX_SC_VALIDATE_FRAME_DISABLED; + + ret += nss_macsec_secy_rx_prc_lut_set(drv->secy_id, channel, &entry); + ret += nss_macsec_secy_rx_sc_create(drv->secy_id, channel); + ret += nss_macsec_secy_rx_sc_validate_frame_set(drv->secy_id, channel, + vf); + ret += nss_macsec_secy_rx_sc_replay_protect_set(drv->secy_id, channel, + drv->replay_protect); + ret += nss_macsec_secy_rx_sc_anti_replay_window_set(drv->secy_id, + channel, + drv->replay_window); + + return ret; +} + + +static int macsec_qca_delete_receive_sc(void *priv, u32 channel) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + fal_rx_prc_lut_t entry; + + wpa_printf(MSG_DEBUG, "%s: channel=%d", __func__, channel); + + /* rx prc lut */ + os_memset(&entry, 0, sizeof(entry)); + + ret += nss_macsec_secy_rx_sc_del(drv->secy_id, channel); + ret += nss_macsec_secy_rx_prc_lut_set(drv->secy_id, channel, &entry); + + return ret; +} + + +static int macsec_qca_create_receive_sa(void *priv, u32 channel, u8 an, + u32 lowest_pn, const u8 *sak) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + fal_rx_sak_t rx_sak; + int i = 0; + + wpa_printf(MSG_DEBUG, "%s, channel=%d, an=%d, lpn=0x%x", + __func__, channel, an, lowest_pn); + + os_memset(&rx_sak, 0, sizeof(rx_sak)); + for (i = 0; i < 16; i++) + rx_sak.sak[i] = sak[15 - i]; + + ret += nss_macsec_secy_rx_sa_create(drv->secy_id, channel, an); + ret += nss_macsec_secy_rx_sak_set(drv->secy_id, channel, an, &rx_sak); + + return ret; +} + + +static int macsec_qca_enable_receive_sa(void *priv, u32 channel, u8 an) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + + wpa_printf(MSG_DEBUG, "%s: channel=%d, an=%d", __func__, channel, an); + + ret += nss_macsec_secy_rx_sa_en_set(drv->secy_id, channel, an, TRUE); + + return ret; +} + + +static int macsec_qca_disable_receive_sa(void *priv, u32 channel, u8 an) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + + wpa_printf(MSG_DEBUG, "%s: channel=%d, an=%d", __func__, channel, an); + + ret += nss_macsec_secy_rx_sa_en_set(drv->secy_id, channel, an, FALSE); + + return ret; +} + + +static int macsec_qca_get_available_transmit_sc(void *priv, u32 *channel) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + u32 sc_ch = 0; + bool in_use = FALSE; + + for (sc_ch = 0; sc_ch < MAXSC; sc_ch++) { + ret = nss_macsec_secy_tx_sc_in_used_get(drv->secy_id, sc_ch, + &in_use); + if (ret) + continue; + + if (!in_use) { + *channel = sc_ch; + wpa_printf(MSG_DEBUG, "%s: channel=%d", + __func__, *channel); + return 0; + } + } + + wpa_printf(MSG_DEBUG, "%s: no avaiable channel", __func__); + + return -1; +} + + +static int macsec_qca_create_transmit_sc(void *priv, u32 channel, + const u8 *sci_addr, u16 sci_port, + unsigned int conf_offset) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + fal_tx_class_lut_t entry; + u8 psci[ETH_ALEN + 2]; + + wpa_printf(MSG_DEBUG, "%s: channel=%d", __func__, channel); + + /* class lut */ + os_memset(&entry, 0, sizeof(entry)); + + entry.valid = 1; + entry.action = FAL_TX_CLASS_ACTION_FORWARD; + entry.channel = channel; + + os_memcpy(psci, sci_addr, ETH_ALEN); + psci[6] = (sci_port >> 8) & 0xf; + psci[7] = sci_port & 0xf; + + ret += nss_macsec_secy_tx_class_lut_set(drv->secy_id, channel, &entry); + ret += nss_macsec_secy_tx_sc_create(drv->secy_id, channel, psci, 8); + ret += nss_macsec_secy_tx_sc_protect_set(drv->secy_id, channel, + drv->protect_frames); + ret += nss_macsec_secy_tx_sc_confidentiality_offset_set(drv->secy_id, + channel, + conf_offset); + + return ret; +} + + +static int macsec_qca_delete_transmit_sc(void *priv, u32 channel) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + fal_tx_class_lut_t entry; + + wpa_printf(MSG_DEBUG, "%s: channel=%d", __func__, channel); + + /* class lut */ + os_memset(&entry, 0, sizeof(entry)); + + ret += nss_macsec_secy_tx_class_lut_set(drv->secy_id, channel, &entry); + ret += nss_macsec_secy_tx_sc_del(drv->secy_id, channel); + + return ret; +} + + +static int macsec_qca_create_transmit_sa(void *priv, u32 channel, u8 an, + u32 next_pn, Boolean confidentiality, + const u8 *sak) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + u8 tci = 0; + fal_tx_sak_t tx_sak; + int i; + + wpa_printf(MSG_DEBUG, + "%s: channel=%d, an=%d, next_pn=0x%x, confidentiality=%d", + __func__, channel, an, next_pn, confidentiality); + + if (drv->always_include_sci) + tci |= TCI_SC; + else if (drv->use_es) + tci |= TCI_ES; + else if (drv->use_scb) + tci |= TCI_SCB; + + if (confidentiality) + tci |= TCI_E | TCI_C; + + os_memset(&tx_sak, 0, sizeof(tx_sak)); + for (i = 0; i < 16; i++) + tx_sak.sak[i] = sak[15 - i]; + + ret += nss_macsec_secy_tx_sa_next_pn_set(drv->secy_id, channel, an, + next_pn); + ret += nss_macsec_secy_tx_sak_set(drv->secy_id, channel, an, &tx_sak); + ret += nss_macsec_secy_tx_sc_tci_7_2_set(drv->secy_id, channel, + (tci >> 2)); + ret += nss_macsec_secy_tx_sc_an_set(drv->secy_id, channel, an); + + return ret; +} + + +static int macsec_qca_enable_transmit_sa(void *priv, u32 channel, u8 an) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + + wpa_printf(MSG_DEBUG, "%s: channel=%d, an=%d", __func__, channel, an); + + ret += nss_macsec_secy_tx_sa_en_set(drv->secy_id, channel, an, TRUE); + + return ret; +} + + +static int macsec_qca_disable_transmit_sa(void *priv, u32 channel, u8 an) +{ + struct macsec_qca_data *drv = priv; + int ret = 0; + + wpa_printf(MSG_DEBUG, "%s: channel=%d, an=%d", __func__, channel, an); + + ret += nss_macsec_secy_tx_sa_en_set(drv->secy_id, channel, an, FALSE); + + return ret; +} + + +const struct wpa_driver_ops wpa_driver_macsec_qca_ops = { + .name = "macsec_qca", + .desc = "QCA MACsec Ethernet driver", + .get_ssid = macsec_qca_get_ssid, + .get_bssid = macsec_qca_get_bssid, + .get_capa = macsec_qca_get_capa, + .init = macsec_qca_init, + .deinit = macsec_qca_deinit, + + .macsec_init = macsec_qca_macsec_init, + .macsec_deinit = macsec_qca_macsec_deinit, + .enable_protect_frames = macsec_qca_enable_protect_frames, + .set_replay_protect = macsec_qca_set_replay_protect, + .set_current_cipher_suite = macsec_qca_set_current_cipher_suite, + .enable_controlled_port = macsec_qca_enable_controlled_port, + .get_receive_lowest_pn = macsec_qca_get_receive_lowest_pn, + .get_transmit_next_pn = macsec_qca_get_transmit_next_pn, + .set_transmit_next_pn = macsec_qca_set_transmit_next_pn, + .get_available_receive_sc = macsec_qca_get_available_receive_sc, + .create_receive_sc = macsec_qca_create_receive_sc, + .delete_receive_sc = macsec_qca_delete_receive_sc, + .create_receive_sa = macsec_qca_create_receive_sa, + .enable_receive_sa = macsec_qca_enable_receive_sa, + .disable_receive_sa = macsec_qca_disable_receive_sa, + .get_available_transmit_sc = macsec_qca_get_available_transmit_sc, + .create_transmit_sc = macsec_qca_create_transmit_sc, + .delete_transmit_sc = macsec_qca_delete_transmit_sc, + .create_transmit_sa = macsec_qca_create_transmit_sa, + .enable_transmit_sa = macsec_qca_enable_transmit_sa, + .disable_transmit_sa = macsec_qca_disable_transmit_sa, +}; diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 5323e99..7568653 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -29,6 +29,7 @@ #include "eloop.h" #include "utils/list.h" #include "common/qca-vendor.h" +#include "common/qca-vendor-attr.h" #include "common/ieee802_11_defs.h" #include "common/ieee802_11_common.h" #include "l2_packet/l2_packet.h" @@ -235,6 +236,7 @@ struct i802_bss { u8 addr[ETH_ALEN]; int freq; + int bandwidth; int if_dynamic; void *ctx; @@ -297,10 +299,14 @@ struct wpa_driver_nl80211_data { unsigned int retry_auth:1; unsigned int use_monitor:1; unsigned int ignore_next_local_disconnect:1; + unsigned int ignore_next_local_deauth:1; unsigned int allow_p2p_device:1; unsigned int hostapd:1; unsigned int start_mode_ap:1; unsigned int start_iface_up:1; + unsigned int test_use_roc_tx:1; + unsigned int ignore_deauth_event:1; + unsigned int dfs_vendor_cmd_avail:1; u64 remain_on_chan_cookie; u64 send_action_cookie; @@ -366,12 +372,29 @@ extern int wpa_driver_nl80211_driver_cmd(void *priv, char *cmd, char *buf, size_t buf_len); #endif /* ANDROID */ #ifdef ANDROID_P2P +#ifdef ANDROID_P2P_STUB +int wpa_driver_set_p2p_noa(void *priv, u8 count, int start, int duration) { + return 0; +} +int wpa_driver_get_p2p_noa(void *priv, u8 *buf, size_t len) { + return 0; +} +int wpa_driver_set_p2p_ps(void *priv, int legacy_ps, int opp_ps, int ctwindow) { + return -1; +} +int wpa_driver_set_ap_wps_p2p_ie(void *priv, const struct wpabuf *beacon, + const struct wpabuf *proberesp, + const struct wpabuf *assocresp) { + return 0; +} +#else /* ANDROID_P2P_STUB */ int wpa_driver_set_p2p_noa(void *priv, u8 count, int start, int duration); int wpa_driver_get_p2p_noa(void *priv, u8 *buf, size_t len); int wpa_driver_set_p2p_ps(void *priv, int legacy_ps, int opp_ps, int ctwindow); int wpa_driver_set_ap_wps_p2p_ie(void *priv, const struct wpabuf *beacon, const struct wpabuf *proberesp, const struct wpabuf *assocresp); +#endif /* ANDROID_P2P_STUB */ #endif /* ANDROID_P2P */ static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx); @@ -381,8 +404,8 @@ static int wpa_driver_nl80211_if_remove(struct i802_bss *bss, enum wpa_driver_if_type type, const char *ifname); -static int wpa_driver_nl80211_set_freq(struct i802_bss *bss, - struct hostapd_freq_params *freq); +static int nl80211_set_channel(struct i802_bss *bss, + struct hostapd_freq_params *freq, int set_chan); static int nl80211_disable_11b_rates(struct wpa_driver_nl80211_data *drv, int ifindex, int disabled); @@ -532,22 +555,22 @@ static enum chan_width convert2width(int width) static int is_ap_interface(enum nl80211_iftype nlmode) { - return (nlmode == NL80211_IFTYPE_AP || - nlmode == NL80211_IFTYPE_P2P_GO); + return nlmode == NL80211_IFTYPE_AP || + nlmode == NL80211_IFTYPE_P2P_GO; } static int is_sta_interface(enum nl80211_iftype nlmode) { - return (nlmode == NL80211_IFTYPE_STATION || - nlmode == NL80211_IFTYPE_P2P_CLIENT); + return nlmode == NL80211_IFTYPE_STATION || + nlmode == NL80211_IFTYPE_P2P_CLIENT; } static int is_p2p_net_interface(enum nl80211_iftype nlmode) { - return (nlmode == NL80211_IFTYPE_P2P_CLIENT || - nlmode == NL80211_IFTYPE_P2P_GO); + return nlmode == NL80211_IFTYPE_P2P_CLIENT || + nlmode == NL80211_IFTYPE_P2P_GO; } @@ -564,6 +587,7 @@ struct nl80211_bss_info_arg { struct wpa_driver_nl80211_data *drv; struct wpa_scan_results *res; unsigned int assoc_freq; + unsigned int ibss_freq; u8 assoc_bssid[ETH_ALEN]; }; @@ -628,7 +652,7 @@ static int send_and_recv(struct nl80211_global *global, while (err > 0) { int res = nl_recvmsgs(nl_handle, cb); - if (res) { + if (res < 0) { wpa_printf(MSG_INFO, "nl80211: %s->nl_recvmsgs failed: %d", __func__, res); @@ -886,7 +910,7 @@ static void nl80211_recv_beacons(int sock, void *eloop_ctx, void *handle) wpa_printf(MSG_EXCESSIVE, "nl80211: Beacon event message available"); res = nl_recvmsgs(handle, w->nl_cb); - if (res) { + if (res < 0) { wpa_printf(MSG_INFO, "nl80211: %s->nl_recvmsgs failed: %d", __func__, res); } @@ -1238,6 +1262,16 @@ static void wpa_driver_nl80211_event_rtm_newlink(void *ctx, drv->if_disabled = 1; wpa_supplicant_event(drv->ctx, EVENT_INTERFACE_DISABLED, NULL); + + /* + * Try to get drv again, since it may be removed as + * part of the EVENT_INTERFACE_DISABLED handling for + * dynamic interfaces + */ + drv = nl80211_find_drv(global, ifi->ifi_index, + buf, len); + if (!drv) + return; } } @@ -1391,11 +1425,12 @@ static unsigned int nl80211_get_assoc_freq(struct wpa_driver_nl80211_data *drv) ret = send_and_recv_msgs(drv, msg, bss_info_handler, &arg); msg = NULL; if (ret == 0) { + unsigned int freq = drv->nlmode == NL80211_IFTYPE_ADHOC ? + arg.ibss_freq : arg.assoc_freq; wpa_printf(MSG_DEBUG, "nl80211: Operating frequency for the " - "associated BSS from scan results: %u MHz", - arg.assoc_freq); - if (arg.assoc_freq) - drv->assoc_freq = arg.assoc_freq; + "associated BSS from scan results: %u MHz", freq); + if (freq) + drv->assoc_freq = freq; return drv->assoc_freq; } wpa_printf(MSG_DEBUG, "nl80211: Scan result fetch failed: ret=%d " @@ -1670,10 +1705,11 @@ static void mlme_timeout_event(struct wpa_driver_nl80211_data *drv, } -static void mlme_event_mgmt(struct wpa_driver_nl80211_data *drv, +static void mlme_event_mgmt(struct i802_bss *bss, struct nlattr *freq, struct nlattr *sig, const u8 *frame, size_t len) { + struct wpa_driver_nl80211_data *drv = bss->drv; const struct ieee80211_mgmt *mgmt; union wpa_event_data event; u16 fc, stype; @@ -1704,6 +1740,7 @@ static void mlme_event_mgmt(struct wpa_driver_nl80211_data *drv, event.rx_mgmt.frame = frame; event.rx_mgmt.frame_len = len; event.rx_mgmt.ssi_signal = ssi_signal; + event.rx_mgmt.drv_priv = bss; wpa_supplicant_event(drv->ctx, EVENT_RX_MGMT, &event); } @@ -1814,8 +1851,21 @@ static void mlme_event_deauth_disassoc(struct wpa_driver_nl80211_data *drv, mgmt->u.disassoc.variable; } } else { + if (drv->ignore_deauth_event) { + wpa_printf(MSG_DEBUG, "nl80211: Ignore deauth event due to previous forced deauth-during-auth"); + drv->ignore_deauth_event = 0; + return; + } event.deauth_info.locally_generated = !os_memcmp(mgmt->sa, drv->first_bss->addr, ETH_ALEN); + if (drv->ignore_next_local_deauth) { + drv->ignore_next_local_deauth = 0; + if (event.deauth_info.locally_generated) { + wpa_printf(MSG_DEBUG, "nl80211: Ignore deauth event triggered due to own deauth request"); + return; + } + wpa_printf(MSG_WARNING, "nl80211: Was expecting local deauth but got another disconnect event first"); + } event.deauth_info.addr = bssid; event.deauth_info.reason_code = reason_code; if (frame + len > mgmt->u.deauth.variable) { @@ -1928,7 +1978,7 @@ static void mlme_event(struct i802_bss *bss, nla_data(frame), nla_len(frame)); break; case NL80211_CMD_FRAME: - mlme_event_mgmt(drv, freq, sig, nla_data(frame), + mlme_event_mgmt(bss, freq, sig, nla_data(frame), nla_len(frame)); break; case NL80211_CMD_FRAME_TX_STATUS: @@ -1988,6 +2038,8 @@ static void mlme_event_michael_mic_failure(struct i802_bss *bss, static void mlme_event_join_ibss(struct wpa_driver_nl80211_data *drv, struct nlattr *tb[]) { + unsigned int freq; + if (tb[NL80211_ATTR_MAC] == NULL) { wpa_printf(MSG_DEBUG, "nl80211: No address in IBSS joined " "event"); @@ -1999,6 +2051,13 @@ static void mlme_event_join_ibss(struct wpa_driver_nl80211_data *drv, wpa_printf(MSG_DEBUG, "nl80211: IBSS " MACSTR " joined", MAC2STR(drv->bssid)); + freq = nl80211_get_assoc_freq(drv); + if (freq) { + wpa_printf(MSG_DEBUG, "nl80211: IBSS on frequency %u MHz", + freq); + drv->first_bss->freq = freq; + } + wpa_supplicant_event(drv->ctx, EVENT_ASSOC, NULL); } @@ -2860,6 +2919,69 @@ static void nl80211_vendor_event(struct wpa_driver_nl80211_data *drv, } +static void nl80211_reg_change_event(struct wpa_driver_nl80211_data *drv, + struct nlattr *tb[]) +{ + union wpa_event_data data; + enum nl80211_reg_initiator init; + + wpa_printf(MSG_DEBUG, "nl80211: Regulatory domain change"); + + if (tb[NL80211_ATTR_REG_INITIATOR] == NULL) + return; + + os_memset(&data, 0, sizeof(data)); + init = nla_get_u8(tb[NL80211_ATTR_REG_INITIATOR]); + wpa_printf(MSG_DEBUG, " * initiator=%d", init); + switch (init) { + case NL80211_REGDOM_SET_BY_CORE: + data.channel_list_changed.initiator = REGDOM_SET_BY_CORE; + break; + case NL80211_REGDOM_SET_BY_USER: + data.channel_list_changed.initiator = REGDOM_SET_BY_USER; + break; + case NL80211_REGDOM_SET_BY_DRIVER: + data.channel_list_changed.initiator = REGDOM_SET_BY_DRIVER; + break; + case NL80211_REGDOM_SET_BY_COUNTRY_IE: + data.channel_list_changed.initiator = REGDOM_SET_BY_COUNTRY_IE; + break; + } + + if (tb[NL80211_ATTR_REG_TYPE]) { + enum nl80211_reg_type type; + type = nla_get_u8(tb[NL80211_ATTR_REG_TYPE]); + wpa_printf(MSG_DEBUG, " * type=%d", type); + switch (type) { + case NL80211_REGDOM_TYPE_COUNTRY: + data.channel_list_changed.type = REGDOM_TYPE_COUNTRY; + break; + case NL80211_REGDOM_TYPE_WORLD: + data.channel_list_changed.type = REGDOM_TYPE_WORLD; + break; + case NL80211_REGDOM_TYPE_CUSTOM_WORLD: + data.channel_list_changed.type = + REGDOM_TYPE_CUSTOM_WORLD; + break; + case NL80211_REGDOM_TYPE_INTERSECTION: + data.channel_list_changed.type = + REGDOM_TYPE_INTERSECTION; + break; + } + } + + if (tb[NL80211_ATTR_REG_ALPHA2]) { + os_strlcpy(data.channel_list_changed.alpha2, + nla_get_string(tb[NL80211_ATTR_REG_ALPHA2]), + sizeof(data.channel_list_changed.alpha2)); + wpa_printf(MSG_DEBUG, " * alpha2=%s", + data.channel_list_changed.alpha2); + } + + wpa_supplicant_event(drv->ctx, EVENT_CHANNEL_LIST_CHANGED, &data); +} + + static void do_process_drv_event(struct i802_bss *bss, int cmd, struct nlattr **tb) { @@ -2881,6 +3003,16 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd, case NL80211_CMD_TRIGGER_SCAN: wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Scan trigger"); drv->scan_state = SCAN_STARTED; + if (drv->scan_for_auth) { + /* + * Cannot indicate EVENT_SCAN_STARTED here since we skip + * EVENT_SCAN_RESULTS in scan_for_auth case and the + * upper layer implementation could get confused about + * scanning state. + */ + wpa_printf(MSG_DEBUG, "nl80211: Do not indicate scan-start event due to internal scan_for_auth"); + break; + } wpa_supplicant_event(drv->ctx, EVENT_SCAN_STARTED, NULL); break; case NL80211_CMD_START_SCHED_SCAN: @@ -2969,34 +3101,7 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd, nl80211_cqm_event(drv, tb); break; case NL80211_CMD_REG_CHANGE: - wpa_printf(MSG_DEBUG, "nl80211: Regulatory domain change"); - if (tb[NL80211_ATTR_REG_INITIATOR] == NULL) - break; - os_memset(&data, 0, sizeof(data)); - switch (nla_get_u8(tb[NL80211_ATTR_REG_INITIATOR])) { - case NL80211_REGDOM_SET_BY_CORE: - data.channel_list_changed.initiator = - REGDOM_SET_BY_CORE; - break; - case NL80211_REGDOM_SET_BY_USER: - data.channel_list_changed.initiator = - REGDOM_SET_BY_USER; - break; - case NL80211_REGDOM_SET_BY_DRIVER: - data.channel_list_changed.initiator = - REGDOM_SET_BY_DRIVER; - break; - case NL80211_REGDOM_SET_BY_COUNTRY_IE: - data.channel_list_changed.initiator = - REGDOM_SET_BY_COUNTRY_IE; - break; - default: - wpa_printf(MSG_DEBUG, "nl80211: Unknown reg change initiator %d received", - nla_get_u8(tb[NL80211_ATTR_REG_INITIATOR])); - break; - } - wpa_supplicant_event(drv->ctx, EVENT_CHANNEL_LIST_CHANGED, - &data); + nl80211_reg_change_event(drv, tb); break; case NL80211_CMD_REG_BEACON_HINT: wpa_printf(MSG_DEBUG, "nl80211: Regulatory beacon hint"); @@ -3171,7 +3276,7 @@ static void wpa_driver_nl80211_event_receive(int sock, void *eloop_ctx, wpa_printf(MSG_MSGDUMP, "nl80211: Event message available"); res = nl_recvmsgs(handle, cb); - if (res) { + if (res < 0) { wpa_printf(MSG_INFO, "nl80211: %s->nl_recvmsgs failed: %d", __func__, res); } @@ -3414,10 +3519,12 @@ static int wiphy_info_iface_comb_process(struct wiphy_info_data *info, } if (combination_has_p2p && combination_has_mgd) { - info->p2p_concurrent = 1; - info->num_multichan_concurrent = + unsigned int num_channels = nla_get_u32(tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS]); - return 1; + + info->p2p_concurrent = 1; + if (info->num_multichan_concurrent < num_channels) + info->num_multichan_concurrent = num_channels; } return 0; @@ -3525,6 +3632,9 @@ static void wiphy_info_cipher_suites(struct wiphy_info_data *info, case WLAN_CIPHER_SUITE_BIP_CMAC_256: info->capa->enc |= WPA_DRIVER_CAPA_ENC_BIP_CMAC_256; break; + case WLAN_CIPHER_SUITE_NO_GROUP_ADDR: + info->capa->enc |= WPA_DRIVER_CAPA_ENC_GTK_NOT_USED; + break; } } } @@ -3576,6 +3686,9 @@ static void wiphy_info_feature_flags(struct wiphy_info_data *info, if (flags & NL80211_FEATURE_NEED_OBSS_SCAN) capa->flags |= WPA_DRIVER_FLAGS_OBSS_SCAN; + + if (flags & NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE) + capa->flags |= WPA_DRIVER_FLAGS_HT_2040_COEX; } @@ -3595,6 +3708,35 @@ static void wiphy_info_probe_resp_offload(struct wpa_driver_capa *capa, } +static void wiphy_info_wowlan_triggers(struct wpa_driver_capa *capa, + struct nlattr *tb) +{ + struct nlattr *triggers[MAX_NL80211_WOWLAN_TRIG + 1]; + + if (tb == NULL) + return; + + if (nla_parse_nested(triggers, MAX_NL80211_WOWLAN_TRIG, + tb, NULL)) + return; + + if (triggers[NL80211_WOWLAN_TRIG_ANY]) + capa->wowlan_triggers.any = 1; + if (triggers[NL80211_WOWLAN_TRIG_DISCONNECT]) + capa->wowlan_triggers.disconnect = 1; + if (triggers[NL80211_WOWLAN_TRIG_MAGIC_PKT]) + capa->wowlan_triggers.magic_pkt = 1; + if (triggers[NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE]) + capa->wowlan_triggers.gtk_rekey_failure = 1; + if (triggers[NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST]) + capa->wowlan_triggers.eap_identity_req = 1; + if (triggers[NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE]) + capa->wowlan_triggers.four_way_handshake = 1; + if (triggers[NL80211_WOWLAN_TRIG_RFKILL_RELEASE]) + capa->wowlan_triggers.rfkill_release = 1; +} + + static int wiphy_info_handler(struct nl_msg *msg, void *arg) { struct nlattr *tb[NL80211_ATTR_MAX + 1]; @@ -3693,6 +3835,10 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg) continue; } vinfo = nla_data(nl); + if (vinfo->subcmd == + QCA_NL80211_VENDOR_SUBCMD_DFS_CAPABILITY) + drv->dfs_vendor_cmd_avail = 1; + wpa_printf(MSG_DEBUG, "nl80211: Supported vendor command: vendor_id=0x%x subcmd=%u", vinfo->vendor_id, vinfo->subcmd); } @@ -3714,6 +3860,13 @@ static int wiphy_info_handler(struct nl_msg *msg, void *arg) } } + wiphy_info_wowlan_triggers(capa, + tb[NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED]); + + if (tb[NL80211_ATTR_MAX_AP_ASSOC_STA]) + capa->max_stations = + nla_get_u32(tb[NL80211_ATTR_MAX_AP_ASSOC_STA]); + return NL_SKIP; } @@ -3804,6 +3957,15 @@ static int wpa_driver_nl80211_capa(struct wpa_driver_nl80211_data *drv) drv->capa.flags |= WPA_DRIVER_FLAGS_SET_KEYS_AFTER_ASSOC_DONE; drv->capa.flags |= WPA_DRIVER_FLAGS_EAPOL_TX_STATUS; + /* + * As all cfg80211 drivers must support cases where the AP interface is + * removed without the knowledge of wpa_supplicant/hostapd, e.g., in + * case that the user space daemon has crashed, they must be able to + * cleanup all stations and key entries in the AP tear down flow. Thus, + * this flag can/should always be set for cfg80211 drivers. + */ + drv->capa.flags |= WPA_DRIVER_FLAGS_AP_TEARDOWN_SUPPORT; + if (!info.device_ap_sme) { drv->capa.flags |= WPA_DRIVER_FLAGS_DEAUTH_TX_STATUS; @@ -4373,6 +4535,12 @@ static int nl80211_mgmt_subscribe_non_ap(struct i802_bss *bss) if (nl80211_register_action_frame(bss, (u8 *) "\x0a\x11", 2) < 0) ret = -1; +#ifdef CONFIG_HS20 + /* WNM-Notification */ + if (nl80211_register_action_frame(bss, (u8 *) "\x0a\x1a", 2) < 0) + return -1; +#endif /* CONFIG_HS20 */ + nl80211_mgmt_handle_register_eloop(bss); return ret; @@ -4423,7 +4591,7 @@ static int nl80211_mgmt_subscribe_ap(struct i802_bss *bss) * it isn't per interface ... maybe just dump the scan * results periodically for OLBC? */ -// WLAN_FC_STYPE_BEACON, + /* WLAN_FC_STYPE_BEACON, */ }; unsigned int i; @@ -4611,26 +4779,25 @@ wpa_driver_nl80211_finish_drv_init(struct wpa_driver_nl80211_data *drv, return -1; } - if (nlmode == NL80211_IFTYPE_P2P_DEVICE) { - int ret = nl80211_set_p2pdev(bss, 1); - if (ret < 0) - wpa_printf(MSG_ERROR, "nl80211: Could not start P2P device"); + if (nlmode == NL80211_IFTYPE_P2P_DEVICE) nl80211_get_macaddr(bss); - return ret; - } - if (linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 1)) { - if (rfkill_is_blocked(drv->rfkill)) { - wpa_printf(MSG_DEBUG, "nl80211: Could not yet enable " - "interface '%s' due to rfkill", - bss->ifname); - drv->if_disabled = 1; - send_rfkill_event = 1; - } else { + if (!rfkill_is_blocked(drv->rfkill)) { + int ret = i802_set_iface_flags(bss, 1); + if (ret) { wpa_printf(MSG_ERROR, "nl80211: Could not set " "interface '%s' UP", bss->ifname); - return -1; + return ret; } + if (nlmode == NL80211_IFTYPE_P2P_DEVICE) + return ret; + } else { + wpa_printf(MSG_DEBUG, "nl80211: Could not yet enable " + "interface '%s' due to rfkill", bss->ifname); + if (nlmode == NL80211_IFTYPE_P2P_DEVICE) + return 0; + drv->if_disabled = 1; + send_rfkill_event = 1; } if (!drv->hostapd) @@ -4721,6 +4888,7 @@ static void wpa_driver_nl80211_deinit(struct i802_bss *bss) netlink_send_oper_ifla(drv->global->netlink, drv->ifindex, 0, IF_OPER_UP); + eloop_cancel_timeout(wpa_driver_nl80211_send_rfkill, drv, drv->ctx); rfkill_deinit(drv->rfkill); eloop_cancel_timeout(wpa_driver_nl80211_scan_timeout, drv, drv->ctx); @@ -4901,6 +5069,8 @@ static int wpa_driver_nl80211_scan(struct i802_bss *bss, wpa_printf(MSG_DEBUG, "nl80211: Scan trigger failed: ret=%d " "(%s)", ret, strerror(-ret)); if (drv->hostapd && is_ap_interface(drv->nlmode)) { + enum nl80211_iftype old_mode = drv->nlmode; + /* * mac80211 does not allow scan requests in AP mode, so * try to do this in station mode. @@ -4915,7 +5085,7 @@ static int wpa_driver_nl80211_scan(struct i802_bss *bss, } /* Restore AP mode when processing scan results */ - drv->ap_scan_as_station = drv->nlmode; + drv->ap_scan_as_station = old_mode; ret = 0; } else goto nla_put_failure; @@ -5178,6 +5348,13 @@ static int bss_info_handler(struct nl_msg *msg, void *arg) wpa_printf(MSG_DEBUG, "nl80211: Associated on %u MHz", _arg->assoc_freq); } + if (status == NL80211_BSS_STATUS_IBSS_JOINED && + bss[NL80211_BSS_FREQUENCY]) { + _arg->ibss_freq = + nla_get_u32(bss[NL80211_BSS_FREQUENCY]); + wpa_printf(MSG_DEBUG, "nl80211: IBSS-joined on %u MHz", + _arg->ibss_freq); + } if (status == NL80211_BSS_STATUS_ASSOCIATED && bss[NL80211_BSS_BSSID]) { os_memcpy(_arg->assoc_bssid, @@ -5509,6 +5686,8 @@ static u32 wpa_cipher_to_cipher_suite(unsigned int cipher) return WLAN_CIPHER_SUITE_WEP104; case WPA_CIPHER_WEP40: return WLAN_CIPHER_SUITE_WEP40; + case WPA_CIPHER_GTK_NOT_USED: + return WLAN_CIPHER_SUITE_NO_GROUP_ADDR; } return 0; @@ -5576,12 +5755,15 @@ static int wpa_driver_nl80211_set_key(const char *ifname, struct i802_bss *bss, } else { nl80211_cmd(drv, msg, 0, NL80211_CMD_NEW_KEY); NLA_PUT(msg, NL80211_ATTR_KEY_DATA, key_len, key); + wpa_hexdump_key(MSG_DEBUG, "nl80211: KEY_DATA", key, key_len); NLA_PUT_U32(msg, NL80211_ATTR_KEY_CIPHER, wpa_alg_to_cipher_suite(alg, key_len)); } - if (seq && seq_len) + if (seq && seq_len) { NLA_PUT(msg, NL80211_ATTR_KEY_SEQ, seq_len, seq); + wpa_hexdump(MSG_DEBUG, "nl80211: KEY_SEQ", seq, seq_len); + } if (addr && !is_broadcast_ether_addr(addr)) { wpa_printf(MSG_DEBUG, " addr=" MACSTR, MAC2STR(addr)); @@ -5818,15 +6000,25 @@ static int wpa_driver_nl80211_deauthenticate(struct i802_bss *bss, const u8 *addr, int reason_code) { struct wpa_driver_nl80211_data *drv = bss->drv; + int ret; + + if (drv->nlmode == NL80211_IFTYPE_ADHOC) { + nl80211_mark_disconnected(drv); + return nl80211_leave_ibss(drv); + } if (!(drv->capa.flags & WPA_DRIVER_FLAGS_SME)) return wpa_driver_nl80211_disconnect(drv, reason_code); wpa_printf(MSG_DEBUG, "%s(addr=" MACSTR " reason_code=%d)", __func__, MAC2STR(addr), reason_code); nl80211_mark_disconnected(drv); - if (drv->nlmode == NL80211_IFTYPE_ADHOC) - return nl80211_leave_ibss(drv); - return wpa_driver_nl80211_mlme(drv, addr, NL80211_CMD_DEAUTHENTICATE, - reason_code, 0); + ret = wpa_driver_nl80211_mlme(drv, addr, NL80211_CMD_DEAUTHENTICATE, + reason_code, 0); + /* + * For locally generated deauthenticate, supplicant already generates a + * DEAUTH event, so ignore the event from NL80211. + */ + drv->ignore_next_local_deauth = ret == 0; + return ret; } @@ -5889,6 +6081,7 @@ static int wpa_driver_nl80211_authenticate( is_retry = drv->retry_auth; drv->retry_auth = 0; + drv->ignore_deauth_event = 0; nl80211_mark_disconnected(drv); os_memset(drv->auth_bssid, 0, ETH_ALEN); @@ -5990,6 +6183,7 @@ retry: */ wpa_printf(MSG_DEBUG, "nl80211: Retry authentication " "after forced deauthentication"); + drv->ignore_deauth_event = 1; wpa_driver_nl80211_deauthenticate( bss, params->bssid, WLAN_REASON_PREV_AUTH_NOT_VALID); @@ -6140,6 +6334,7 @@ static void phy_info_freq(struct hostapd_hw_modes *mode, u8 channel; chan->freq = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]); chan->flag = 0; + chan->dfs_cac_ms = 0; if (ieee80211_freq_to_chan(chan->freq, &channel) != NUM_HOSTAPD_MODES) chan->chan = channel; @@ -6166,6 +6361,11 @@ static void phy_info_freq(struct hostapd_hw_modes *mode, break; } } + + if (tb_freq[NL80211_FREQUENCY_ATTR_DFS_CAC_TIME]) { + chan->dfs_cac_ms = nla_get_u32( + tb_freq[NL80211_FREQUENCY_ATTR_DFS_CAC_TIME]); + } } @@ -6640,7 +6840,7 @@ static int nl80211_get_reg(struct nl_msg *msg, void *arg) nla_for_each_nested(nl_rule, tb_msg[NL80211_ATTR_REG_RULES], rem_rule) { - u32 start, end, max_eirp = 0, max_bw = 0; + u32 start, end, max_eirp = 0, max_bw = 0, flags = 0; nla_parse(tb_rule, NL80211_FREQUENCY_ATTR_MAX, nla_data(nl_rule), nla_len(nl_rule), reg_policy); if (tb_rule[NL80211_ATTR_FREQ_RANGE_START] == NULL || @@ -6652,9 +6852,20 @@ static int nl80211_get_reg(struct nl_msg *msg, void *arg) max_eirp = nla_get_u32(tb_rule[NL80211_ATTR_POWER_RULE_MAX_EIRP]) / 100; if (tb_rule[NL80211_ATTR_FREQ_RANGE_MAX_BW]) max_bw = nla_get_u32(tb_rule[NL80211_ATTR_FREQ_RANGE_MAX_BW]) / 1000; - - wpa_printf(MSG_DEBUG, "nl80211: %u-%u @ %u MHz %u mBm", - start, end, max_bw, max_eirp); + if (tb_rule[NL80211_ATTR_REG_RULE_FLAGS]) + flags = nla_get_u32(tb_rule[NL80211_ATTR_REG_RULE_FLAGS]); + + wpa_printf(MSG_DEBUG, "nl80211: %u-%u @ %u MHz %u mBm%s%s%s%s%s%s%s%s", + start, end, max_bw, max_eirp, + flags & NL80211_RRF_NO_OFDM ? " (no OFDM)" : "", + flags & NL80211_RRF_NO_CCK ? " (no CCK)" : "", + flags & NL80211_RRF_NO_INDOOR ? " (no indoor)" : "", + flags & NL80211_RRF_NO_OUTDOOR ? " (no outdoor)" : + "", + flags & NL80211_RRF_DFS ? " (DFS)" : "", + flags & NL80211_RRF_PTP_ONLY ? " (PTP only)" : "", + flags & NL80211_RRF_PTMP_ONLY ? " (PTMP only)" : "", + flags & NL80211_RRF_NO_IR ? " (no IR)" : ""); if (max_bw >= 40) nl80211_reg_rule_ht40(start, end, results); if (tb_rule[NL80211_ATTR_POWER_RULE_MAX_EIRP]) @@ -6721,7 +6932,8 @@ wpa_driver_nl80211_get_hw_feature_data(void *priv, u16 *num_modes, u16 *flags) nl80211_cmd(drv, msg, 0, NL80211_CMD_GET_WIPHY); NLA_PUT_FLAG(msg, NL80211_ATTR_SPLIT_WIPHY_DUMP); - NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex); + if (nl80211_set_iface_id(msg, bss) < 0) + goto nla_put_failure; if (send_and_recv_msgs(drv, msg, phy_info_handler, &result) == 0) { nl80211_set_regulatory_flags(drv, &result); @@ -6802,6 +7014,12 @@ static int wpa_driver_nl80211_send_frame(struct i802_bss *bss, u64 cookie; int res; + if (freq == 0 && drv->nlmode == NL80211_IFTYPE_ADHOC) { + freq = nl80211_get_assoc_freq(drv); + wpa_printf(MSG_DEBUG, + "nl80211: send_frame - Use assoc_freq=%u for IBSS", + freq); + } if (freq == 0) { wpa_printf(MSG_DEBUG, "nl80211: send_frame - Use bss->freq=%u", bss->freq); @@ -7164,6 +7382,30 @@ static int wpa_driver_nl80211_set_ap(void *priv, nl80211_set_bss(bss, params->cts_protect, params->preamble, params->short_slot_time, params->ht_opmode, params->isolate, params->basic_rates); + if (beacon_set && params->freq && + params->freq->bandwidth != bss->bandwidth) { + wpa_printf(MSG_DEBUG, + "nl80211: Update BSS %s bandwidth: %d -> %d", + bss->ifname, bss->bandwidth, + params->freq->bandwidth); + ret = nl80211_set_channel(bss, params->freq, 1); + if (ret) { + wpa_printf(MSG_DEBUG, + "nl80211: Frequency set failed: %d (%s)", + ret, strerror(-ret)); + } else { + wpa_printf(MSG_DEBUG, + "nl80211: Frequency set succeeded for ht2040 coex"); + bss->bandwidth = params->freq->bandwidth; + } + } else if (!beacon_set) { + /* + * cfg80211 updates the driver on frequence change in AP + * mode only at the point when beaconing is started, so + * set the initial value here. + */ + bss->bandwidth = params->freq->bandwidth; + } } return ret; nla_put_failure: @@ -7228,8 +7470,8 @@ nla_put_failure: } -static int wpa_driver_nl80211_set_freq(struct i802_bss *bss, - struct hostapd_freq_params *freq) +static int nl80211_set_channel(struct i802_bss *bss, + struct hostapd_freq_params *freq, int set_chan) { struct wpa_driver_nl80211_data *drv = bss->drv; struct nl_msg *msg; @@ -7243,7 +7485,8 @@ static int wpa_driver_nl80211_set_freq(struct i802_bss *bss, if (!msg) return -1; - nl80211_cmd(drv, msg, 0, NL80211_CMD_SET_WIPHY); + nl80211_cmd(drv, msg, 0, set_chan ? NL80211_CMD_SET_CHANNEL : + NL80211_CMD_SET_WIPHY); NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex); if (nl80211_put_freq_params(msg, freq) < 0) @@ -7349,6 +7592,12 @@ static int wpa_driver_nl80211_sta_add(void *priv, params->vht_capabilities); } + if (params->vht_opmode_enabled) { + wpa_printf(MSG_DEBUG, " * opmode=%u", params->vht_opmode); + NLA_PUT_U8(msg, NL80211_ATTR_OPMODE_NOTIF, + params->vht_opmode); + } + wpa_printf(MSG_DEBUG, " * capability=0x%x", params->capability); NLA_PUT_U16(msg, NL80211_ATTR_STA_CAPABILITY, params->capability); @@ -7444,11 +7693,14 @@ static void nl80211_remove_iface(struct wpa_driver_nl80211_data *drv, int ifidx) { struct nl_msg *msg; + struct wpa_driver_nl80211_data *drv2; wpa_printf(MSG_DEBUG, "nl80211: Remove interface ifindex=%d", ifidx); /* stop listening for EAPOL on this interface */ - del_ifidx(drv, ifidx); + dl_list_for_each(drv2, &drv->global->interfaces, + struct wpa_driver_nl80211_data, list) + del_ifidx(drv2, ifidx); msg = nlmsg_alloc(); if (!msg) @@ -7533,6 +7785,12 @@ static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv, NLA_PUT_U8(msg, NL80211_ATTR_4ADDR, wds); } + /* + * Tell cfg80211 that the interface belongs to the socket that created + * it, and the interface should be deleted when the socket is closed. + */ + NLA_PUT_FLAG(msg, NL80211_ATTR_IFACE_SOCKET_OWNER); + ret = send_and_recv_msgs(drv, msg, handler, arg); msg = NULL; if (ret) { @@ -7553,8 +7811,17 @@ static int nl80211_create_iface_once(struct wpa_driver_nl80211_data *drv, if (ifidx <= 0) return -1; - /* start listening for EAPOL on this interface */ - add_ifidx(drv, ifidx); + /* + * Some virtual interfaces need to process EAPOL packets and events on + * the parent interface. This is used mainly with hostapd. + */ + if (drv->hostapd || + iftype == NL80211_IFTYPE_AP_VLAN || + iftype == NL80211_IFTYPE_WDS || + iftype == NL80211_IFTYPE_MONITOR) { + /* start listening for EAPOL on this interface */ + add_ifidx(drv, ifidx); + } if (addr && iftype != NL80211_IFTYPE_MONITOR && linux_set_ifhwaddr(drv->global->ioctl_sock, ifname, addr)) { @@ -7582,6 +7849,16 @@ static int nl80211_create_iface(struct wpa_driver_nl80211_data *drv, if (use_existing) { wpa_printf(MSG_DEBUG, "nl80211: Continue using existing interface %s", ifname); + if (addr && iftype != NL80211_IFTYPE_MONITOR && + linux_set_ifhwaddr(drv->global->ioctl_sock, ifname, + addr) < 0 && + (linux_set_iface_flags(drv->global->ioctl_sock, + ifname, 0) < 0 || + linux_set_ifhwaddr(drv->global->ioctl_sock, ifname, + addr) < 0 || + linux_set_iface_flags(drv->global->ioctl_sock, + ifname, 1) < 0)) + return -1; return -ENFILE; } wpa_printf(MSG_INFO, "Try to remove and re-create %s", ifname); @@ -7690,7 +7967,7 @@ static void handle_monitor_read(int sock, void *eloop_ctx, void *sock_ctx) return; } - if (ieee80211_radiotap_iterator_init(&iter, (void*)buf, len)) { + if (ieee80211_radiotap_iterator_init(&iter, (void *) buf, len, NULL)) { wpa_printf(MSG_INFO, "nl80211: received invalid radiotap frame"); return; } @@ -7735,11 +8012,11 @@ static void handle_monitor_read(int sock, void *eloop_ctx, void *sock_ctx) return; if (!injected) - handle_frame(drv, buf + iter.max_length, - len - iter.max_length, datarate, ssi_signal); + handle_frame(drv, buf + iter._max_length, + len - iter._max_length, datarate, ssi_signal); else - handle_tx_callback(drv->ctx, buf + iter.max_length, - len - iter.max_length, !failed); + handle_tx_callback(drv->ctx, buf + iter._max_length, + len - iter._max_length, !failed); } @@ -8244,7 +8521,7 @@ static int wpa_driver_nl80211_ap(struct wpa_driver_nl80211_data *drv, return -1; } - if (wpa_driver_nl80211_set_freq(drv->first_bss, &freq)) { + if (nl80211_set_channel(drv->first_bss, &freq, 0)) { if (old_mode != nlmode) wpa_driver_nl80211_set_mode(drv->first_bss, old_mode); nl80211_remove_monitor_interface(drv); @@ -8326,6 +8603,12 @@ retry: wpa_printf(MSG_DEBUG, " * freq=%d", params->freq); NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, params->freq); + if (params->beacon_int > 0) { + wpa_printf(MSG_DEBUG, " * beacon_int=%d", params->beacon_int); + NLA_PUT_U32(msg, NL80211_ATTR_BEACON_INTERVAL, + params->beacon_int); + } + ret = nl80211_set_conn_keys(params, msg); if (ret) goto nla_put_failure; @@ -8389,6 +8672,13 @@ static int nl80211_connect_common(struct wpa_driver_nl80211_data *drv, NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, params->bssid); } + if (params->bssid_hint) { + wpa_printf(MSG_DEBUG, " * bssid_hint=" MACSTR, + MAC2STR(params->bssid_hint)); + NLA_PUT(msg, NL80211_ATTR_MAC_HINT, ETH_ALEN, + params->bssid_hint); + } + if (params->freq) { wpa_printf(MSG_DEBUG, " * freq=%d", params->freq); NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, params->freq); @@ -8396,6 +8686,12 @@ static int nl80211_connect_common(struct wpa_driver_nl80211_data *drv, } else drv->assoc_freq = 0; + if (params->freq_hint) { + wpa_printf(MSG_DEBUG, " * freq_hint=%d", params->freq_hint); + NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ_HINT, + params->freq_hint); + } + if (params->bg_scan_period >= 0) { wpa_printf(MSG_DEBUG, " * bg scan period=%d", params->bg_scan_period); @@ -8437,7 +8733,14 @@ static int nl80211_connect_common(struct wpa_driver_nl80211_data *drv, NLA_PUT_U32(msg, NL80211_ATTR_CIPHER_SUITES_PAIRWISE, cipher); } - if (params->group_suite != WPA_CIPHER_NONE) { + if (params->group_suite == WPA_CIPHER_GTK_NOT_USED && + !(drv->capa.enc & WPA_DRIVER_CAPA_ENC_GTK_NOT_USED)) { + /* + * This is likely to work even though many drivers do not + * advertise support for operations without GTK. + */ + wpa_printf(MSG_DEBUG, " * skip group cipher configuration for GTK_NOT_USED due to missing driver support advertisement"); + } else if (params->group_suite != WPA_CIPHER_NONE) { u32 cipher = wpa_cipher_to_cipher_suite(params->group_suite); wpa_printf(MSG_DEBUG, " * group=0x%x", cipher); NLA_PUT_U32(msg, NL80211_ATTR_CIPHER_SUITE_GROUP, cipher); @@ -8447,7 +8750,10 @@ static int nl80211_connect_common(struct wpa_driver_nl80211_data *drv, params->key_mgmt_suite == WPA_KEY_MGMT_PSK || params->key_mgmt_suite == WPA_KEY_MGMT_FT_IEEE8021X || params->key_mgmt_suite == WPA_KEY_MGMT_FT_PSK || - params->key_mgmt_suite == WPA_KEY_MGMT_CCKM) { + params->key_mgmt_suite == WPA_KEY_MGMT_CCKM || + params->key_mgmt_suite == WPA_KEY_MGMT_OSEN || + params->key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA256 || + params->key_mgmt_suite == WPA_KEY_MGMT_PSK_SHA256) { int mgmt = WLAN_AKM_SUITE_PSK; switch (params->key_mgmt_suite) { @@ -8463,11 +8769,21 @@ static int nl80211_connect_common(struct wpa_driver_nl80211_data *drv, case WPA_KEY_MGMT_FT_PSK: mgmt = WLAN_AKM_SUITE_FT_PSK; break; + case WPA_KEY_MGMT_IEEE8021X_SHA256: + mgmt = WLAN_AKM_SUITE_8021X_SHA256; + break; + case WPA_KEY_MGMT_PSK_SHA256: + mgmt = WLAN_AKM_SUITE_PSK_SHA256; + break; + case WPA_KEY_MGMT_OSEN: + mgmt = WLAN_AKM_SUITE_OSEN; + break; case WPA_KEY_MGMT_PSK: default: mgmt = WLAN_AKM_SUITE_PSK; break; } + wpa_printf(MSG_DEBUG, " * akm=0x%x", mgmt); NLA_PUT_U32(msg, NL80211_ATTR_AKM_SUITES, mgmt); } @@ -8481,7 +8797,10 @@ static int nl80211_connect_common(struct wpa_driver_nl80211_data *drv, if (params->htcaps && params->htcaps_mask) { int sz = sizeof(struct ieee80211_ht_capabilities); + wpa_hexdump(MSG_DEBUG, " * htcaps", params->htcaps, sz); NLA_PUT(msg, NL80211_ATTR_HT_CAPABILITY, sz, params->htcaps); + wpa_hexdump(MSG_DEBUG, " * htcaps_mask", + params->htcaps_mask, sz); NLA_PUT(msg, NL80211_ATTR_HT_CAPABILITY_MASK, sz, params->htcaps_mask); } @@ -8494,7 +8813,10 @@ static int nl80211_connect_common(struct wpa_driver_nl80211_data *drv, if (params->vhtcaps && params->vhtcaps_mask) { int sz = sizeof(struct ieee80211_vht_capabilities); + wpa_hexdump(MSG_DEBUG, " * vhtcaps", params->vhtcaps, sz); NLA_PUT(msg, NL80211_ATTR_VHT_CAPABILITY, sz, params->vhtcaps); + wpa_hexdump(MSG_DEBUG, " * vhtcaps_mask", + params->vhtcaps_mask, sz); NLA_PUT(msg, NL80211_ATTR_VHT_CAPABILITY_MASK, sz, params->vhtcaps_mask); } @@ -8790,11 +9112,44 @@ done: } +static int dfs_info_handler(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + int *dfs_capability_ptr = arg; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + + if (tb[NL80211_ATTR_VENDOR_DATA]) { + struct nlattr *nl_vend = tb[NL80211_ATTR_VENDOR_DATA]; + struct nlattr *tb_vendor[QCA_WLAN_VENDOR_ATTR_MAX + 1]; + + nla_parse(tb_vendor, QCA_WLAN_VENDOR_ATTR_MAX, + nla_data(nl_vend), nla_len(nl_vend), NULL); + + if (tb_vendor[QCA_WLAN_VENDOR_ATTR_DFS]) { + u32 val; + val = nla_get_u32(tb_vendor[QCA_WLAN_VENDOR_ATTR_DFS]); + wpa_printf(MSG_DEBUG, "nl80211: DFS offload capability: %u", + val); + *dfs_capability_ptr = val; + } + } + + return NL_SKIP; +} + + static int wpa_driver_nl80211_get_capa(void *priv, struct wpa_driver_capa *capa) { struct i802_bss *bss = priv; struct wpa_driver_nl80211_data *drv = bss->drv; + struct nl_msg *msg; + int dfs_capability = 0; + int ret = 0; + if (!drv->has_capability) return -1; os_memcpy(capa, &drv->capa, sizeof(*capa)); @@ -8810,7 +9165,31 @@ static int wpa_driver_nl80211_get_capa(void *priv, capa->flags &= ~WPA_DRIVER_FLAGS_DEDICATED_P2P_DEVICE; } - return 0; + if (drv->dfs_vendor_cmd_avail == 1) { + msg = nlmsg_alloc(); + if (!msg) + return -ENOMEM; + + nl80211_cmd(drv, msg, 0, NL80211_CMD_VENDOR); + + NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex); + NLA_PUT_U32(msg, NL80211_ATTR_VENDOR_ID, OUI_QCA); + NLA_PUT_U32(msg, NL80211_ATTR_VENDOR_SUBCMD, + QCA_NL80211_VENDOR_SUBCMD_DFS_CAPABILITY); + + ret = send_and_recv_msgs(drv, msg, dfs_info_handler, + &dfs_capability); + if (!ret) { + if (dfs_capability) + capa->flags |= WPA_DRIVER_FLAGS_DFS_OFFLOAD; + } + } + + return ret; + + nla_put_failure: + nlmsg_free(msg); + return -ENOBUFS; } @@ -8876,7 +9255,7 @@ static int wpa_driver_nl80211_set_supp_port(void *priv, int authorized) static int i802_set_freq(void *priv, struct hostapd_freq_params *freq) { struct i802_bss *bss = priv; - return wpa_driver_nl80211_set_freq(bss, freq); + return nl80211_set_channel(bss, freq, 0); } @@ -9280,6 +9659,29 @@ static int i802_sta_disassoc(void *priv, const u8 *own_addr, const u8 *addr, } +static void dump_ifidx(struct wpa_driver_nl80211_data *drv) +{ + char buf[200], *pos, *end; + int i, res; + + pos = buf; + end = pos + sizeof(buf); + + for (i = 0; i < drv->num_if_indices; i++) { + if (!drv->if_indices[i]) + continue; + res = os_snprintf(pos, end - pos, " %d", drv->if_indices[i]); + if (res < 0 || res >= end - pos) + break; + pos += res; + } + *pos = '\0'; + + wpa_printf(MSG_DEBUG, "nl80211: if_indices[%d]:%s", + drv->num_if_indices, buf); +} + + static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx) { int i; @@ -9287,9 +9689,15 @@ static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx) wpa_printf(MSG_DEBUG, "nl80211: Add own interface ifindex %d", ifidx); + if (have_ifidx(drv, ifidx)) { + wpa_printf(MSG_DEBUG, "nl80211: ifindex %d already in the list", + ifidx); + return; + } for (i = 0; i < drv->num_if_indices; i++) { if (drv->if_indices[i] == 0) { drv->if_indices[i] = ifidx; + dump_ifidx(drv); return; } } @@ -9315,6 +9723,7 @@ static void add_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx) sizeof(drv->default_if_indices)); drv->if_indices[drv->num_if_indices] = ifidx; drv->num_if_indices++; + dump_ifidx(drv); } @@ -9328,6 +9737,7 @@ static void del_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx) break; } } + dump_ifidx(drv); } @@ -9344,7 +9754,7 @@ static int have_ifidx(struct wpa_driver_nl80211_data *drv, int ifidx) static int i802_set_wds_sta(void *priv, const u8 *addr, int aid, int val, - const char *bridge_ifname, char *ifname_wds) + const char *bridge_ifname, char *ifname_wds) { struct i802_bss *bss = priv; struct wpa_driver_nl80211_data *drv = bss->drv; @@ -9379,8 +9789,8 @@ static int i802_set_wds_sta(void *priv, const u8 *addr, int aid, int val, name); i802_set_sta_vlan(priv, addr, bss->ifname, 0); - return wpa_driver_nl80211_if_remove(priv, WPA_IF_AP_VLAN, - name); + nl80211_remove_iface(drv, if_nametoindex(name)); + return 0; } } @@ -9697,19 +10107,22 @@ static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type, if (linux_get_ifhwaddr(drv->global->ioctl_sock, ifname, new_addr) < 0) { - nl80211_remove_iface(drv, ifidx); + if (added) + nl80211_remove_iface(drv, ifidx); return -1; } if (nl80211_addr_in_use(drv->global, new_addr)) { wpa_printf(MSG_DEBUG, "nl80211: Allocate new address " "for P2P group interface"); if (nl80211_p2p_interface_addr(drv, new_addr) < 0) { - nl80211_remove_iface(drv, ifidx); + if (added) + nl80211_remove_iface(drv, ifidx); return -1; } if (linux_set_ifhwaddr(drv->global->ioctl_sock, ifname, new_addr) < 0) { - nl80211_remove_iface(drv, ifidx); + if (added) + nl80211_remove_iface(drv, ifidx); return -1; } } @@ -9738,7 +10151,8 @@ static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type, if (linux_set_iface_flags(drv->global->ioctl_sock, ifname, 1)) { - nl80211_remove_iface(drv, ifidx); + if (added) + nl80211_remove_iface(drv, ifidx); os_free(new_bss); return -1; } @@ -9763,6 +10177,9 @@ static int wpa_driver_nl80211_if_add(void *priv, enum wpa_driver_if_type type, if (drv->global) drv->global->if_add_ifindex = ifidx; + if (ifidx > 0) + add_ifidx(drv, ifidx); + return 0; } @@ -9778,6 +10195,12 @@ static int wpa_driver_nl80211_if_remove(struct i802_bss *bss, __func__, type, ifname, ifindex, bss->added_if); if (ifindex > 0 && (bss->added_if || bss->ifindex != ifindex)) nl80211_remove_iface(drv, ifindex); + else if (ifindex > 0 && !bss->added_if) { + struct wpa_driver_nl80211_data *drv2; + dl_list_for_each(drv2, &drv->global->interfaces, + struct wpa_driver_nl80211_data, list) + del_ifidx(drv2, ifindex); + } if (type != WPA_IF_AP_BSS) return 0; @@ -9806,6 +10229,8 @@ static int wpa_driver_nl80211_if_remove(struct i802_bss *bss, /* Unsubscribe management frames */ nl80211_teardown_ap(bss); nl80211_destroy_bss(bss); + if (!bss->added_if) + i802_set_iface_flags(bss, 0); os_free(bss); bss = NULL; break; @@ -9875,7 +10300,8 @@ static int nl80211_send_frame_cmd(struct i802_bss *bss, NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, freq); if (wait) NLA_PUT_U32(msg, NL80211_ATTR_DURATION, wait); - if (offchanok && (drv->capa.flags & WPA_DRIVER_FLAGS_OFFCHANNEL_TX)) + if (offchanok && ((drv->capa.flags & WPA_DRIVER_FLAGS_OFFCHANNEL_TX) || + drv->test_use_roc_tx)) NLA_PUT_FLAG(msg, NL80211_ATTR_OFFCHANNEL_TX_OK); if (no_cck) NLA_PUT_FLAG(msg, NL80211_ATTR_TX_NO_CCK_RATE); @@ -10459,6 +10885,13 @@ static int nl80211_set_param(void *priv, const char *param) drv->capa.flags &= ~WPA_DRIVER_FLAGS_SME; } + if (os_strstr(param, "no_offchannel_tx=1")) { + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + drv->capa.flags &= ~WPA_DRIVER_FLAGS_OFFCHANNEL_TX; + drv->test_use_roc_tx = 1; + } + return 0; } @@ -11002,7 +11435,7 @@ nla_put_failure: static int nl80211_send_tdls_mgmt(void *priv, const u8 *dst, u8 action_code, u8 dialog_token, u16 status_code, - const u8 *buf, size_t len) + u32 peer_capab, const u8 *buf, size_t len) { struct i802_bss *bss = priv; struct wpa_driver_nl80211_data *drv = bss->drv; @@ -11024,6 +11457,15 @@ static int nl80211_send_tdls_mgmt(void *priv, const u8 *dst, u8 action_code, NLA_PUT_U8(msg, NL80211_ATTR_TDLS_ACTION, action_code); NLA_PUT_U8(msg, NL80211_ATTR_TDLS_DIALOG_TOKEN, dialog_token); NLA_PUT_U16(msg, NL80211_ATTR_STATUS_CODE, status_code); + if (peer_capab) { + /* + * The internal enum tdls_peer_capability definition is + * currently identical with the nl80211 enum + * nl80211_tdls_peer_capability, so no conversion is needed + * here. + */ + NLA_PUT_U32(msg, NL80211_ATTR_TDLS_PEER_CAPABILITY, peer_capab); + } NLA_PUT(msg, NL80211_ATTR_IE, len, buf); return send_and_recv_msgs(drv, msg, NULL, NULL); @@ -11450,7 +11892,7 @@ static int wpa_driver_nl80211_status(void *priv, char *buf, size_t buflen) "monitor_refcount=%d\n" "last_mgmt_freq=%u\n" "eapol_tx_sock=%d\n" - "%s%s%s%s%s%s%s%s%s%s%s%s%s", + "%s%s%s%s%s%s%s%s%s%s%s%s%s%s", drv->phyname, drv->ifindex, drv->operstate, @@ -11484,6 +11926,8 @@ static int wpa_driver_nl80211_status(void *priv, char *buf, size_t buflen) drv->use_monitor ? "use_monitor=1\n" : "", drv->ignore_next_local_disconnect ? "ignore_next_local_disconnect=1\n" : "", + drv->ignore_next_local_deauth ? + "ignore_next_local_deauth=1\n" : "", drv->allow_p2p_device ? "allow_p2p_device=1\n" : ""); if (res < 0 || res >= end - pos) return pos - buf; @@ -11650,6 +12094,106 @@ error: } +#ifdef CONFIG_TESTING_OPTIONS +static int cmd_reply_handler(struct nl_msg *msg, void *arg) +{ + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct wpabuf *buf = arg; + + if (!buf) + return NL_SKIP; + + if ((size_t) genlmsg_attrlen(gnlh, 0) > wpabuf_tailroom(buf)) { + wpa_printf(MSG_INFO, "nl80211: insufficient buffer space for reply"); + return NL_SKIP; + } + + wpabuf_put_data(buf, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0)); + + return NL_SKIP; +} +#endif /* CONFIG_TESTING_OPTIONS */ + + +static int vendor_reply_handler(struct nl_msg *msg, void *arg) +{ + struct nlattr *tb[NL80211_ATTR_MAX + 1]; + struct nlattr *nl_vendor_reply, *nl; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct wpabuf *buf = arg; + int rem; + + if (!buf) + return NL_SKIP; + + nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), + genlmsg_attrlen(gnlh, 0), NULL); + nl_vendor_reply = tb[NL80211_ATTR_VENDOR_DATA]; + + if (!nl_vendor_reply) + return NL_SKIP; + + if ((size_t) nla_len(nl_vendor_reply) > wpabuf_tailroom(buf)) { + wpa_printf(MSG_INFO, "nl80211: Vendor command: insufficient buffer space for reply"); + return NL_SKIP; + } + + nla_for_each_nested(nl, nl_vendor_reply, rem) { + wpabuf_put_data(buf, nla_data(nl), nla_len(nl)); + } + + return NL_SKIP; +} + + +static int nl80211_vendor_cmd(void *priv, unsigned int vendor_id, + unsigned int subcmd, const u8 *data, + size_t data_len, struct wpabuf *buf) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + struct nl_msg *msg; + int ret; + + msg = nlmsg_alloc(); + if (!msg) + return -ENOMEM; + +#ifdef CONFIG_TESTING_OPTIONS + if (vendor_id == 0xffffffff) { + nl80211_cmd(drv, msg, 0, subcmd); + if (nlmsg_append(msg, (void *) data, data_len, NLMSG_ALIGNTO) < + 0) + goto nla_put_failure; + ret = send_and_recv_msgs(drv, msg, cmd_reply_handler, buf); + if (ret) + wpa_printf(MSG_DEBUG, "nl80211: command failed err=%d", + ret); + return ret; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + nl80211_cmd(drv, msg, 0, NL80211_CMD_VENDOR); + if (nl80211_set_iface_id(msg, bss) < 0) + goto nla_put_failure; + NLA_PUT_U32(msg, NL80211_ATTR_VENDOR_ID, vendor_id); + NLA_PUT_U32(msg, NL80211_ATTR_VENDOR_SUBCMD, subcmd); + if (data) + NLA_PUT(msg, NL80211_ATTR_VENDOR_DATA, data_len, data); + + ret = send_and_recv_msgs(drv, msg, vendor_reply_handler, buf); + if (ret) + wpa_printf(MSG_DEBUG, "nl80211: vendor command failed err=%d", + ret); + return ret; + +nla_put_failure: + nlmsg_free(msg); + return -ENOBUFS; +} + + static int nl80211_set_qos_map(void *priv, const u8 *qos_map_set, u8 qos_map_set_len) { @@ -11681,6 +12225,57 @@ nla_put_failure: } +static int nl80211_set_wowlan(void *priv, + const struct wowlan_triggers *triggers) +{ + struct i802_bss *bss = priv; + struct wpa_driver_nl80211_data *drv = bss->drv; + struct nl_msg *msg; + struct nlattr *wowlan_triggers; + int ret; + + msg = nlmsg_alloc(); + if (!msg) + return -ENOMEM; + + wpa_printf(MSG_DEBUG, "nl80211: Setting wowlan"); + + nl80211_cmd(drv, msg, 0, NL80211_CMD_SET_WOWLAN); + NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, drv->ifindex); + + wowlan_triggers = nla_nest_start(msg, NL80211_ATTR_WOWLAN_TRIGGERS); + if (!wowlan_triggers) + goto nla_put_failure; + + if (triggers->any) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_ANY); + if (triggers->disconnect) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_DISCONNECT); + if (triggers->magic_pkt) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_MAGIC_PKT); + if (triggers->gtk_rekey_failure) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_GTK_REKEY_FAILURE); + if (triggers->eap_identity_req) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_EAP_IDENT_REQUEST); + if (triggers->four_way_handshake) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_4WAY_HANDSHAKE); + if (triggers->rfkill_release) + NLA_PUT_FLAG(msg, NL80211_WOWLAN_TRIG_RFKILL_RELEASE); + + nla_nest_end(msg, wowlan_triggers); + + ret = send_and_recv_msgs(drv, msg, NULL, NULL); + if (ret) + wpa_printf(MSG_DEBUG, "nl80211: Setting wowlan failed"); + + return ret; + +nla_put_failure: + nlmsg_free(msg); + return -ENOBUFS; +} + + const struct wpa_driver_ops wpa_driver_nl80211_ops = { .name = "nl80211", .desc = "Linux nl80211/cfg80211", @@ -11769,5 +12364,7 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { #ifdef ANDROID .driver_cmd = wpa_driver_nl80211_driver_cmd, #endif /* ANDROID */ + .vendor_cmd = nl80211_vendor_cmd, .set_qos_map = nl80211_set_qos_map, + .set_wowlan = nl80211_set_wowlan, }; diff --git a/src/drivers/driver_roboswitch.c b/src/drivers/driver_roboswitch.c index 0a9078a..9ce3fa2 100644 --- a/src/drivers/driver_roboswitch.c +++ b/src/drivers/driver_roboswitch.c @@ -260,17 +260,17 @@ static int wpa_driver_roboswitch_join(struct wpa_driver_roboswitch_data *drv, ROBO_ARLCTRL_CONF, read1, 1); } else { /* if both multiport addresses are the same we can add */ - wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, - ROBO_ARLCTRL_ADDR_1, read1, 3); - wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, - ROBO_ARLCTRL_ADDR_2, read2, 3); - if (os_memcmp(read1, read2, 6) != 0) + if (wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_ADDR_1, read1, 3) || + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_ADDR_2, read2, 3) || + os_memcmp(read1, read2, 6) != 0) return -1; - wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, - ROBO_ARLCTRL_VEC_1, read1, 1); - wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, - ROBO_ARLCTRL_VEC_2, read2, 1); - if (read1[0] != read2[0]) + if (wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_VEC_1, read1, 1) || + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_VEC_2, read2, 1) || + read1[0] != read2[0]) return -1; wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, ROBO_ARLCTRL_ADDR_1, addr_be16, 3); diff --git a/src/drivers/driver_test.c b/src/drivers/driver_test.c index 7d30655..3608b52 100644 --- a/src/drivers/driver_test.c +++ b/src/drivers/driver_test.c @@ -1529,7 +1529,8 @@ static int wpa_driver_test_associate( #endif /* DRIVER_TEST_UNIX */ if (params->mode == IEEE80211_MODE_AP) { - os_memcpy(dbss->ssid, params->ssid, params->ssid_len); + if (params->ssid) + os_memcpy(dbss->ssid, params->ssid, params->ssid_len); dbss->ssid_len = params->ssid_len; os_memcpy(dbss->bssid, drv->own_addr, ETH_ALEN); if (params->wpa_ie && params->wpa_ie_len) { @@ -1550,8 +1551,9 @@ static int wpa_driver_test_associate( MAC2STR(drv->own_addr)); if (ret >= 0 && ret < end - pos) pos += ret; - pos += wpa_snprintf_hex(pos, end - pos, params->ssid, - params->ssid_len); + if (params->ssid) + pos += wpa_snprintf_hex(pos, end - pos, params->ssid, + params->ssid_len); ret = os_snprintf(pos, end - pos, " "); if (ret >= 0 && ret < end - pos) pos += ret; @@ -1575,12 +1577,15 @@ static int wpa_driver_test_associate( return -1; } - os_memcpy(dbss->ssid, params->ssid, params->ssid_len); + if (params->ssid) + os_memcpy(dbss->ssid, params->ssid, params->ssid_len); dbss->ssid_len = params->ssid_len; } else { drv->associated = 1; if (params->mode == IEEE80211_MODE_IBSS) { - os_memcpy(dbss->ssid, params->ssid, params->ssid_len); + if (params->ssid) + os_memcpy(dbss->ssid, params->ssid, + params->ssid_len); dbss->ssid_len = params->ssid_len; if (params->bssid) os_memcpy(dbss->bssid, params->bssid, @@ -1901,7 +1906,7 @@ static void wpa_driver_test_scan_cmd(struct wpa_driver_test_data *drv, /* data: optional [ STA-addr | ' ' | IEs(hex) ] */ - if (!drv->ibss) + if (bss == NULL || !drv->ibss) return; pos = buf; diff --git a/src/drivers/drivers.c b/src/drivers/drivers.c index 446ab63..d0e42ec 100644 --- a/src/drivers/drivers.c +++ b/src/drivers/drivers.c @@ -34,6 +34,10 @@ extern struct wpa_driver_ops wpa_driver_ndis_ops; /* driver_ndis.c */ #ifdef CONFIG_DRIVER_WIRED extern struct wpa_driver_ops wpa_driver_wired_ops; /* driver_wired.c */ #endif /* CONFIG_DRIVER_WIRED */ +#ifdef CONFIG_DRIVER_MACSEC_QCA + /* driver_macsec_qca.c */ +extern struct wpa_driver_ops wpa_driver_macsec_qca_ops; +#endif /* CONFIG_DRIVER_MACSEC_QCA */ #ifdef CONFIG_DRIVER_TEST extern struct wpa_driver_ops wpa_driver_test_ops; /* driver_test.c */ #endif /* CONFIG_DRIVER_TEST */ @@ -75,6 +79,9 @@ struct wpa_driver_ops *wpa_drivers[] = #ifdef CONFIG_DRIVER_WIRED &wpa_driver_wired_ops, #endif /* CONFIG_DRIVER_WIRED */ +#ifdef CONFIG_DRIVER_MACSEC_QCA + &wpa_driver_macsec_qca_ops, +#endif /* CONFIG_DRIVER_MACSEC_QCA */ #ifdef CONFIG_DRIVER_TEST &wpa_driver_test_ops, #endif /* CONFIG_DRIVER_TEST */ diff --git a/src/drivers/drivers.mak b/src/drivers/drivers.mak index 68ff910..7e175f4 100644 --- a/src/drivers/drivers.mak +++ b/src/drivers/drivers.mak @@ -17,6 +17,11 @@ DRV_CFLAGS += -DCONFIG_DRIVER_WIRED DRV_OBJS += ../src/drivers/driver_wired.o endif +ifdef CONFIG_DRIVER_MACSEC_QCA +DRV_CFLAGS += -DCONFIG_DRIVER_MACSEC_QCA +DRV_OBJS += ../src/drivers/driver_macsec_qca.o +endif + ifdef CONFIG_DRIVER_NL80211 DRV_CFLAGS += -DCONFIG_DRIVER_NL80211 DRV_OBJS += ../src/drivers/driver_nl80211.o diff --git a/src/drivers/nl80211_copy.h b/src/drivers/nl80211_copy.h index 91054fd..406010d 100644 --- a/src/drivers/nl80211_copy.h +++ b/src/drivers/nl80211_copy.h @@ -303,8 +303,9 @@ * passed, all channels allowed for the current regulatory domain * are used. Extra IEs can also be passed from the userspace by * using the %NL80211_ATTR_IE attribute. - * @NL80211_CMD_STOP_SCHED_SCAN: stop a scheduled scan. Returns -ENOENT - * if scheduled scan is not running. + * @NL80211_CMD_STOP_SCHED_SCAN: stop a scheduled scan. Returns -ENOENT if + * scheduled scan is not running. The caller may assume that as soon + * as the call returns, it is safe to start a new scheduled scan again. * @NL80211_CMD_SCHED_SCAN_RESULTS: indicates that there are scheduled scan * results available. * @NL80211_CMD_SCHED_SCAN_STOPPED: indicates that the scheduled scan has @@ -418,8 +419,18 @@ * %NL80211_ATTR_SSID attribute, and can optionally specify the association * IEs in %NL80211_ATTR_IE, %NL80211_ATTR_AUTH_TYPE, %NL80211_ATTR_USE_MFP, * %NL80211_ATTR_MAC, %NL80211_ATTR_WIPHY_FREQ, %NL80211_ATTR_CONTROL_PORT, - * %NL80211_ATTR_CONTROL_PORT_ETHERTYPE and - * %NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT. + * %NL80211_ATTR_CONTROL_PORT_ETHERTYPE, + * %NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT, %NL80211_ATTR_MAC_HINT, and + * %NL80211_ATTR_WIPHY_FREQ_HINT. + * If included, %NL80211_ATTR_MAC and %NL80211_ATTR_WIPHY_FREQ are + * restrictions on BSS selection, i.e., they effectively prevent roaming + * within the ESS. %NL80211_ATTR_MAC_HINT and %NL80211_ATTR_WIPHY_FREQ_HINT + * can be included to provide a recommendation of the initial BSS while + * allowing the driver to roam to other BSSes within the ESS and also to + * ignore this recommendation if the indicated BSS is not ideal. Only one + * set of BSSID,frequency parameters is used (i.e., either the enforcing + * %NL80211_ATTR_MAC,%NL80211_ATTR_WIPHY_FREQ or the less strict + * %NL80211_ATTR_MAC_HINT and %NL80211_ATTR_WIPHY_FREQ_HINT). * Background scan period can optionally be * specified in %NL80211_ATTR_BG_SCAN_PERIOD, * if not specified default background scan configuration @@ -1555,6 +1566,23 @@ enum nl80211_commands { * data is in the format defined for the payload of the QoS Map Set element * in IEEE Std 802.11-2012, 8.4.2.97. * + * @NL80211_ATTR_MAC_HINT: MAC address recommendation as initial BSS + * @NL80211_ATTR_WIPHY_FREQ_HINT: frequency of the recommended initial BSS + * + * @NL80211_ATTR_MAX_AP_ASSOC_STA: Device attribute that indicates how many + * associated stations are supported in AP mode (including P2P GO); u32. + * Since drivers may not have a fixed limit on the maximum number (e.g., + * other concurrent operations may affect this), drivers are allowed to + * advertise values that cannot always be met. In such cases, an attempt + * to add a new station entry with @NL80211_CMD_NEW_STATION may fail. + * + * @NL80211_ATTR_TDLS_PEER_CAPABILITY: flags for TDLS peer capabilities, u32. + * As specified in the &enum nl80211_tdls_peer_capability. + * + * @NL80211_ATTR_IFACE_SOCKET_OWNER: flag attribute, if set during interface + * creation then the new interface will be owned by the netlink socket + * that created it and will be destroyed when the socket is closed + * * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use */ @@ -1883,6 +1911,15 @@ enum nl80211_attrs { NL80211_ATTR_QOS_MAP, + NL80211_ATTR_MAC_HINT, + NL80211_ATTR_WIPHY_FREQ_HINT, + + NL80211_ATTR_MAX_AP_ASSOC_STA, + + NL80211_ATTR_TDLS_PEER_CAPABILITY, + + NL80211_ATTR_IFACE_SOCKET_OWNER, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -2304,9 +2341,35 @@ enum nl80211_band_attr { * @NL80211_FREQUENCY_ATTR_NO_160MHZ: any 160 MHz (but not 80+80) channel * using this channel as the primary or any of the secondary channels * isn't possible + * @NL80211_FREQUENCY_ATTR_DFS_CAC_TIME: DFS CAC time in milliseconds. + * @NL80211_FREQUENCY_ATTR_INDOOR_ONLY: Only indoor use is permitted on this + * channel. A channel that has the INDOOR_ONLY attribute can only be + * used when there is a clear assessment that the device is operating in + * an indoor surroundings, i.e., it is connected to AC power (and not + * through portable DC inverters) or is under the control of a master + * that is acting as an AP and is connected to AC power. + * @NL80211_FREQUENCY_ATTR_GO_CONCURRENT: GO operation is allowed on this + * channel if it's connected concurrently to a BSS on the same channel on + * the 2 GHz band or to a channel in the same UNII band (on the 5 GHz + * band), and IEEE80211_CHAN_RADAR is not set. Instantiating a GO on a + * channel that has the GO_CONCURRENT attribute set can be done when there + * is a clear assessment that the device is operating under the guidance of + * an authorized master, i.e., setting up a GO while the device is also + * connected to an AP with DFS and radar detection on the UNII band (it is + * up to user-space, i.e., wpa_supplicant to perform the required + * verifications) + * @NL80211_FREQUENCY_ATTR_NO_20MHZ: 20 MHz operation is not allowed + * on this channel in current regulatory domain. + * @NL80211_FREQUENCY_ATTR_NO_10MHZ: 10 MHz operation is not allowed + * on this channel in current regulatory domain. * @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number * currently defined * @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use + * + * See https://apps.fcc.gov/eas/comments/GetPublishedDocument.html?id=327&tn=528122 + * for more information on the FCC description of the relaxations allowed + * by NL80211_FREQUENCY_ATTR_INDOOR_ONLY and + * NL80211_FREQUENCY_ATTR_GO_CONCURRENT. */ enum nl80211_frequency_attr { __NL80211_FREQUENCY_ATTR_INVALID, @@ -2322,6 +2385,11 @@ enum nl80211_frequency_attr { NL80211_FREQUENCY_ATTR_NO_HT40_PLUS, NL80211_FREQUENCY_ATTR_NO_80MHZ, NL80211_FREQUENCY_ATTR_NO_160MHZ, + NL80211_FREQUENCY_ATTR_DFS_CAC_TIME, + NL80211_FREQUENCY_ATTR_INDOOR_ONLY, + NL80211_FREQUENCY_ATTR_GO_CONCURRENT, + NL80211_FREQUENCY_ATTR_NO_20MHZ, + NL80211_FREQUENCY_ATTR_NO_10MHZ, /* keep last */ __NL80211_FREQUENCY_ATTR_AFTER_LAST, @@ -2412,12 +2480,14 @@ enum nl80211_reg_type { * in KHz. This is not a center a frequency but an actual regulatory * band edge. * @NL80211_ATTR_FREQ_RANGE_MAX_BW: maximum allowed bandwidth for this - * frequency range, in KHz. + * frequency range, in KHz. * @NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN: the maximum allowed antenna gain * for a given frequency range. The value is in mBi (100 * dBi). * If you don't have one then don't send this. * @NL80211_ATTR_POWER_RULE_MAX_EIRP: the maximum allowed EIRP for * a given frequency range. The value is in mBm (100 * dBm). + * @NL80211_ATTR_DFS_CAC_TIME: DFS CAC time in milliseconds. + * If not present or 0 default CAC time will be used. * @NL80211_REG_RULE_ATTR_MAX: highest regulatory rule attribute number * currently defined * @__NL80211_REG_RULE_ATTR_AFTER_LAST: internal use @@ -2433,6 +2503,8 @@ enum nl80211_reg_rule_attr { NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN, NL80211_ATTR_POWER_RULE_MAX_EIRP, + NL80211_ATTR_DFS_CAC_TIME, + /* keep last */ __NL80211_REG_RULE_ATTR_AFTER_LAST, NL80211_REG_RULE_ATTR_MAX = __NL80211_REG_RULE_ATTR_AFTER_LAST - 1 @@ -2442,9 +2514,15 @@ enum nl80211_reg_rule_attr { * enum nl80211_sched_scan_match_attr - scheduled scan match attributes * @__NL80211_SCHED_SCAN_MATCH_ATTR_INVALID: attribute number 0 is reserved * @NL80211_SCHED_SCAN_MATCH_ATTR_SSID: SSID to be used for matching, - * only report BSS with matching SSID. + * only report BSS with matching SSID. * @NL80211_SCHED_SCAN_MATCH_ATTR_RSSI: RSSI threshold (in dBm) for reporting a - * BSS in scan results. Filtering is turned off if not specified. + * BSS in scan results. Filtering is turned off if not specified. Note that + * if this attribute is in a match set of its own, then it is treated as + * the default value for all matchsets with an SSID, rather than being a + * matchset of its own without an RSSI filter. This is due to problems with + * how this API was implemented in the past. Also, due to the same problem, + * the only way to create a matchset with only an RSSI filter (with this + * attribute) is if there's only a single matchset with the RSSI attribute. * @NL80211_SCHED_SCAN_MATCH_ATTR_MAX: highest scheduled scan filter * attribute number currently defined * @__NL80211_SCHED_SCAN_MATCH_ATTR_AFTER_LAST: internal use @@ -2477,6 +2555,9 @@ enum nl80211_sched_scan_match_attr { * @NL80211_RRF_NO_IR: no mechanisms that initiate radiation are allowed, * this includes probe requests or modes of operation that require * beaconing. + * @NL80211_RRF_AUTO_BW: maximum available bandwidth should be calculated + * base on contiguous rules and wider channels will be allowed to cross + * multiple contiguous/overlapping frequency ranges. */ enum nl80211_reg_rule_flags { NL80211_RRF_NO_OFDM = 1<<0, @@ -2488,6 +2569,7 @@ enum nl80211_reg_rule_flags { NL80211_RRF_PTMP_ONLY = 1<<6, NL80211_RRF_NO_IR = 1<<7, __NL80211_RRF_NO_IBSS = 1<<8, + NL80211_RRF_AUTO_BW = 1<<11, }; #define NL80211_RRF_PASSIVE_SCAN NL80211_RRF_NO_IR @@ -2526,10 +2608,13 @@ enum nl80211_dfs_regions { * present has been registered with the wireless core that * has listed NL80211_FEATURE_CELL_BASE_REG_HINTS as a * supported feature. + * @NL80211_USER_REG_HINT_INDOOR: a user sent an hint indicating that the + * platform is operating in an indoor environment. */ enum nl80211_user_reg_hint_type { NL80211_USER_REG_HINT_USER = 0, NL80211_USER_REG_HINT_CELL_BASE = 1, + NL80211_USER_REG_HINT_INDOOR = 2, }; /** @@ -3131,6 +3216,7 @@ enum nl80211_key_attributes { * in an array of MCS numbers. * @NL80211_TXRATE_VHT: VHT rates allowed for TX rate selection, * see &struct nl80211_txrate_vht + * @NL80211_TXRATE_GI: configure GI, see &enum nl80211_txrate_gi * @__NL80211_TXRATE_AFTER_LAST: internal * @NL80211_TXRATE_MAX: highest TX rate attribute */ @@ -3139,6 +3225,7 @@ enum nl80211_tx_rate_attributes { NL80211_TXRATE_LEGACY, NL80211_TXRATE_HT, NL80211_TXRATE_VHT, + NL80211_TXRATE_GI, /* keep last */ __NL80211_TXRATE_AFTER_LAST, @@ -3156,6 +3243,12 @@ struct nl80211_txrate_vht { __u16 mcs[NL80211_VHT_NSS_MAX]; }; +enum nl80211_txrate_gi { + NL80211_TXRATE_DEFAULT_GI, + NL80211_TXRATE_FORCE_SGI, + NL80211_TXRATE_FORCE_LGI, +}; + /** * enum nl80211_band - Frequency band * @NL80211_BAND_2GHZ: 2.4 GHz ISM band @@ -3801,11 +3894,6 @@ enum nl80211_ap_sme_features { * @NL80211_FEATURE_CELL_BASE_REG_HINTS: This driver has been tested * to work properly to suppport receiving regulatory hints from * cellular base stations. - * @NL80211_FEATURE_P2P_DEVICE_NEEDS_CHANNEL: If this is set, an active - * P2P Device (%NL80211_IFTYPE_P2P_DEVICE) requires its own channel - * in the interface combinations, even when it's only used for scan - * and remain-on-channel. This could be due to, for example, the - * remain-on-channel implementation requiring a channel context. * @NL80211_FEATURE_SAE: This driver supports simultaneous authentication of * equals (SAE) with user space SME (NL80211_CMD_AUTHENTICATE) in station * mode @@ -3841,13 +3929,16 @@ enum nl80211_ap_sme_features { * interface. An active monitor interface behaves like a normal monitor * interface, but gets added to the driver. It ensures that incoming * unicast packets directed at the configured interface address get ACKed. + * @NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE: This driver supports dynamic + * channel bandwidth change (e.g., HT 20 <-> 40 MHz channel) during the + * lifetime of a BSS. */ enum nl80211_feature_flags { NL80211_FEATURE_SK_TX_STATUS = 1 << 0, NL80211_FEATURE_HT_IBSS = 1 << 1, NL80211_FEATURE_INACTIVITY_TIMER = 1 << 2, NL80211_FEATURE_CELL_BASE_REG_HINTS = 1 << 3, - NL80211_FEATURE_P2P_DEVICE_NEEDS_CHANNEL = 1 << 4, + /* bit 4 is reserved - don't use */ NL80211_FEATURE_SAE = 1 << 5, NL80211_FEATURE_LOW_PRIORITY_SCAN = 1 << 6, NL80211_FEATURE_SCAN_FLUSH = 1 << 7, @@ -3861,6 +3952,7 @@ enum nl80211_feature_flags { NL80211_FEATURE_FULL_AP_CLIENT_STATE = 1 << 15, NL80211_FEATURE_USERSPACE_MPM = 1 << 16, NL80211_FEATURE_ACTIVE_MONITOR = 1 << 17, + NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE = 1 << 18, }; /** @@ -4037,4 +4129,20 @@ struct nl80211_vendor_cmd_info { __u32 subcmd; }; +/** + * enum nl80211_tdls_peer_capability - TDLS peer flags. + * + * Used by tdls_mgmt() to determine which conditional elements need + * to be added to TDLS Setup frames. + * + * @NL80211_TDLS_PEER_HT: TDLS peer is HT capable. + * @NL80211_TDLS_PEER_VHT: TDLS peer is VHT capable. + * @NL80211_TDLS_PEER_WMM: TDLS peer is WMM capable. + */ +enum nl80211_tdls_peer_capability { + NL80211_TDLS_PEER_HT = 1<<0, + NL80211_TDLS_PEER_VHT = 1<<1, + NL80211_TDLS_PEER_WMM = 1<<2, +}; + #endif /* __LINUX_NL80211_H */ diff --git a/src/eap_common/eap_defs.h b/src/eap_common/eap_defs.h index f5890be..4f14a01 100644 --- a/src/eap_common/eap_defs.h +++ b/src/eap_common/eap_defs.h @@ -72,13 +72,16 @@ typedef enum { enum { EAP_VENDOR_IETF = 0, EAP_VENDOR_MICROSOFT = 0x000137 /* Microsoft */, - EAP_VENDOR_WFA = 0x00372A /* Wi-Fi Alliance */, - EAP_VENDOR_HOSTAP = 39068 /* hostapd/wpa_supplicant project */ + EAP_VENDOR_WFA = 0x00372A /* Wi-Fi Alliance (moved to WBA) */, + EAP_VENDOR_HOSTAP = 39068 /* hostapd/wpa_supplicant project */, + EAP_VENDOR_WFA_NEW = 40808 /* Wi-Fi Alliance */ }; #define EAP_VENDOR_UNAUTH_TLS EAP_VENDOR_HOSTAP #define EAP_VENDOR_TYPE_UNAUTH_TLS 1 +#define EAP_VENDOR_WFA_UNAUTH_TLS 13 + #define EAP_MSK_LEN 64 #define EAP_EMSK_LEN 64 diff --git a/src/eap_common/eap_pwd_common.c b/src/eap_common/eap_pwd_common.c index 7d6e6b8..96c9efd 100644 --- a/src/eap_common/eap_pwd_common.c +++ b/src/eap_common/eap_pwd_common.c @@ -284,11 +284,10 @@ int compute_password_element(EAP_PWD_group *grp, u16 num, int compute_keys(EAP_PWD_group *grp, BN_CTX *bnctx, BIGNUM *k, BIGNUM *peer_scalar, BIGNUM *server_scalar, u8 *confirm_peer, u8 *confirm_server, - u32 *ciphersuite, u8 *msk, u8 *emsk) + u32 *ciphersuite, u8 *msk, u8 *emsk, u8 *session_id) { struct crypto_hash *hash; u8 mk[SHA256_MAC_LEN], *cruft; - u8 session_id[SHA256_MAC_LEN + 1]; u8 msk_emsk[EAP_MSK_LEN + EAP_EMSK_LEN]; int offset; diff --git a/src/eap_common/eap_pwd_common.h b/src/eap_common/eap_pwd_common.h index 816e58c..c54c441 100644 --- a/src/eap_common/eap_pwd_common.h +++ b/src/eap_common/eap_pwd_common.h @@ -59,7 +59,7 @@ struct eap_pwd_id { int compute_password_element(EAP_PWD_group *, u16, u8 *, int, u8 *, int, u8 *, int, u8 *); int compute_keys(EAP_PWD_group *, BN_CTX *, BIGNUM *, BIGNUM *, BIGNUM *, - u8 *, u8 *, u32 *, u8 *, u8 *); + u8 *, u8 *, u32 *, u8 *, u8 *, u8 *); struct crypto_hash * eap_pwd_h_init(void); void eap_pwd_h_update(struct crypto_hash *hash, const u8 *data, size_t len); void eap_pwd_h_final(struct crypto_hash *hash, u8 *digest); diff --git a/src/eap_peer/eap_aka.c b/src/eap_peer/eap_aka.c index d3cbaca..fee1b7b 100644 --- a/src/eap_peer/eap_aka.c +++ b/src/eap_peer/eap_aka.c @@ -316,7 +316,7 @@ static int eap_aka_umts_auth(struct eap_sm *sm, struct eap_aka_data *data) #else /* CONFIG_USIM_HARDCODED */ - wpa_printf(MSG_DEBUG, "EAP-AKA: No UMTS authentication algorith " + wpa_printf(MSG_DEBUG, "EAP-AKA: No UMTS authentication algorithm " "enabled"); return -1; diff --git a/src/eap_peer/eap_config.h b/src/eap_peer/eap_config.h index 98ec1f7..daa1d32 100644 --- a/src/eap_peer/eap_config.h +++ b/src/eap_peer/eap_config.h @@ -678,6 +678,14 @@ struct eap_peer_config { * SIM/USIM processing. */ char *external_sim_resp; + + /** + * sim_num - User selected SIM identifier + * + * This variable is used for identifying which SIM is used if the system + * has more than one. + */ + int sim_num; }; diff --git a/src/eap_peer/eap_fast.c b/src/eap_peer/eap_fast.c index 1b0c562..cc1f264 100644 --- a/src/eap_peer/eap_fast.c +++ b/src/eap_peer/eap_fast.c @@ -149,14 +149,16 @@ static void * eap_fast_init(struct eap_sm *sm) struct eap_fast_data *data; struct eap_peer_config *config = eap_get_config(sm); + if (config == NULL) + return NULL; + data = os_zalloc(sizeof(*data)); if (data == NULL) return NULL; data->fast_version = EAP_FAST_VERSION; data->max_pac_list_len = 10; - if (config && config->phase1 && - eap_fast_parse_phase1(data, config->phase1) < 0) { + if (config->phase1 && eap_fast_parse_phase1(data, config->phase1) < 0) { eap_fast_deinit(sm, data); return NULL; } diff --git a/src/eap_peer/eap_fast_pac.c b/src/eap_peer/eap_fast_pac.c index 8c480b9..21d6098 100644 --- a/src/eap_peer/eap_fast_pac.c +++ b/src/eap_peer/eap_fast_pac.c @@ -330,6 +330,8 @@ static const char * eap_fast_parse_end(struct eap_fast_pac **pac_root, static const char * eap_fast_parse_pac_type(struct eap_fast_pac *pac, char *pos) { + if (!pos) + return "Cannot parse pac type"; pac->pac_type = atoi(pos); if (pac->pac_type != PAC_TYPE_TUNNEL_PAC && pac->pac_type != PAC_TYPE_USER_AUTHORIZATION && diff --git a/src/eap_peer/eap_ikev2.c b/src/eap_peer/eap_ikev2.c index 2d7841d..45945fe 100644 --- a/src/eap_peer/eap_ikev2.c +++ b/src/eap_peer/eap_ikev2.c @@ -251,7 +251,8 @@ static struct wpabuf * eap_ikev2_build_msg(struct eap_ikev2_data *data, static int eap_ikev2_process_icv(struct eap_ikev2_data *data, const struct wpabuf *reqData, - u8 flags, const u8 *pos, const u8 **end) + u8 flags, const u8 *pos, const u8 **end, + int frag_ack) { if (flags & IKEV2_FLAGS_ICV_INCLUDED) { int icv_len = eap_ikev2_validate_icv( @@ -261,7 +262,7 @@ static int eap_ikev2_process_icv(struct eap_ikev2_data *data, return -1; /* Hide Integrity Checksum Data from further processing */ *end -= icv_len; - } else if (data->keys_ready) { + } else if (data->keys_ready && !frag_ack) { wpa_printf(MSG_INFO, "EAP-IKEV2: The message should have " "included integrity checksum"); return -1; @@ -351,7 +352,9 @@ static struct wpabuf * eap_ikev2_process(struct eap_sm *sm, void *priv, else flags = *pos++; - if (eap_ikev2_process_icv(data, reqData, flags, pos, &end) < 0) { + if (eap_ikev2_process_icv(data, reqData, flags, pos, &end, + data->state == WAIT_FRAG_ACK && len == 0) < 0) + { ret->ignore = TRUE; return NULL; } diff --git a/src/eap_peer/eap_methods.h b/src/eap_peer/eap_methods.h index a465fd2..e35c919 100644 --- a/src/eap_peer/eap_methods.h +++ b/src/eap_peer/eap_methods.h @@ -86,6 +86,7 @@ static inline int eap_peer_method_unload(struct eap_method *method) int eap_peer_md5_register(void); int eap_peer_tls_register(void); int eap_peer_unauth_tls_register(void); +int eap_peer_wfa_unauth_tls_register(void); int eap_peer_mschapv2_register(void); int eap_peer_peap_register(void); int eap_peer_ttls_register(void); diff --git a/src/eap_peer/eap_pwd.c b/src/eap_peer/eap_pwd.c index fef4783..2aa7ba5 100644 --- a/src/eap_peer/eap_pwd.c +++ b/src/eap_peer/eap_pwd.c @@ -16,7 +16,8 @@ struct eap_pwd_data { enum { - PWD_ID_Req, PWD_Commit_Req, PWD_Confirm_Req, SUCCESS, FAILURE + PWD_ID_Req, PWD_Commit_Req, PWD_Confirm_Req, + SUCCESS_ON_FRAG_COMPLETION, SUCCESS, FAILURE } state; u8 *id_peer; size_t id_peer_len; @@ -42,6 +43,7 @@ struct eap_pwd_data { u8 msk[EAP_MSK_LEN]; u8 emsk[EAP_EMSK_LEN]; + u8 session_id[1 + SHA256_MAC_LEN]; BN_CTX *bnctx; }; @@ -57,6 +59,8 @@ static const char * eap_pwd_state_txt(int state) return "PWD-Commit-Req"; case PWD_Confirm_Req: return "PWD-Confirm-Req"; + case SUCCESS_ON_FRAG_COMPLETION: + return "SUCCESS_ON_FRAG_COMPLETION"; case SUCCESS: return "SUCCESS"; case FAILURE: @@ -161,6 +165,8 @@ static void eap_pwd_deinit(struct eap_sm *sm, void *priv) BN_free(data->grp->prime); os_free(data->grp); } + wpabuf_free(data->inbuf); + wpabuf_free(data->outbuf); os_free(data); } @@ -184,6 +190,25 @@ static u8 * eap_pwd_getkey(struct eap_sm *sm, void *priv, size_t *len) } +static u8 * eap_pwd_get_session_id(struct eap_sm *sm, void *priv, size_t *len) +{ + struct eap_pwd_data *data = priv; + u8 *id; + + if (data->state != SUCCESS) + return NULL; + + id = os_malloc(1 + SHA256_MAC_LEN); + if (id == NULL) + return NULL; + + os_memcpy(id, data->session_id, 1 + SHA256_MAC_LEN); + *len = 1 + SHA256_MAC_LEN; + + return id; +} + + static void eap_pwd_perform_id_exchange(struct eap_sm *sm, struct eap_pwd_data *data, struct eap_method_ret *ret, @@ -227,8 +252,8 @@ eap_pwd_perform_id_exchange(struct eap_sm *sm, struct eap_pwd_data *data, wpa_hexdump_ascii(MSG_INFO, "EAP-PWD (peer): server sent id of", data->id_server, data->id_server_len); - if ((data->grp = (EAP_PWD_group *) os_malloc(sizeof(EAP_PWD_group))) == - NULL) { + data->grp = os_zalloc(sizeof(EAP_PWD_group)); + if (data->grp == NULL) { wpa_printf(MSG_INFO, "EAP-PWD: failed to allocate memory for " "group"); eap_pwd_state(data, FAILURE); @@ -642,7 +667,7 @@ eap_pwd_perform_confirm_exchange(struct eap_sm *sm, struct eap_pwd_data *data, if (compute_keys(data->grp, data->bnctx, data->k, data->my_scalar, data->server_scalar, conf, ptr, - &cs, data->msk, data->emsk) < 0) { + &cs, data->msk, data->emsk, data->session_id) < 0) { wpa_printf(MSG_INFO, "EAP-PWD (peer): unable to compute MSK | " "EMSK"); goto fin; @@ -658,13 +683,12 @@ fin: os_free(cruft); BN_free(x); BN_free(y); - ret->methodState = METHOD_DONE; if (data->outbuf == NULL) { + ret->methodState = METHOD_DONE; ret->decision = DECISION_FAIL; eap_pwd_state(data, FAILURE); } else { - ret->decision = DECISION_UNCOND_SUCC; - eap_pwd_state(data, SUCCESS); + eap_pwd_state(data, SUCCESS_ON_FRAG_COMPLETION); } } @@ -741,6 +765,11 @@ eap_pwd_process(struct eap_sm *sm, void *priv, struct eap_method_ret *ret, wpa_printf(MSG_DEBUG, "EAP-pwd: Send %s fragment of %d bytes", data->out_frag_pos == 0 ? "last" : "next", (int) len); + if (data->state == SUCCESS_ON_FRAG_COMPLETION) { + ret->methodState = METHOD_DONE; + ret->decision = DECISION_UNCOND_SUCC; + eap_pwd_state(data, SUCCESS); + } return resp; } @@ -773,6 +802,7 @@ eap_pwd_process(struct eap_sm *sm, void *priv, struct eap_method_ret *ret, (int) data->in_frag_pos, (int) wpabuf_len(data->inbuf)); wpabuf_free(data->inbuf); + data->inbuf = NULL; data->in_frag_pos = 0; return NULL; } @@ -824,11 +854,15 @@ eap_pwd_process(struct eap_sm *sm, void *priv, struct eap_method_ret *ret, */ if (data->in_frag_pos) { wpabuf_free(data->inbuf); + data->inbuf = NULL; data->in_frag_pos = 0; } - if (data->outbuf == NULL) + if (data->outbuf == NULL) { + ret->methodState = METHOD_DONE; + ret->decision = DECISION_FAIL; return NULL; /* generic failure */ + } /* * we have output! Do we need to fragment it? @@ -871,6 +905,11 @@ eap_pwd_process(struct eap_sm *sm, void *priv, struct eap_method_ret *ret, wpabuf_free(data->outbuf); data->outbuf = NULL; data->out_frag_pos = 0; + if (data->state == SUCCESS_ON_FRAG_COMPLETION) { + ret->methodState = METHOD_DONE; + ret->decision = DECISION_UNCOND_SUCC; + eap_pwd_state(data, SUCCESS); + } } return resp; @@ -918,6 +957,7 @@ int eap_peer_pwd_register(void) eap->process = eap_pwd_process; eap->isKeyAvailable = eap_pwd_key_available; eap->getKey = eap_pwd_getkey; + eap->getSessionId = eap_pwd_get_session_id; eap->get_emsk = eap_pwd_get_emsk; ret = eap_peer_method_register(eap); diff --git a/src/eap_peer/eap_sim.c b/src/eap_peer/eap_sim.c index d856054..fc9df96 100644 --- a/src/eap_peer/eap_sim.c +++ b/src/eap_peer/eap_sim.c @@ -952,9 +952,11 @@ static struct wpabuf * eap_sim_process_reauthentication( } if (eattr.counter < 0 || (size_t) eattr.counter <= data->counter) { + struct wpabuf *res; wpa_printf(MSG_INFO, "EAP-SIM: (encr) Invalid counter " "(%d <= %d)", eattr.counter, data->counter); data->counter_too_small = eattr.counter; + /* Reply using Re-auth w/ AT_COUNTER_TOO_SMALL. The current * reauth_id must not be used to start a new reauthentication. * However, since it was used in the last EAP-Response-Identity @@ -965,8 +967,11 @@ static struct wpabuf * eap_sim_process_reauthentication( data->last_eap_identity_len = data->reauth_id_len; data->reauth_id = NULL; data->reauth_id_len = 0; + + res = eap_sim_response_reauth(data, id, 1, eattr.nonce_s); os_free(decrypted); - return eap_sim_response_reauth(data, id, 1, eattr.nonce_s); + + return res; } data->counter = eattr.counter; diff --git a/src/eap_peer/eap_tls.c b/src/eap_peer/eap_tls.c index d2066cd..bb9f3f2 100644 --- a/src/eap_peer/eap_tls.c +++ b/src/eap_peer/eap_tls.c @@ -98,6 +98,33 @@ static void * eap_unauth_tls_init(struct eap_sm *sm) #endif /* EAP_UNAUTH_TLS */ +#ifdef CONFIG_HS20 +static void * eap_wfa_unauth_tls_init(struct eap_sm *sm) +{ + struct eap_tls_data *data; + struct eap_peer_config *config = eap_get_config(sm); + + data = os_zalloc(sizeof(*data)); + if (data == NULL) + return NULL; + + data->ssl_ctx = sm->init_phase2 && sm->ssl_ctx2 ? sm->ssl_ctx2 : + sm->ssl_ctx; + + if (eap_peer_tls_ssl_init(sm, &data->ssl, config, + EAP_WFA_UNAUTH_TLS_TYPE)) { + wpa_printf(MSG_INFO, "EAP-TLS: Failed to initialize SSL."); + eap_tls_deinit(sm, data); + return NULL; + } + + data->eap_type = EAP_WFA_UNAUTH_TLS_TYPE; + + return data; +} +#endif /* CONFIG_HS20 */ + + static void eap_tls_deinit(struct eap_sm *sm, void *priv) { struct eap_tls_data *data = priv; @@ -382,3 +409,35 @@ int eap_peer_unauth_tls_register(void) return ret; } #endif /* EAP_UNAUTH_TLS */ + + +#ifdef CONFIG_HS20 +int eap_peer_wfa_unauth_tls_register(void) +{ + struct eap_method *eap; + int ret; + + eap = eap_peer_method_alloc(EAP_PEER_METHOD_INTERFACE_VERSION, + EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, + "WFA-UNAUTH-TLS"); + if (eap == NULL) + return -1; + + eap->init = eap_wfa_unauth_tls_init; + eap->deinit = eap_tls_deinit; + eap->process = eap_tls_process; + eap->isKeyAvailable = eap_tls_isKeyAvailable; + eap->getKey = eap_tls_getKey; + eap->get_status = eap_tls_get_status; + eap->has_reauth_data = eap_tls_has_reauth_data; + eap->deinit_for_reauth = eap_tls_deinit_for_reauth; + eap->init_for_reauth = eap_tls_init_for_reauth; + eap->get_emsk = eap_tls_get_emsk; + + ret = eap_peer_method_register(eap); + if (ret) + eap_peer_method_free(eap); + return ret; +} +#endif /* CONFIG_HS20 */ diff --git a/src/eap_peer/eap_tls_common.c b/src/eap_peer/eap_tls_common.c index 008af37..fe9bfe0 100644 --- a/src/eap_peer/eap_tls_common.c +++ b/src/eap_peer/eap_tls_common.c @@ -23,6 +23,10 @@ static struct wpabuf * eap_tls_msg_alloc(EapType type, size_t payload_len, return eap_msg_alloc(EAP_VENDOR_UNAUTH_TLS, EAP_VENDOR_TYPE_UNAUTH_TLS, payload_len, code, identifier); + if (type == EAP_WFA_UNAUTH_TLS_TYPE) + return eap_msg_alloc(EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, payload_len, + code, identifier); return eap_msg_alloc(EAP_VENDOR_IETF, type, payload_len, code, identifier); } @@ -64,6 +68,14 @@ static void eap_tls_params_flags(struct tls_connection_params *params, params->flags |= TLS_CONN_DISABLE_SESSION_TICKET; if (os_strstr(txt, "tls_disable_session_ticket=0")) params->flags &= ~TLS_CONN_DISABLE_SESSION_TICKET; + if (os_strstr(txt, "tls_disable_tlsv1_1=1")) + params->flags |= TLS_CONN_DISABLE_TLSv1_1; + if (os_strstr(txt, "tls_disable_tlsv1_1=0")) + params->flags &= ~TLS_CONN_DISABLE_TLSv1_1; + if (os_strstr(txt, "tls_disable_tlsv1_2=1")) + params->flags |= TLS_CONN_DISABLE_TLSv1_2; + if (os_strstr(txt, "tls_disable_tlsv1_2=0")) + params->flags &= ~TLS_CONN_DISABLE_TLSv1_2; } @@ -838,6 +850,10 @@ const u8 * eap_peer_tls_process_init(struct eap_sm *sm, pos = eap_hdr_validate(EAP_VENDOR_UNAUTH_TLS, EAP_VENDOR_TYPE_UNAUTH_TLS, reqData, &left); + else if (eap_type == EAP_WFA_UNAUTH_TLS_TYPE) + pos = eap_hdr_validate(EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, reqData, + &left); else pos = eap_hdr_validate(EAP_VENDOR_IETF, eap_type, reqData, &left); diff --git a/src/eap_peer/eap_tls_common.h b/src/eap_peer/eap_tls_common.h index 1a5e0f8..390c216 100644 --- a/src/eap_peer/eap_tls_common.h +++ b/src/eap_peer/eap_tls_common.h @@ -87,6 +87,7 @@ struct eap_ssl_data { /* dummy type used as a flag for UNAUTH-TLS */ #define EAP_UNAUTH_TLS_TYPE 255 +#define EAP_WFA_UNAUTH_TLS_TYPE 254 int eap_peer_tls_ssl_init(struct eap_sm *sm, struct eap_ssl_data *data, diff --git a/src/eap_peer/eap_wsc.c b/src/eap_peer/eap_wsc.c index 6bdd341..23e9823 100644 --- a/src/eap_peer/eap_wsc.c +++ b/src/eap_peer/eap_wsc.c @@ -106,8 +106,10 @@ static int eap_wsc_new_ap_settings(struct wps_credential *cred, } if (os_strncmp(pos + 9, "NONE", 4) == 0) cred->encr_type = WPS_ENCR_NONE; +#ifdef CONFIG_TESTING_OPTIONS else if (os_strncmp(pos + 9, "WEP", 3) == 0) cred->encr_type = WPS_ENCR_WEP; +#endif /* CONFIG_TESTING_OPTIONS */ else if (os_strncmp(pos + 9, "TKIP", 4) == 0) cred->encr_type = WPS_ENCR_TKIP; else if (os_strncmp(pos + 9, "CCMP", 4) == 0) diff --git a/src/eap_peer/tncc.c b/src/eap_peer/tncc.c index a3ec395..5b1a2d4 100644 --- a/src/eap_peer/tncc.c +++ b/src/eap_peer/tncc.c @@ -13,6 +13,7 @@ #include "common.h" #include "base64.h" +#include "common/tnc.h" #include "tncc.h" #include "eap_common/eap_tlv_common.h" #include "eap_common/eap_defs.h" @@ -25,7 +26,9 @@ #endif /* UNICODE */ +#ifndef TNC_CONFIG_FILE #define TNC_CONFIG_FILE "/etc/tnc_config" +#endif /* TNC_CONFIG_FILE */ #define TNC_WINREG_PATH TEXT("SOFTWARE\\Trusted Computing Group\\TNC\\IMCs") #define IF_TNCCS_START \ "<?xml version=\"1.0\"?>\n" \ @@ -38,56 +41,6 @@ /* TNC IF-IMC */ -typedef unsigned long TNC_UInt32; -typedef unsigned char *TNC_BufferReference; - -typedef TNC_UInt32 TNC_IMCID; -typedef TNC_UInt32 TNC_ConnectionID; -typedef TNC_UInt32 TNC_ConnectionState; -typedef TNC_UInt32 TNC_RetryReason; -typedef TNC_UInt32 TNC_MessageType; -typedef TNC_MessageType *TNC_MessageTypeList; -typedef TNC_UInt32 TNC_VendorID; -typedef TNC_UInt32 TNC_MessageSubtype; -typedef TNC_UInt32 TNC_Version; -typedef TNC_UInt32 TNC_Result; - -typedef TNC_Result (*TNC_TNCC_BindFunctionPointer)( - TNC_IMCID imcID, - char *functionName, - void **pOutfunctionPointer); - -#define TNC_RESULT_SUCCESS 0 -#define TNC_RESULT_NOT_INITIALIZED 1 -#define TNC_RESULT_ALREADY_INITIALIZED 2 -#define TNC_RESULT_NO_COMMON_VERSION 3 -#define TNC_RESULT_CANT_RETRY 4 -#define TNC_RESULT_WONT_RETRY 5 -#define TNC_RESULT_INVALID_PARAMETER 6 -#define TNC_RESULT_CANT_RESPOND 7 -#define TNC_RESULT_ILLEGAL_OPERATION 8 -#define TNC_RESULT_OTHER 9 -#define TNC_RESULT_FATAL 10 - -#define TNC_CONNECTION_STATE_CREATE 0 -#define TNC_CONNECTION_STATE_HANDSHAKE 1 -#define TNC_CONNECTION_STATE_ACCESS_ALLOWED 2 -#define TNC_CONNECTION_STATE_ACCESS_ISOLATED 3 -#define TNC_CONNECTION_STATE_ACCESS_NONE 4 -#define TNC_CONNECTION_STATE_DELETE 5 - -#define TNC_IFIMC_VERSION_1 1 - -#define TNC_VENDORID_ANY ((TNC_VendorID) 0xffffff) -#define TNC_SUBTYPE_ANY ((TNC_MessageSubtype) 0xff) - -/* TNCC-TNCS Message Types */ -#define TNC_TNCCS_RECOMMENDATION 0x00000001 -#define TNC_TNCCS_ERROR 0x00000002 -#define TNC_TNCCS_PREFERREDLANGUAGE 0x00000003 -#define TNC_TNCCS_REASONSTRINGS 0x00000004 - - /* IF-TNCCS-SOH - SSoH and SSoHR Attributes */ enum { SSOH_MS_MACHINE_INVENTORY = 1, diff --git a/src/eap_server/eap.h b/src/eap_server/eap.h index 36b230b..1253bd6 100644 --- a/src/eap_server/eap.h +++ b/src/eap_server/eap.h @@ -32,8 +32,11 @@ struct eap_user { * nt_password_hash() */ int phase2; int force_version; + unsigned int remediation:1; + unsigned int macacl:1; int ttls_auth; /* bitfield of * EAP_TTLS_AUTH_{PAP,CHAP,MSCHAP,MSCHAPV2} */ + struct hostapd_radius_attr *accept_attr; }; struct eap_eapol_interface { @@ -79,6 +82,7 @@ struct eapol_callbacks { int (*get_eap_user)(void *ctx, const u8 *identity, size_t identity_len, int phase2, struct eap_user *user); const char * (*get_eap_req_id_text)(void *ctx, size_t *len); + void (*log_msg)(void *ctx, const char *msg); }; struct eap_config { @@ -107,6 +111,10 @@ struct eap_config { const u8 *server_id; size_t server_id_len; + +#ifdef CONFIG_TESTING_OPTIONS + u32 tls_test_flags; +#endif /* CONFIG_TESTING_OPTIONS */ }; diff --git a/src/eap_server/eap_i.h b/src/eap_server/eap_i.h index 003e202..3a6802b 100644 --- a/src/eap_server/eap_i.h +++ b/src/eap_server/eap_i.h @@ -191,10 +191,16 @@ struct eap_sm { const u8 *server_id; size_t server_id_len; + +#ifdef CONFIG_TESTING_OPTIONS + u32 tls_test_flags; +#endif /* CONFIG_TESTING_OPTIONS */ }; int eap_user_get(struct eap_sm *sm, const u8 *identity, size_t identity_len, int phase2); +void eap_log_msg(struct eap_sm *sm, const char *fmt, ...) +PRINTF_FORMAT(2, 3); void eap_sm_process_nak(struct eap_sm *sm, const u8 *nak_list, size_t len); #endif /* EAP_I_H */ diff --git a/src/eap_server/eap_methods.h b/src/eap_server/eap_methods.h index 429cb72..0baa327 100644 --- a/src/eap_server/eap_methods.h +++ b/src/eap_server/eap_methods.h @@ -27,6 +27,7 @@ int eap_server_identity_register(void); int eap_server_md5_register(void); int eap_server_tls_register(void); int eap_server_unauth_tls_register(void); +int eap_server_wfa_unauth_tls_register(void); int eap_server_mschapv2_register(void); int eap_server_peap_register(void); int eap_server_tlv_register(void); diff --git a/src/eap_server/eap_server.c b/src/eap_server/eap_server.c index 233e272..65d00dd 100644 --- a/src/eap_server/eap_server.c +++ b/src/eap_server/eap_server.c @@ -119,6 +119,32 @@ int eap_user_get(struct eap_sm *sm, const u8 *identity, size_t identity_len, } +void eap_log_msg(struct eap_sm *sm, const char *fmt, ...) +{ + va_list ap; + char *buf; + int buflen; + + if (sm == NULL || sm->eapol_cb == NULL || sm->eapol_cb->log_msg == NULL) + return; + + va_start(ap, fmt); + buflen = vsnprintf(NULL, 0, fmt, ap) + 1; + va_end(ap); + + buf = os_malloc(buflen); + if (buf == NULL) + return; + va_start(ap, fmt); + vsnprintf(buf, buflen, fmt, ap); + va_end(ap); + + sm->eapol_cb->log_msg(sm->eapol_ctx, buf); + + os_free(buf); +} + + SM_STATE(EAP, DISABLED) { SM_ENTRY(EAP, DISABLED); @@ -366,6 +392,7 @@ try_another_method: } if (sm->m == NULL) { wpa_printf(MSG_DEBUG, "EAP: Could not find suitable EAP method"); + eap_log_msg(sm, "Could not find suitable EAP method"); sm->decision = DECISION_FAILURE; return; } @@ -377,6 +404,8 @@ try_another_method: wpa_msg(sm->msg_ctx, MSG_INFO, WPA_EVENT_EAP_PROPOSED_METHOD "vendor=%u method=%u", vendor, sm->currentMethod); + eap_log_msg(sm, "Propose EAP method vendor=%u method=%u", + vendor, sm->currentMethod); } @@ -693,6 +722,7 @@ SM_STEP(EAP) "respMethod=%d currentMethod=%d", sm->rxResp, sm->respId, sm->currentId, sm->respMethod, sm->currentMethod); + eap_log_msg(sm, "Discard received EAP message"); SM_ENTER(EAP, DISCARD); } break; @@ -1297,6 +1327,10 @@ struct eap_sm * eap_server_sm_init(void *eapol_ctx, sm->server_id = conf->server_id; sm->server_id_len = conf->server_id_len; +#ifdef CONFIG_TESTING_OPTIONS + sm->tls_test_flags = conf->tls_test_flags; +#endif /* CONFIG_TESTING_OPTIONS */ + wpa_printf(MSG_DEBUG, "EAP: Server state machine created"); return sm; diff --git a/src/eap_server/eap_server_identity.c b/src/eap_server/eap_server_identity.c index 51dc4e8..4501533 100644 --- a/src/eap_server/eap_server_identity.c +++ b/src/eap_server/eap_server_identity.c @@ -102,6 +102,7 @@ static void eap_identity_process(struct eap_sm *sm, void *priv, struct eap_identity_data *data = priv; const u8 *pos; size_t len; + char *buf; if (data->pick_up) { if (eap_identity_check(sm, data, respData)) { @@ -119,6 +120,12 @@ static void eap_identity_process(struct eap_sm *sm, void *priv, return; /* Should not happen - frame already validated */ wpa_hexdump_ascii(MSG_DEBUG, "EAP-Identity: Peer identity", pos, len); + buf = os_malloc(len * 4 + 1); + if (buf) { + printf_encode(buf, len * 4 + 1, pos, len); + eap_log_msg(sm, "EAP-Response/Identity '%s'", buf); + os_free(buf); + } if (sm->identity) sm->update_user = TRUE; os_free(sm->identity); diff --git a/src/eap_server/eap_server_ikev2.c b/src/eap_server/eap_server_ikev2.c index 1ada0c8..3e32cc9 100644 --- a/src/eap_server/eap_server_ikev2.c +++ b/src/eap_server/eap_server_ikev2.c @@ -256,7 +256,8 @@ static Boolean eap_ikev2_check(struct eap_sm *sm, void *priv, static int eap_ikev2_process_icv(struct eap_ikev2_data *data, const struct wpabuf *respData, - u8 flags, const u8 *pos, const u8 **end) + u8 flags, const u8 *pos, const u8 **end, + int frag_ack) { if (flags & IKEV2_FLAGS_ICV_INCLUDED) { int icv_len = eap_ikev2_validate_icv( @@ -266,7 +267,7 @@ static int eap_ikev2_process_icv(struct eap_ikev2_data *data, return -1; /* Hide Integrity Checksum Data from further processing */ *end -= icv_len; - } else if (data->keys_ready) { + } else if (data->keys_ready && !frag_ack) { wpa_printf(MSG_INFO, "EAP-IKEV2: The message should have " "included integrity checksum"); return -1; @@ -365,7 +366,9 @@ static void eap_ikev2_process(struct eap_sm *sm, void *priv, } else flags = *pos++; - if (eap_ikev2_process_icv(data, respData, flags, pos, &end) < 0) { + if (eap_ikev2_process_icv(data, respData, flags, pos, &end, + data->state == WAIT_FRAG_ACK && len == 0) < 0) + { eap_ikev2_state(data, FAIL); return; } diff --git a/src/eap_server/eap_server_mschapv2.c b/src/eap_server/eap_server_mschapv2.c index 3153d2e..0eb7908 100644 --- a/src/eap_server/eap_server_mschapv2.c +++ b/src/eap_server/eap_server_mschapv2.c @@ -290,6 +290,7 @@ static void eap_mschapv2_process_response(struct eap_sm *sm, const u8 *username, *user; size_t username_len, user_len; int res; + char *buf; pos = eap_hdr_validate(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2, respData, &len); @@ -329,6 +330,13 @@ static void eap_mschapv2_process_response(struct eap_sm *sm, wpa_printf(MSG_MSGDUMP, "EAP-MSCHAPV2: Flags 0x%x", flags); wpa_hexdump_ascii(MSG_MSGDUMP, "EAP-MSCHAPV2: Name", name, name_len); + buf = os_malloc(name_len * 4 + 1); + if (buf) { + printf_encode(buf, name_len * 4 + 1, name, name_len); + eap_log_msg(sm, "EAP-MSCHAPV2 Name '%s'", buf); + os_free(buf); + } + /* MSCHAPv2 does not include optional domain name in the * challenge-response calculation, so remove domain prefix * (if present). */ diff --git a/src/eap_server/eap_server_pwd.c b/src/eap_server/eap_server_pwd.c index b61061b..ec53481 100644 --- a/src/eap_server/eap_server_pwd.c +++ b/src/eap_server/eap_server_pwd.c @@ -45,6 +45,7 @@ struct eap_pwd_data { u8 msk[EAP_MSK_LEN]; u8 emsk[EAP_EMSK_LEN]; + u8 session_id[1 + SHA256_MAC_LEN]; BN_CTX *bnctx; }; @@ -123,7 +124,8 @@ static void * eap_pwd_init(struct eap_sm *sm) data->in_frag_pos = data->out_frag_pos = 0; data->inbuf = data->outbuf = NULL; - data->mtu = 1020; /* default from RFC 5931, make it configurable! */ + /* use default MTU from RFC 5931 if not configured otherwise */ + data->mtu = sm->fragment_size > 0 ? sm->fragment_size : 1020; return data; } @@ -150,6 +152,8 @@ static void eap_pwd_reset(struct eap_sm *sm, void *priv) BN_free(data->grp->prime); os_free(data->grp); } + wpabuf_free(data->inbuf); + wpabuf_free(data->outbuf); os_free(data); } @@ -523,6 +527,7 @@ eap_pwd_build_req(struct eap_sm *sm, void *priv, u8 id) */ if (data->out_frag_pos >= wpabuf_len(data->outbuf)) { wpabuf_free(data->outbuf); + data->outbuf = NULL; data->out_frag_pos = 0; } @@ -595,7 +600,8 @@ static void eap_pwd_process_id_resp(struct eap_sm *sm, wpa_hexdump_ascii(MSG_DEBUG, "EAP-PWD (server): peer sent id of", data->id_peer, data->id_peer_len); - if ((data->grp = os_malloc(sizeof(EAP_PWD_group))) == NULL) { + data->grp = os_zalloc(sizeof(EAP_PWD_group)); + if (data->grp == NULL) { wpa_printf(MSG_INFO, "EAP-PWD: failed to allocate memory for " "group"); return; @@ -838,7 +844,8 @@ eap_pwd_process_confirm_resp(struct eap_sm *sm, struct eap_pwd_data *data, wpa_printf(MSG_DEBUG, "EAP-pwd (server): confirm verified"); if (compute_keys(data->grp, data->bnctx, data->k, data->peer_scalar, data->my_scalar, conf, - data->my_confirm, &cs, data->msk, data->emsk) < 0) + data->my_confirm, &cs, data->msk, data->emsk, + data->session_id) < 0) eap_pwd_state(data, FAILURE); else eap_pwd_state(data, SUCCESS); @@ -949,6 +956,7 @@ static void eap_pwd_process(struct eap_sm *sm, void *priv, */ if (data->in_frag_pos) { wpabuf_free(data->inbuf); + data->inbuf = NULL; data->in_frag_pos = 0; } } diff --git a/src/eap_server/eap_server_tls.c b/src/eap_server/eap_server_tls.c index 447f47c..6bed62f 100644 --- a/src/eap_server/eap_server_tls.c +++ b/src/eap_server/eap_server_tls.c @@ -94,6 +94,28 @@ static void * eap_unauth_tls_init(struct eap_sm *sm) #endif /* EAP_SERVER_UNAUTH_TLS */ +#ifdef CONFIG_HS20 +static void * eap_wfa_unauth_tls_init(struct eap_sm *sm) +{ + struct eap_tls_data *data; + + data = os_zalloc(sizeof(*data)); + if (data == NULL) + return NULL; + data->state = START; + + if (eap_server_tls_ssl_init(sm, &data->ssl, 0)) { + wpa_printf(MSG_INFO, "EAP-TLS: Failed to initialize SSL."); + eap_tls_reset(sm, data); + return NULL; + } + + data->eap_type = EAP_WFA_UNAUTH_TLS_TYPE; + return data; +} +#endif /* CONFIG_HS20 */ + + static void eap_tls_reset(struct eap_sm *sm, void *priv) { struct eap_tls_data *data = priv; @@ -178,6 +200,10 @@ static Boolean eap_tls_check(struct eap_sm *sm, void *priv, pos = eap_hdr_validate(EAP_VENDOR_UNAUTH_TLS, EAP_VENDOR_TYPE_UNAUTH_TLS, respData, &len); + else if (data->eap_type == EAP_WFA_UNAUTH_TLS_TYPE) + pos = eap_hdr_validate(EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, respData, + &len); else pos = eap_hdr_validate(EAP_VENDOR_IETF, data->eap_type, respData, &len); @@ -340,3 +366,34 @@ int eap_server_unauth_tls_register(void) return ret; } #endif /* EAP_SERVER_UNAUTH_TLS */ + + +#ifdef CONFIG_HS20 +int eap_server_wfa_unauth_tls_register(void) +{ + struct eap_method *eap; + int ret; + + eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION, + EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, + "WFA-UNAUTH-TLS"); + if (eap == NULL) + return -1; + + eap->init = eap_wfa_unauth_tls_init; + eap->reset = eap_tls_reset; + eap->buildReq = eap_tls_buildReq; + eap->check = eap_tls_check; + eap->process = eap_tls_process; + eap->isDone = eap_tls_isDone; + eap->getKey = eap_tls_getKey; + eap->isSuccess = eap_tls_isSuccess; + eap->get_emsk = eap_tls_get_emsk; + + ret = eap_server_method_register(eap); + if (ret) + eap_server_method_free(eap); + return ret; +} +#endif /* CONFIG_HS20 */ diff --git a/src/eap_server/eap_server_tls_common.c b/src/eap_server/eap_server_tls_common.c index 526e1bc..01853e6 100644 --- a/src/eap_server/eap_server_tls_common.c +++ b/src/eap_server/eap_server_tls_common.c @@ -25,11 +25,24 @@ struct wpabuf * eap_tls_msg_alloc(EapType type, size_t payload_len, return eap_msg_alloc(EAP_VENDOR_UNAUTH_TLS, EAP_VENDOR_TYPE_UNAUTH_TLS, payload_len, code, identifier); + else if (type == EAP_WFA_UNAUTH_TLS_TYPE) + return eap_msg_alloc(EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, payload_len, + code, identifier); return eap_msg_alloc(EAP_VENDOR_IETF, type, payload_len, code, identifier); } +#ifdef CONFIG_TLS_INTERNAL +static void eap_server_tls_log_cb(void *ctx, const char *msg) +{ + struct eap_sm *sm = ctx; + eap_log_msg(sm, "TLS: %s", msg); +} +#endif /* CONFIG_TLS_INTERNAL */ + + int eap_server_tls_ssl_init(struct eap_sm *sm, struct eap_ssl_data *data, int verify_peer) { @@ -48,6 +61,13 @@ int eap_server_tls_ssl_init(struct eap_sm *sm, struct eap_ssl_data *data, return -1; } +#ifdef CONFIG_TLS_INTERNAL + tls_connection_set_log_cb(data->conn, eap_server_tls_log_cb, sm); +#ifdef CONFIG_TESTING_OPTIONS + tls_connection_set_test_flags(data->conn, sm->tls_test_flags); +#endif /* CONFIG_TESTING_OPTIONS */ +#endif /* CONFIG_TLS_INTERNAL */ + if (tls_connection_set_verify(sm->ssl_ctx, data->conn, verify_peer)) { wpa_printf(MSG_INFO, "SSL: Failed to configure verification " "of TLS peer certificate"); @@ -393,6 +413,10 @@ int eap_server_tls_process(struct eap_sm *sm, struct eap_ssl_data *data, pos = eap_hdr_validate(EAP_VENDOR_UNAUTH_TLS, EAP_VENDOR_TYPE_UNAUTH_TLS, respData, &left); + else if (eap_type == EAP_WFA_UNAUTH_TLS_TYPE) + pos = eap_hdr_validate(EAP_VENDOR_WFA_NEW, + EAP_VENDOR_WFA_UNAUTH_TLS, respData, + &left); else pos = eap_hdr_validate(EAP_VENDOR_IETF, eap_type, respData, &left); diff --git a/src/eap_server/eap_server_ttls.c b/src/eap_server/eap_server_ttls.c index 647bd2f..d09a769 100644 --- a/src/eap_server/eap_server_ttls.c +++ b/src/eap_server/eap_server_ttls.c @@ -984,6 +984,16 @@ static void eap_ttls_process_phase2(struct eap_sm *sm, } if (parse.user_name) { + char *nbuf; + nbuf = os_malloc(parse.user_name_len * 4 + 1); + if (nbuf) { + printf_encode(nbuf, parse.user_name_len * 4 + 1, + parse.user_name, + parse.user_name_len); + eap_log_msg(sm, "TTLS-User-Name '%s'", nbuf); + os_free(nbuf); + } + os_free(sm->identity); sm->identity = os_malloc(parse.user_name_len); if (sm->identity == NULL) { diff --git a/src/eap_server/eap_sim_db.c b/src/eap_server/eap_sim_db.c index 45660ed..bc2cbe5 100644 --- a/src/eap_server/eap_sim_db.c +++ b/src/eap_server/eap_sim_db.c @@ -639,6 +639,11 @@ static int eap_sim_db_open_socket(struct eap_sim_db_data *data) "/tmp/eap_sim_db_%d-%d", getpid(), counter++); os_free(data->local_sock); data->local_sock = os_strdup(addr.sun_path); + if (data->local_sock == NULL) { + close(data->sock); + data->sock = -1; + return -1; + } if (bind(data->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { wpa_printf(MSG_INFO, "bind(eap_sim_db): %s", strerror(errno)); close(data->sock); @@ -657,6 +662,9 @@ static int eap_sim_db_open_socket(struct eap_sim_db_data *data) os_strlen(addr.sun_path)); close(data->sock); data->sock = -1; + unlink(data->local_sock); + os_free(data->local_sock); + data->local_sock = NULL; return -1; } diff --git a/src/eap_server/eap_tls_common.h b/src/eap_server/eap_tls_common.h index 11f5827..91449af 100644 --- a/src/eap_server/eap_tls_common.h +++ b/src/eap_server/eap_tls_common.h @@ -64,6 +64,7 @@ struct eap_ssl_data { /* dummy type used as a flag for UNAUTH-TLS */ #define EAP_UNAUTH_TLS_TYPE 255 +#define EAP_WFA_UNAUTH_TLS_TYPE 254 struct wpabuf * eap_tls_msg_alloc(EapType type, size_t payload_len, diff --git a/src/eap_server/tncs.c b/src/eap_server/tncs.c index e429f1e..dc6f689 100644 --- a/src/eap_server/tncs.c +++ b/src/eap_server/tncs.c @@ -11,6 +11,7 @@ #include "common.h" #include "base64.h" +#include "common/tnc.h" #include "tncs.h" #include "eap_common/eap_tlv_common.h" #include "eap_common/eap_defs.h" @@ -19,7 +20,9 @@ /* TODO: TNCS must be thread-safe; review the code and add locking etc. if * needed.. */ +#ifndef TNC_CONFIG_FILE #define TNC_CONFIG_FILE "/etc/tnc_config" +#endif /* TNC_CONFIG_FILE */ #define IF_TNCCS_START \ "<?xml version=\"1.0\"?>\n" \ "<TNCCS-Batch BatchId=\"%d\" Recipient=\"TNCS\" " \ @@ -31,75 +34,6 @@ /* TNC IF-IMV */ -typedef unsigned long TNC_UInt32; -typedef unsigned char *TNC_BufferReference; - -typedef TNC_UInt32 TNC_IMVID; -typedef TNC_UInt32 TNC_ConnectionID; -typedef TNC_UInt32 TNC_ConnectionState; -typedef TNC_UInt32 TNC_RetryReason; -typedef TNC_UInt32 TNC_IMV_Action_Recommendation; -typedef TNC_UInt32 TNC_IMV_Evaluation_Result; -typedef TNC_UInt32 TNC_MessageType; -typedef TNC_MessageType *TNC_MessageTypeList; -typedef TNC_UInt32 TNC_VendorID; -typedef TNC_UInt32 TNC_Subtype; -typedef TNC_UInt32 TNC_Version; -typedef TNC_UInt32 TNC_Result; -typedef TNC_UInt32 TNC_AttributeID; - -typedef TNC_Result (*TNC_TNCS_BindFunctionPointer)( - TNC_IMVID imvID, - char *functionName, - void **pOutfunctionPointer); - -#define TNC_RESULT_SUCCESS 0 -#define TNC_RESULT_NOT_INITIALIZED 1 -#define TNC_RESULT_ALREADY_INITIALIZED 2 -#define TNC_RESULT_NO_COMMON_VERSION 3 -#define TNC_RESULT_CANT_RETRY 4 -#define TNC_RESULT_WONT_RETRY 5 -#define TNC_RESULT_INVALID_PARAMETER 6 -#define TNC_RESULT_CANT_RESPOND 7 -#define TNC_RESULT_ILLEGAL_OPERATION 8 -#define TNC_RESULT_OTHER 9 -#define TNC_RESULT_FATAL 10 - -#define TNC_CONNECTION_STATE_CREATE 0 -#define TNC_CONNECTION_STATE_HANDSHAKE 1 -#define TNC_CONNECTION_STATE_ACCESS_ALLOWED 2 -#define TNC_CONNECTION_STATE_ACCESS_ISOLATED 3 -#define TNC_CONNECTION_STATE_ACCESS_NONE 4 -#define TNC_CONNECTION_STATE_DELETE 5 - -#define TNC_IFIMV_VERSION_1 1 - -#define TNC_VENDORID_ANY ((TNC_VendorID) 0xffffff) -#define TNC_SUBTYPE_ANY ((TNC_Subtype) 0xff) - -/* TNCC-TNCS Message Types */ -#define TNC_TNCCS_RECOMMENDATION 0x00000001 -#define TNC_TNCCS_ERROR 0x00000002 -#define TNC_TNCCS_PREFERREDLANGUAGE 0x00000003 -#define TNC_TNCCS_REASONSTRINGS 0x00000004 - -/* Possible TNC_IMV_Action_Recommendation values: */ -enum IMV_Action_Recommendation { - TNC_IMV_ACTION_RECOMMENDATION_ALLOW, - TNC_IMV_ACTION_RECOMMENDATION_NO_ACCESS, - TNC_IMV_ACTION_RECOMMENDATION_ISOLATE, - TNC_IMV_ACTION_RECOMMENDATION_NO_RECOMMENDATION -}; - -/* Possible TNC_IMV_Evaluation_Result values: */ -enum IMV_Evaluation_Result { - TNC_IMV_EVALUATION_RESULT_COMPLIANT, - TNC_IMV_EVALUATION_RESULT_NONCOMPLIANT_MINOR, - TNC_IMV_EVALUATION_RESULT_NONCOMPLIANT_MAJOR, - TNC_IMV_EVALUATION_RESULT_ERROR, - TNC_IMV_EVALUATION_RESULT_DONT_KNOW -}; - struct tnc_if_imv { struct tnc_if_imv *next; char *name; @@ -1181,6 +1115,9 @@ int tncs_global_init(void) { struct tnc_if_imv *imv; + if (tncs_global_data) + return 0; + tncs_global_data = os_zalloc(sizeof(*tncs_global_data)); if (tncs_global_data == NULL) return -1; diff --git a/src/eapol_auth/eapol_auth_sm.c b/src/eapol_auth/eapol_auth_sm.c index a257781..a76fa13 100644 --- a/src/eapol_auth/eapol_auth_sm.c +++ b/src/eapol_auth/eapol_auth_sm.c @@ -219,7 +219,8 @@ SM_STATE(AUTH_PAE, DISCONNECTED) sm->eapolLogoff = FALSE; if (!from_initialize) { sm->eapol->cb.finished(sm->eapol->conf.ctx, sm->sta, 0, - sm->flags & EAPOL_SM_PREAUTH); + sm->flags & EAPOL_SM_PREAUTH, + sm->remediation); } } @@ -276,7 +277,7 @@ SM_STATE(AUTH_PAE, HELD) eap_server_get_name(0, sm->eap_type_supp)); } sm->eapol->cb.finished(sm->eapol->conf.ctx, sm->sta, 0, - sm->flags & EAPOL_SM_PREAUTH); + sm->flags & EAPOL_SM_PREAUTH, sm->remediation); } @@ -302,7 +303,7 @@ SM_STATE(AUTH_PAE, AUTHENTICATED) eap_server_get_name(0, sm->eap_type_authsrv), extra); sm->eapol->cb.finished(sm->eapol->conf.ctx, sm->sta, 1, - sm->flags & EAPOL_SM_PREAUTH); + sm->flags & EAPOL_SM_PREAUTH, sm->remediation); } @@ -1001,8 +1002,13 @@ static int eapol_sm_get_eap_user(void *ctx, const u8 *identity, struct eap_user *user) { struct eapol_state_machine *sm = ctx; - return sm->eapol->cb.get_eap_user(sm->eapol->conf.ctx, identity, - identity_len, phase2, user); + int ret; + + ret = sm->eapol->cb.get_eap_user(sm->eapol->conf.ctx, identity, + identity_len, phase2, user); + if (user->remediation) + sm->remediation = 1; + return ret; } @@ -1017,7 +1023,8 @@ static const char * eapol_sm_get_eap_req_id_text(void *ctx, size_t *len) static struct eapol_callbacks eapol_cb = { eapol_sm_get_eap_user, - eapol_sm_get_eap_req_id_text + eapol_sm_get_eap_req_id_text, + NULL }; diff --git a/src/eapol_auth/eapol_auth_sm.h b/src/eapol_auth/eapol_auth_sm.h index f0ff464..320a0ad 100644 --- a/src/eapol_auth/eapol_auth_sm.h +++ b/src/eapol_auth/eapol_auth_sm.h @@ -60,7 +60,8 @@ struct eapol_auth_cb { size_t datalen); void (*aaa_send)(void *ctx, void *sta_ctx, const u8 *data, size_t datalen); - void (*finished)(void *ctx, void *sta_ctx, int success, int preauth); + void (*finished)(void *ctx, void *sta_ctx, int success, int preauth, + int remediation); int (*get_eap_user)(void *ctx, const u8 *identity, size_t identity_len, int phase2, struct eap_user *user); int (*sta_entry_alive)(void *ctx, const u8 *addr); diff --git a/src/eapol_auth/eapol_auth_sm_i.h b/src/eapol_auth/eapol_auth_sm_i.h index d7f893a..25baddb 100644 --- a/src/eapol_auth/eapol_auth_sm_i.h +++ b/src/eapol_auth/eapol_auth_sm_i.h @@ -173,6 +173,8 @@ struct eapol_state_machine { struct eapol_authenticator *eapol; void *sta; /* station context pointer to use in callbacks */ + + int remediation; }; #endif /* EAPOL_AUTH_SM_I_H */ diff --git a/src/eapol_supp/eapol_supp_sm.c b/src/eapol_supp/eapol_supp_sm.c index cbcde7e..1004b1a 100644 --- a/src/eapol_supp/eapol_supp_sm.c +++ b/src/eapol_supp/eapol_supp_sm.c @@ -1345,6 +1345,13 @@ int eapol_sm_rx_eapol(struct eapol_sm *sm, const u8 *src, const u8 *buf, eapol_sm_step(sm); } 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, "EAPOL: Received unknown EAPOL type %d", hdr->type); @@ -1557,6 +1564,24 @@ key_fetched: /** + * eapol_sm_get_session_id - Get EAP Session-Id + * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() + * @len: Pointer to variable that will be set to number of bytes in the session + * Returns: Pointer to the EAP Session-Id or %NULL on failure + * + * The Session-Id is available only after a successful authentication. + */ +const u8 * eapol_sm_get_session_id(struct eapol_sm *sm, size_t *len) +{ + if (sm == NULL || !eap_key_available(sm->eap)) { + wpa_printf(MSG_DEBUG, "EAPOL: EAP Session-Id not available"); + return NULL; + } + return eap_get_eapSessionId(sm->eap, len); +} + + +/** * eapol_sm_notify_logoff - Notification of logon/logoff commands * @sm: Pointer to EAPOL state machine allocated with eapol_sm_init() * @logoff: Whether command was logoff diff --git a/src/eapol_supp/eapol_supp_sm.h b/src/eapol_supp/eapol_supp_sm.h index 934eda0..d76c8c2 100644 --- a/src/eapol_supp/eapol_supp_sm.h +++ b/src/eapol_supp/eapol_supp_sm.h @@ -285,6 +285,7 @@ void eapol_sm_notify_config(struct eapol_sm *sm, struct eap_peer_config *config, const struct eapol_config *conf); int eapol_sm_get_key(struct eapol_sm *sm, u8 *key, size_t len); +const u8 * eapol_sm_get_session_id(struct eapol_sm *sm, size_t *len); void eapol_sm_notify_logoff(struct eapol_sm *sm, Boolean logoff); void eapol_sm_notify_cached(struct eapol_sm *sm); void eapol_sm_notify_pmkid_attempt(struct eapol_sm *sm, int attempt); diff --git a/src/l2_packet/l2_packet_none.c b/src/l2_packet/l2_packet_none.c index b01e830..6896c4e 100644 --- a/src/l2_packet/l2_packet_none.c +++ b/src/l2_packet/l2_packet_none.c @@ -84,7 +84,8 @@ struct l2_packet_data * l2_packet_init( * TODO: open connection for receiving frames */ l2->fd = -1; - eloop_register_read_sock(l2->fd, l2_packet_receive, l2, NULL); + if (l2->fd >= 0) + eloop_register_read_sock(l2->fd, l2_packet_receive, l2, NULL); return l2; } diff --git a/src/p2p/p2p.c b/src/p2p/p2p.c index 957dee5..1875ca4 100644 --- a/src/p2p/p2p.c +++ b/src/p2p/p2p.c @@ -218,6 +218,8 @@ void p2p_go_neg_failed(struct p2p_data *p2p, struct p2p_device *peer, os_memset(&res, 0, sizeof(res)); res.status = status; if (peer) { + wpabuf_free(peer->go_neg_conf); + peer->go_neg_conf = NULL; os_memcpy(res.peer_device_addr, peer->info.p2p_device_addr, ETH_ALEN); os_memcpy(res.peer_interface_addr, peer->intended_addr, @@ -236,6 +238,12 @@ static void p2p_listen_in_find(struct p2p_data *p2p, int dev_disc) p2p_dbg(p2p, "Starting short listen state (state=%s)", p2p_state_txt(p2p->state)); + if (p2p->pending_listen_freq) { + /* We have a pending p2p_listen request */ + p2p_dbg(p2p, "p2p_listen command pending already"); + return; + } + freq = p2p_channel_to_freq(p2p->cfg->reg_class, p2p->cfg->channel); if (freq < 0) { p2p_dbg(p2p, "Unknown regulatory class/channel"); @@ -258,14 +266,14 @@ static void p2p_listen_in_find(struct p2p_data *p2p, int dev_disc) return; } - p2p->pending_listen_freq = freq; - p2p->pending_listen_sec = 0; - p2p->pending_listen_usec = 1024 * tu; - ies = p2p_build_probe_resp_ies(p2p); if (ies == NULL) return; + p2p->pending_listen_freq = freq; + p2p->pending_listen_sec = 0; + p2p->pending_listen_usec = 1024 * tu; + if (p2p->cfg->start_listen(p2p->cfg->cb_ctx, freq, 1024 * tu / 1000, ies) < 0) { p2p_dbg(p2p, "Failed to start listen mode"); @@ -282,13 +290,18 @@ int p2p_listen(struct p2p_data *p2p, unsigned int timeout) p2p_dbg(p2p, "Going to listen(only) state"); + if (p2p->pending_listen_freq) { + /* We have a pending p2p_listen request */ + p2p_dbg(p2p, "p2p_listen command pending already"); + return -1; + } + freq = p2p_channel_to_freq(p2p->cfg->reg_class, p2p->cfg->channel); if (freq < 0) { p2p_dbg(p2p, "Unknown regulatory class/channel"); return -1; } - p2p->pending_listen_freq = freq; p2p->pending_listen_sec = timeout / 1000; p2p->pending_listen_usec = (timeout % 1000) * 1000; @@ -306,6 +319,8 @@ int p2p_listen(struct p2p_data *p2p, unsigned int timeout) if (ies == NULL) return -1; + p2p->pending_listen_freq = freq; + if (p2p->cfg->start_listen(p2p->cfg->cb_ctx, freq, timeout, ies) < 0) { p2p_dbg(p2p, "Failed to start listen mode"); p2p->pending_listen_freq = 0; @@ -733,9 +748,6 @@ int p2p_add_device(struct p2p_data *p2p, const u8 *addr, int freq, p2p_parse_free(&msg); - if (p2p_pending_sd_req(p2p, dev)) - dev->flags |= P2P_DEV_SD_SCHEDULE; - if (dev->flags & P2P_DEV_REPORTED) return 0; @@ -805,6 +817,7 @@ static void p2p_device_free(struct p2p_data *p2p, struct p2p_device *dev) } wpabuf_free(dev->info.wfd_subelems); + wpabuf_free(dev->go_neg_conf); os_free(dev); } @@ -1049,6 +1062,7 @@ int p2p_find(struct p2p_data *p2p, unsigned int timeout, } else if (p2p->p2p_scan_running) { p2p_dbg(p2p, "Failed to start p2p_scan - another p2p_scan was already running"); /* wait for the previous p2p_scan to complete */ + res = 0; /* do not report failure */ } else { p2p_dbg(p2p, "Failed to start p2p_scan"); p2p_set_state(p2p, P2P_IDLE); @@ -1115,6 +1129,7 @@ void p2p_stop_listen(struct p2p_data *p2p) void p2p_stop_find(struct p2p_data *p2p) { + p2p->pending_listen_freq = 0; p2p_stop_find_for_freq(p2p, 0); } @@ -1210,10 +1225,25 @@ static void p2p_prepare_channel_best(struct p2p_data *p2p) 0) { p2p_dbg(p2p, "Select possible 5 GHz channel (op_class %u channel %u) as operating channel preference", p2p->op_reg_class, p2p->op_channel); - } else { + } else if (p2p_channels_includes(&p2p->cfg->channels, + p2p->cfg->op_reg_class, + p2p->cfg->op_channel)) { p2p_dbg(p2p, "Select pre-configured channel as operating channel preference"); p2p->op_reg_class = p2p->cfg->op_reg_class; p2p->op_channel = p2p->cfg->op_channel; + } else if (p2p_channel_random_social(&p2p->cfg->channels, + &p2p->op_reg_class, + &p2p->op_channel) == 0) { + p2p_dbg(p2p, "Select random available social channel %d from 2.4 GHz band as operating channel preference", + p2p->op_channel); + } else { + /* Select any random available channel from the first available + * operating class */ + p2p_channel_select(&p2p->cfg->channels, NULL, + &p2p->op_reg_class, + &p2p->op_channel); + p2p_dbg(p2p, "Select random available channel %d from operating class %d as operating channel preference", + p2p->op_channel, p2p->op_reg_class); } os_memcpy(&p2p->channels, &p2p->cfg->channels, @@ -1598,6 +1628,8 @@ void p2p_go_complete(struct p2p_data *p2p, struct p2p_device *peer) peer->go_neg_req_sent = 0; peer->wps_method = WPS_NOT_READY; peer->oob_pw_id = 0; + wpabuf_free(peer->go_neg_conf); + peer->go_neg_conf = NULL; p2p_set_state(p2p, P2P_PROVISIONING); p2p->cfg->go_neg_completed(p2p->cfg->cb_ctx, &res); @@ -1662,20 +1694,15 @@ static void p2p_rx_action_public(struct p2p_data *p2p, const u8 *da, case WLAN_PA_VENDOR_SPECIFIC: data++; len--; - if (len < 3) + if (len < 4) return; - if (WPA_GET_BE24(data) != OUI_WFA) + if (WPA_GET_BE32(data) != P2P_IE_VENDOR_TYPE) return; - data += 3; - len -= 3; - if (len < 1) - return; - - if (*data != P2P_OUI_TYPE) - return; + data += 4; + len -= 4; - p2p_rx_p2p_action(p2p, sa, data + 1, len - 1, freq); + p2p_rx_p2p_action(p2p, sa, data, len, freq); break; case WLAN_PA_GAS_INITIAL_REQ: p2p_rx_gas_initial_req(p2p, sa, data + 1, len - 1, freq); @@ -1708,15 +1735,10 @@ void p2p_rx_action(struct p2p_data *p2p, const u8 *da, const u8 *sa, if (len < 4) return; - if (WPA_GET_BE24(data) != OUI_WFA) - return; - data += 3; - len -= 3; - - if (*data != P2P_OUI_TYPE) + if (WPA_GET_BE32(data) != P2P_IE_VENDOR_TYPE) return; - data++; - len--; + data += 4; + len -= 4; /* P2P action frame */ p2p_dbg(p2p, "RX P2P Action from " MACSTR, MAC2STR(sa)); @@ -1973,17 +1995,21 @@ p2p_reply_probe(struct p2p_data *p2p, const u8 *addr, const u8 *dst, if (!p2p->in_listen || !p2p->drv_in_listen) { /* not in Listen state - ignore Probe Request */ + p2p_dbg(p2p, "Not in Listen state (in_listen=%d drv_in_listen=%d) - ignore Probe Request", + p2p->in_listen, p2p->drv_in_listen); return P2P_PREQ_NOT_LISTEN; } if (ieee802_11_parse_elems((u8 *) ie, ie_len, &elems, 0) == ParseFailed) { /* Ignore invalid Probe Request frames */ + p2p_dbg(p2p, "Could not parse Probe Request frame - ignore it"); return P2P_PREQ_MALFORMED; } if (elems.p2p == NULL) { /* not a P2P probe - ignore it */ + p2p_dbg(p2p, "Not a P2P probe - ignore it"); return P2P_PREQ_NOT_P2P; } @@ -1991,11 +2017,15 @@ p2p_reply_probe(struct p2p_data *p2p, const u8 *addr, const u8 *dst, os_memcmp(dst, p2p->cfg->dev_addr, ETH_ALEN) != 0) { /* Not sent to the broadcast address or our P2P Device Address */ + p2p_dbg(p2p, "Probe Req DA " MACSTR " not ours - ignore it", + MAC2STR(dst)); return P2P_PREQ_NOT_PROCESSED; } if (bssid && !is_broadcast_ether_addr(bssid)) { /* Not sent to the Wildcard BSSID */ + p2p_dbg(p2p, "Probe Req BSSID " MACSTR " not wildcard - ignore it", + MAC2STR(bssid)); return P2P_PREQ_NOT_PROCESSED; } @@ -2003,23 +2033,28 @@ p2p_reply_probe(struct p2p_data *p2p, const u8 *addr, const u8 *dst, os_memcmp(elems.ssid, P2P_WILDCARD_SSID, P2P_WILDCARD_SSID_LEN) != 0) { /* not using P2P Wildcard SSID - ignore */ + p2p_dbg(p2p, "Probe Req not using P2P Wildcard SSID - ignore it"); return P2P_PREQ_NOT_PROCESSED; } if (supp_rates_11b_only(&elems)) { /* Indicates support for 11b rates only */ + p2p_dbg(p2p, "Probe Req with 11b rates only supported - ignore it"); return P2P_PREQ_NOT_P2P; } os_memset(&msg, 0, sizeof(msg)); if (p2p_parse_ies(ie, ie_len, &msg) < 0) { /* Could not parse P2P attributes */ + p2p_dbg(p2p, "Could not parse P2P attributes in Probe Req - ignore it"); return P2P_PREQ_NOT_P2P; } if (msg.device_id && os_memcmp(msg.device_id, p2p->cfg->dev_addr, ETH_ALEN) != 0) { /* Device ID did not match */ + p2p_dbg(p2p, "Probe Req requested Device ID " MACSTR " did not match - ignore it", + MAC2STR(msg.device_id)); p2p_parse_free(&msg); return P2P_PREQ_NOT_PROCESSED; } @@ -2028,6 +2063,7 @@ p2p_reply_probe(struct p2p_data *p2p, const u8 *addr, const u8 *dst, if (msg.wps_attributes && !p2p_match_dev_type(p2p, msg.wps_attributes)) { /* No match with Requested Device Type */ + p2p_dbg(p2p, "Probe Req requestred Device Type did not match - ignore it"); p2p_parse_free(&msg); return P2P_PREQ_NOT_PROCESSED; } @@ -2035,6 +2071,7 @@ p2p_reply_probe(struct p2p_data *p2p, const u8 *addr, const u8 *dst, if (!p2p->cfg->send_probe_resp) { /* Response generated elsewhere */ + p2p_dbg(p2p, "Probe Resp generated elsewhere - do not generate additional response"); return P2P_PREQ_NOT_PROCESSED; } @@ -2125,10 +2162,12 @@ p2p_probe_req_rx(struct p2p_data *p2p, const u8 *addr, const u8 *dst, if ((p2p->state == P2P_INVITE || p2p->state == P2P_INVITE_LISTEN) && p2p->invite_peer && + (p2p->invite_peer->flags & P2P_DEV_WAIT_INV_REQ_ACK) && os_memcmp(addr, p2p->invite_peer->info.p2p_device_addr, ETH_ALEN) == 0) { /* Received a Probe Request from Invite peer */ p2p_dbg(p2p, "Found Invite peer - try to start Invite from timeout"); + eloop_cancel_timeout(p2p_invite_start, p2p, NULL); eloop_register_timeout(0, 0, p2p_invite_start, p2p, NULL); return P2P_PREQ_PROCESSED; } @@ -2390,6 +2429,7 @@ struct p2p_data * p2p_init(const struct p2p_config *cfg) p2p->go_timeout = 100; p2p->client_timeout = 20; + p2p->num_p2p_sd_queries = 0; p2p_dbg(p2p, "initialized"); p2p_channels_dump(p2p, "channels", &p2p->cfg->channels); @@ -2625,13 +2665,16 @@ void p2p_continue_find(struct p2p_data *p2p) struct p2p_device *dev; p2p_set_state(p2p, P2P_SEARCH); dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) { - if (dev->flags & P2P_DEV_SD_SCHEDULE) { - if (p2p_start_sd(p2p, dev) == 0) - return; - else - break; - } else if (dev->req_config_methods && - !(dev->flags & P2P_DEV_PD_FOR_JOIN)) { + if (dev->sd_pending_bcast_queries == 0) { + /* Initialize with total number of registered broadcast + * SD queries. */ + dev->sd_pending_bcast_queries = p2p->num_p2p_sd_queries; + } + + if (p2p_start_sd(p2p, dev) == 0) + return; + if (dev->req_config_methods && + !(dev->flags & P2P_DEV_PD_FOR_JOIN)) { p2p_dbg(p2p, "Send pending Provision Discovery Request to " MACSTR " (config methods 0x%x)", MAC2STR(dev->info.p2p_device_addr), @@ -2652,10 +2695,7 @@ static void p2p_sd_cb(struct p2p_data *p2p, int success) p2p->pending_action_state = P2P_NO_PENDING_ACTION; if (!success) { - if (p2p->sd_peer) { - p2p->sd_peer->flags &= ~P2P_DEV_SD_SCHEDULE; - p2p->sd_peer = NULL; - } + p2p->sd_peer = NULL; p2p_continue_find(p2p); return; } @@ -2931,13 +2971,44 @@ static void p2p_go_neg_conf_cb(struct p2p_data *p2p, struct p2p_device *dev; p2p_dbg(p2p, "GO Negotiation Confirm TX callback: result=%d", result); - p2p->cfg->send_action_done(p2p->cfg->cb_ctx); if (result == P2P_SEND_ACTION_FAILED) { + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); p2p_go_neg_failed(p2p, p2p->go_neg_peer, -1); return; } + + dev = p2p->go_neg_peer; + if (result == P2P_SEND_ACTION_NO_ACK) { /* + * Retry GO Negotiation Confirmation + * P2P_GO_NEG_CNF_MAX_RETRY_COUNT times if we did not receive + * ACK for confirmation. + */ + if (dev && dev->go_neg_conf && + dev->go_neg_conf_sent <= P2P_GO_NEG_CNF_MAX_RETRY_COUNT) { + p2p_dbg(p2p, "GO Negotiation Confirm retry %d", + dev->go_neg_conf_sent); + p2p->pending_action_state = P2P_PENDING_GO_NEG_CONFIRM; + if (p2p_send_action(p2p, dev->go_neg_conf_freq, + dev->info.p2p_device_addr, + p2p->cfg->dev_addr, + dev->info.p2p_device_addr, + wpabuf_head(dev->go_neg_conf), + wpabuf_len(dev->go_neg_conf), 0) >= + 0) { + dev->go_neg_conf_sent++; + return; + } + p2p_dbg(p2p, "Failed to re-send Action frame"); + + /* + * Continue with the assumption that the first attempt + * went through and just the ACK frame was lost. + */ + } + + /* * It looks like the TX status for GO Negotiation Confirm is * often showing failure even when the peer has actually * received the frame. Since the peer may change channels @@ -2950,7 +3021,8 @@ static void p2p_go_neg_conf_cb(struct p2p_data *p2p, p2p_dbg(p2p, "Assume GO Negotiation Confirm TX was actually received by the peer even though Ack was not reported"); } - dev = p2p->go_neg_peer; + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + if (dev == NULL) return; @@ -2974,6 +3046,10 @@ void p2p_send_action_cb(struct p2p_data *p2p, unsigned int freq, const u8 *dst, p2p->pending_action_state = P2P_NO_PENDING_ACTION; switch (state) { case P2P_NO_PENDING_ACTION: + if (p2p->send_action_in_progress) { + p2p->send_action_in_progress = 0; + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + } if (p2p->after_scan_tx_in_progress) { p2p->after_scan_tx_in_progress = 0; if (p2p->start_after_scan != P2P_AFTER_SCAN_NOTHING && @@ -3176,14 +3252,15 @@ static void p2p_timeout_wait_peer_connect(struct p2p_data *p2p) static void p2p_timeout_wait_peer_idle(struct p2p_data *p2p) { struct p2p_device *dev = p2p->go_neg_peer; + struct os_reltime now; if (dev == NULL) { p2p_dbg(p2p, "Unknown GO Neg peer - stop GO Neg wait"); return; } - dev->wait_count++; - if (dev->wait_count >= 120) { + os_get_reltime(&now); + if (os_reltime_expired(&now, &dev->go_neg_wait_started, 120)) { p2p_dbg(p2p, "Timeout on waiting peer to become ready for GO Negotiation"); p2p_go_neg_failed(p2p, dev, -1); return; @@ -3200,7 +3277,6 @@ static void p2p_timeout_sd_during_find(struct p2p_data *p2p) p2p_dbg(p2p, "Service Discovery Query timeout"); if (p2p->sd_peer) { p2p->cfg->send_action_done(p2p->cfg->cb_ctx); - p2p->sd_peer->flags &= ~P2P_DEV_SD_SCHEDULE; p2p->sd_peer = NULL; } p2p_continue_find(p2p); @@ -3286,7 +3362,7 @@ static void p2p_timeout_invite_listen(struct p2p_data *p2p) p2p->cfg->invitation_result( p2p->cfg->cb_ctx, -1, NULL, NULL, p2p->invite_peer->info.p2p_device_addr, - 0); + 0, 0); } p2p_set_state(p2p, P2P_IDLE); } @@ -3471,9 +3547,8 @@ int p2p_get_peer_info_txt(const struct p2p_peer_info *info, "country=%c%c\n" "oper_freq=%d\n" "req_config_methods=0x%x\n" - "flags=%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n" + "flags=%s%s%s%s%s%s%s%s%s%s%s%s%s\n" "status=%d\n" - "wait_count=%u\n" "invitation_reqs=%u\n", (int) (now.sec - dev->last_seen.sec), dev->listen_freq, @@ -3494,9 +3569,6 @@ int p2p_get_peer_info_txt(const struct p2p_peer_info *info, dev->flags & P2P_DEV_REPORTED ? "[REPORTED]" : "", dev->flags & P2P_DEV_NOT_YET_READY ? "[NOT_YET_READY]" : "", - dev->flags & P2P_DEV_SD_INFO ? "[SD_INFO]" : "", - dev->flags & P2P_DEV_SD_SCHEDULE ? "[SD_SCHEDULE]" : - "", dev->flags & P2P_DEV_PD_PEER_DISPLAY ? "[PD_PEER_DISPLAY]" : "", dev->flags & P2P_DEV_PD_PEER_KEYPAD ? @@ -3518,7 +3590,6 @@ int p2p_get_peer_info_txt(const struct p2p_peer_info *info, dev->flags & P2P_DEV_PD_FOR_JOIN ? "[PD_FOR_JOIN]" : "", dev->status, - dev->wait_count, dev->invitation_reqs); if (res < 0 || res >= end - pos) return pos - buf; @@ -3791,6 +3862,15 @@ static void p2p_ext_listen_timeout(void *eloop_ctx, void *timeout_ctx) p2p_ext_listen_timeout, p2p, NULL); } + if ((p2p->cfg->is_p2p_in_progress && + p2p->cfg->is_p2p_in_progress(p2p->cfg->cb_ctx)) || + (p2p->pending_action_state == P2P_PENDING_PD && + p2p->pd_retries > 0)) { + p2p_dbg(p2p, "Operation in progress - skip Extended Listen timeout (%s)", + p2p_state_txt(p2p->state)); + return; + } + if (p2p->state == P2P_LISTEN_ONLY && p2p->ext_listen_only) { /* * This should not really happen, but it looks like the Listen @@ -4152,7 +4232,7 @@ p2p_get_peer_found(struct p2p_data *p2p, const u8 *addr, int next) dev = dl_list_first(&dev->list, struct p2p_device, list); - if (&dev->list == &p2p->devices) + if (!dev || &dev->list == &p2p->devices) return NULL; } while (dev->flags & P2P_DEV_PROBE_REQ_ONLY); } @@ -4164,7 +4244,7 @@ p2p_get_peer_found(struct p2p_data *p2p, const u8 *addr, int next) dev = dl_list_first(&dev->list, struct p2p_device, list); - if (&dev->list == &p2p->devices) + if (!dev || &dev->list == &p2p->devices) return NULL; } } @@ -4405,12 +4485,24 @@ static struct wpabuf * p2p_build_nfc_handover(struct p2p_data *p2p, p2p_buf_add_device_info(buf, p2p, NULL); if (p2p->num_groups > 0) { + int freq = p2p_group_get_freq(p2p->groups[0]); role = P2P_GO_IN_A_GROUP; - p2p_freq_to_channel(p2p_group_get_freq(p2p->groups[0]), - &op_class, &channel); + if (p2p_freq_to_channel(freq, &op_class, &channel) < 0) { + p2p_dbg(p2p, + "Unknown GO operating frequency %d MHz for NFC handover", + freq); + wpabuf_free(buf); + return NULL; + } } else if (client_freq > 0) { role = P2P_CLIENT_IN_A_GROUP; - p2p_freq_to_channel(client_freq, &op_class, &channel); + if (p2p_freq_to_channel(client_freq, &op_class, &channel) < 0) { + p2p_dbg(p2p, + "Unknown client operating frequency %d MHz for NFC handover", + client_freq); + wpabuf_free(buf); + return NULL; + } } p2p_buf_add_oob_go_neg_channel(buf, p2p->cfg->country, op_class, @@ -4546,10 +4638,9 @@ int p2p_process_nfc_connection_handover(struct p2p_data *p2p, params->go_ssid_len); } - p2p_parse_free(&msg); - if (dev->flags & P2P_DEV_USER_REJECTED) { p2p_dbg(p2p, "Do not report rejected device"); + p2p_parse_free(&msg); return 0; } @@ -4558,6 +4649,7 @@ int p2p_process_nfc_connection_handover(struct p2p_data *p2p, !(dev->flags & P2P_DEV_REPORTED_ONCE)); dev->flags |= P2P_DEV_REPORTED | P2P_DEV_REPORTED_ONCE; } + p2p_parse_free(&msg); if (role == P2P_GO_IN_A_GROUP && p2p->num_groups > 0) params->next_step = BOTH_GO; diff --git a/src/p2p/p2p.h b/src/p2p/p2p.h index 08e7176..fa8031d 100644 --- a/src/p2p/p2p.h +++ b/src/p2p/p2p.h @@ -764,6 +764,8 @@ struct p2p_config { * @channels: Available operating channels for the group * @addr: Peer address * @freq: Frequency (in MHz) indicated during invitation or 0 + * @peer_oper_freq: Operating frequency (in MHz) advertized by the peer + * during invitation or 0 * * This callback is used to indicate result of an Invitation procedure * started with a call to p2p_invite(). The indicated status code is @@ -773,7 +775,7 @@ struct p2p_config { */ void (*invitation_result)(void *ctx, int status, const u8 *bssid, const struct p2p_channels *channels, - const u8 *addr, int freq); + const u8 *addr, int freq, int peer_oper_freq); /** * go_connected - Check whether we are connected to a GO @@ -803,6 +805,14 @@ struct p2p_config { * or 0 if not. */ int (*is_concurrent_session_active)(void *ctx); + + /** + * is_p2p_in_progress - Check whether P2P operation is in progress + * @ctx: Callback context from cb_ctx + * Returns: 1 if P2P operation (e.g., group formation) is in progress + * or 0 if not. + */ + int (*is_p2p_in_progress)(void *ctx); }; diff --git a/src/p2p/p2p_build.c b/src/p2p/p2p_build.c index 664fade..e9b683d 100644 --- a/src/p2p/p2p_build.c +++ b/src/p2p/p2p_build.c @@ -17,8 +17,7 @@ void p2p_buf_add_action_hdr(struct wpabuf *buf, u8 subtype, u8 dialog_token) { wpabuf_put_u8(buf, WLAN_ACTION_VENDOR_SPECIFIC); - wpabuf_put_be24(buf, OUI_WFA); - wpabuf_put_u8(buf, P2P_OUI_TYPE); + wpabuf_put_be32(buf, P2P_IE_VENDOR_TYPE); wpabuf_put_u8(buf, subtype); /* OUI Subtype */ wpabuf_put_u8(buf, dialog_token); @@ -31,8 +30,7 @@ void p2p_buf_add_public_action_hdr(struct wpabuf *buf, u8 subtype, { wpabuf_put_u8(buf, WLAN_ACTION_PUBLIC); wpabuf_put_u8(buf, WLAN_PA_VENDOR_SPECIFIC); - wpabuf_put_be24(buf, OUI_WFA); - wpabuf_put_u8(buf, P2P_OUI_TYPE); + wpabuf_put_be32(buf, P2P_IE_VENDOR_TYPE); wpabuf_put_u8(buf, subtype); /* OUI Subtype */ wpabuf_put_u8(buf, dialog_token); @@ -47,8 +45,7 @@ u8 * p2p_buf_add_ie_hdr(struct wpabuf *buf) /* P2P IE header */ wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); len = wpabuf_put(buf, 1); /* IE length to be filled */ - wpabuf_put_be24(buf, OUI_WFA); - wpabuf_put_u8(buf, P2P_OUI_TYPE); + wpabuf_put_be32(buf, P2P_IE_VENDOR_TYPE); wpa_printf(MSG_DEBUG, "P2P: * P2P IE header"); return len; } diff --git a/src/p2p/p2p_go_neg.c b/src/p2p/p2p_go_neg.c index e28f93e..f24fe23 100644 --- a/src/p2p/p2p_go_neg.c +++ b/src/p2p/p2p_go_neg.c @@ -594,12 +594,32 @@ void p2p_process_go_neg_req(struct p2p_data *p2p, const u8 *sa, if (msg.status && *msg.status) { p2p_dbg(p2p, "Unexpected Status attribute (%d) in GO Negotiation Request", *msg.status); + if (dev && p2p->go_neg_peer == dev && + *msg.status == P2P_SC_FAIL_REJECTED_BY_USER) { + /* + * This mechanism for using Status attribute in GO + * Negotiation Request is not compliant with the P2P + * specification, but some deployed devices use it to + * indicate rejection of GO Negotiation in a case where + * they have sent out GO Negotiation Response with + * status 1. The P2P specification explicitly disallows + * this. To avoid unnecessary interoperability issues + * and extra frames, mark the pending negotiation as + * failed and do not reply to this GO Negotiation + * Request frame. + */ + p2p->cfg->send_action_done(p2p->cfg->cb_ctx); + p2p_go_neg_failed(p2p, dev, *msg.status); + p2p_parse_free(&msg); + return; + } goto fail; } if (dev == NULL) dev = p2p_add_dev_from_go_neg_req(p2p, sa, &msg); - else if (dev->flags & P2P_DEV_PROBE_REQ_ONLY) + else if ((dev->flags & P2P_DEV_PROBE_REQ_ONLY) || + !(dev->flags & P2P_DEV_REPORTED)) p2p_add_dev_info(p2p, sa, dev, &msg); else if (!dev->listen_freq && !dev->oper_freq) { /* @@ -876,7 +896,6 @@ void p2p_process_go_neg_resp(struct p2p_data *p2p, const u8 *sa, const u8 *data, size_t len, int rx_freq) { struct p2p_device *dev; - struct wpabuf *conf; int go = -1; struct p2p_message msg; u8 status = P2P_SC_SUCCESS; @@ -920,7 +939,7 @@ void p2p_process_go_neg_resp(struct p2p_data *p2p, const u8 *sa, if (*msg.status == P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE) { p2p_dbg(p2p, "Wait for the peer to become ready for GO Negotiation"); dev->flags |= P2P_DEV_NOT_YET_READY; - dev->wait_count = 0; + os_get_reltime(&dev->go_neg_wait_started); p2p_set_state(p2p, P2P_WAIT_PEER_IDLE); p2p_set_timeout(p2p, 0, 0); } else { @@ -1081,10 +1100,13 @@ void p2p_process_go_neg_resp(struct p2p_data *p2p, const u8 *sa, os_memcpy(dev->intended_addr, msg.intended_addr, ETH_ALEN); fail: - conf = p2p_build_go_neg_conf(p2p, dev, msg.dialog_token, status, - msg.operating_channel, go); + /* Store GO Negotiation Confirmation to allow retransmission */ + wpabuf_free(dev->go_neg_conf); + dev->go_neg_conf = p2p_build_go_neg_conf(p2p, dev, msg.dialog_token, + status, msg.operating_channel, + go); p2p_parse_free(&msg); - if (conf == NULL) + if (dev->go_neg_conf == NULL) return; p2p_dbg(p2p, "Sending GO Negotiation Confirm"); if (status == P2P_SC_SUCCESS) { @@ -1096,13 +1118,18 @@ fail: freq = rx_freq; else freq = dev->listen_freq; + + dev->go_neg_conf_freq = freq; + dev->go_neg_conf_sent = 0; + if (p2p_send_action(p2p, freq, sa, p2p->cfg->dev_addr, sa, - wpabuf_head(conf), wpabuf_len(conf), 200) < 0) { + wpabuf_head(dev->go_neg_conf), + wpabuf_len(dev->go_neg_conf), 200) < 0) { p2p_dbg(p2p, "Failed to send Action frame"); p2p_go_neg_failed(p2p, dev, -1); p2p->cfg->send_action_done(p2p->cfg->cb_ctx); - } - wpabuf_free(conf); + } else + dev->go_neg_conf_sent++; if (status != P2P_SC_SUCCESS) { p2p_dbg(p2p, "GO Negotiation failed"); p2p_go_neg_failed(p2p, dev, status); diff --git a/src/p2p/p2p_i.h b/src/p2p/p2p_i.h index 6ebaa84..65ff9ef 100644 --- a/src/p2p/p2p_i.h +++ b/src/p2p/p2p_i.h @@ -12,6 +12,8 @@ #include "utils/list.h" #include "p2p.h" +#define P2P_GO_NEG_CNF_MAX_RETRY_COUNT 1 + enum p2p_role_indication; enum p2p_go_state { @@ -81,8 +83,6 @@ struct p2p_device { #define P2P_DEV_PROBE_REQ_ONLY BIT(0) #define P2P_DEV_REPORTED BIT(1) #define P2P_DEV_NOT_YET_READY BIT(2) -#define P2P_DEV_SD_INFO BIT(3) -#define P2P_DEV_SD_SCHEDULE BIT(4) #define P2P_DEV_PD_PEER_DISPLAY BIT(5) #define P2P_DEV_PD_PEER_KEYPAD BIT(6) #define P2P_DEV_USER_REJECTED BIT(7) @@ -97,9 +97,11 @@ struct p2p_device { #define P2P_DEV_PREFER_PERSISTENT_RECONN BIT(16) #define P2P_DEV_PD_BEFORE_GO_NEG BIT(17) #define P2P_DEV_NO_PREF_CHAN BIT(18) +#define P2P_DEV_WAIT_INV_REQ_ACK BIT(19) unsigned int flags; int status; /* enum p2p_status_code */ + struct os_reltime go_neg_wait_started; unsigned int wait_count; unsigned int connect_reqs; unsigned int invitation_reqs; @@ -109,6 +111,23 @@ struct p2p_device { u8 go_timeout; u8 client_timeout; + + /** + * go_neg_conf_sent - Number of GO Negotiation Confirmation retries + */ + u8 go_neg_conf_sent; + + /** + * freq - Frquency on which the GO Negotiation Confirmation is sent + */ + int go_neg_conf_freq; + + /** + * go_neg_conf - GO Negotiation Confirmation frame + */ + struct wpabuf *go_neg_conf; + + int sd_pending_bcast_queries; }; struct p2p_sd_query { @@ -255,6 +274,12 @@ struct p2p_data { */ struct p2p_sd_query *sd_query; + /** + * num_p2p_sd_queries - Total number of broadcast SD queries present in + * the list + */ + int num_p2p_sd_queries; + /* GO Negotiation data */ /** @@ -383,6 +408,7 @@ struct p2p_data { u8 after_scan_peer[ETH_ALEN]; struct p2p_pending_action_tx *after_scan_tx; unsigned int after_scan_tx_in_progress:1; + unsigned int send_action_in_progress:1; /* Requested device types for find/search */ unsigned int num_req_dev_types; @@ -585,6 +611,8 @@ void p2p_channels_dump(struct p2p_data *p2p, const char *title, const struct p2p_channels *chan); int p2p_channel_select(struct p2p_channels *chans, const int *classes, u8 *op_class, u8 *op_channel); +int p2p_channel_random_social(struct p2p_channels *chans, u8 *op_class, + u8 *op_channel); /* p2p_parse.c */ int p2p_parse_p2p_ie(const struct wpabuf *buf, struct p2p_message *msg); diff --git a/src/p2p/p2p_invitation.c b/src/p2p/p2p_invitation.c index 98cfb33..a36898e 100644 --- a/src/p2p/p2p_invitation.c +++ b/src/p2p/p2p_invitation.c @@ -227,8 +227,11 @@ void p2p_process_invitation_req(struct p2p_data *p2p, const u8 *sa, goto fail; } + p2p_channels_dump(p2p, "own channels", &p2p->cfg->channels); + p2p_channels_dump(p2p, "peer channels", &dev->channels); p2p_channels_intersect(&p2p->cfg->channels, &dev->channels, &intersection); + p2p_channels_dump(p2p, "intersection", &intersection); if (p2p->cfg->invitation_process) { status = p2p->cfg->invitation_process( @@ -288,7 +291,9 @@ void p2p_process_invitation_req(struct p2p_data *p2p, const u8 *sa, } } - if (!p2p_channels_includes(&intersection, p2p->op_reg_class, + /* Reselect the channel only for the case of the GO */ + if (go && + !p2p_channels_includes(&intersection, p2p->op_reg_class, p2p->op_channel)) { p2p_dbg(p2p, "Initially selected channel (op_class %d channel %d) not in channel intersection - try to reselect", p2p->op_reg_class, p2p->op_channel); @@ -303,7 +308,7 @@ void p2p_process_invitation_req(struct p2p_data *p2p, const u8 *sa, status = P2P_SC_FAIL_NO_COMMON_CHANNELS; goto fail; } - } else if (!(dev->flags & P2P_DEV_FORCE_FREQ) && + } else if (go && !(dev->flags & P2P_DEV_FORCE_FREQ) && !p2p->cfg->cfg_op_channel) { p2p_dbg(p2p, "Try to reselect channel selection with peer information received; previously selected op_class %u channel %u", p2p->op_reg_class, p2p->op_channel); @@ -359,12 +364,17 @@ fail: p2p->inv_group_bssid_ptr = p2p->inv_group_bssid; } else p2p->inv_group_bssid_ptr = NULL; - if (msg.group_id_len - ETH_ALEN <= 32) { - os_memcpy(p2p->inv_ssid, msg.group_id + ETH_ALEN, - msg.group_id_len - ETH_ALEN); - p2p->inv_ssid_len = msg.group_id_len - ETH_ALEN; + if (msg.group_id) { + if (msg.group_id_len - ETH_ALEN <= 32) { + os_memcpy(p2p->inv_ssid, msg.group_id + ETH_ALEN, + msg.group_id_len - ETH_ALEN); + p2p->inv_ssid_len = msg.group_id_len - ETH_ALEN; + } + os_memcpy(p2p->inv_go_dev_addr, msg.group_id, ETH_ALEN); + } else { + p2p->inv_ssid_len = 0; + os_memset(p2p->inv_go_dev_addr, 0, ETH_ALEN); } - os_memcpy(p2p->inv_go_dev_addr, msg.group_id, ETH_ALEN); p2p->inv_status = status; p2p->inv_op_freq = op_freq; @@ -439,13 +449,23 @@ void p2p_process_invitation_resp(struct p2p_data *p2p, const u8 *sa, } if (p2p->cfg->invitation_result) { + int peer_oper_freq = 0; int freq = p2p_channel_to_freq(p2p->op_reg_class, p2p->op_channel); if (freq < 0) freq = 0; + + if (msg.operating_channel) { + peer_oper_freq = p2p_channel_to_freq( + msg.operating_channel[3], + msg.operating_channel[4]); + if (peer_oper_freq < 0) + peer_oper_freq = 0; + } + p2p->cfg->invitation_result(p2p->cfg->cb_ctx, *msg.status, msg.group_bssid, channels, sa, - freq); + freq, peer_oper_freq); } p2p_parse_free(&msg); @@ -488,6 +508,8 @@ int p2p_invite_send(struct p2p_data *p2p, struct p2p_device *dev, p2p_dbg(p2p, "Failed to send Action frame"); /* Use P2P find to recover and retry */ p2p_set_timeout(p2p, 0, 0); + } else { + dev->flags |= P2P_DEV_WAIT_INV_REQ_ACK; } wpabuf_free(req); @@ -505,6 +527,9 @@ void p2p_invitation_req_cb(struct p2p_data *p2p, int success) return; } + if (success) + p2p->invite_peer->flags &= ~P2P_DEV_WAIT_INV_REQ_ACK; + /* * Use P2P find, if needed, to find the other device from its listen * channel. diff --git a/src/p2p/p2p_pd.c b/src/p2p/p2p_pd.c index 409405f..68d79d2 100644 --- a/src/p2p/p2p_pd.c +++ b/src/p2p/p2p_pd.c @@ -224,7 +224,8 @@ out: p2p->cfg->dev_addr, wpabuf_head(resp), wpabuf_len(resp), 200) < 0) { p2p_dbg(p2p, "Failed to send Action frame"); - } + } else + p2p->send_action_in_progress = 1; wpabuf_free(resp); diff --git a/src/p2p/p2p_sd.c b/src/p2p/p2p_sd.c index 0e0c7f1..9df834c 100644 --- a/src/p2p/p2p_sd.c +++ b/src/p2p/p2p_sd.c @@ -52,6 +52,7 @@ struct p2p_sd_query * p2p_pending_sd_req(struct p2p_data *p2p, { struct p2p_sd_query *q; int wsd = 0; + int count = 0; if (!(dev->info.dev_capab & P2P_DEV_CAPAB_SERVICE_DISCOVERY)) return NULL; /* peer does not support SD */ @@ -64,8 +65,19 @@ struct p2p_sd_query * p2p_pending_sd_req(struct p2p_data *p2p, /* Use WSD only if the peer indicates support or it */ if (q->wsd && !wsd) continue; - if (q->for_all_peers && !(dev->flags & P2P_DEV_SD_INFO)) - return q; + /* if the query is a broadcast query */ + if (q->for_all_peers) { + /* + * check if there are any broadcast queries pending for + * this device + */ + if (dev->sd_pending_bcast_queries <= 0) + return NULL; + /* query number that needs to be send to the device */ + if (count == dev->sd_pending_bcast_queries - 1) + return q; + count++; + } if (!q->for_all_peers && os_memcmp(q->peer, dev->info.p2p_device_addr, ETH_ALEN) == 0) @@ -76,14 +88,37 @@ struct p2p_sd_query * p2p_pending_sd_req(struct p2p_data *p2p, } +static void p2p_decrease_sd_bc_queries(struct p2p_data *p2p, int query_number) +{ + struct p2p_device *dev; + + p2p->num_p2p_sd_queries--; + dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) { + if (query_number <= dev->sd_pending_bcast_queries - 1) { + /* + * Query not yet sent to the device and it is to be + * removed, so update the pending count. + */ + dev->sd_pending_bcast_queries--; + } + } +} + + static int p2p_unlink_sd_query(struct p2p_data *p2p, struct p2p_sd_query *query) { struct p2p_sd_query *q, *prev; + int query_number = 0; + q = p2p->sd_queries; prev = NULL; while (q) { if (q == query) { + /* If the query is a broadcast query, decrease one from + * all the devices */ + if (query->for_all_peers) + p2p_decrease_sd_bc_queries(p2p, query_number); if (prev) prev->next = q->next; else @@ -92,6 +127,8 @@ static int p2p_unlink_sd_query(struct p2p_data *p2p, p2p->sd_query = NULL; return 1; } + if (q->for_all_peers) + query_number++; prev = q; q = q->next; } @@ -118,6 +155,7 @@ void p2p_free_sd_queries(struct p2p_data *p2p) q = q->next; p2p_free_sd_query(prev); } + p2p->num_p2p_sd_queries = 0; } @@ -133,8 +171,7 @@ static struct wpabuf * p2p_build_sd_query(u16 update_indic, /* ANQP Query Request Frame */ len_pos = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); - wpabuf_put_be24(buf, OUI_WFA); - wpabuf_put_u8(buf, P2P_OUI_TYPE); + wpabuf_put_be32(buf, P2P_IE_VENDOR_TYPE); wpabuf_put_le16(buf, update_indic); /* Service Update Indicator */ wpabuf_put_buf(buf, tlvs); gas_anqp_set_element_len(buf, len_pos); @@ -180,8 +217,7 @@ static struct wpabuf * p2p_build_sd_response(u8 dialog_token, u16 status_code, if (tlvs) { /* ANQP Query Response Frame */ len_pos = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); - wpabuf_put_be24(buf, OUI_WFA); - wpabuf_put_u8(buf, P2P_OUI_TYPE); + wpabuf_put_be32(buf, P2P_IE_VENDOR_TYPE); /* Service Update Indicator */ wpabuf_put_le16(buf, update_indic); wpabuf_put_buf(buf, tlvs); @@ -212,8 +248,7 @@ static struct wpabuf * p2p_build_gas_comeback_resp(u8 dialog_token, /* ANQP Query Response Frame */ wpabuf_put_le16(buf, ANQP_VENDOR_SPECIFIC); /* Info ID */ wpabuf_put_le16(buf, 3 + 1 + 2 + total_len); - wpabuf_put_be24(buf, OUI_WFA); - wpabuf_put_u8(buf, P2P_OUI_TYPE); + wpabuf_put_be32(buf, P2P_IE_VENDOR_TYPE); /* Service Update Indicator */ wpabuf_put_le16(buf, update_indic); } @@ -262,6 +297,16 @@ int p2p_start_sd(struct p2p_data *p2p, struct p2p_device *dev) ret = -1; } + /* Update the pending broadcast SD query count for this device */ + dev->sd_pending_bcast_queries--; + + /* + * If there are no pending broadcast queries for this device, mark it as + * done (-1). + */ + if (dev->sd_pending_bcast_queries == 0) + dev->sd_pending_bcast_queries = -1; + wpabuf_free(req); return ret; @@ -345,17 +390,12 @@ void p2p_rx_gas_initial_req(struct p2p_data *p2p, const u8 *sa, return; } - if (WPA_GET_BE24(pos) != OUI_WFA) { - p2p_dbg(p2p, "Unsupported ANQP OUI %06x", WPA_GET_BE24(pos)); + if (WPA_GET_BE32(pos) != P2P_IE_VENDOR_TYPE) { + p2p_dbg(p2p, "Unsupported ANQP vendor OUI-type %08x", + WPA_GET_BE32(pos)); return; } - pos += 3; - - if (*pos != P2P_OUI_TYPE) { - p2p_dbg(p2p, "Unsupported ANQP vendor type %u", *pos); - return; - } - pos++; + pos += 4; if (pos + 2 > end) return; @@ -523,17 +563,12 @@ void p2p_rx_gas_initial_resp(struct p2p_data *p2p, const u8 *sa, return; } - if (WPA_GET_BE24(pos) != OUI_WFA) { - p2p_dbg(p2p, "Unsupported ANQP OUI %06x", WPA_GET_BE24(pos)); - return; - } - pos += 3; - - if (*pos != P2P_OUI_TYPE) { - p2p_dbg(p2p, "Unsupported ANQP vendor type %u", *pos); + if (WPA_GET_BE32(pos) != P2P_IE_VENDOR_TYPE) { + p2p_dbg(p2p, "Unsupported ANQP vendor OUI-type %08x", + WPA_GET_BE32(pos)); return; } - pos++; + pos += 4; if (pos + 2 > end) return; @@ -541,8 +576,6 @@ void p2p_rx_gas_initial_resp(struct p2p_data *p2p, const u8 *sa, p2p_dbg(p2p, "Service Update Indicator: %u", update_indic); pos += 2; - p2p->sd_peer->flags |= P2P_DEV_SD_INFO; - p2p->sd_peer->flags &= ~P2P_DEV_SD_SCHEDULE; p2p->sd_peer = NULL; if (p2p->sd_query) { @@ -749,17 +782,12 @@ void p2p_rx_gas_comeback_resp(struct p2p_data *p2p, const u8 *sa, if (pos + 4 > end) return; - if (WPA_GET_BE24(pos) != OUI_WFA) { - p2p_dbg(p2p, "Unsupported ANQP OUI %06x", WPA_GET_BE24(pos)); + if (WPA_GET_BE32(pos) != P2P_IE_VENDOR_TYPE) { + p2p_dbg(p2p, "Unsupported ANQP vendor OUI-type %08x", + WPA_GET_BE32(pos)); return; } - pos += 3; - - if (*pos != P2P_OUI_TYPE) { - p2p_dbg(p2p, "Unsupported ANQP vendor type %u", *pos); - return; - } - pos++; + pos += 4; if (pos + 2 > end) return; @@ -787,8 +815,6 @@ skip_nqp_header: return; } - p2p->sd_peer->flags |= P2P_DEV_SD_INFO; - p2p->sd_peer->flags &= ~P2P_DEV_SD_SCHEDULE; p2p->sd_peer = NULL; if (p2p->sd_query) { @@ -841,8 +867,16 @@ void * p2p_sd_request(struct p2p_data *p2p, const u8 *dst, if (dst == NULL) { struct p2p_device *dev; - dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) - dev->flags &= ~P2P_DEV_SD_INFO; + + p2p->num_p2p_sd_queries++; + + /* Update all the devices for the newly added broadcast query */ + dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) { + if (dev->sd_pending_bcast_queries <= 0) + dev->sd_pending_bcast_queries = 1; + else + dev->sd_pending_bcast_queries++; + } } return q; diff --git a/src/p2p/p2p_utils.c b/src/p2p/p2p_utils.c index 161a402..ac19064 100644 --- a/src/p2p/p2p_utils.c +++ b/src/p2p/p2p_utils.c @@ -98,6 +98,10 @@ int p2p_channel_to_freq(int op_class, int channel) if (channel < 36 || channel > 161) return -1; return 5000 + 5 * channel; + case 180: /* 60 GHz band, channels 1..4 */ + if (channel < 1 || channel > 4) + return -1; + return 56160 + 2160 * channel; } return -1; } @@ -384,23 +388,14 @@ unsigned int p2p_get_pref_freq(struct p2p_data *p2p, const struct p2p_channels *channels) { unsigned int i; - int freq = 0; - - if (channels == NULL) { - if (p2p->cfg->num_pref_chan) { - freq = p2p_channel_to_freq( - p2p->cfg->pref_chan[0].op_class, - p2p->cfg->pref_chan[0].chan); - if (freq < 0) - freq = 0; - } - return freq; - } + int freq; for (i = 0; p2p->cfg->pref_chan && i < p2p->cfg->num_pref_chan; i++) { freq = p2p_channel_to_freq(p2p->cfg->pref_chan[i].op_class, p2p->cfg->pref_chan[i].chan); - if (p2p_channels_includes_freq(channels, freq)) + if (freq <= 0) + continue; + if (!channels || p2p_channels_includes_freq(channels, freq)) return freq; } @@ -441,31 +436,65 @@ void p2p_channels_dump(struct p2p_data *p2p, const char *title, } +static u8 p2p_channel_pick_random(const u8 *channels, unsigned int num_channels) +{ + unsigned int r; + os_get_random((u8 *) &r, sizeof(r)); + r %= num_channels; + return channels[r]; +} + + int p2p_channel_select(struct p2p_channels *chans, const int *classes, u8 *op_class, u8 *op_channel) { - unsigned int i, j, r; + unsigned int i, j; - for (j = 0; classes[j]; j++) { + for (j = 0; classes == NULL || classes[j]; j++) { for (i = 0; i < chans->reg_classes; i++) { struct p2p_reg_class *c = &chans->reg_class[i]; if (c->channels == 0) continue; - if (c->reg_class == classes[j]) { + if (classes == NULL || c->reg_class == classes[j]) { /* * Pick one of the available channels in the * operating class at random. */ - os_get_random((u8 *) &r, sizeof(r)); - r %= c->channels; *op_class = c->reg_class; - *op_channel = c->channel[r]; + *op_channel = p2p_channel_pick_random( + c->channel, c->channels); return 0; } } + if (classes == NULL) + break; } return -1; } + + +int p2p_channel_random_social(struct p2p_channels *chans, u8 *op_class, + u8 *op_channel) +{ + u8 chan[3]; + unsigned int num_channels = 0; + + /* Try to find available social channels from 2.4 GHz */ + if (p2p_channels_includes(chans, 81, 1)) + chan[num_channels++] = 1; + if (p2p_channels_includes(chans, 81, 6)) + chan[num_channels++] = 6; + if (p2p_channels_includes(chans, 81, 11)) + chan[num_channels++] = 11; + + if (num_channels == 0) + return -1; + + *op_class = 81; + *op_channel = p2p_channel_pick_random(chan, num_channels); + + return 0; +} diff --git a/src/pae/Makefile b/src/pae/Makefile new file mode 100644 index 0000000..9c41962 --- /dev/null +++ b/src/pae/Makefile @@ -0,0 +1,8 @@ +all: + @echo Nothing to be made. + +clean: + rm -f *~ *.o *.d + +install: + @echo Nothing to be made. diff --git a/src/pae/ieee802_1x_cp.c b/src/pae/ieee802_1x_cp.c new file mode 100644 index 0000000..cf43c59 --- /dev/null +++ b/src/pae/ieee802_1x_cp.c @@ -0,0 +1,744 @@ +/* + * IEEE 802.1X-2010 Controlled Port of PAE state machine - CP state machine + * 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 "utils/includes.h" + +#include "utils/common.h" +#include "utils/eloop.h" +#include "common/defs.h" +#include "common/ieee802_1x_defs.h" +#include "utils/state_machine.h" +#include "ieee802_1x_kay.h" +#include "ieee802_1x_secy_ops.h" +#include "pae/ieee802_1x_cp.h" + +#define STATE_MACHINE_DATA struct ieee802_1x_cp_sm +#define STATE_MACHINE_DEBUG_PREFIX "CP" + +static u8 default_cs_id[] = CS_ID_GCM_AES_128; + +/* The variable defined in clause 12 in IEEE Std 802.1X-2010 */ +enum connect_type { PENDING, UNAUTHENTICATED, AUTHENTICATED, SECURE }; + +struct ieee802_1x_cp_sm { + enum cp_states { + CP_BEGIN, CP_INIT, CP_CHANGE, CP_ALLOWED, CP_AUTHENTICATED, + CP_SECURED, CP_RECEIVE, CP_RECEIVING, CP_READY, CP_TRANSMIT, + CP_TRANSMITTING, CP_ABANDON, CP_RETIRE + } CP_state; + Boolean changed; + + /* CP -> Client */ + Boolean port_valid; + + /* Logon -> CP */ + enum connect_type connect; + u8 *authorization_data; + + /* KaY -> CP */ + Boolean chgd_server; /* clear by CP */ + Boolean elected_self; + u8 *authorization_data1; + enum confidentiality_offset cipher_offset; + u8 *cipher_suite; + Boolean new_sak; /* clear by CP */ + struct ieee802_1x_mka_ki distributed_ki; + u8 distributed_an; + Boolean using_receive_sas; + Boolean all_receiving; + Boolean server_transmitting; + Boolean using_transmit_sa; + + /* CP -> KaY */ + struct ieee802_1x_mka_ki *lki; + u8 lan; + Boolean ltx; + Boolean lrx; + struct ieee802_1x_mka_ki *oki; + u8 oan; + Boolean otx; + Boolean orx; + + /* CP -> SecY */ + Boolean protect_frames; + enum validate_frames validate_frames; + + Boolean replay_protect; + u32 replay_window; + + u8 *current_cipher_suite; + enum confidentiality_offset confidentiality_offset; + Boolean controlled_port_enabled; + + /* SecY -> CP */ + Boolean port_enabled; /* SecY->CP */ + + /* private */ + u32 transmit_when; + u32 transmit_delay; + u32 retire_when; + u32 retire_delay; + + /* not defined IEEE Std 802.1X-2010 */ + struct ieee802_1x_kay *kay; +}; + +static void ieee802_1x_cp_retire_when_timeout(void *eloop_ctx, + void *timeout_ctx); +static void ieee802_1x_cp_transmit_when_timeout(void *eloop_ctx, + void *timeout_ctx); + + +static int changed_cipher(struct ieee802_1x_cp_sm *sm) +{ + return sm->confidentiality_offset != sm->cipher_offset || + os_memcmp(sm->current_cipher_suite, sm->cipher_suite, + CS_ID_LEN) != 0; +} + + +static int changed_connect(struct ieee802_1x_cp_sm *sm) +{ + return sm->connect != SECURE || sm->chgd_server || changed_cipher(sm); +} + + +SM_STATE(CP, INIT) +{ + SM_ENTRY(CP, INIT); + + sm->controlled_port_enabled = FALSE; + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + + sm->port_valid = FALSE; + + os_free(sm->lki); + sm->lki = NULL; + sm->ltx = FALSE; + sm->lrx = FALSE; + + os_free(sm->oki); + sm->oki = NULL; + sm->otx = FALSE; + sm->orx = FALSE; + + sm->port_enabled = TRUE; + sm->chgd_server = FALSE; +} + + +SM_STATE(CP, CHANGE) +{ + SM_ENTRY(CP, CHANGE); + + sm->port_valid = FALSE; + sm->controlled_port_enabled = FALSE; + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + + if (sm->lki) + ieee802_1x_kay_delete_sas(sm->kay, sm->lki); + if (sm->oki) + ieee802_1x_kay_delete_sas(sm->kay, sm->oki); +} + + +SM_STATE(CP, ALLOWED) +{ + SM_ENTRY(CP, ALLOWED); + + sm->protect_frames = FALSE; + sm->replay_protect = FALSE; + sm->validate_frames = Checked; + + sm->port_valid = FALSE; + sm->controlled_port_enabled = TRUE; + + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + secy_cp_control_protect_frames(sm->kay, sm->protect_frames); + secy_cp_control_validate_frames(sm->kay, sm->validate_frames); + secy_cp_control_replay(sm->kay, sm->replay_protect, sm->replay_window); +} + + +SM_STATE(CP, AUTHENTICATED) +{ + SM_ENTRY(CP, AUTHENTICATED); + + sm->protect_frames = FALSE; + sm->replay_protect = FALSE; + sm->validate_frames = Checked; + + sm->port_valid = FALSE; + sm->controlled_port_enabled = TRUE; + + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + secy_cp_control_protect_frames(sm->kay, sm->protect_frames); + secy_cp_control_validate_frames(sm->kay, sm->validate_frames); + secy_cp_control_replay(sm->kay, sm->replay_protect, sm->replay_window); +} + + +SM_STATE(CP, SECURED) +{ + struct ieee802_1x_cp_conf conf; + + SM_ENTRY(CP, SECURED); + + sm->chgd_server = FALSE; + + ieee802_1x_kay_cp_conf(sm->kay, &conf); + sm->protect_frames = conf.protect; + sm->replay_protect = conf.replay_protect; + sm->validate_frames = conf.validate; + + /* NOTE: now no other than default cipher suiter(AES-GCM-128) */ + os_memcpy(sm->current_cipher_suite, sm->cipher_suite, CS_ID_LEN); + secy_cp_control_current_cipher_suite(sm->kay, sm->current_cipher_suite, + CS_ID_LEN); + + sm->confidentiality_offset = sm->cipher_offset; + + sm->port_valid = TRUE; + + secy_cp_control_confidentiality_offset(sm->kay, + sm->confidentiality_offset); + secy_cp_control_protect_frames(sm->kay, sm->protect_frames); + secy_cp_control_validate_frames(sm->kay, sm->validate_frames); + secy_cp_control_replay(sm->kay, sm->replay_protect, sm->replay_window); +} + + +SM_STATE(CP, RECEIVE) +{ + SM_ENTRY(CP, RECEIVE); + /* RECEIVE state machine not keep with Figure 12-2 in + * IEEE Std 802.1X-2010 */ + sm->oki = sm->lki; + sm->oan = sm->lan; + sm->otx = sm->ltx; + sm->orx = sm->lrx; + ieee802_1x_kay_set_old_sa_attr(sm->kay, sm->oki, sm->oan, + sm->otx, sm->orx); + + sm->lki = os_malloc(sizeof(*sm->lki)); + if (!sm->lki) { + wpa_printf(MSG_ERROR, "CP-%s: Out of memory", __func__); + return; + } + os_memcpy(sm->lki, &sm->distributed_ki, sizeof(*sm->lki)); + sm->lan = sm->distributed_an; + sm->ltx = FALSE; + sm->lrx = FALSE; + ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, + sm->ltx, sm->lrx); + ieee802_1x_kay_create_sas(sm->kay, sm->lki); + ieee802_1x_kay_enable_rx_sas(sm->kay, sm->lki); + sm->new_sak = FALSE; + sm->all_receiving = FALSE; +} + + +SM_STATE(CP, RECEIVING) +{ + SM_ENTRY(CP, RECEIVING); + + sm->lrx = TRUE; + ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, + sm->ltx, sm->lrx); + sm->transmit_when = sm->transmit_delay; + eloop_cancel_timeout(ieee802_1x_cp_transmit_when_timeout, sm, NULL); + eloop_register_timeout(sm->transmit_when / 1000, 0, + ieee802_1x_cp_transmit_when_timeout, sm, NULL); + /* the electedSelf have been set before CP entering to RECEIVING + * but the CP will transmit from RECEIVING to READY under + * the !electedSelf when KaY is not key server */ + ieee802_1x_cp_sm_step(sm); + sm->using_receive_sas = FALSE; + sm->server_transmitting = FALSE; +} + + +SM_STATE(CP, READY) +{ + SM_ENTRY(CP, READY); + + ieee802_1x_kay_enable_new_info(sm->kay); +} + + +SM_STATE(CP, TRANSMIT) +{ + SM_ENTRY(CP, TRANSMIT); + + sm->controlled_port_enabled = TRUE; + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + sm->ltx = TRUE; + ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, + sm->ltx, sm->lrx); + ieee802_1x_kay_enable_tx_sas(sm->kay, sm->lki); + sm->all_receiving = FALSE; + sm->server_transmitting = FALSE; +} + + +SM_STATE(CP, TRANSMITTING) +{ + SM_ENTRY(CP, TRANSMITTING); + sm->retire_when = sm->orx ? sm->retire_delay : 0; + sm->otx = FALSE; + ieee802_1x_kay_set_old_sa_attr(sm->kay, sm->oki, sm->oan, + sm->otx, sm->orx); + ieee802_1x_kay_enable_new_info(sm->kay); + eloop_cancel_timeout(ieee802_1x_cp_retire_when_timeout, sm, NULL); + eloop_register_timeout(sm->retire_when / 1000, 0, + ieee802_1x_cp_retire_when_timeout, sm, NULL); + sm->using_transmit_sa = FALSE; +} + + +SM_STATE(CP, ABANDON) +{ + SM_ENTRY(CP, ABANDON); + sm->lrx = FALSE; + ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, + sm->ltx, sm->lrx); + ieee802_1x_kay_delete_sas(sm->kay, sm->lki); + + os_free(sm->lki); + sm->lki = NULL; + ieee802_1x_kay_set_latest_sa_attr(sm->kay, sm->lki, sm->lan, + sm->ltx, sm->lrx); + sm->new_sak = FALSE; +} + + +SM_STATE(CP, RETIRE) +{ + SM_ENTRY(CP, RETIRE); + /* RETIRE state machine not keep with Figure 12-2 in + * IEEE Std 802.1X-2010 */ + os_free(sm->oki); + sm->oki = NULL; + sm->orx = FALSE; + sm->otx = FALSE; + ieee802_1x_kay_set_old_sa_attr(sm->kay, sm->oki, sm->oan, + sm->otx, sm->orx); +} + + +/** + * CP state machine handler entry + */ +SM_STEP(CP) +{ + if (!sm->port_enabled) + SM_ENTER(CP, INIT); + + switch (sm->CP_state) { + case CP_BEGIN: + SM_ENTER(CP, INIT); + break; + + case CP_INIT: + SM_ENTER(CP, CHANGE); + break; + + case CP_CHANGE: + if (sm->connect == UNAUTHENTICATED) + SM_ENTER(CP, ALLOWED); + else if (sm->connect == AUTHENTICATED) + SM_ENTER(CP, AUTHENTICATED); + else if (sm->connect == SECURE) + SM_ENTER(CP, SECURED); + break; + + case CP_ALLOWED: + if (sm->connect != UNAUTHENTICATED) + SM_ENTER(CP, CHANGE); + break; + + case CP_AUTHENTICATED: + if (sm->connect != AUTHENTICATED) + SM_ENTER(CP, CHANGE); + break; + + case CP_SECURED: + if (changed_connect(sm)) + SM_ENTER(CP, CHANGE); + else if (sm->new_sak) + SM_ENTER(CP, RECEIVE); + break; + + case CP_RECEIVE: + if (sm->using_receive_sas) + SM_ENTER(CP, RECEIVING); + break; + + case CP_RECEIVING: + if (sm->new_sak || changed_connect(sm)) + SM_ENTER(CP, ABANDON); + if (!sm->elected_self) + SM_ENTER(CP, READY); + if (sm->elected_self && + (sm->all_receiving || !sm->transmit_when)) + SM_ENTER(CP, TRANSMIT); + break; + + case CP_TRANSMIT: + if (sm->using_transmit_sa) + SM_ENTER(CP, TRANSMITTING); + break; + + case CP_TRANSMITTING: + if (!sm->retire_when || changed_connect(sm)) + SM_ENTER(CP, RETIRE); + break; + + case CP_RETIRE: + if (changed_connect(sm)) + SM_ENTER(CP, CHANGE); + else if (sm->new_sak) + SM_ENTER(CP, RECEIVE); + break; + + case CP_READY: + if (sm->new_sak || changed_connect(sm)) + SM_ENTER(CP, RECEIVE); + if (sm->server_transmitting) + SM_ENTER(CP, TRANSMIT); + break; + case CP_ABANDON: + if (changed_connect(sm)) + SM_ENTER(CP, RETIRE); + else if (sm->new_sak) + SM_ENTER(CP, RECEIVE); + break; + default: + wpa_printf(MSG_ERROR, "CP: the state machine is not defined"); + break; + } +} + + +/** + * ieee802_1x_cp_sm_init - + */ +struct ieee802_1x_cp_sm * ieee802_1x_cp_sm_init( + struct ieee802_1x_kay *kay, + struct ieee802_1x_cp_conf *pcp_conf) +{ + struct ieee802_1x_cp_sm *sm; + + sm = os_zalloc(sizeof(*sm)); + if (sm == NULL) { + wpa_printf(MSG_ERROR, "CP-%s: out of memory", __func__); + return NULL; + } + + sm->kay = kay; + + sm->port_valid = FALSE; + + sm->chgd_server = FALSE; + + sm->protect_frames = pcp_conf->protect; + sm->validate_frames = pcp_conf->validate; + sm->replay_protect = pcp_conf->replay_protect; + sm->replay_window = pcp_conf->replay_window; + + sm->controlled_port_enabled = FALSE; + + sm->lki = NULL; + sm->lrx = FALSE; + sm->ltx = FALSE; + sm->oki = NULL; + sm->orx = FALSE; + sm->otx = FALSE; + + sm->cipher_suite = os_zalloc(CS_ID_LEN); + sm->current_cipher_suite = os_zalloc(CS_ID_LEN); + if (!sm->cipher_suite || !sm->current_cipher_suite) { + wpa_printf(MSG_ERROR, "CP-%s: out of memory", __func__); + os_free(sm->cipher_suite); + os_free(sm->current_cipher_suite); + os_free(sm); + return NULL; + } + os_memcpy(sm->current_cipher_suite, default_cs_id, CS_ID_LEN); + os_memcpy(sm->cipher_suite, default_cs_id, CS_ID_LEN); + sm->cipher_offset = CONFIDENTIALITY_OFFSET_0; + sm->confidentiality_offset = sm->cipher_offset; + sm->transmit_delay = MKA_LIFE_TIME; + sm->retire_delay = MKA_SAK_RETIRE_TIME; + sm->CP_state = CP_BEGIN; + sm->changed = FALSE; + sm->authorization_data = NULL; + + wpa_printf(MSG_DEBUG, "CP: state machine created"); + + secy_cp_control_protect_frames(sm->kay, sm->protect_frames); + secy_cp_control_validate_frames(sm->kay, sm->validate_frames); + secy_cp_control_replay(sm->kay, sm->replay_protect, sm->replay_window); + secy_cp_control_enable_port(sm->kay, sm->controlled_port_enabled); + secy_cp_control_confidentiality_offset(sm->kay, + sm->confidentiality_offset); + + SM_ENTER(CP, INIT); + SM_STEP_RUN(CP); + + return sm; +} + + +static void ieee802_1x_cp_step_run(struct ieee802_1x_cp_sm *sm) +{ + enum cp_states prev_state; + int i; + + for (i = 0; i < 100; i++) { + prev_state = sm->CP_state; + SM_STEP_RUN(CP); + if (prev_state == sm->CP_state) + break; + } +} + + +static void ieee802_1x_cp_step_cb(void *eloop_ctx, void *timeout_ctx) +{ + struct ieee802_1x_cp_sm *sm = eloop_ctx; + ieee802_1x_cp_step_run(sm); +} + + +/** + * ieee802_1x_cp_sm_deinit - + */ +void ieee802_1x_cp_sm_deinit(struct ieee802_1x_cp_sm *sm) +{ + wpa_printf(MSG_DEBUG, "CP: state machine removed"); + if (!sm) + return; + + eloop_cancel_timeout(ieee802_1x_cp_retire_when_timeout, sm, NULL); + eloop_cancel_timeout(ieee802_1x_cp_transmit_when_timeout, sm, NULL); + eloop_cancel_timeout(ieee802_1x_cp_step_cb, sm, NULL); + os_free(sm->lki); + os_free(sm->oki); + os_free(sm->cipher_suite); + os_free(sm->current_cipher_suite); + os_free(sm->authorization_data); + os_free(sm); +} + + +/** + * ieee802_1x_cp_connect_pending + */ +void ieee802_1x_cp_connect_pending(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + + sm->connect = PENDING; +} + + +/** + * ieee802_1x_cp_connect_unauthenticated + */ +void ieee802_1x_cp_connect_unauthenticated(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = (struct ieee802_1x_cp_sm *)cp_ctx; + + sm->connect = UNAUTHENTICATED; +} + + +/** + * ieee802_1x_cp_connect_authenticated + */ +void ieee802_1x_cp_connect_authenticated(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + + sm->connect = AUTHENTICATED; +} + + +/** + * ieee802_1x_cp_connect_secure + */ +void ieee802_1x_cp_connect_secure(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + + sm->connect = SECURE; +} + + +/** + * ieee802_1x_cp_set_chgdserver - + */ +void ieee802_1x_cp_signal_chgdserver(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + + sm->chgd_server = TRUE; +} + + +/** + * ieee802_1x_cp_set_electedself - + */ +void ieee802_1x_cp_set_electedself(void *cp_ctx, Boolean status) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->elected_self = status; +} + + +/** + * ieee802_1x_cp_set_authorizationdata - + */ +void ieee802_1x_cp_set_authorizationdata(void *cp_ctx, u8 *pdata, int len) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + os_free(sm->authorization_data); + sm->authorization_data = os_zalloc(len); + if (sm->authorization_data) + os_memcpy(sm->authorization_data, pdata, len); +} + + +/** + * ieee802_1x_cp_set_ciphersuite - + */ +void ieee802_1x_cp_set_ciphersuite(void *cp_ctx, void *pid) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + os_memcpy(sm->cipher_suite, pid, CS_ID_LEN); +} + + +/** + * ieee802_1x_cp_set_offset - + */ +void ieee802_1x_cp_set_offset(void *cp_ctx, enum confidentiality_offset offset) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->cipher_offset = offset; +} + + +/** + * ieee802_1x_cp_signal_newsak - + */ +void ieee802_1x_cp_signal_newsak(void *cp_ctx) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->new_sak = TRUE; +} + + +/** + * ieee802_1x_cp_set_distributedki - + */ +void ieee802_1x_cp_set_distributedki(void *cp_ctx, + const struct ieee802_1x_mka_ki *dki) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + os_memcpy(&sm->distributed_ki, dki, sizeof(struct ieee802_1x_mka_ki)); +} + + +/** + * ieee802_1x_cp_set_distributedan - + */ +void ieee802_1x_cp_set_distributedan(void *cp_ctx, u8 an) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->distributed_an = an; +} + + +/** + * ieee802_1x_cp_set_usingreceivesas - + */ +void ieee802_1x_cp_set_usingreceivesas(void *cp_ctx, Boolean status) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->using_receive_sas = status; +} + + +/** + * ieee802_1x_cp_set_allreceiving - + */ +void ieee802_1x_cp_set_allreceiving(void *cp_ctx, Boolean status) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->all_receiving = status; +} + + +/** + * ieee802_1x_cp_set_servertransmitting - + */ +void ieee802_1x_cp_set_servertransmitting(void *cp_ctx, Boolean status) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->server_transmitting = status; +} + + +/** + * ieee802_1x_cp_set_usingtransmitsas - + */ +void ieee802_1x_cp_set_usingtransmitas(void *cp_ctx, Boolean status) +{ + struct ieee802_1x_cp_sm *sm = cp_ctx; + sm->using_transmit_sa = status; +} + + +/** + * ieee802_1x_cp_sm_step - Advance EAPOL state machines + * @sm: EAPOL state machine + * + * This function is called to advance CP state machines after any change + * that could affect their state. + */ +void ieee802_1x_cp_sm_step(void *cp_ctx) +{ + /* + * Run ieee802_1x_cp_step_run from a registered timeout + * to make sure that other possible timeouts/events are processed + * and to avoid long function call chains. + */ + struct ieee802_1x_cp_sm *sm = cp_ctx; + eloop_cancel_timeout(ieee802_1x_cp_step_cb, sm, NULL); + eloop_register_timeout(0, 0, ieee802_1x_cp_step_cb, sm, NULL); +} + + +static void ieee802_1x_cp_retire_when_timeout(void *eloop_ctx, + void *timeout_ctx) +{ + struct ieee802_1x_cp_sm *sm = eloop_ctx; + sm->retire_when = 0; + ieee802_1x_cp_step_run(sm); +} + + +static void +ieee802_1x_cp_transmit_when_timeout(void *eloop_ctx, void *timeout_ctx) +{ + struct ieee802_1x_cp_sm *sm = eloop_ctx; + sm->transmit_when = 0; + ieee802_1x_cp_step_run(sm); +} diff --git a/src/pae/ieee802_1x_cp.h b/src/pae/ieee802_1x_cp.h new file mode 100644 index 0000000..773c930 --- /dev/null +++ b/src/pae/ieee802_1x_cp.h @@ -0,0 +1,50 @@ +/* + * IEEE Std 802.1X-2010 Controlled Port of PAE state machine - CP state machine + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef IEEE802_1X_CP_H +#define IEEE802_1X_CP_H + +#include "common/defs.h" +#include "common/ieee802_1x_defs.h" + +struct ieee802_1x_cp_sm; +struct ieee802_1x_kay; +struct ieee802_1x_mka_ki; + +struct ieee802_1x_cp_conf { + Boolean protect; + Boolean replay_protect; + enum validate_frames validate; + u32 replay_window; +}; + + +struct ieee802_1x_cp_sm * +ieee802_1x_cp_sm_init(struct ieee802_1x_kay *kay, + struct ieee802_1x_cp_conf *pcp_conf); +void ieee802_1x_cp_sm_deinit(struct ieee802_1x_cp_sm *sm); +void ieee802_1x_cp_sm_step(void *cp_ctx); +void ieee802_1x_cp_connect_pending(void *cp_ctx); +void ieee802_1x_cp_connect_unauthenticated(void *cp_ctx); +void ieee802_1x_cp_connect_authenticated(void *cp_ctx); +void ieee802_1x_cp_connect_secure(void *cp_ctx); +void ieee802_1x_cp_signal_chgdserver(void *cp_ctx); +void ieee802_1x_cp_set_electedself(void *cp_ctx, Boolean status); +void ieee802_1x_cp_set_authorizationdata(void *cp_ctx, u8 *pdata, int len); +void ieee802_1x_cp_set_ciphersuite(void *cp_ctx, void *pid); +void ieee802_1x_cp_set_offset(void *cp_ctx, enum confidentiality_offset offset); +void ieee802_1x_cp_signal_newsak(void *cp_ctx); +void ieee802_1x_cp_set_distributedki(void *cp_ctx, + const struct ieee802_1x_mka_ki *dki); +void ieee802_1x_cp_set_distributedan(void *cp_ctx, u8 an); +void ieee802_1x_cp_set_usingreceivesas(void *cp_ctx, Boolean status); +void ieee802_1x_cp_set_allreceiving(void *cp_ctx, Boolean status); +void ieee802_1x_cp_set_servertransmitting(void *cp_ctx, Boolean status); +void ieee802_1x_cp_set_usingtransmitas(void *cp_ctx, Boolean status); + +#endif /* IEEE802_1X_CP_H */ diff --git a/src/pae/ieee802_1x_kay.c b/src/pae/ieee802_1x_kay.c new file mode 100644 index 0000000..fb8a8ca --- /dev/null +++ b/src/pae/ieee802_1x_kay.c @@ -0,0 +1,3526 @@ +/* + * IEEE 802.1X-2010 Key Agree Protocol of PAE state machine + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include <time.h> +#include "includes.h" +#include "common.h" +#include "list.h" +#include "eloop.h" +#include "wpabuf.h" +#include "state_machine.h" +#include "l2_packet/l2_packet.h" +#include "common/eapol_common.h" +#include "crypto/aes_wrap.h" +#include "ieee802_1x_cp.h" +#include "ieee802_1x_key.h" +#include "ieee802_1x_kay.h" +#include "ieee802_1x_kay_i.h" +#include "ieee802_1x_secy_ops.h" + + +#define DEFAULT_SA_KEY_LEN 16 +#define DEFAULT_ICV_LEN 16 +#define MAX_ICV_LEN 32 /* 32 bytes, 256 bits */ + +#define PENDING_PN_EXHAUSTION 0xC0000000 + +/* IEEE Std 802.1X-2010, Table 9-1 - MKA Algorithm Agility */ +#define MKA_ALGO_AGILITY_2009 { 0x00, 0x80, 0xC2, 0x01 } +static u8 mka_algo_agility[4] = MKA_ALGO_AGILITY_2009; + +/* IEEE802.1AE-2006 Table 14-1 MACsec Cipher Suites */ +static struct macsec_ciphersuite cipher_suite_tbl[] = { + /* GCM-AES-128 */ + { + CS_ID_GCM_AES_128, + CS_NAME_GCM_AES_128, + MACSEC_CAP_INTEG_AND_CONF_0_30_50, + 16, + + 0 /* index */ + }, +}; +#define CS_TABLE_SIZE (ARRAY_SIZE(cipher_suite_tbl)) +#define DEFAULT_CS_INDEX 0 + +static struct mka_alg mka_alg_tbl[] = { + { + MKA_ALGO_AGILITY_2009, + /* 128-bit CAK, KEK, ICK, ICV */ + 16, 16, 16, 16, + ieee802_1x_cak_128bits_aes_cmac, + ieee802_1x_ckn_128bits_aes_cmac, + ieee802_1x_kek_128bits_aes_cmac, + ieee802_1x_ick_128bits_aes_cmac, + ieee802_1x_icv_128bits_aes_cmac, + + 1, /* index */ + }, +}; +#define MKA_ALG_TABLE_SIZE (ARRAY_SIZE(mka_alg_tbl)) + + +static int is_ki_equal(struct ieee802_1x_mka_ki *ki1, + struct ieee802_1x_mka_ki *ki2) +{ + return os_memcmp(ki1->mi, ki2->mi, MI_LEN) == 0 && + ki1->kn == ki2->kn; +} + + +struct mka_param_body_handler { + int (*body_tx)(struct ieee802_1x_mka_participant *participant, + struct wpabuf *buf); + int (*body_rx)(struct ieee802_1x_mka_participant *participant, + const u8 *mka_msg, size_t msg_len); + int (*body_length)(struct ieee802_1x_mka_participant *participant); + Boolean (*body_present)(struct ieee802_1x_mka_participant *participant); +}; + + +static void set_mka_param_body_len(void *body, unsigned int len) +{ + struct ieee802_1x_mka_hdr *hdr = body; + hdr->length = (len >> 8) & 0x0f; + hdr->length1 = len & 0xff; +} + + +static unsigned int get_mka_param_body_len(const void *body) +{ + const struct ieee802_1x_mka_hdr *hdr = body; + return (hdr->length << 8) | hdr->length1; +} + + +static int get_mka_param_body_type(const void *body) +{ + const struct ieee802_1x_mka_hdr *hdr = body; + return hdr->type; +} + + +/** + * ieee802_1x_mka_dump_basic_body - + */ +static void +ieee802_1x_mka_dump_basic_body(struct ieee802_1x_mka_basic_body *body) +{ + size_t body_len; + + if (!body) + return; + + body_len = get_mka_param_body_len(body); + wpa_printf(MSG_DEBUG, "*** MKA Basic Parameter set ***"); + wpa_printf(MSG_DEBUG, "\tVersion.......: %d", body->version); + wpa_printf(MSG_DEBUG, "\tPriority......: %d", body->priority); + wpa_printf(MSG_DEBUG, "\tKeySvr........: %d", body->key_server); + wpa_printf(MSG_DEBUG, "\tMACSecDesired.: %d", body->macsec_desired); + wpa_printf(MSG_DEBUG, "\tMACSecCapable.: %d", body->macsec_capbility); + wpa_printf(MSG_DEBUG, "\tBody Length...: %d", (int) body_len); + wpa_printf(MSG_DEBUG, "\tSCI MAC.......: " MACSTR, + MAC2STR(body->actor_sci.addr)); + wpa_printf(MSG_DEBUG, "\tSCI Port .....: %d", + be_to_host16(body->actor_sci.port)); + wpa_hexdump(MSG_DEBUG, "\tMember Id.....:", + body->actor_mi, sizeof(body->actor_mi)); + wpa_printf(MSG_DEBUG, "\tMessage Number: %d", + be_to_host32(body->actor_mn)); + wpa_hexdump(MSG_DEBUG, "\tAlgo Agility..:", + body->algo_agility, sizeof(body->algo_agility)); + wpa_hexdump_ascii(MSG_DEBUG, "\tCAK Name......:", body->ckn, + body_len + MKA_HDR_LEN - sizeof(*body)); +} + + +/** + * ieee802_1x_mka_dump_peer_body - + */ +static void +ieee802_1x_mka_dump_peer_body(struct ieee802_1x_mka_peer_body *body) +{ + size_t body_len; + size_t i; + u8 *mi; + u32 mn; + + if (body == NULL) + return; + + body_len = get_mka_param_body_len(body); + if (body->type == MKA_LIVE_PEER_LIST) { + wpa_printf(MSG_DEBUG, "*** Live Peer List ***"); + wpa_printf(MSG_DEBUG, "\tBody Length...: %d", (int) body_len); + } else if (body->type == MKA_POTENTIAL_PEER_LIST) { + wpa_printf(MSG_DEBUG, "*** Potential Live Peer List ***"); + wpa_printf(MSG_DEBUG, "\tBody Length...: %d", (int) body_len); + } + + for (i = 0; i < body_len; i += MI_LEN + sizeof(mn)) { + mi = body->peer + i; + os_memcpy(&mn, mi + MI_LEN, sizeof(mn)); + wpa_hexdump_ascii(MSG_DEBUG, "\tMember Id.....:", mi, MI_LEN); + wpa_printf(MSG_DEBUG, "\tMessage Number: %d", be_to_host32(mn)); + } +} + + +/** + * ieee802_1x_mka_dump_dist_sak_body - + */ +static void +ieee802_1x_mka_dump_dist_sak_body(struct ieee802_1x_mka_dist_sak_body *body) +{ + size_t body_len; + + if (body == NULL) + return; + + body_len = get_mka_param_body_len(body); + wpa_printf(MSG_INFO, "*** Distributed SAK ***"); + wpa_printf(MSG_INFO, "\tDistributed AN........: %d", body->dan); + wpa_printf(MSG_INFO, "\tConfidentiality Offset: %d", + body->confid_offset); + wpa_printf(MSG_INFO, "\tBody Length...........: %d", (int) body_len); + if (!body_len) + return; + + wpa_printf(MSG_INFO, "\tKey Number............: %d", + be_to_host32(body->kn)); + wpa_hexdump(MSG_INFO, "\tAES Key Wrap of SAK...:", body->sak, 24); +} + + +static const char * yes_no(int val) +{ + return val ? "Yes" : "No"; +} + + +/** + * ieee802_1x_mka_dump_sak_use_body - + */ +static void +ieee802_1x_mka_dump_sak_use_body(struct ieee802_1x_mka_sak_use_body *body) +{ + int body_len; + + if (body == NULL) + return; + + body_len = get_mka_param_body_len(body); + wpa_printf(MSG_DEBUG, "*** MACsec SAK Use ***"); + wpa_printf(MSG_DEBUG, "\tLatest Key AN....: %d", body->lan); + wpa_printf(MSG_DEBUG, "\tLatest Key Tx....: %s", yes_no(body->ltx)); + wpa_printf(MSG_DEBUG, "\tLatest Key Rx....: %s", yes_no(body->lrx)); + wpa_printf(MSG_DEBUG, "\tOld Key AN....: %d", body->oan); + wpa_printf(MSG_DEBUG, "\tOld Key Tx....: %s", yes_no(body->otx)); + wpa_printf(MSG_DEBUG, "\tOld Key Rx....: %s", yes_no(body->orx)); + wpa_printf(MSG_DEBUG, "\tPlain Key Tx....: %s", yes_no(body->ptx)); + wpa_printf(MSG_DEBUG, "\tPlain Key Rx....: %s", yes_no(body->prx)); + wpa_printf(MSG_DEBUG, "\tDelay Protect....: %s", + yes_no(body->delay_protect)); + wpa_printf(MSG_DEBUG, "\tBody Length......: %d", body_len); + if (!body_len) + return; + + wpa_hexdump(MSG_DEBUG, "\tKey Server MI....:", + body->lsrv_mi, sizeof(body->lsrv_mi)); + wpa_printf(MSG_DEBUG, "\tKey Number.......: %u", + be_to_host32(body->lkn)); + wpa_printf(MSG_DEBUG, "\tLowest PN........: %u", + be_to_host32(body->llpn)); + wpa_hexdump_ascii(MSG_DEBUG, "\tOld Key Server MI....:", + body->osrv_mi, sizeof(body->osrv_mi)); + wpa_printf(MSG_DEBUG, "\tOld Key Number.......: %u", + be_to_host32(body->okn)); + wpa_printf(MSG_DEBUG, "\tOld Lowest PN........: %u", + be_to_host32(body->olpn)); +} + + +/** + * ieee802_1x_kay_get_participant - + */ +static struct ieee802_1x_mka_participant * +ieee802_1x_kay_get_participant(struct ieee802_1x_kay *kay, const u8 *ckn) +{ + struct ieee802_1x_mka_participant *participant; + + dl_list_for_each(participant, &kay->participant_list, + struct ieee802_1x_mka_participant, list) { + if (os_memcmp(participant->ckn.name, ckn, + participant->ckn.len) == 0) + return participant; + } + + wpa_printf(MSG_DEBUG, "KaY: participant is not found"); + + return NULL; +} + + +/** + * ieee802_1x_kay_get_principal_participant - + */ +static struct ieee802_1x_mka_participant * +ieee802_1x_kay_get_principal_participant(struct ieee802_1x_kay *kay) +{ + struct ieee802_1x_mka_participant *participant; + + dl_list_for_each(participant, &kay->participant_list, + struct ieee802_1x_mka_participant, list) { + if (participant->principal) + return participant; + } + + wpa_printf(MSG_DEBUG, "KaY: principal participant is not founded"); + return NULL; +} + + +static struct ieee802_1x_kay_peer * get_peer_mi(struct dl_list *peers, + const u8 *mi) +{ + struct ieee802_1x_kay_peer *peer; + + dl_list_for_each(peer, peers, struct ieee802_1x_kay_peer, list) { + if (os_memcmp(peer->mi, mi, MI_LEN) == 0) + return peer; + } + + return NULL; +} + + +/** + * ieee802_1x_kay_is_in_potential_peer + */ +static Boolean +ieee802_1x_kay_is_in_potential_peer( + struct ieee802_1x_mka_participant *participant, const u8 *mi) +{ + return get_peer_mi(&participant->potential_peers, mi) != NULL; +} + + +/** + * ieee802_1x_kay_is_in_live_peer + */ +static Boolean +ieee802_1x_kay_is_in_live_peer( + struct ieee802_1x_mka_participant *participant, const u8 *mi) +{ + return get_peer_mi(&participant->live_peers, mi) != NULL; +} + + +/** + * ieee802_1x_kay_is_in_peer + */ +static Boolean +ieee802_1x_kay_is_in_peer(struct ieee802_1x_mka_participant *participant, + const u8 *mi) +{ + return ieee802_1x_kay_is_in_live_peer(participant, mi) || + ieee802_1x_kay_is_in_potential_peer(participant, mi); +} + + +/** + * ieee802_1x_kay_get_peer + */ +static struct ieee802_1x_kay_peer * +ieee802_1x_kay_get_peer(struct ieee802_1x_mka_participant *participant, + const u8 *mi) +{ + struct ieee802_1x_kay_peer *peer; + + peer = get_peer_mi(&participant->live_peers, mi); + if (peer) + return peer; + + return get_peer_mi(&participant->potential_peers, mi); +} + + +/** + * ieee802_1x_kay_get_live_peer + */ +static struct ieee802_1x_kay_peer * +ieee802_1x_kay_get_live_peer(struct ieee802_1x_mka_participant *participant, + const u8 *mi) +{ + return get_peer_mi(&participant->live_peers, mi); +} + + +/** + * ieee802_1x_kay_get_cipher_suite + */ +static struct macsec_ciphersuite * +ieee802_1x_kay_get_cipher_suite(struct ieee802_1x_mka_participant *participant, + u8 *cs_id) +{ + unsigned int i; + + for (i = 0; i < CS_TABLE_SIZE; i++) { + if (os_memcmp(cipher_suite_tbl[i].id, cs_id, CS_ID_LEN) == 0) + break; + } + if (i >= CS_TABLE_SIZE) + return NULL; + + return &cipher_suite_tbl[i]; +} + + +/** + * ieee802_1x_kay_get_peer_sci + */ +static struct ieee802_1x_kay_peer * +ieee802_1x_kay_get_peer_sci(struct ieee802_1x_mka_participant *participant, + const struct ieee802_1x_mka_sci *sci) +{ + struct ieee802_1x_kay_peer *peer; + + dl_list_for_each(peer, &participant->live_peers, + struct ieee802_1x_kay_peer, list) { + if (os_memcmp(&peer->sci, sci, sizeof(peer->sci)) == 0) + return peer; + } + + dl_list_for_each(peer, &participant->potential_peers, + struct ieee802_1x_kay_peer, list) { + if (os_memcmp(&peer->sci, sci, sizeof(peer->sci)) == 0) + return peer; + } + + return NULL; +} + + +/** + * ieee802_1x_kay_init_receive_sa - + */ +static struct receive_sa * +ieee802_1x_kay_init_receive_sa(struct receive_sc *psc, u8 an, u32 lowest_pn, + struct data_key *key) +{ + struct receive_sa *psa; + + if (!psc || !key) + return NULL; + + psa = os_zalloc(sizeof(*psa)); + if (!psa) { + wpa_printf(MSG_ERROR, "%s: out of memory", __func__); + return NULL; + } + + psa->pkey = key; + psa->lowest_pn = lowest_pn; + psa->next_pn = lowest_pn; + psa->an = an; + psa->sc = psc; + + os_get_time(&psa->created_time); + psa->in_use = FALSE; + + dl_list_add(&psc->sa_list, &psa->list); + wpa_printf(MSG_DEBUG, + "KaY: Create receive SA(AN: %d lowest_pn: %u of SC(channel: %d)", + (int) an, lowest_pn, psc->channel); + + return psa; +} + + +/** + * ieee802_1x_kay_deinit_receive_sa - + */ +static void ieee802_1x_kay_deinit_receive_sa(struct receive_sa *psa) +{ + psa->pkey = NULL; + wpa_printf(MSG_DEBUG, + "KaY: Delete receive SA(an: %d) of SC(channel: %d)", + psa->an, psa->sc->channel); + dl_list_del(&psa->list); + os_free(psa); +} + + +/** + * ieee802_1x_kay_init_receive_sc - + */ +static struct receive_sc * +ieee802_1x_kay_init_receive_sc(const struct ieee802_1x_mka_sci *psci, + int channel) +{ + struct receive_sc *psc; + + if (!psci) + return NULL; + + psc = os_zalloc(sizeof(*psc)); + if (!psc) { + wpa_printf(MSG_ERROR, "%s: out of memory", __func__); + return NULL; + } + + os_memcpy(&psc->sci, psci, sizeof(psc->sci)); + psc->channel = channel; + + os_get_time(&psc->created_time); + psc->receiving = FALSE; + + dl_list_init(&psc->sa_list); + wpa_printf(MSG_DEBUG, "KaY: Create receive SC(channel: %d)", channel); + wpa_hexdump(MSG_DEBUG, "SCI: ", (u8 *)psci, sizeof(*psci)); + + return psc; +} + + +/** + * ieee802_1x_kay_deinit_receive_sc - + **/ +static void +ieee802_1x_kay_deinit_receive_sc( + struct ieee802_1x_mka_participant *participant, struct receive_sc *psc) +{ + struct receive_sa *psa, *pre_sa; + + wpa_printf(MSG_DEBUG, "KaY: Delete receive SC(channel: %d)", + psc->channel); + dl_list_for_each_safe(psa, pre_sa, &psc->sa_list, struct receive_sa, + list) { + secy_disable_receive_sa(participant->kay, psa); + ieee802_1x_kay_deinit_receive_sa(psa); + } + dl_list_del(&psc->list); + os_free(psc); +} + + +/** + * ieee802_1x_kay_create_live_peer + */ +static struct ieee802_1x_kay_peer * +ieee802_1x_kay_create_live_peer(struct ieee802_1x_mka_participant *participant, + u8 *mi, u32 mn) +{ + struct ieee802_1x_kay_peer *peer; + struct receive_sc *rxsc; + u32 sc_ch = 0; + + peer = os_zalloc(sizeof(*peer)); + if (peer == NULL) { + wpa_printf(MSG_ERROR, "KaY-%s: out of memory", __func__); + return NULL; + } + + os_memcpy(peer->mi, mi, MI_LEN); + peer->mn = mn; + peer->expire = time(NULL) + MKA_LIFE_TIME / 1000; + peer->sak_used = FALSE; + os_memcpy(&peer->sci, &participant->current_peer_sci, + sizeof(peer->sci)); + dl_list_add(&participant->live_peers, &peer->list); + + secy_get_available_receive_sc(participant->kay, &sc_ch); + + rxsc = ieee802_1x_kay_init_receive_sc(&peer->sci, sc_ch); + if (!rxsc) + return NULL; + + dl_list_add(&participant->rxsc_list, &rxsc->list); + secy_create_receive_sc(participant->kay, rxsc); + + wpa_printf(MSG_DEBUG, "KaY: Live peer created"); + wpa_hexdump(MSG_DEBUG, "\tMI: ", peer->mi, sizeof(peer->mi)); + wpa_printf(MSG_DEBUG, "\tMN: %d", peer->mn); + wpa_hexdump(MSG_DEBUG, "\tSCI Addr: ", peer->sci.addr, ETH_ALEN); + wpa_printf(MSG_DEBUG, "\tPort: %d", peer->sci.port); + + return peer; +} + + +/** + * ieee802_1x_kay_create_potential_peer + */ +static struct ieee802_1x_kay_peer * +ieee802_1x_kay_create_potential_peer( + struct ieee802_1x_mka_participant *participant, const u8 *mi, u32 mn) +{ + struct ieee802_1x_kay_peer *peer; + + peer = os_zalloc(sizeof(*peer)); + if (peer == NULL) { + wpa_printf(MSG_ERROR, "KaY-%s: out of memory", __func__); + return NULL; + } + + os_memcpy(peer->mi, mi, MI_LEN); + peer->mn = mn; + peer->expire = time(NULL) + MKA_LIFE_TIME / 1000; + peer->sak_used = FALSE; + + dl_list_add(&participant->potential_peers, &peer->list); + + wpa_printf(MSG_DEBUG, "KaY: potential peer created"); + wpa_hexdump(MSG_DEBUG, "\tMI: ", peer->mi, sizeof(peer->mi)); + wpa_printf(MSG_DEBUG, "\tMN: %d", peer->mn); + wpa_hexdump(MSG_DEBUG, "\tSCI Addr: ", peer->sci.addr, ETH_ALEN); + wpa_printf(MSG_DEBUG, "\tPort: %d", peer->sci.port); + + return peer; +} + + +/** + * ieee802_1x_kay_move_live_peer + */ +static struct ieee802_1x_kay_peer * +ieee802_1x_kay_move_live_peer(struct ieee802_1x_mka_participant *participant, + u8 *mi, u32 mn) +{ + struct ieee802_1x_kay_peer *peer; + struct receive_sc *rxsc; + u32 sc_ch = 0; + + dl_list_for_each(peer, &participant->potential_peers, + struct ieee802_1x_kay_peer, list) { + if (os_memcmp(peer->mi, mi, MI_LEN) == 0) + break; + } + + os_memcpy(&peer->sci, &participant->current_peer_sci, + sizeof(peer->sci)); + peer->mn = mn; + peer->expire = time(NULL) + MKA_LIFE_TIME / 1000; + + wpa_printf(MSG_DEBUG, "KaY: move potential peer to live peer"); + wpa_hexdump(MSG_DEBUG, "\tMI: ", peer->mi, sizeof(peer->mi)); + wpa_printf(MSG_DEBUG, "\tMN: %d", peer->mn); + wpa_hexdump(MSG_DEBUG, "\tSCI Addr: ", peer->sci.addr, ETH_ALEN); + wpa_printf(MSG_DEBUG, "\tPort: %d", peer->sci.port); + + dl_list_del(&peer->list); + dl_list_add_tail(&participant->live_peers, &peer->list); + + secy_get_available_receive_sc(participant->kay, &sc_ch); + + rxsc = ieee802_1x_kay_init_receive_sc(&peer->sci, sc_ch); + if (!rxsc) + return NULL; + + dl_list_add(&participant->rxsc_list, &rxsc->list); + secy_create_receive_sc(participant->kay, rxsc); + + return peer; +} + + + +/** + * ieee802_1x_mka_basic_body_present - + */ +static Boolean +ieee802_1x_mka_basic_body_present( + struct ieee802_1x_mka_participant *participant) +{ + return TRUE; +} + + +/** + * ieee802_1x_mka_basic_body_length - + */ +static int +ieee802_1x_mka_basic_body_length(struct ieee802_1x_mka_participant *participant) +{ + int length; + + length = sizeof(struct ieee802_1x_mka_basic_body); + length += participant->ckn.len; + return (length + 0x3) & ~0x3; +} + + +/** + * ieee802_1x_mka_encode_basic_body + */ +static int +ieee802_1x_mka_encode_basic_body( + struct ieee802_1x_mka_participant *participant, + struct wpabuf *buf) +{ + struct ieee802_1x_mka_basic_body *body; + struct ieee802_1x_kay *kay = participant->kay; + unsigned int length = ieee802_1x_mka_basic_body_length(participant); + + body = wpabuf_put(buf, length); + + body->version = kay->mka_version; + body->priority = kay->actor_priority; + if (participant->is_elected) + body->key_server = participant->is_key_server; + else + body->key_server = participant->can_be_key_server; + + body->macsec_desired = kay->macsec_desired; + body->macsec_capbility = kay->macsec_capable; + set_mka_param_body_len(body, length - MKA_HDR_LEN); + + os_memcpy(body->actor_sci.addr, kay->actor_sci.addr, + sizeof(kay->actor_sci.addr)); + body->actor_sci.port = host_to_be16(kay->actor_sci.port); + + os_memcpy(body->actor_mi, participant->mi, sizeof(body->actor_mi)); + participant->mn = participant->mn + 1; + body->actor_mn = host_to_be32(participant->mn); + os_memcpy(body->algo_agility, participant->kay->algo_agility, + sizeof(body->algo_agility)); + + os_memcpy(body->ckn, participant->ckn.name, participant->ckn.len); + + ieee802_1x_mka_dump_basic_body(body); + + return 0; +} + + +/** + * ieee802_1x_mka_decode_basic_body - + */ +static struct ieee802_1x_mka_participant * +ieee802_1x_mka_decode_basic_body(struct ieee802_1x_kay *kay, const u8 *mka_msg, + size_t msg_len) +{ + struct ieee802_1x_mka_participant *participant; + const struct ieee802_1x_mka_basic_body *body; + struct ieee802_1x_kay_peer *peer; + + body = (const struct ieee802_1x_mka_basic_body *) mka_msg; + + if (body->version > MKA_VERSION_ID) { + wpa_printf(MSG_DEBUG, + "KaY: peer's version(%d) greater than mka current version(%d)", + body->version, MKA_VERSION_ID); + } + if (kay->is_obliged_key_server && body->key_server) { + wpa_printf(MSG_DEBUG, "I must be as key server"); + return NULL; + } + + participant = ieee802_1x_kay_get_participant(kay, body->ckn); + if (!participant) { + wpa_printf(MSG_DEBUG, "Peer is not included in my CA"); + return NULL; + } + + /* If the peer's MI is my MI, I will choose new MI */ + if (os_memcmp(body->actor_mi, participant->mi, MI_LEN) == 0) { + os_get_random(participant->mi, sizeof(participant->mi)); + participant->mn = 0; + } + + os_memcpy(participant->current_peer_id.mi, body->actor_mi, MI_LEN); + participant->current_peer_id.mn = be_to_host32(body->actor_mn); + os_memcpy(participant->current_peer_sci.addr, body->actor_sci.addr, + sizeof(participant->current_peer_sci.addr)); + participant->current_peer_sci.port = be_to_host16(body->actor_sci.port); + + /* handler peer */ + peer = ieee802_1x_kay_get_peer(participant, body->actor_mi); + if (!peer) { + /* Check duplicated SCI */ + /* TODO: What policy should be applied to detect duplicated SCI + * is active attacker or a valid peer whose MI is be changed? + */ + peer = ieee802_1x_kay_get_peer_sci(participant, + &body->actor_sci); + if (peer) { + wpa_printf(MSG_WARNING, + "KaY: duplicated SCI detected, Maybe active attacker"); + dl_list_del(&peer->list); + os_free(peer); + } + + peer = ieee802_1x_kay_create_potential_peer( + participant, body->actor_mi, + be_to_host32(body->actor_mn)); + if (!peer) + return NULL; + + peer->macsec_desired = body->macsec_desired; + peer->macsec_capbility = body->macsec_capbility; + peer->is_key_server = (Boolean) body->key_server; + peer->key_server_priority = body->priority; + } else if (peer->mn < be_to_host32(body->actor_mn)) { + peer->mn = be_to_host32(body->actor_mn); + peer->expire = time(NULL) + MKA_LIFE_TIME / 1000; + peer->macsec_desired = body->macsec_desired; + peer->macsec_capbility = body->macsec_capbility; + peer->is_key_server = (Boolean) body->key_server; + peer->key_server_priority = body->priority; + } else { + wpa_printf(MSG_WARNING, "KaY: The peer MN have received"); + return NULL; + } + + return participant; +} + + +/** + * ieee802_1x_mka_live_peer_body_present + */ +static Boolean +ieee802_1x_mka_live_peer_body_present( + struct ieee802_1x_mka_participant *participant) +{ + return !dl_list_empty(&participant->live_peers); +} + + +/** + * ieee802_1x_kay_get_live_peer_length + */ +static int +ieee802_1x_mka_get_live_peer_length( + struct ieee802_1x_mka_participant *participant) +{ + int len = MKA_HDR_LEN; + struct ieee802_1x_kay_peer *peer; + + dl_list_for_each(peer, &participant->live_peers, + struct ieee802_1x_kay_peer, list) + len += sizeof(struct ieee802_1x_mka_peer_id); + + return (len + 0x3) & ~0x3; +} + + +/** + * ieee802_1x_mka_encode_live_peer_body - + */ +static int +ieee802_1x_mka_encode_live_peer_body( + struct ieee802_1x_mka_participant *participant, + struct wpabuf *buf) +{ + struct ieee802_1x_mka_peer_body *body; + struct ieee802_1x_kay_peer *peer; + unsigned int length; + struct ieee802_1x_mka_peer_id *body_peer; + + length = ieee802_1x_mka_get_live_peer_length(participant); + body = wpabuf_put(buf, sizeof(struct ieee802_1x_mka_peer_body)); + + body->type = MKA_LIVE_PEER_LIST; + set_mka_param_body_len(body, length - MKA_HDR_LEN); + + dl_list_for_each(peer, &participant->live_peers, + struct ieee802_1x_kay_peer, list) { + body_peer = wpabuf_put(buf, + sizeof(struct ieee802_1x_mka_peer_id)); + os_memcpy(body_peer->mi, peer->mi, MI_LEN); + body_peer->mn = host_to_be32(peer->mn); + body_peer++; + } + + ieee802_1x_mka_dump_peer_body(body); + return 0; +} + +/** + * ieee802_1x_mka_potential_peer_body_present + */ +static Boolean +ieee802_1x_mka_potential_peer_body_present( + struct ieee802_1x_mka_participant *participant) +{ + return !dl_list_empty(&participant->potential_peers); +} + + +/** + * ieee802_1x_kay_get_potential_peer_length + */ +static int +ieee802_1x_mka_get_potential_peer_length( + struct ieee802_1x_mka_participant *participant) +{ + int len = MKA_HDR_LEN; + struct ieee802_1x_kay_peer *peer; + + dl_list_for_each(peer, &participant->potential_peers, + struct ieee802_1x_kay_peer, list) + len += sizeof(struct ieee802_1x_mka_peer_id); + + return (len + 0x3) & ~0x3; +} + + +/** + * ieee802_1x_mka_encode_potential_peer_body - + */ +static int +ieee802_1x_mka_encode_potential_peer_body( + struct ieee802_1x_mka_participant *participant, + struct wpabuf *buf) +{ + struct ieee802_1x_mka_peer_body *body; + struct ieee802_1x_kay_peer *peer; + unsigned int length; + struct ieee802_1x_mka_peer_id *body_peer; + + length = ieee802_1x_mka_get_potential_peer_length(participant); + body = wpabuf_put(buf, sizeof(struct ieee802_1x_mka_peer_body)); + + body->type = MKA_POTENTIAL_PEER_LIST; + set_mka_param_body_len(body, length - MKA_HDR_LEN); + + dl_list_for_each(peer, &participant->potential_peers, + struct ieee802_1x_kay_peer, list) { + body_peer = wpabuf_put(buf, + sizeof(struct ieee802_1x_mka_peer_id)); + os_memcpy(body_peer->mi, peer->mi, MI_LEN); + body_peer->mn = host_to_be32(peer->mn); + body_peer++; + } + + ieee802_1x_mka_dump_peer_body(body); + return 0; +} + + +/** + * ieee802_1x_mka_i_in_peerlist - + */ +static Boolean +ieee802_1x_mka_i_in_peerlist(struct ieee802_1x_mka_participant *participant, + const u8 *mka_msg, size_t msg_len) +{ + Boolean included = FALSE; + struct ieee802_1x_mka_hdr *hdr; + size_t body_len; + size_t left_len; + int body_type; + u32 peer_mn; + const u8 *peer_mi; + const u8 *pos; + size_t i; + + pos = mka_msg; + left_len = msg_len; + while (left_len > (MKA_HDR_LEN + DEFAULT_ICV_LEN)) { + hdr = (struct ieee802_1x_mka_hdr *) pos; + body_len = get_mka_param_body_len(hdr); + body_type = get_mka_param_body_type(hdr); + + if (body_type != MKA_LIVE_PEER_LIST && + body_type != MKA_POTENTIAL_PEER_LIST) + goto SKIP_PEER; + + ieee802_1x_mka_dump_peer_body( + (struct ieee802_1x_mka_peer_body *)pos); + + if (left_len < (MKA_HDR_LEN + body_len + DEFAULT_ICV_LEN)) { + wpa_printf(MSG_ERROR, + "KaY: MKA Peer Packet Body Length (%d bytes) is less than the Parameter Set Header Length (%d bytes) + the Parameter Set Body Length (%d bytes) + %d bytes of ICV", + (int) left_len, (int) MKA_HDR_LEN, + (int) body_len, DEFAULT_ICV_LEN); + goto SKIP_PEER; + } + + if ((body_len % 16) != 0) { + wpa_printf(MSG_ERROR, + "KaY: MKA Peer Packet Body Length (%d bytes) should multiple of 16 octets", + (int) body_len); + goto SKIP_PEER; + } + + for (i = 0; i < body_len; i += MI_LEN + sizeof(peer_mn)) { + peer_mi = MKA_HDR_LEN + pos + i; + os_memcpy(&peer_mn, peer_mi + MI_LEN, sizeof(peer_mn)); + peer_mn = be_to_host32(peer_mn); + if (os_memcmp(peer_mi, participant->mi, MI_LEN) == 0 && + peer_mn == participant->mn) { + included = TRUE; + break; + } + } + + if (included) + return TRUE; + +SKIP_PEER: + left_len -= body_len + MKA_HDR_LEN; + pos += body_len + MKA_HDR_LEN; + } + + return FALSE; +} + + +/** + * ieee802_1x_mka_decode_live_peer_body - + */ +static int ieee802_1x_mka_decode_live_peer_body( + struct ieee802_1x_mka_participant *participant, + const u8 *peer_msg, size_t msg_len) +{ + const struct ieee802_1x_mka_hdr *hdr; + struct ieee802_1x_kay_peer *peer; + size_t body_len; + u32 peer_mn; + const u8 *peer_mi; + size_t i; + Boolean is_included; + + is_included = ieee802_1x_kay_is_in_live_peer( + participant, participant->current_peer_id.mi); + + hdr = (const struct ieee802_1x_mka_hdr *) peer_msg; + body_len = get_mka_param_body_len(hdr); + + for (i = 0; i < body_len; i += MI_LEN + sizeof(peer_mn)) { + peer_mi = MKA_HDR_LEN + peer_msg + i; + os_memcpy(&peer_mn, peer_mi + MI_LEN, sizeof(peer_mn)); + peer_mn = be_to_host32(peer_mn); + + /* it is myself */ + if (os_memcmp(peer_mi, participant->mi, MI_LEN) == 0) { + /* My message id is used by other participant */ + if (peer_mn > participant->mn) { + os_get_random(participant->mi, + sizeof(participant->mi)); + participant->mn = 0; + } + continue; + } + if (!is_included) + continue; + + peer = ieee802_1x_kay_get_peer(participant, peer_mi); + if (NULL != peer) { + peer->mn = peer_mn; + peer->expire = time(NULL) + MKA_LIFE_TIME / 1000; + } else { + if (!ieee802_1x_kay_create_potential_peer( + participant, peer_mi, peer_mn)) { + return -1; + } + } + } + + return 0; +} + + +/** + * ieee802_1x_mka_decode_potential_peer_body - + */ +static int +ieee802_1x_mka_decode_potential_peer_body( + struct ieee802_1x_mka_participant *participant, + const u8 *peer_msg, size_t msg_len) +{ + struct ieee802_1x_mka_hdr *hdr; + size_t body_len; + u32 peer_mn; + const u8 *peer_mi; + size_t i; + + hdr = (struct ieee802_1x_mka_hdr *) peer_msg; + body_len = get_mka_param_body_len(hdr); + + for (i = 0; i < body_len; i += MI_LEN + sizeof(peer_mn)) { + peer_mi = MKA_HDR_LEN + peer_msg + i; + os_memcpy(&peer_mn, peer_mi + MI_LEN, sizeof(peer_mn)); + peer_mn = be_to_host32(peer_mn); + + /* it is myself */ + if (os_memcmp(peer_mi, participant->mi, MI_LEN) == 0) { + /* My message id is used by other participant */ + if (peer_mn > participant->mn) { + os_get_random(participant->mi, + sizeof(participant->mi)); + participant->mn = 0; + } + continue; + } + } + + return 0; +} + + +/** + * ieee802_1x_mka_sak_use_body_present + */ +static Boolean +ieee802_1x_mka_sak_use_body_present( + struct ieee802_1x_mka_participant *participant) +{ + if (participant->to_use_sak) + return TRUE; + else + return FALSE; +} + + +/** + * ieee802_1x_mka_get_sak_use_length + */ +static int +ieee802_1x_mka_get_sak_use_length( + struct ieee802_1x_mka_participant *participant) +{ + int length = MKA_HDR_LEN; + + if (participant->kay->macsec_desired && participant->advised_desired) + length = sizeof(struct ieee802_1x_mka_sak_use_body); + else + length = MKA_HDR_LEN; + + length = (length + 0x3) & ~0x3; + + return length; +} + + +/** + * + */ +static u32 +ieee802_1x_mka_get_lpn(struct ieee802_1x_mka_participant *principal, + struct ieee802_1x_mka_ki *ki) +{ + struct receive_sa *rxsa; + struct receive_sc *rxsc; + u32 lpn = 0; + + dl_list_for_each(rxsc, &principal->rxsc_list, struct receive_sc, list) { + dl_list_for_each(rxsa, &rxsc->sa_list, struct receive_sa, list) + { + if (is_ki_equal(&rxsa->pkey->key_identifier, ki)) { + secy_get_receive_lowest_pn(principal->kay, + rxsa); + + lpn = lpn > rxsa->lowest_pn ? + lpn : rxsa->lowest_pn; + break; + } + } + } + + if (lpn == 0) + lpn = 1; + + return lpn; +} + + +/** + * ieee802_1x_mka_encode_sak_use_body - + */ +static int +ieee802_1x_mka_encode_sak_use_body( + struct ieee802_1x_mka_participant *participant, + struct wpabuf *buf) +{ + struct ieee802_1x_mka_sak_use_body *body; + unsigned int length; + u32 pn = 1; + + length = ieee802_1x_mka_get_sak_use_length(participant); + body = wpabuf_put(buf, sizeof(struct ieee802_1x_mka_sak_use_body)); + + body->type = MKA_SAK_USE; + set_mka_param_body_len(body, length - MKA_HDR_LEN); + + if (length == MKA_HDR_LEN) { + body->ptx = TRUE; + body->prx = TRUE; + body->lan = 0; + body->lrx = FALSE; + body->ltx = FALSE; + body->delay_protect = FALSE; + return 0; + } + + /* data protect, lowest accept packet number */ + body->delay_protect = participant->kay->macsec_replay_protect; + pn = ieee802_1x_mka_get_lpn(participant, &participant->lki); + if (pn > participant->kay->pn_exhaustion) { + wpa_printf(MSG_WARNING, "KaY: My LPN exhaustion"); + if (participant->is_key_server) + participant->new_sak = TRUE; + } + + body->llpn = host_to_be32(pn); + pn = ieee802_1x_mka_get_lpn(participant, &participant->oki); + body->olpn = host_to_be32(pn); + + /* plain tx, plain rx */ + if (participant->kay->macsec_protect) + body->ptx = FALSE; + else + body->ptx = TRUE; + + if (participant->kay->macsec_validate == Strict) + body->prx = FALSE; + else + body->prx = TRUE; + + /* latest key: rx, tx, key server member identifier key number */ + body->lan = participant->lan; + os_memcpy(body->lsrv_mi, participant->lki.mi, + sizeof(body->lsrv_mi)); + body->lkn = host_to_be32(participant->lki.kn); + body->lrx = participant->lrx; + body->ltx = participant->ltx; + + /* old key: rx, tx, key server member identifier key number */ + body->oan = participant->oan; + if (participant->oki.kn != participant->lki.kn && + participant->oki.kn != 0) { + body->otx = TRUE; + body->orx = TRUE; + os_memcpy(body->osrv_mi, participant->oki.mi, + sizeof(body->osrv_mi)); + body->okn = host_to_be32(participant->oki.kn); + } else { + body->otx = FALSE; + body->orx = FALSE; + } + + /* set CP's variable */ + if (body->ltx) { + if (!participant->kay->tx_enable) + participant->kay->tx_enable = TRUE; + + if (!participant->kay->port_enable) + participant->kay->port_enable = TRUE; + } + if (body->lrx) { + if (!participant->kay->rx_enable) + participant->kay->rx_enable = TRUE; + } + + ieee802_1x_mka_dump_sak_use_body(body); + return 0; +} + + +/** + * ieee802_1x_mka_decode_sak_use_body - + */ +static int +ieee802_1x_mka_decode_sak_use_body( + struct ieee802_1x_mka_participant *participant, + const u8 *mka_msg, size_t msg_len) +{ + struct ieee802_1x_mka_hdr *hdr; + struct ieee802_1x_mka_sak_use_body *body; + struct ieee802_1x_kay_peer *peer; + struct transmit_sa *txsa; + struct data_key *sa_key = NULL; + size_t body_len; + struct ieee802_1x_mka_ki ki; + u32 lpn; + Boolean all_receiving; + Boolean founded; + + if (!participant->principal) { + wpa_printf(MSG_WARNING, "KaY: Participant is not principal"); + return -1; + } + peer = ieee802_1x_kay_get_live_peer(participant, + participant->current_peer_id.mi); + if (!peer) { + wpa_printf(MSG_WARNING, "KaY: the peer is not my live peer"); + return -1; + } + + hdr = (struct ieee802_1x_mka_hdr *) mka_msg; + body_len = get_mka_param_body_len(hdr); + body = (struct ieee802_1x_mka_sak_use_body *) mka_msg; + ieee802_1x_mka_dump_sak_use_body(body); + + if ((body_len != 0) && (body_len < 40)) { + wpa_printf(MSG_ERROR, + "KaY: MKA Use SAK Packet Body Length (%d bytes) should be 0, 40, or more octets", + (int) body_len); + return -1; + } + + /* TODO: what action should I take when peer does not support MACsec */ + if (body_len == 0) { + wpa_printf(MSG_WARNING, "KaY: Peer does not support MACsec"); + return 0; + } + + /* TODO: when the plain tx or rx of peer is true, should I change + * the attribute of controlled port + */ + if (body->prx) + wpa_printf(MSG_WARNING, "KaY: peer's plain rx are TRUE"); + + if (body->ptx) + wpa_printf(MSG_WARNING, "KaY: peer's plain tx are TRUE"); + + /* check latest key is valid */ + if (body->ltx || body->lrx) { + founded = FALSE; + os_memcpy(ki.mi, body->lsrv_mi, sizeof(ki.mi)); + ki.kn = ntohl(body->lkn); + dl_list_for_each(sa_key, &participant->sak_list, + struct data_key, list) { + if (is_ki_equal(&sa_key->key_identifier, &ki)) { + founded = TRUE; + break; + } + } + if (!founded) { + wpa_printf(MSG_WARNING, "KaY: Latest key is invalid"); + return -1; + } + if (os_memcmp(participant->lki.mi, body->lsrv_mi, + sizeof(participant->lki.mi)) == 0 && + ntohl(body->lkn) == participant->lki.kn && + body->lan == participant->lan) { + peer->sak_used = TRUE; + } + if (body->ltx && peer->is_key_server) { + ieee802_1x_cp_set_servertransmitting( + participant->kay->cp, TRUE); + ieee802_1x_cp_sm_step(participant->kay->cp); + } + } + + /* check old key is valid */ + if (body->otx || body->orx) { + if (os_memcmp(participant->oki.mi, body->osrv_mi, + sizeof(participant->oki.mi)) != 0 || + ntohl(body->okn) != participant->oki.kn || + body->oan != participant->oan) { + wpa_printf(MSG_WARNING, "KaY: Old key is invalid"); + return -1; + } + } + + /* TODO: how to set the MACsec hardware when delay_protect is true */ + if (body->delay_protect && (!ntohl(body->llpn) || !ntohl(body->olpn))) { + wpa_printf(MSG_WARNING, + "KaY: Lowest packet number should greater than 0 when delay_protect is TRUE"); + return -1; + } + + /* check all live peer have used the sak for receiving sa */ + all_receiving = TRUE; + dl_list_for_each(peer, &participant->live_peers, + struct ieee802_1x_kay_peer, list) { + if (!peer->sak_used) { + all_receiving = FALSE; + break; + } + } + if (all_receiving) { + participant->to_dist_sak = FALSE; + ieee802_1x_cp_set_allreceiving(participant->kay->cp, TRUE); + ieee802_1x_cp_sm_step(participant->kay->cp); + } + + /* if i'm key server, and detects peer member pn exhaustion, rekey.*/ + lpn = ntohl(body->llpn); + if (lpn > participant->kay->pn_exhaustion) { + if (participant->is_key_server) { + participant->new_sak = TRUE; + wpa_printf(MSG_WARNING, "KaY: Peer LPN exhaustion"); + } + } + + founded = FALSE; + dl_list_for_each(txsa, &participant->txsc->sa_list, + struct transmit_sa, list) { + if (sa_key != NULL && txsa->pkey == sa_key) { + founded = TRUE; + break; + } + } + if (!founded) { + wpa_printf(MSG_WARNING, "KaY: Can't find txsa"); + return -1; + } + + /* FIXME: Secy creates txsa with default npn. If MKA detected Latest Key + * npn is larger than txsa's npn, set it to txsa. + */ + secy_get_transmit_next_pn(participant->kay, txsa); + if (lpn > txsa->next_pn) { + secy_set_transmit_next_pn(participant->kay, txsa); + wpa_printf(MSG_INFO, "KaY: update lpn =0x%x", lpn); + } + + return 0; +} + + +/** + * ieee802_1x_mka_dist_sak_body_present + */ +static Boolean +ieee802_1x_mka_dist_sak_body_present( + struct ieee802_1x_mka_participant *participant) +{ + if (!participant->to_dist_sak || !participant->new_key) + return FALSE; + + return TRUE; +} + + +/** + * ieee802_1x_kay_get_dist_sak_length + */ +static int +ieee802_1x_mka_get_dist_sak_length( + struct ieee802_1x_mka_participant *participant) +{ + int length; + int cs_index = participant->kay->macsec_csindex; + + if (participant->advised_desired) { + length = sizeof(struct ieee802_1x_mka_dist_sak_body); + if (cs_index != DEFAULT_CS_INDEX) + length += CS_ID_LEN; + + length += cipher_suite_tbl[cs_index].sak_len + 8; + } else { + length = MKA_HDR_LEN; + } + length = (length + 0x3) & ~0x3; + + return length; +} + + +/** + * ieee802_1x_mka_encode_dist_sak_body - + */ +static int +ieee802_1x_mka_encode_dist_sak_body( + struct ieee802_1x_mka_participant *participant, + struct wpabuf *buf) +{ + struct ieee802_1x_mka_dist_sak_body *body; + struct data_key *sak; + unsigned int length; + int cs_index; + int sak_pos; + + length = ieee802_1x_mka_get_dist_sak_length(participant); + body = wpabuf_put(buf, length); + body->type = MKA_DISTRIBUTED_SAK; + set_mka_param_body_len(body, length - MKA_HDR_LEN); + if (length == MKA_HDR_LEN) { + body->confid_offset = 0; + body->dan = 0; + return 0; + } + + sak = participant->new_key; + body->confid_offset = sak->confidentiality_offset; + body->dan = sak->an; + body->kn = host_to_be32(sak->key_identifier.kn); + cs_index = participant->kay->macsec_csindex; + sak_pos = 0; + if (cs_index != DEFAULT_CS_INDEX) { + os_memcpy(body->sak, cipher_suite_tbl[cs_index].id, CS_ID_LEN); + sak_pos = CS_ID_LEN; + } + if (aes_wrap(participant->kek.key, + cipher_suite_tbl[cs_index].sak_len / 8, + sak->key, body->sak + sak_pos)) { + wpa_printf(MSG_ERROR, "KaY: AES wrap failed"); + return -1; + } + + ieee802_1x_mka_dump_dist_sak_body(body); + + return 0; +} + + +/** + * ieee802_1x_kay_init_data_key - + */ +static struct data_key * +ieee802_1x_kay_init_data_key(const struct key_conf *conf) +{ + struct data_key *pkey; + + if (!conf) + return NULL; + + pkey = os_zalloc(sizeof(*pkey)); + if (pkey == NULL) { + wpa_printf(MSG_ERROR, "%s: out of memory", __func__); + return NULL; + } + + pkey->key = os_zalloc(conf->key_len); + if (pkey->key == NULL) { + wpa_printf(MSG_ERROR, "%s: out of memory", __func__); + os_free(pkey); + return NULL; + } + + os_memcpy(pkey->key, conf->key, conf->key_len); + os_memcpy(&pkey->key_identifier, &conf->ki, + sizeof(pkey->key_identifier)); + pkey->confidentiality_offset = conf->offset; + pkey->an = conf->an; + pkey->transmits = conf->tx; + pkey->receives = conf->rx; + os_get_time(&pkey->created_time); + + pkey->user = 1; + + return pkey; +} + + +/** + * ieee802_1x_kay_decode_dist_sak_body - + */ +static int +ieee802_1x_mka_decode_dist_sak_body( + struct ieee802_1x_mka_participant *participant, + const u8 *mka_msg, size_t msg_len) +{ + struct ieee802_1x_mka_hdr *hdr; + struct ieee802_1x_mka_dist_sak_body *body; + struct ieee802_1x_kay_peer *peer; + struct macsec_ciphersuite *cs; + size_t body_len; + struct key_conf *conf; + struct data_key *sa_key = NULL; + struct ieee802_1x_mka_ki sak_ki; + int sak_len; + u8 *wrap_sak; + u8 *unwrap_sak; + + hdr = (struct ieee802_1x_mka_hdr *) mka_msg; + body_len = get_mka_param_body_len(hdr); + if ((body_len != 0) && (body_len != 28) && (body_len < 36)) { + wpa_printf(MSG_ERROR, + "KaY: MKA Use SAK Packet Body Length (%d bytes) should be 0, 28, 36, or more octets", + (int) body_len); + return -1; + } + + if (!participant->principal) { + wpa_printf(MSG_ERROR, + "KaY: I can't accept the distributed SAK as I am not principal"); + return -1; + } + if (participant->is_key_server) { + wpa_printf(MSG_ERROR, + "KaY: I can't accept the distributed SAK as myself is key server "); + return -1; + } + if (!participant->kay->macsec_desired || + participant->kay->macsec_capable == MACSEC_CAP_NOT_IMPLEMENTED) { + wpa_printf(MSG_ERROR, + "KaY: I am not MACsec-desired or without MACsec capable"); + return -1; + } + + peer = ieee802_1x_kay_get_live_peer(participant, + participant->current_peer_id.mi); + if (!peer) { + wpa_printf(MSG_ERROR, + "KaY: The key server is not in my live peers list"); + return -1; + } + if (os_memcmp(&participant->kay->key_server_sci, + &peer->sci, sizeof(struct ieee802_1x_mka_sci)) != 0) { + wpa_printf(MSG_ERROR, "KaY: The key server is not elected"); + return -1; + } + if (body_len == 0) { + participant->kay->authenticated = TRUE; + participant->kay->secured = FALSE; + participant->kay->failed = FALSE; + participant->advised_desired = FALSE; + ieee802_1x_cp_connect_authenticated(participant->kay->cp); + ieee802_1x_cp_sm_step(participant->kay->cp); + wpa_printf(MSG_WARNING, "KaY:The Key server advise no MACsec"); + participant->to_use_sak = TRUE; + return 0; + } + participant->advised_desired = TRUE; + participant->kay->authenticated = FALSE; + participant->kay->secured = TRUE; + participant->kay->failed = FALSE; + ieee802_1x_cp_connect_secure(participant->kay->cp); + ieee802_1x_cp_sm_step(participant->kay->cp); + + body = (struct ieee802_1x_mka_dist_sak_body *)mka_msg; + ieee802_1x_mka_dump_dist_sak_body(body); + dl_list_for_each(sa_key, &participant->sak_list, struct data_key, list) + { + if (os_memcmp(sa_key->key_identifier.mi, + participant->current_peer_id.mi, MI_LEN) == 0 && + sa_key->key_identifier.kn == be_to_host32(body->kn)) { + wpa_printf(MSG_WARNING, "KaY:The Key has installed"); + return 0; + } + } + if (body_len == 28) { + sak_len = DEFAULT_SA_KEY_LEN; + wrap_sak = body->sak; + participant->kay->macsec_csindex = DEFAULT_CS_INDEX; + } else { + cs = ieee802_1x_kay_get_cipher_suite(participant, body->sak); + if (!cs) { + wpa_printf(MSG_ERROR, + "KaY: I can't support the Cipher Suite advised by key server"); + return -1; + } + sak_len = cs->sak_len; + wrap_sak = body->sak + CS_ID_LEN; + participant->kay->macsec_csindex = cs->index; + } + + unwrap_sak = os_zalloc(sak_len); + if (!unwrap_sak) { + wpa_printf(MSG_ERROR, "KaY-%s: Out of memory", __func__); + return -1; + } + if (aes_unwrap(participant->kek.key, sak_len >> 3, wrap_sak, + unwrap_sak)) { + wpa_printf(MSG_ERROR, "KaY: AES unwrap failed"); + os_free(unwrap_sak); + return -1; + } + wpa_hexdump(MSG_DEBUG, "\tAES Key Unwrap of SAK:", unwrap_sak, sak_len); + + conf = os_zalloc(sizeof(*conf)); + if (!conf) { + wpa_printf(MSG_ERROR, "KaY-%s: Out of memory", __func__); + os_free(unwrap_sak); + return -1; + } + conf->key_len = sak_len; + + conf->key = os_zalloc(conf->key_len); + if (!conf->key) { + wpa_printf(MSG_ERROR, "KaY-%s: Out of memory", __func__); + os_free(unwrap_sak); + os_free(conf); + return -1; + } + + os_memcpy(conf->key, unwrap_sak, conf->key_len); + + os_memcpy(&sak_ki.mi, &participant->current_peer_id.mi, + sizeof(sak_ki.mi)); + sak_ki.kn = be_to_host32(body->kn); + + os_memcpy(conf->ki.mi, sak_ki.mi, MI_LEN); + conf->ki.kn = sak_ki.kn; + conf->an = body->dan; + conf->offset = body->confid_offset; + conf->rx = TRUE; + conf->tx = TRUE; + + sa_key = ieee802_1x_kay_init_data_key(conf); + if (!sa_key) { + os_free(unwrap_sak); + os_free(conf->key); + os_free(conf); + return -1; + } + + dl_list_add(&participant->sak_list, &sa_key->list); + + ieee802_1x_cp_set_ciphersuite( + participant->kay->cp, + cipher_suite_tbl[participant->kay->macsec_csindex].id); + ieee802_1x_cp_sm_step(participant->kay->cp); + ieee802_1x_cp_set_offset(participant->kay->cp, body->confid_offset); + ieee802_1x_cp_sm_step(participant->kay->cp); + ieee802_1x_cp_set_distributedki(participant->kay->cp, &sak_ki); + ieee802_1x_cp_set_distributedan(participant->kay->cp, body->dan); + ieee802_1x_cp_signal_newsak(participant->kay->cp); + ieee802_1x_cp_sm_step(participant->kay->cp); + + participant->to_use_sak = TRUE; + + os_free(unwrap_sak); + os_free(conf->key); + os_free(conf); + + return 0; +} + + +/** + * ieee802_1x_mka_icv_body_present + */ +static Boolean +ieee802_1x_mka_icv_body_present(struct ieee802_1x_mka_participant *participant) +{ + return TRUE; +} + + +/** + * ieee802_1x_kay_get_icv_length + */ +static int +ieee802_1x_mka_get_icv_length(struct ieee802_1x_mka_participant *participant) +{ + int length; + + length = sizeof(struct ieee802_1x_mka_icv_body); + length += mka_alg_tbl[participant->kay->mka_algindex].icv_len; + + return (length + 0x3) & ~0x3; +} + + +/** + * ieee802_1x_mka_encode_icv_body - + */ +static int +ieee802_1x_mka_encode_icv_body(struct ieee802_1x_mka_participant *participant, + struct wpabuf *buf) +{ + struct ieee802_1x_mka_icv_body *body; + unsigned int length; + u8 cmac[MAX_ICV_LEN]; + + length = ieee802_1x_mka_get_icv_length(participant); + if (length != DEFAULT_ICV_LEN) { + body = wpabuf_put(buf, MKA_HDR_LEN); + body->type = MKA_ICV_INDICATOR; + set_mka_param_body_len(body, length - MKA_HDR_LEN); + } + + if (mka_alg_tbl[participant->kay->mka_algindex].icv_hash( + participant->ick.key, wpabuf_head(buf), buf->used, cmac)) { + wpa_printf(MSG_ERROR, "KaY, omac1_aes_128 failed"); + return -1; + } + + if (length != DEFAULT_ICV_LEN) { + os_memcpy(wpabuf_put(buf, length - MKA_HDR_LEN), cmac, + length - MKA_HDR_LEN); + } else { + os_memcpy(wpabuf_put(buf, length), cmac, length); + } + + return 0; +} + +/** + * ieee802_1x_mka_decode_icv_body - + */ +static u8 * +ieee802_1x_mka_decode_icv_body(struct ieee802_1x_mka_participant *participant, + const u8 *mka_msg, size_t msg_len) +{ + struct ieee802_1x_mka_hdr *hdr; + struct ieee802_1x_mka_icv_body *body; + size_t body_len; + size_t left_len; + int body_type; + const u8 *pos; + + pos = mka_msg; + left_len = msg_len; + while (left_len > (MKA_HDR_LEN + DEFAULT_ICV_LEN)) { + hdr = (struct ieee802_1x_mka_hdr *) pos; + body_len = get_mka_param_body_len(hdr); + body_type = get_mka_param_body_type(hdr); + + if (left_len < (body_len + MKA_HDR_LEN)) + break; + + if (body_type != MKA_ICV_INDICATOR) { + left_len -= MKA_HDR_LEN + body_len; + pos += MKA_HDR_LEN + body_len; + continue; + } + + body = (struct ieee802_1x_mka_icv_body *)pos; + if (body_len + < mka_alg_tbl[participant->kay->mka_algindex].icv_len) { + return NULL; + } + + return body->icv; + } + + return (u8 *) (mka_msg + msg_len - DEFAULT_ICV_LEN); +} + + +/** + * ieee802_1x_mka_decode_dist_cak_body- + */ +static int +ieee802_1x_mka_decode_dist_cak_body( + struct ieee802_1x_mka_participant *participant, + const u8 *mka_msg, size_t msg_len) +{ + struct ieee802_1x_mka_hdr *hdr; + size_t body_len; + + hdr = (struct ieee802_1x_mka_hdr *) mka_msg; + body_len = get_mka_param_body_len(hdr); + if (body_len < 28) { + wpa_printf(MSG_ERROR, + "KaY: MKA Use SAK Packet Body Length (%d bytes) should be 28 or more octets", + (int) body_len); + return -1; + } + + return 0; +} + + +/** + * ieee802_1x_mka_decode_kmd_body - + */ +static int +ieee802_1x_mka_decode_kmd_body( + struct ieee802_1x_mka_participant *participant, + const u8 *mka_msg, size_t msg_len) +{ + struct ieee802_1x_mka_hdr *hdr; + size_t body_len; + + hdr = (struct ieee802_1x_mka_hdr *) mka_msg; + body_len = get_mka_param_body_len(hdr); + if (body_len < 5) { + wpa_printf(MSG_ERROR, + "KaY: MKA Use SAK Packet Body Length (%d bytes) should be 5 or more octets", + (int) body_len); + return -1; + } + + return 0; +} + + +/** + * ieee802_1x_mka_decode_announce_body - + */ +static int ieee802_1x_mka_decode_announce_body( + struct ieee802_1x_mka_participant *participant, + const u8 *mka_msg, size_t msg_len) +{ + return 0; +} + + +static struct mka_param_body_handler mak_body_handler[] = { + /* basic parameter set */ + { + ieee802_1x_mka_encode_basic_body, + NULL, + ieee802_1x_mka_basic_body_length, + ieee802_1x_mka_basic_body_present + }, + + /* live peer list parameter set */ + { + ieee802_1x_mka_encode_live_peer_body, + ieee802_1x_mka_decode_live_peer_body, + ieee802_1x_mka_get_live_peer_length, + ieee802_1x_mka_live_peer_body_present + }, + + /* potential peer list parameter set */ + { + ieee802_1x_mka_encode_potential_peer_body, + ieee802_1x_mka_decode_potential_peer_body, + ieee802_1x_mka_get_potential_peer_length, + ieee802_1x_mka_potential_peer_body_present + }, + + /* sak use parameter set */ + { + ieee802_1x_mka_encode_sak_use_body, + ieee802_1x_mka_decode_sak_use_body, + ieee802_1x_mka_get_sak_use_length, + ieee802_1x_mka_sak_use_body_present + }, + + /* distribute sak parameter set */ + { + ieee802_1x_mka_encode_dist_sak_body, + ieee802_1x_mka_decode_dist_sak_body, + ieee802_1x_mka_get_dist_sak_length, + ieee802_1x_mka_dist_sak_body_present + }, + + /* distribute cak parameter set */ + { + NULL, + ieee802_1x_mka_decode_dist_cak_body, + NULL, + NULL + }, + + /* kmd parameter set */ + { + NULL, + ieee802_1x_mka_decode_kmd_body, + NULL, + NULL + }, + + /* announce parameter set */ + { + NULL, + ieee802_1x_mka_decode_announce_body, + NULL, + NULL + }, + + /* icv parameter set */ + { + ieee802_1x_mka_encode_icv_body, + NULL, + ieee802_1x_mka_get_icv_length, + ieee802_1x_mka_icv_body_present + }, +}; + + +/** + * ieee802_1x_kay_deinit_data_key - + */ +void ieee802_1x_kay_deinit_data_key(struct data_key *pkey) +{ + if (!pkey) + return; + + pkey->user--; + if (pkey->user > 1) + return; + + dl_list_del(&pkey->list); + os_free(pkey->key); + os_free(pkey); +} + + +/** + * ieee802_1x_kay_generate_new_sak - + */ +static int +ieee802_1x_kay_generate_new_sak(struct ieee802_1x_mka_participant *participant) +{ + struct data_key *sa_key = NULL; + struct key_conf *conf; + struct ieee802_1x_kay_peer *peer; + struct ieee802_1x_kay *kay = participant->kay; + int ctx_len, ctx_offset; + u8 *context; + + /* check condition for generating a fresh SAK: + * must have one live peer + * and MKA life time elapse since last distribution + * or potential peer is empty + */ + if (dl_list_empty(&participant->live_peers)) { + wpa_printf(MSG_ERROR, + "KaY: Live peers list must not empty when generating fresh SAK"); + return -1; + } + + /* FIXME: A fresh SAK not generated until + * the live peer list contains at least one peer and + * MKA life time has elapsed since the prior SAK was first distributed, + * or the Key server's potential peer is empty + * but I can't understand the second item, so + * here only check first item and ingore + * && (!dl_list_empty(&participant->potential_peers))) { + */ + if ((time(NULL) - kay->dist_time) < MKA_LIFE_TIME / 1000) { + wpa_printf(MSG_ERROR, + "KaY: Life time have not elapsed since prior SAK distributed"); + return -1; + } + + conf = os_zalloc(sizeof(*conf)); + if (!conf) { + wpa_printf(MSG_ERROR, "KaY-%s: Out of memory", __func__); + return -1; + } + conf->key_len = cipher_suite_tbl[kay->macsec_csindex].sak_len; + + conf->key = os_zalloc(conf->key_len); + if (!conf->key) { + os_free(conf); + wpa_printf(MSG_ERROR, "KaY-%s: Out of memory", __func__); + return -1; + } + + ctx_len = conf->key_len + sizeof(kay->dist_kn); + dl_list_for_each(peer, &participant->live_peers, + struct ieee802_1x_kay_peer, list) + ctx_len += sizeof(peer->mi); + ctx_len += sizeof(participant->mi); + + context = os_zalloc(ctx_len); + if (!context) { + os_free(conf->key); + os_free(conf); + return -1; + } + ctx_offset = 0; + os_get_random(context + ctx_offset, conf->key_len); + ctx_offset += conf->key_len; + dl_list_for_each(peer, &participant->live_peers, + struct ieee802_1x_kay_peer, list) { + os_memcpy(context + ctx_offset, peer->mi, sizeof(peer->mi)); + ctx_offset += sizeof(peer->mi); + } + os_memcpy(context + ctx_offset, participant->mi, + sizeof(participant->mi)); + ctx_offset += sizeof(participant->mi); + os_memcpy(context + ctx_offset, &kay->dist_kn, sizeof(kay->dist_kn)); + + if (conf->key_len == 16) { + ieee802_1x_sak_128bits_aes_cmac(participant->cak.key, + context, ctx_len, conf->key); + } else if (conf->key_len == 32) { + ieee802_1x_sak_128bits_aes_cmac(participant->cak.key, + context, ctx_len, conf->key); + } else { + wpa_printf(MSG_ERROR, "KaY: SAK Length not support"); + os_free(conf->key); + os_free(conf); + os_free(context); + return -1; + } + wpa_hexdump(MSG_DEBUG, "KaY: generated new SAK", + conf->key, conf->key_len); + + os_memcpy(conf->ki.mi, participant->mi, MI_LEN); + conf->ki.kn = participant->kay->dist_kn; + conf->an = participant->kay->dist_an; + conf->offset = kay->macsec_confidentiality; + conf->rx = TRUE; + conf->tx = TRUE; + + sa_key = ieee802_1x_kay_init_data_key(conf); + if (!sa_key) { + os_free(conf->key); + os_free(conf); + os_free(context); + return -1; + } + participant->new_key = sa_key; + + dl_list_add(&participant->sak_list, &sa_key->list); + ieee802_1x_cp_set_ciphersuite(participant->kay->cp, + cipher_suite_tbl[kay->macsec_csindex].id); + ieee802_1x_cp_sm_step(kay->cp); + ieee802_1x_cp_set_offset(kay->cp, conf->offset); + ieee802_1x_cp_sm_step(kay->cp); + ieee802_1x_cp_set_distributedki(kay->cp, &conf->ki); + ieee802_1x_cp_set_distributedan(kay->cp, conf->an); + ieee802_1x_cp_signal_newsak(kay->cp); + ieee802_1x_cp_sm_step(kay->cp); + + dl_list_for_each(peer, &participant->live_peers, + struct ieee802_1x_kay_peer, list) + peer->sak_used = FALSE; + + participant->kay->dist_kn++; + participant->kay->dist_an++; + if (participant->kay->dist_an > 3) + participant->kay->dist_an = 0; + + participant->kay->dist_time = time(NULL); + + os_free(conf->key); + os_free(conf); + os_free(context); + return 0; +} + + +/** + * ieee802_1x_kay_elect_key_server - elect the key server + * when to elect: whenever the live peers list changes + */ +static int +ieee802_1x_kay_elect_key_server(struct ieee802_1x_mka_participant *participant) +{ + struct ieee802_1x_kay_peer *peer; + struct ieee802_1x_kay_peer *key_server = NULL; + struct ieee802_1x_kay *kay = participant->kay; + Boolean i_is_key_server; + int i; + + if (participant->is_obliged_key_server) { + participant->new_sak = TRUE; + participant->to_dist_sak = FALSE; + ieee802_1x_cp_set_electedself(kay->cp, TRUE); + return 0; + } + + /* elect the key server among the peers */ + dl_list_for_each(peer, &participant->live_peers, + struct ieee802_1x_kay_peer, list) { + if (!peer->is_key_server) + continue; + + if (!key_server) { + key_server = peer; + continue; + } + + if (peer->key_server_priority < + key_server->key_server_priority) { + key_server = peer; + } else if (peer->key_server_priority == + key_server->key_server_priority) { + for (i = 0; i < 6; i++) { + if (peer->sci.addr[i] < + key_server->sci.addr[i]) + key_server = peer; + } + } + } + + /* elect the key server between me and the above elected peer */ + i_is_key_server = FALSE; + if (key_server && participant->can_be_key_server) { + if (kay->actor_priority + < key_server->key_server_priority) { + i_is_key_server = TRUE; + } else if (kay->actor_priority + == key_server->key_server_priority) { + for (i = 0; i < 6; i++) { + if (kay->actor_sci.addr[i] + < key_server->sci.addr[i]) { + i_is_key_server = TRUE; + } + } + } + } + + if (!key_server && !i_is_key_server) { + participant->principal = FALSE; + participant->is_key_server = FALSE; + participant->is_elected = FALSE; + return 0; + } + + if (i_is_key_server) { + ieee802_1x_cp_set_electedself(kay->cp, TRUE); + if (os_memcmp(&kay->key_server_sci, &kay->actor_sci, + sizeof(kay->key_server_sci))) { + ieee802_1x_cp_signal_chgdserver(kay->cp); + ieee802_1x_cp_sm_step(kay->cp); + } + + participant->is_key_server = TRUE; + participant->principal = TRUE; + participant->new_sak = TRUE; + wpa_printf(MSG_DEBUG, "KaY: I is elected as key server"); + participant->to_dist_sak = FALSE; + participant->is_elected = TRUE; + + os_memcpy(&kay->key_server_sci, &kay->actor_sci, + sizeof(kay->key_server_sci)); + kay->key_server_priority = kay->actor_priority; + } + + if (key_server) { + ieee802_1x_cp_set_electedself(kay->cp, FALSE); + if (os_memcmp(&kay->key_server_sci, &key_server->sci, + sizeof(kay->key_server_sci))) { + ieee802_1x_cp_signal_chgdserver(kay->cp); + ieee802_1x_cp_sm_step(kay->cp); + } + + participant->is_key_server = FALSE; + participant->principal = TRUE; + participant->is_elected = TRUE; + + os_memcpy(&kay->key_server_sci, &key_server->sci, + sizeof(kay->key_server_sci)); + kay->key_server_priority = key_server->key_server_priority; + } + + return 0; +} + + +/** + * ieee802_1x_kay_decide_macsec_use - the key server determinate + * how to use MACsec: whether use MACsec and its capability + * protectFrames will be advised if the key server and one of its live peers are + * MACsec capable and one of those request MACsec protection + */ +static int +ieee802_1x_kay_decide_macsec_use( + struct ieee802_1x_mka_participant *participant) +{ + struct ieee802_1x_kay *kay = participant->kay; + struct ieee802_1x_kay_peer *peer; + enum macsec_cap less_capability; + Boolean has_peer; + + if (!participant->is_key_server) + return -1; + + /* key server self is MACsec-desired and requesting MACsec */ + if (!kay->macsec_desired) { + participant->advised_desired = FALSE; + return -1; + } + if (kay->macsec_capable == MACSEC_CAP_NOT_IMPLEMENTED) { + participant->advised_desired = FALSE; + return -1; + } + less_capability = kay->macsec_capable; + + /* at least one of peers is MACsec-desired and requesting MACsec */ + has_peer = FALSE; + dl_list_for_each(peer, &participant->live_peers, + struct ieee802_1x_kay_peer, list) { + if (!peer->macsec_desired) + continue; + + if (peer->macsec_capbility == MACSEC_CAP_NOT_IMPLEMENTED) + continue; + + less_capability = (less_capability < peer->macsec_capbility) ? + less_capability : peer->macsec_capbility; + has_peer = TRUE; + } + + if (has_peer) { + participant->advised_desired = TRUE; + participant->advised_capability = less_capability; + kay->authenticated = FALSE; + kay->secured = TRUE; + kay->failed = FALSE; + ieee802_1x_cp_connect_secure(kay->cp); + ieee802_1x_cp_sm_step(kay->cp); + } else { + participant->advised_desired = FALSE; + participant->advised_capability = MACSEC_CAP_NOT_IMPLEMENTED; + participant->to_use_sak = FALSE; + kay->authenticated = TRUE; + kay->secured = FALSE; + kay->failed = FALSE; + kay->ltx_kn = 0; + kay->ltx_an = 0; + kay->lrx_kn = 0; + kay->lrx_an = 0; + kay->otx_kn = 0; + kay->otx_an = 0; + kay->orx_kn = 0; + kay->orx_an = 0; + ieee802_1x_cp_connect_authenticated(kay->cp); + ieee802_1x_cp_sm_step(kay->cp); + } + + return 0; +} + +static const u8 pae_group_addr[ETH_ALEN] = { + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 +}; + + +/** + * ieee802_1x_kay_encode_mkpdu - + */ +static int +ieee802_1x_kay_encode_mkpdu(struct ieee802_1x_mka_participant *participant, + struct wpabuf *pbuf) +{ + unsigned int i; + struct ieee8023_hdr *ether_hdr; + struct ieee802_1x_hdr *eapol_hdr; + + ether_hdr = wpabuf_put(pbuf, sizeof(*ether_hdr)); + os_memcpy(ether_hdr->dest, pae_group_addr, sizeof(ether_hdr->dest)); + os_memcpy(ether_hdr->src, participant->kay->actor_sci.addr, + sizeof(ether_hdr->dest)); + ether_hdr->ethertype = host_to_be16(ETH_P_EAPOL); + + eapol_hdr = wpabuf_put(pbuf, sizeof(*eapol_hdr)); + eapol_hdr->version = EAPOL_VERSION; + eapol_hdr->type = IEEE802_1X_TYPE_EAPOL_MKA; + eapol_hdr->length = host_to_be16(pbuf->size - pbuf->used); + + for (i = 0; i < ARRAY_SIZE(mak_body_handler); i++) { + if (mak_body_handler[i].body_present && + mak_body_handler[i].body_present(participant)) { + if (mak_body_handler[i].body_tx(participant, pbuf)) + return -1; + } + } + + return 0; +} + +/** + * ieee802_1x_participant_send_mkpdu - + */ +static int +ieee802_1x_participant_send_mkpdu( + struct ieee802_1x_mka_participant *participant) +{ + struct wpabuf *buf; + struct ieee802_1x_kay *kay = participant->kay; + size_t length = 0; + unsigned int i; + + wpa_printf(MSG_DEBUG, "KaY: to enpacket and send the MKPDU"); + length += sizeof(struct ieee802_1x_hdr) + sizeof(struct ieee8023_hdr); + for (i = 0; i < ARRAY_SIZE(mak_body_handler); i++) { + if (mak_body_handler[i].body_present && + mak_body_handler[i].body_present(participant)) + length += mak_body_handler[i].body_length(participant); + } + + buf = wpabuf_alloc(length); + if (!buf) { + wpa_printf(MSG_ERROR, "KaY: out of memory"); + return -1; + } + + if (ieee802_1x_kay_encode_mkpdu(participant, buf)) { + wpa_printf(MSG_ERROR, "KaY: encode mkpdu fail!"); + return -1; + } + + l2_packet_send(kay->l2_mka, NULL, 0, wpabuf_head(buf), wpabuf_len(buf)); + wpabuf_free(buf); + + kay->active = TRUE; + participant->active = TRUE; + + return 0; +} + + +static void ieee802_1x_kay_deinit_transmit_sa(struct transmit_sa *psa); +/** + * ieee802_1x_participant_timer - + */ +static void ieee802_1x_participant_timer(void *eloop_ctx, void *timeout_ctx) +{ + struct ieee802_1x_mka_participant *participant; + struct ieee802_1x_kay *kay; + struct ieee802_1x_kay_peer *peer, *pre_peer; + time_t now = time(NULL); + Boolean lp_changed; + struct receive_sc *rxsc, *pre_rxsc; + struct transmit_sa *txsa, *pre_txsa; + + participant = (struct ieee802_1x_mka_participant *)eloop_ctx; + kay = participant->kay; + if (participant->cak_life) { + if (now > participant->cak_life) { + kay->authenticated = FALSE; + kay->secured = FALSE; + kay->failed = TRUE; + ieee802_1x_kay_delete_mka(kay, &participant->ckn); + return; + } + } + + /* should delete MKA instance if there are not live peers + * when the MKA life elapsed since its creating */ + if (participant->mka_life) { + if (dl_list_empty(&participant->live_peers)) { + if (now > participant->mka_life) { + kay->authenticated = FALSE; + kay->secured = FALSE; + kay->failed = TRUE; + ieee802_1x_kay_delete_mka(kay, + &participant->ckn); + return; + } + } else { + participant->mka_life = 0; + } + } + + lp_changed = FALSE; + dl_list_for_each_safe(peer, pre_peer, &participant->live_peers, + struct ieee802_1x_kay_peer, list) { + if (now > peer->expire) { + wpa_printf(MSG_DEBUG, "KaY: Live peer removed"); + wpa_hexdump(MSG_DEBUG, "\tMI: ", peer->mi, + sizeof(peer->mi)); + wpa_printf(MSG_DEBUG, "\tMN: %d", peer->mn); + dl_list_for_each_safe(rxsc, pre_rxsc, + &participant->rxsc_list, + struct receive_sc, list) { + if (os_memcmp(&rxsc->sci, &peer->sci, + sizeof(rxsc->sci)) == 0) { + secy_delete_receive_sc(kay, rxsc); + ieee802_1x_kay_deinit_receive_sc( + participant, rxsc); + } + } + dl_list_del(&peer->list); + os_free(peer); + lp_changed = TRUE; + } + } + + if (lp_changed) { + if (dl_list_empty(&participant->live_peers)) { + participant->advised_desired = FALSE; + participant->advised_capability = + MACSEC_CAP_NOT_IMPLEMENTED; + participant->to_use_sak = FALSE; + kay->authenticated = TRUE; + kay->secured = FALSE; + kay->failed = FALSE; + kay->ltx_kn = 0; + kay->ltx_an = 0; + kay->lrx_kn = 0; + kay->lrx_an = 0; + kay->otx_kn = 0; + kay->otx_an = 0; + kay->orx_kn = 0; + kay->orx_an = 0; + dl_list_for_each_safe(txsa, pre_txsa, + &participant->txsc->sa_list, + struct transmit_sa, list) { + secy_disable_transmit_sa(kay, txsa); + ieee802_1x_kay_deinit_transmit_sa(txsa); + } + + ieee802_1x_cp_connect_authenticated(kay->cp); + ieee802_1x_cp_sm_step(kay->cp); + } else { + ieee802_1x_kay_elect_key_server(participant); + ieee802_1x_kay_decide_macsec_use(participant); + } + } + + dl_list_for_each_safe(peer, pre_peer, &participant->potential_peers, + struct ieee802_1x_kay_peer, list) { + if (now > peer->expire) { + wpa_printf(MSG_DEBUG, "KaY: Potential peer removed"); + wpa_hexdump(MSG_DEBUG, "\tMI: ", peer->mi, + sizeof(peer->mi)); + wpa_printf(MSG_DEBUG, "\tMN: %d", peer->mn); + dl_list_del(&peer->list); + os_free(peer); + } + } + + if (participant->new_sak) { + if (!ieee802_1x_kay_generate_new_sak(participant)) + participant->to_dist_sak = TRUE; + + participant->new_sak = FALSE; + } + + if (participant->retry_count < MAX_RETRY_CNT) { + ieee802_1x_participant_send_mkpdu(participant); + participant->retry_count++; + } + + eloop_register_timeout(MKA_HELLO_TIME / 1000, 0, + ieee802_1x_participant_timer, + participant, NULL); +} + + +/** + * ieee802_1x_kay_init_transmit_sa - + */ +static struct transmit_sa * +ieee802_1x_kay_init_transmit_sa(struct transmit_sc *psc, u8 an, u32 next_PN, + struct data_key *key) +{ + struct transmit_sa *psa; + + key->tx_latest = TRUE; + key->rx_latest = TRUE; + + psa = os_zalloc(sizeof(*psa)); + if (!psa) { + wpa_printf(MSG_ERROR, "%s: out of memory", __func__); + return NULL; + } + + if (key->confidentiality_offset >= CONFIDENTIALITY_OFFSET_0 && + key->confidentiality_offset <= CONFIDENTIALITY_OFFSET_50) + psa->confidentiality = TRUE; + else + psa->confidentiality = FALSE; + + psa->an = an; + psa->pkey = key; + psa->next_pn = next_PN; + psa->sc = psc; + + os_get_time(&psa->created_time); + psa->in_use = FALSE; + + dl_list_add(&psc->sa_list, &psa->list); + wpa_printf(MSG_DEBUG, + "KaY: Create transmit SA(an: %d, next_PN: %u) of SC(channel: %d)", + (int) an, next_PN, psc->channel); + + return psa; +} + + +/** + * ieee802_1x_kay_deinit_transmit_sa - + */ +static void ieee802_1x_kay_deinit_transmit_sa(struct transmit_sa *psa) +{ + psa->pkey = NULL; + wpa_printf(MSG_DEBUG, + "KaY: Delete transmit SA(an: %d) of SC(channel: %d)", + psa->an, psa->sc->channel); + dl_list_del(&psa->list); + os_free(psa); +} + + +/** + * init_transmit_sc - + */ +static struct transmit_sc * +ieee802_1x_kay_init_transmit_sc(const struct ieee802_1x_mka_sci *sci, + int channel) +{ + struct transmit_sc *psc; + + psc = os_zalloc(sizeof(*psc)); + if (!psc) { + wpa_printf(MSG_ERROR, "%s: out of memory", __func__); + return NULL; + } + os_memcpy(&psc->sci, sci, sizeof(psc->sci)); + psc->channel = channel; + + os_get_time(&psc->created_time); + psc->transmitting = FALSE; + psc->encoding_sa = FALSE; + psc->enciphering_sa = FALSE; + + dl_list_init(&psc->sa_list); + wpa_printf(MSG_DEBUG, "KaY: Create transmit SC(channel: %d)", channel); + wpa_hexdump(MSG_DEBUG, "SCI: ", (u8 *)sci , sizeof(*sci)); + + return psc; +} + + +/** + * ieee802_1x_kay_deinit_transmit_sc - + */ +static void +ieee802_1x_kay_deinit_transmit_sc( + struct ieee802_1x_mka_participant *participant, struct transmit_sc *psc) +{ + struct transmit_sa *psa, *tmp; + + wpa_printf(MSG_DEBUG, "KaY: Delete transmit SC(channel: %d)", + psc->channel); + dl_list_for_each_safe(psa, tmp, &psc->sa_list, struct transmit_sa, + list) { + secy_disable_transmit_sa(participant->kay, psa); + ieee802_1x_kay_deinit_transmit_sa(psa); + } + + os_free(psc); +} + + +/****************** Interface between CP and KAY *********************/ +/** + * ieee802_1x_kay_set_latest_sa_attr - + */ +int ieee802_1x_kay_set_latest_sa_attr(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *lki, u8 lan, + Boolean ltx, Boolean lrx) +{ + struct ieee802_1x_mka_participant *principal; + + principal = ieee802_1x_kay_get_principal_participant(kay); + if (!principal) + return -1; + + if (!lki) + os_memset(&principal->lki, 0, sizeof(principal->lki)); + else + os_memcpy(&principal->lki, lki, sizeof(principal->lki)); + + principal->lan = lan; + principal->ltx = ltx; + principal->lrx = lrx; + if (!lki) { + kay->ltx_kn = 0; + kay->lrx_kn = 0; + } else { + kay->ltx_kn = lki->kn; + kay->lrx_kn = lki->kn; + } + kay->ltx_an = lan; + kay->lrx_an = lan; + + return 0; +} + + +/** + * ieee802_1x_kay_set_old_sa_attr - + */ +int ieee802_1x_kay_set_old_sa_attr(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *oki, + u8 oan, Boolean otx, Boolean orx) +{ + struct ieee802_1x_mka_participant *principal; + + principal = ieee802_1x_kay_get_principal_participant(kay); + if (!principal) + return -1; + + if (!oki) + os_memset(&principal->oki, 0, sizeof(principal->oki)); + else + os_memcpy(&principal->oki, oki, sizeof(principal->oki)); + + principal->oan = oan; + principal->otx = otx; + principal->orx = orx; + + if (!oki) { + kay->otx_kn = 0; + kay->orx_kn = 0; + } else { + kay->otx_kn = oki->kn; + kay->orx_kn = oki->kn; + } + kay->otx_an = oan; + kay->orx_an = oan; + + return 0; +} + + +/** + * ieee802_1x_kay_create_sas - + */ +int ieee802_1x_kay_create_sas(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *lki) +{ + struct data_key *sa_key, *latest_sak; + struct ieee802_1x_mka_participant *principal; + struct receive_sc *rxsc; + struct receive_sa *rxsa; + struct transmit_sa *txsa; + + principal = ieee802_1x_kay_get_principal_participant(kay); + if (!principal) + return -1; + + latest_sak = NULL; + dl_list_for_each(sa_key, &principal->sak_list, struct data_key, list) { + if (is_ki_equal(&sa_key->key_identifier, lki)) { + sa_key->rx_latest = TRUE; + sa_key->tx_latest = TRUE; + latest_sak = sa_key; + principal->to_use_sak = TRUE; + } else { + sa_key->rx_latest = FALSE; + sa_key->tx_latest = FALSE; + } + } + if (!latest_sak) { + wpa_printf(MSG_ERROR, "lki related sak not found"); + return -1; + } + + dl_list_for_each(rxsc, &principal->rxsc_list, struct receive_sc, list) { + rxsa = ieee802_1x_kay_init_receive_sa(rxsc, latest_sak->an, 1, + latest_sak); + if (!rxsa) + return -1; + + secy_create_receive_sa(kay, rxsa); + } + + txsa = ieee802_1x_kay_init_transmit_sa(principal->txsc, latest_sak->an, + 1, latest_sak); + if (!txsa) + return -1; + + secy_create_transmit_sa(kay, txsa); + + + + return 0; +} + + +/** + * ieee802_1x_kay_delete_sas - + */ +int ieee802_1x_kay_delete_sas(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *ki) +{ + struct data_key *sa_key, *pre_key; + struct transmit_sa *txsa, *pre_txsa; + struct receive_sa *rxsa, *pre_rxsa; + struct receive_sc *rxsc; + struct ieee802_1x_mka_participant *principal; + + wpa_printf(MSG_DEBUG, "KaY: Entry into %s", __func__); + principal = ieee802_1x_kay_get_principal_participant(kay); + if (!principal) + return -1; + + /* remove the transmit sa */ + dl_list_for_each_safe(txsa, pre_txsa, &principal->txsc->sa_list, + struct transmit_sa, list) { + if (is_ki_equal(&txsa->pkey->key_identifier, ki)) { + secy_disable_transmit_sa(kay, txsa); + ieee802_1x_kay_deinit_transmit_sa(txsa); + } + } + + /* remove the receive sa */ + dl_list_for_each(rxsc, &principal->rxsc_list, struct receive_sc, list) { + dl_list_for_each_safe(rxsa, pre_rxsa, &rxsc->sa_list, + struct receive_sa, list) { + if (is_ki_equal(&rxsa->pkey->key_identifier, ki)) { + secy_disable_receive_sa(kay, rxsa); + ieee802_1x_kay_deinit_receive_sa(rxsa); + } + } + } + + /* remove the sak */ + dl_list_for_each_safe(sa_key, pre_key, &principal->sak_list, + struct data_key, list) { + if (is_ki_equal(&sa_key->key_identifier, ki)) { + ieee802_1x_kay_deinit_data_key(sa_key); + break; + } + if (principal->new_key == sa_key) + principal->new_key = NULL; + } + + return 0; +} + + +/** + * ieee802_1x_kay_enable_tx_sas - + */ +int ieee802_1x_kay_enable_tx_sas(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *lki) +{ + struct ieee802_1x_mka_participant *principal; + struct transmit_sa *txsa; + + principal = ieee802_1x_kay_get_principal_participant(kay); + if (!principal) + return -1; + + dl_list_for_each(txsa, &principal->txsc->sa_list, struct transmit_sa, + list) { + if (is_ki_equal(&txsa->pkey->key_identifier, lki)) { + txsa->in_use = TRUE; + secy_enable_transmit_sa(kay, txsa); + ieee802_1x_cp_set_usingtransmitas( + principal->kay->cp, TRUE); + ieee802_1x_cp_sm_step(principal->kay->cp); + } + } + + return 0; +} + + +/** + * ieee802_1x_kay_enable_rx_sas - + */ +int ieee802_1x_kay_enable_rx_sas(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *lki) +{ + struct ieee802_1x_mka_participant *principal; + struct receive_sa *rxsa; + struct receive_sc *rxsc; + + principal = ieee802_1x_kay_get_principal_participant(kay); + if (!principal) + return -1; + + dl_list_for_each(rxsc, &principal->rxsc_list, struct receive_sc, list) { + dl_list_for_each(rxsa, &rxsc->sa_list, struct receive_sa, list) + { + if (is_ki_equal(&rxsa->pkey->key_identifier, lki)) { + rxsa->in_use = TRUE; + secy_enable_receive_sa(kay, rxsa); + ieee802_1x_cp_set_usingreceivesas( + principal->kay->cp, TRUE); + ieee802_1x_cp_sm_step(principal->kay->cp); + } + } + } + + return 0; +} + + +/** + * ieee802_1x_kay_enable_new_info - + */ +int ieee802_1x_kay_enable_new_info(struct ieee802_1x_kay *kay) +{ + struct ieee802_1x_mka_participant *principal; + + principal = ieee802_1x_kay_get_principal_participant(kay); + if (!principal) + return -1; + + if (principal->retry_count < MAX_RETRY_CNT) { + ieee802_1x_participant_send_mkpdu(principal); + principal->retry_count++; + } + + return 0; +} + + +/** + * ieee802_1x_kay_cp_conf - + */ +int ieee802_1x_kay_cp_conf(struct ieee802_1x_kay *kay, + struct ieee802_1x_cp_conf *pconf) +{ + pconf->protect = kay->macsec_protect; + pconf->replay_protect = kay->macsec_replay_protect; + pconf->validate = kay->macsec_validate; + + return 0; +} + + +/** + * ieee802_1x_kay_alloc_cp_sm - + */ +static struct ieee802_1x_cp_sm * +ieee802_1x_kay_alloc_cp_sm(struct ieee802_1x_kay *kay) +{ + struct ieee802_1x_cp_conf conf; + + os_memset(&conf, 0, sizeof(conf)); + conf.protect = kay->macsec_protect; + conf.replay_protect = kay->macsec_replay_protect; + conf.validate = kay->macsec_validate; + conf.replay_window = kay->macsec_replay_window; + + return ieee802_1x_cp_sm_init(kay, &conf); +} + + +/** + * ieee802_1x_kay_mkpdu_sanity_check - + * sanity check specified in clause 11.11.2 of IEEE802.1X-2010 + */ +static int ieee802_1x_kay_mkpdu_sanity_check(struct ieee802_1x_kay *kay, + const u8 *buf, size_t len) +{ + struct ieee8023_hdr *eth_hdr; + struct ieee802_1x_hdr *eapol_hdr; + struct ieee802_1x_mka_hdr *mka_hdr; + struct ieee802_1x_mka_basic_body *body; + size_t mka_msg_len; + struct ieee802_1x_mka_participant *participant; + size_t body_len; + u8 icv[MAX_ICV_LEN]; + u8 *msg_icv; + + eth_hdr = (struct ieee8023_hdr *) buf; + eapol_hdr = (struct ieee802_1x_hdr *) (eth_hdr + 1); + mka_hdr = (struct ieee802_1x_mka_hdr *) (eapol_hdr + 1); + + /* destination address should be not individual address */ + if (os_memcmp(eth_hdr->dest, pae_group_addr, ETH_ALEN) != 0) { + wpa_printf(MSG_MSGDUMP, + "KaY: ethernet destination address is not PAE group address"); + return -1; + } + + /* MKPDU should not less than 32 octets */ + mka_msg_len = be_to_host16(eapol_hdr->length); + if (mka_msg_len < 32) { + wpa_printf(MSG_MSGDUMP, "KaY: MKPDU is less than 32 octets"); + return -1; + } + /* MKPDU should multiple 4 octets */ + if ((mka_msg_len % 4) != 0) { + wpa_printf(MSG_MSGDUMP, + "KaY: MKPDU is not multiple of 4 octets"); + return -1; + } + + body = (struct ieee802_1x_mka_basic_body *) mka_hdr; + ieee802_1x_mka_dump_basic_body(body); + body_len = get_mka_param_body_len(body); + /* EAPOL-MKA body should comprise basic parameter set and ICV */ + if (mka_msg_len < MKA_HDR_LEN + body_len + DEFAULT_ICV_LEN) { + wpa_printf(MSG_ERROR, + "KaY: Received EAPOL-MKA Packet Body Length (%d bytes) is less than the Basic Parameter Set Header Length (%d bytes) + the Basic Parameter Set Body Length (%d bytes) + %d bytes of ICV", + (int) mka_msg_len, (int) MKA_HDR_LEN, + (int) body_len, DEFAULT_ICV_LEN); + return -1; + } + + /* CKN should be owned by I */ + participant = ieee802_1x_kay_get_participant(kay, body->ckn); + if (!participant) { + wpa_printf(MSG_DEBUG, "CKN is not included in my CA"); + return -1; + } + + /* algorithm agility check */ + if (os_memcmp(body->algo_agility, mka_algo_agility, + sizeof(body->algo_agility)) != 0) { + wpa_printf(MSG_ERROR, + "KaY: peer's algorithm agility not supported for me"); + return -1; + } + + /* ICV check */ + /* + * The ICV will comprise the final octets of the packet body, whatever + * its size, not the fixed length 16 octets, indicated by the EAPOL + * packet body length. + */ + if (mka_alg_tbl[kay->mka_algindex].icv_hash( + participant->ick.key, + buf, len - mka_alg_tbl[kay->mka_algindex].icv_len, icv)) { + wpa_printf(MSG_ERROR, "KaY: omac1_aes_128 failed"); + return -1; + } + msg_icv = ieee802_1x_mka_decode_icv_body(participant, (u8 *) mka_hdr, + mka_msg_len); + + if (msg_icv) { + if (os_memcmp(msg_icv, icv, + mka_alg_tbl[kay->mka_algindex].icv_len) != 0) { + wpa_printf(MSG_ERROR, + "KaY: Computed ICV is not equal to Received ICV"); + return -1; + } + } else { + wpa_printf(MSG_ERROR, "KaY: No ICV"); + return -1; + } + + return 0; +} + + +/** + * ieee802_1x_kay_decode_mkpdu - + */ +static int ieee802_1x_kay_decode_mkpdu(struct ieee802_1x_kay *kay, + const u8 *buf, size_t len) +{ + struct ieee802_1x_mka_participant *participant; + struct ieee802_1x_mka_hdr *hdr; + size_t body_len; + size_t left_len; + int body_type; + int i; + const u8 *pos; + Boolean my_included; + Boolean handled[256]; + + if (ieee802_1x_kay_mkpdu_sanity_check(kay, buf, len)) + return -1; + + /* handle basic parameter set */ + pos = buf + sizeof(struct ieee8023_hdr) + sizeof(struct ieee802_1x_hdr); + left_len = len - sizeof(struct ieee8023_hdr) - + sizeof(struct ieee802_1x_hdr); + participant = ieee802_1x_mka_decode_basic_body(kay, pos, left_len); + if (!participant) + return -1; + + /* to skip basic parameter set */ + hdr = (struct ieee802_1x_mka_hdr *) pos; + body_len = get_mka_param_body_len(hdr); + pos += body_len + MKA_HDR_LEN; + left_len -= body_len + MKA_HDR_LEN; + + /* check i am in the peer's peer list */ + my_included = ieee802_1x_mka_i_in_peerlist(participant, pos, left_len); + if (my_included) { + /* accept the peer as live peer */ + if (!ieee802_1x_kay_is_in_peer( + participant, + participant->current_peer_id.mi)) { + if (!ieee802_1x_kay_create_live_peer( + participant, + participant->current_peer_id.mi, + participant->current_peer_id.mn)) + return -1; + ieee802_1x_kay_elect_key_server(participant); + ieee802_1x_kay_decide_macsec_use(participant); + } + if (ieee802_1x_kay_is_in_potential_peer( + participant, participant->current_peer_id.mi)) { + ieee802_1x_kay_move_live_peer( + participant, participant->current_peer_id.mi, + participant->current_peer_id.mn); + ieee802_1x_kay_elect_key_server(participant); + ieee802_1x_kay_decide_macsec_use(participant); + } + } + + /* + * Handle other parameter set than basic parameter set. + * Each parameter set should be present only once. + */ + for (i = 0; i < 256; i++) + handled[i] = FALSE; + + handled[0] = TRUE; + while (left_len > MKA_HDR_LEN + DEFAULT_ICV_LEN) { + hdr = (struct ieee802_1x_mka_hdr *) pos; + body_len = get_mka_param_body_len(hdr); + body_type = get_mka_param_body_type(hdr); + + if (body_type == MKA_ICV_INDICATOR) + return 0; + + if (left_len < (MKA_HDR_LEN + body_len + DEFAULT_ICV_LEN)) { + wpa_printf(MSG_ERROR, + "KaY: MKA Peer Packet Body Length (%d bytes) is less than the Parameter Set Header Length (%d bytes) + the Parameter Set Body Length (%d bytes) + %d bytes of ICV", + (int) left_len, (int) MKA_HDR_LEN, + (int) body_len, DEFAULT_ICV_LEN); + goto next_para_set; + } + + if (handled[body_type]) + goto next_para_set; + + handled[body_type] = TRUE; + if (mak_body_handler[body_type].body_rx) { + mak_body_handler[body_type].body_rx + (participant, pos, left_len); + } else { + wpa_printf(MSG_ERROR, + "The type %d not supported in this MKA version %d", + body_type, MKA_VERSION_ID); + } + +next_para_set: + pos += body_len + MKA_HDR_LEN; + left_len -= body_len + MKA_HDR_LEN; + } + + kay->active = TRUE; + participant->retry_count = 0; + participant->active = TRUE; + + return 0; +} + + + +static void kay_l2_receive(void *ctx, const u8 *src_addr, const u8 *buf, + size_t len) +{ + struct ieee802_1x_kay *kay = ctx; + struct ieee8023_hdr *eth_hdr; + struct ieee802_1x_hdr *eapol_hdr; + + /* must contain at least ieee8023_hdr + ieee802_1x_hdr */ + if (len < sizeof(*eth_hdr) + sizeof(*eapol_hdr)) { + wpa_printf(MSG_MSGDUMP, "KaY: EAPOL frame too short (%lu)", + (unsigned long) len); + return; + } + + eth_hdr = (struct ieee8023_hdr *) buf; + eapol_hdr = (struct ieee802_1x_hdr *) (eth_hdr + 1); + if (len != sizeof(*eth_hdr) + sizeof(*eapol_hdr) + + ntohs(eapol_hdr->length)) { + wpa_printf(MSG_MSGDUMP, "KAY: EAPOL MPDU is invalid: (%lu-%lu)", + (unsigned long) len, + (unsigned long) ntohs(eapol_hdr->length)); + return; + } + + if (eapol_hdr->version < EAPOL_VERSION) { + wpa_printf(MSG_MSGDUMP, "KaY: version %d does not support MKA", + eapol_hdr->version); + return; + } + if (ntohs(eth_hdr->ethertype) != ETH_P_PAE || + eapol_hdr->type != IEEE802_1X_TYPE_EAPOL_MKA) + return; + + wpa_hexdump(MSG_DEBUG, "RX EAPOL-MKA: ", buf, len); + if (dl_list_empty(&kay->participant_list)) { + wpa_printf(MSG_ERROR, "KaY: no MKA participant instance"); + return; + } + + ieee802_1x_kay_decode_mkpdu(kay, buf, len); +} + + +/** + * ieee802_1x_kay_init - + */ +struct ieee802_1x_kay * +ieee802_1x_kay_init(struct ieee802_1x_kay_ctx *ctx, enum macsec_policy policy, + const char *ifname, const u8 *addr) +{ + struct ieee802_1x_kay *kay; + + kay = os_zalloc(sizeof(*kay)); + if (!kay) { + wpa_printf(MSG_ERROR, "KaY-%s: out of memory", __func__); + return NULL; + } + + kay->ctx = ctx; + + kay->enable = TRUE; + kay->active = FALSE; + + kay->authenticated = FALSE; + kay->secured = FALSE; + kay->failed = FALSE; + kay->policy = policy; + + os_strlcpy(kay->if_name, ifname, IFNAMSIZ); + os_memcpy(kay->actor_sci.addr, addr, ETH_ALEN); + kay->actor_sci.port = 0x0001; + kay->actor_priority = DEFAULT_PRIO_NOT_KEY_SERVER; + + /* While actor acts as a key server, shall distribute sakey */ + kay->dist_kn = 1; + kay->dist_an = 0; + kay->dist_time = 0; + + kay->pn_exhaustion = PENDING_PN_EXHAUSTION; + kay->macsec_csindex = DEFAULT_CS_INDEX; + kay->mka_algindex = DEFAULT_MKA_ALG_INDEX; + kay->mka_version = MKA_VERSION_ID; + + os_memcpy(kay->algo_agility, mka_algo_agility, + sizeof(kay->algo_agility)); + + dl_list_init(&kay->participant_list); + + if (policy == DO_NOT_SECURE) { + kay->macsec_capable = MACSEC_CAP_NOT_IMPLEMENTED; + kay->macsec_desired = FALSE; + kay->macsec_protect = FALSE; + kay->macsec_validate = FALSE; + kay->macsec_replay_protect = FALSE; + kay->macsec_replay_window = 0; + kay->macsec_confidentiality = CONFIDENTIALITY_NONE; + } else { + kay->macsec_capable = MACSEC_CAP_INTEG_AND_CONF_0_30_50; + kay->macsec_desired = TRUE; + kay->macsec_protect = TRUE; + kay->macsec_validate = TRUE; + kay->macsec_replay_protect = FALSE; + kay->macsec_replay_window = 0; + kay->macsec_confidentiality = CONFIDENTIALITY_OFFSET_0; + } + + wpa_printf(MSG_DEBUG, "KaY: state machine created"); + + /* Initialize the SecY must be prio to CP, as CP will control SecY */ + secy_init_macsec(kay); + secy_get_available_transmit_sc(kay, &kay->sc_ch); + + wpa_printf(MSG_DEBUG, "KaY: secy init macsec done"); + + /* init CP */ + kay->cp = ieee802_1x_kay_alloc_cp_sm(kay); + if (kay->cp == NULL) { + ieee802_1x_kay_deinit(kay); + return NULL; + } + + if (policy == DO_NOT_SECURE) { + ieee802_1x_cp_connect_authenticated(kay->cp); + ieee802_1x_cp_sm_step(kay->cp); + } else { + kay->l2_mka = l2_packet_init(kay->if_name, NULL, ETH_P_PAE, + kay_l2_receive, kay, 1); + if (kay->l2_mka == NULL) { + wpa_printf(MSG_WARNING, + "KaY: Failed to initialize L2 packet processing for MKA packet"); + ieee802_1x_kay_deinit(kay); + return NULL; + } + } + + return kay; +} + + +/** + * ieee802_1x_kay_deinit - + */ +void +ieee802_1x_kay_deinit(struct ieee802_1x_kay *kay) +{ + struct ieee802_1x_mka_participant *participant; + + if (!kay) + return; + + wpa_printf(MSG_DEBUG, "KaY: state machine removed"); + + while (!dl_list_empty(&kay->participant_list)) { + participant = dl_list_entry(kay->participant_list.next, + struct ieee802_1x_mka_participant, + list); + ieee802_1x_kay_delete_mka(kay, &participant->ckn); + } + + ieee802_1x_cp_sm_deinit(kay->cp); + secy_deinit_macsec(kay); + + if (kay->l2_mka) { + l2_packet_deinit(kay->l2_mka); + kay->l2_mka = NULL; + } + + os_free(kay->ctx); + os_free(kay); +} + + +/** + * ieee802_1x_kay_create_mka - + */ +struct ieee802_1x_mka_participant * +ieee802_1x_kay_create_mka(struct ieee802_1x_kay *kay, struct mka_key_name *ckn, + struct mka_key *cak, u32 life, + enum mka_created_mode mode, Boolean is_authenticator) +{ + struct ieee802_1x_mka_participant *participant; + unsigned int usecs; + + if (!kay || !ckn || !cak) { + wpa_printf(MSG_ERROR, "KaY: ckn or cak is null"); + return NULL; + } + + if (cak->len != mka_alg_tbl[kay->mka_algindex].cak_len) { + wpa_printf(MSG_ERROR, "KaY: CAK length not follow key schema"); + return NULL; + } + if (ckn->len > MAX_CKN_LEN) { + wpa_printf(MSG_ERROR, "KaY: CKN is out of range(<=32 bytes)"); + return NULL; + } + if (!kay->enable) { + wpa_printf(MSG_ERROR, "KaY: Now is at disable state"); + return NULL; + } + + participant = os_zalloc(sizeof(*participant)); + if (!participant) { + wpa_printf(MSG_ERROR, "KaY-%s: out of memory", __func__); + return NULL; + } + + participant->ckn.len = ckn->len; + os_memcpy(participant->ckn.name, ckn->name, ckn->len); + participant->cak.len = cak->len; + os_memcpy(participant->cak.key, cak->key, cak->len); + if (life) + participant->cak_life = life + time(NULL); + + switch (mode) { + case EAP_EXCHANGE: + if (is_authenticator) { + participant->is_obliged_key_server = TRUE; + participant->can_be_key_server = TRUE; + participant->is_key_server = TRUE; + participant->principal = TRUE; + + os_memcpy(&kay->key_server_sci, &kay->actor_sci, + sizeof(kay->key_server_sci)); + kay->key_server_priority = kay->actor_priority; + participant->is_elected = TRUE; + } else { + participant->is_obliged_key_server = FALSE; + participant->can_be_key_server = FALSE; + participant->is_key_server = FALSE; + participant->is_elected = TRUE; + } + break; + + default: + participant->is_obliged_key_server = FALSE; + participant->can_be_key_server = TRUE; + participant->is_key_server = FALSE; + participant->is_elected = FALSE; + break; + } + + participant->cached = FALSE; + + participant->active = FALSE; + participant->participant = FALSE; + participant->retain = FALSE; + participant->activate = DEFAULT; + + if (participant->is_key_server) + participant->principal = TRUE; + + dl_list_init(&participant->live_peers); + dl_list_init(&participant->potential_peers); + + participant->retry_count = 0; + participant->kay = kay; + + os_get_random(participant->mi, sizeof(participant->mi)); + participant->mn = 0; + + participant->lrx = FALSE; + participant->ltx = FALSE; + participant->orx = FALSE; + participant->otx = FALSE; + participant->to_dist_sak = FALSE; + participant->to_use_sak = FALSE; + participant->new_sak = FALSE; + dl_list_init(&participant->sak_list); + participant->new_key = NULL; + dl_list_init(&participant->rxsc_list); + participant->txsc = ieee802_1x_kay_init_transmit_sc(&kay->actor_sci, + kay->sc_ch); + secy_create_transmit_sc(kay, participant->txsc); + + /* to derive KEK from CAK and CKN */ + participant->kek.len = mka_alg_tbl[kay->mka_algindex].kek_len; + if (mka_alg_tbl[kay->mka_algindex].kek_trfm(participant->cak.key, + participant->ckn.name, + participant->ckn.len, + participant->kek.key)) { + wpa_printf(MSG_ERROR, "KaY: Derived KEK failed"); + goto fail; + } + wpa_hexdump_key(MSG_DEBUG, "KaY: Derived KEK", + participant->kek.key, participant->kek.len); + + /* to derive ICK from CAK and CKN */ + participant->ick.len = mka_alg_tbl[kay->mka_algindex].ick_len; + if (mka_alg_tbl[kay->mka_algindex].ick_trfm(participant->cak.key, + participant->ckn.name, + participant->ckn.len, + participant->ick.key)) { + wpa_printf(MSG_ERROR, "KaY: Derived ICK failed"); + goto fail; + } + wpa_hexdump_key(MSG_DEBUG, "KaY: Derived ICK", + participant->ick.key, participant->ick.len); + + dl_list_add(&kay->participant_list, &participant->list); + wpa_hexdump(MSG_DEBUG, "KaY: Participant created:", + ckn->name, ckn->len); + + usecs = os_random() % (MKA_HELLO_TIME * 1000); + eloop_register_timeout(0, usecs, ieee802_1x_participant_timer, + participant, NULL); + participant->mka_life = MKA_LIFE_TIME / 1000 + time(NULL) + + usecs / 1000000; + + return participant; + +fail: + os_free(participant); + return NULL; +} + + +/** + * ieee802_1x_kay_delete_mka - + */ +void +ieee802_1x_kay_delete_mka(struct ieee802_1x_kay *kay, struct mka_key_name *ckn) +{ + struct ieee802_1x_mka_participant *participant; + struct ieee802_1x_kay_peer *peer; + struct data_key *sak; + struct receive_sc *rxsc; + + if (!kay || !ckn) + return; + + wpa_printf(MSG_DEBUG, "KaY: participant removed"); + + /* get the participant */ + participant = ieee802_1x_kay_get_participant(kay, ckn->name); + if (!participant) { + wpa_hexdump(MSG_DEBUG, "KaY: participant is not found", + ckn->name, ckn->len); + return; + } + + dl_list_del(&participant->list); + + /* remove live peer */ + while (!dl_list_empty(&participant->live_peers)) { + peer = dl_list_entry(participant->live_peers.next, + struct ieee802_1x_kay_peer, list); + dl_list_del(&peer->list); + os_free(peer); + } + + /* remove potential peer */ + while (!dl_list_empty(&participant->potential_peers)) { + peer = dl_list_entry(participant->potential_peers.next, + struct ieee802_1x_kay_peer, list); + dl_list_del(&peer->list); + os_free(peer); + } + + /* remove sak */ + while (!dl_list_empty(&participant->sak_list)) { + sak = dl_list_entry(participant->sak_list.next, + struct data_key, list); + dl_list_del(&sak->list); + os_free(sak->key); + os_free(sak); + } + while (!dl_list_empty(&participant->rxsc_list)) { + rxsc = dl_list_entry(participant->rxsc_list.next, + struct receive_sc, list); + secy_delete_receive_sc(kay, rxsc); + ieee802_1x_kay_deinit_receive_sc(participant, rxsc); + } + secy_delete_transmit_sc(kay, participant->txsc); + ieee802_1x_kay_deinit_transmit_sc(participant, participant->txsc); + + os_memset(&participant->cak, 0, sizeof(participant->cak)); + os_memset(&participant->kek, 0, sizeof(participant->kek)); + os_memset(&participant->ick, 0, sizeof(participant->ick)); + os_free(participant); +} + + +/** + * ieee802_1x_kay_mka_participate - + */ +void ieee802_1x_kay_mka_participate(struct ieee802_1x_kay *kay, + struct mka_key_name *ckn, + Boolean status) +{ + struct ieee802_1x_mka_participant *participant; + + if (!kay || !ckn) + return; + + participant = ieee802_1x_kay_get_participant(kay, ckn->name); + if (!participant) + return; + + participant->active = status; +} + + +/** + * ieee802_1x_kay_new_sak - + */ +int +ieee802_1x_kay_new_sak(struct ieee802_1x_kay *kay) +{ + struct ieee802_1x_mka_participant *participant; + + if (!kay) + return -1; + + participant = ieee802_1x_kay_get_principal_participant(kay); + if (!participant) + return -1; + + participant->new_sak = TRUE; + wpa_printf(MSG_DEBUG, "KaY: new SAK signal"); + + return 0; +} + + +/** + * ieee802_1x_kay_change_cipher_suite - + */ +int +ieee802_1x_kay_change_cipher_suite(struct ieee802_1x_kay *kay, int cs_index) +{ + struct ieee802_1x_mka_participant *participant; + + if (!kay) + return -1; + + if ((unsigned int) cs_index >= CS_TABLE_SIZE) { + wpa_printf(MSG_ERROR, + "KaY: Configured cipher suite index is out of range"); + return -1; + } + if (kay->macsec_csindex == cs_index) + return -2; + + if (cs_index == 0) + kay->macsec_desired = FALSE; + + kay->macsec_csindex = cs_index; + kay->macsec_capable = cipher_suite_tbl[kay->macsec_csindex].capable; + + participant = ieee802_1x_kay_get_principal_participant(kay); + if (participant) { + wpa_printf(MSG_INFO, "KaY: Cipher Suite changed"); + participant->new_sak = TRUE; + } + + return 0; +} diff --git a/src/pae/ieee802_1x_kay.h b/src/pae/ieee802_1x_kay.h new file mode 100644 index 0000000..064417e --- /dev/null +++ b/src/pae/ieee802_1x_kay.h @@ -0,0 +1,194 @@ +/* + * IEEE 802.1X-2010 Key Agree Protocol of PAE state machine + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef IEEE802_1X_KAY_H +#define IEEE802_1X_KAY_H + +#include "utils/list.h" +#include "common/defs.h" +#include "common/ieee802_1x_defs.h" + +struct macsec_init_params; +struct ieee802_1x_cp_conf; + +#define MI_LEN 12 +#define MAX_KEY_LEN 32 /* 32 bytes, 256 bits */ +#define MAX_CKN_LEN 32 /* 32 bytes, 256 bits */ + +/* MKA timer, unit: millisecond */ +#define MKA_HELLO_TIME 2000 +#define MKA_LIFE_TIME 6000 +#define MKA_SAK_RETIRE_TIME 3000 + +struct ieee802_1x_mka_ki { + u8 mi[MI_LEN]; + u32 kn; +}; + +struct ieee802_1x_mka_sci { + u8 addr[ETH_ALEN]; + u16 port; +}; + +struct mka_key { + u8 key[MAX_KEY_LEN]; + size_t len; +}; + +struct mka_key_name { + u8 name[MAX_CKN_LEN]; + size_t len; +}; + +enum mka_created_mode { + PSK, + EAP_EXCHANGE, + DISTRIBUTED, + CACHED, +}; + +struct ieee802_1x_kay_ctx { + /* pointer to arbitrary upper level context */ + void *ctx; + + /* abstract wpa driver interface */ + int (*macsec_init)(void *ctx, struct macsec_init_params *params); + int (*macsec_deinit)(void *ctx); + int (*enable_protect_frames)(void *ctx, Boolean enabled); + int (*set_replay_protect)(void *ctx, Boolean enabled, u32 window); + int (*set_current_cipher_suite)(void *ctx, const u8 *cs, size_t cs_len); + int (*enable_controlled_port)(void *ctx, Boolean enabled); + int (*get_receive_lowest_pn)(void *ctx, u32 channel, u8 an, + u32 *lowest_pn); + int (*get_transmit_next_pn)(void *ctx, u32 channel, u8 an, + u32 *next_pn); + int (*set_transmit_next_pn)(void *ctx, u32 channel, u8 an, u32 next_pn); + int (*get_available_receive_sc)(void *ctx, u32 *channel); + int (*create_receive_sc)(void *ctx, u32 channel, + struct ieee802_1x_mka_sci *sci, + enum validate_frames vf, + enum confidentiality_offset co); + int (*delete_receive_sc)(void *ctx, u32 channel); + int (*create_receive_sa)(void *ctx, u32 channel, u8 an, u32 lowest_pn, + const u8 *sak); + int (*enable_receive_sa)(void *ctx, u32 channel, u8 an); + int (*disable_receive_sa)(void *ctx, u32 channel, u8 an); + int (*get_available_transmit_sc)(void *ctx, u32 *channel); + int (*create_transmit_sc)(void *ctx, u32 channel, + const struct ieee802_1x_mka_sci *sci, + enum confidentiality_offset co); + int (*delete_transmit_sc)(void *ctx, u32 channel); + int (*create_transmit_sa)(void *ctx, u32 channel, u8 an, u32 next_pn, + Boolean confidentiality, const u8 *sak); + int (*enable_transmit_sa)(void *ctx, u32 channel, u8 an); + int (*disable_transmit_sa)(void *ctx, u32 channel, u8 an); +}; + +struct ieee802_1x_kay { + Boolean enable; + Boolean active; + + Boolean authenticated; + Boolean secured; + Boolean failed; + + struct ieee802_1x_mka_sci actor_sci; + u8 actor_priority; + struct ieee802_1x_mka_sci key_server_sci; + u8 key_server_priority; + + enum macsec_cap macsec_capable; + Boolean macsec_desired; + Boolean macsec_protect; + Boolean macsec_replay_protect; + u32 macsec_replay_window; + enum validate_frames macsec_validate; + enum confidentiality_offset macsec_confidentiality; + + u32 ltx_kn; + u8 ltx_an; + u32 lrx_kn; + u8 lrx_an; + + u32 otx_kn; + u8 otx_an; + u32 orx_kn; + u8 orx_an; + + /* not defined in IEEE802.1X */ + struct ieee802_1x_kay_ctx *ctx; + Boolean is_key_server; + Boolean is_obliged_key_server; + char if_name[IFNAMSIZ]; + + int macsec_csindex; /* MACsec cipher suite table index */ + int mka_algindex; /* MKA alg table index */ + + u32 dist_kn; + u8 dist_an; + time_t dist_time; + + u8 mka_version; + u8 algo_agility[4]; + u32 sc_ch; + + u32 pn_exhaustion; + Boolean port_enable; + Boolean rx_enable; + Boolean tx_enable; + + struct dl_list participant_list; + enum macsec_policy policy; + + struct ieee802_1x_cp_sm *cp; + + struct l2_packet_data *l2_mka; + + enum validate_frames vf; + enum confidentiality_offset co; +}; + + +struct ieee802_1x_kay * +ieee802_1x_kay_init(struct ieee802_1x_kay_ctx *ctx, enum macsec_policy policy, + const char *ifname, const u8 *addr); +void ieee802_1x_kay_deinit(struct ieee802_1x_kay *kay); + +struct ieee802_1x_mka_participant * +ieee802_1x_kay_create_mka(struct ieee802_1x_kay *kay, + struct mka_key_name *ckn, struct mka_key *cak, + u32 life, enum mka_created_mode mode, + Boolean is_authenticator); +void ieee802_1x_kay_delete_mka(struct ieee802_1x_kay *kay, + struct mka_key_name *ckn); +void ieee802_1x_kay_mka_participate(struct ieee802_1x_kay *kay, + struct mka_key_name *ckn, + Boolean status); +int ieee802_1x_kay_new_sak(struct ieee802_1x_kay *kay); +int ieee802_1x_kay_change_cipher_suite(struct ieee802_1x_kay *kay, + int cs_index); + +int ieee802_1x_kay_set_latest_sa_attr(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *lki, u8 lan, + Boolean ltx, Boolean lrx); +int ieee802_1x_kay_set_old_sa_attr(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *oki, + u8 oan, Boolean otx, Boolean orx); +int ieee802_1x_kay_create_sas(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *lki); +int ieee802_1x_kay_delete_sas(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *ki); +int ieee802_1x_kay_enable_tx_sas(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *lki); +int ieee802_1x_kay_enable_rx_sas(struct ieee802_1x_kay *kay, + struct ieee802_1x_mka_ki *lki); +int ieee802_1x_kay_enable_new_info(struct ieee802_1x_kay *kay); +int ieee802_1x_kay_cp_conf(struct ieee802_1x_kay *kay, + struct ieee802_1x_cp_conf *pconf); + +#endif /* IEEE802_1X_KAY_H */ diff --git a/src/pae/ieee802_1x_kay_i.h b/src/pae/ieee802_1x_kay_i.h new file mode 100644 index 0000000..bdad3a5 --- /dev/null +++ b/src/pae/ieee802_1x_kay_i.h @@ -0,0 +1,419 @@ +/* + * IEEE 802.1X-2010 Key Agree Protocol of PAE state machine + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef IEEE802_1X_KAY_I_H +#define IEEE802_1X_KAY_I_H + +#include "utils/list.h" +#include "common/defs.h" +#include "common/ieee802_1x_defs.h" + +#define MKA_VERSION_ID 1 + +/* IEEE Std 802.1X-2010, 11.11.1, Table 11-7 */ +enum mka_packet_type { + MKA_BASIC_PARAMETER_SET = MKA_VERSION_ID, + MKA_LIVE_PEER_LIST = 1, + MKA_POTENTIAL_PEER_LIST = 2, + MKA_SAK_USE = 3, + MKA_DISTRIBUTED_SAK = 4, + MKA_DISTRIBUTED_CAK = 5, + MKA_KMD = 6, + MKA_ANNOUNCEMENT = 7, + MKA_ICV_INDICATOR = 255 +}; + +#define ICV_LEN 16 /* 16 bytes */ +#define SAK_WRAPPED_LEN 24 +/* KN + Wrapper SAK */ +#define DEFAULT_DIS_SAK_BODY_LENGTH (SAK_WRAPPED_LEN + 4) +#define MAX_RETRY_CNT 5 + +struct ieee802_1x_kay; + +struct ieee802_1x_mka_peer_id { + u8 mi[MI_LEN]; + u32 mn; +}; + +struct ieee802_1x_kay_peer { + struct ieee802_1x_mka_sci sci; + u8 mi[MI_LEN]; + u32 mn; + time_t expire; + Boolean is_key_server; + u8 key_server_priority; + Boolean macsec_desired; + enum macsec_cap macsec_capbility; + Boolean sak_used; + struct dl_list list; +}; + +struct key_conf { + u8 *key; + struct ieee802_1x_mka_ki ki; + enum confidentiality_offset offset; + u8 an; + Boolean tx; + Boolean rx; + int key_len; /* unit: byte */ +}; + +struct data_key { + u8 *key; + int key_len; + struct ieee802_1x_mka_ki key_identifier; + enum confidentiality_offset confidentiality_offset; + u8 an; + Boolean transmits; + Boolean receives; + struct os_time created_time; + u32 next_pn; + + /* not defined data */ + Boolean rx_latest; + Boolean tx_latest; + + int user; /* FIXME: to indicate if it can be delete safely */ + + struct dl_list list; +}; + +/* TransmitSC in IEEE Std 802.1AE-2006, Figure 10-6 */ +struct transmit_sc { + struct ieee802_1x_mka_sci sci; /* const SCI sci */ + Boolean transmitting; /* bool transmitting (read only) */ + + struct os_time created_time; /* Time createdTime */ + + u8 encoding_sa; /* AN encodingSA (read only) */ + u8 enciphering_sa; /* AN encipheringSA (read only) */ + + /* not defined data */ + unsigned int channel; + + struct dl_list list; + struct dl_list sa_list; +}; + +/* TransmitSA in IEEE Std 802.1AE-2006, Figure 10-6 */ +struct transmit_sa { + Boolean in_use; /* bool inUse (read only) */ + u32 next_pn; /* PN nextPN (read only) */ + struct os_time created_time; /* Time createdTime */ + + Boolean enable_transmit; /* bool EnableTransmit */ + + u8 an; + Boolean confidentiality; + struct data_key *pkey; + + struct transmit_sc *sc; + struct dl_list list; /* list entry in struct transmit_sc::sa_list */ +}; + +/* ReceiveSC in IEEE Std 802.1AE-2006, Figure 10-6 */ +struct receive_sc { + struct ieee802_1x_mka_sci sci; /* const SCI sci */ + Boolean receiving; /* bool receiving (read only) */ + + struct os_time created_time; /* Time createdTime */ + + unsigned int channel; + + struct dl_list list; + struct dl_list sa_list; +}; + +/* ReceiveSA in IEEE Std 802.1AE-2006, Figure 10-6 */ +struct receive_sa { + Boolean enable_receive; /* bool enableReceive */ + Boolean in_use; /* bool inUse (read only) */ + + u32 next_pn; /* PN nextPN (read only) */ + u32 lowest_pn; /* PN lowestPN (read only) */ + u8 an; + struct os_time created_time; + + struct data_key *pkey; + struct receive_sc *sc; /* list entry in struct receive_sc::sa_list */ + + struct dl_list list; +}; + +struct macsec_ciphersuite { + u8 id[CS_ID_LEN]; + char name[32]; + enum macsec_cap capable; + int sak_len; /* unit: byte */ + + u32 index; +}; + +struct mka_alg { + u8 parameter[4]; + size_t cak_len; + size_t kek_len; + size_t ick_len; + size_t icv_len; + + int (*cak_trfm)(const u8 *msk, const u8 *mac1, const u8 *mac2, u8 *cak); + int (*ckn_trfm)(const u8 *msk, const u8 *mac1, const u8 *mac2, + const u8 *sid, size_t sid_len, u8 *ckn); + int (*kek_trfm)(const u8 *cak, const u8 *ckn, size_t ckn_len, u8 *kek); + int (*ick_trfm)(const u8 *cak, const u8 *ckn, size_t ckn_len, u8 *ick); + int (*icv_hash)(const u8 *ick, const u8 *msg, size_t msg_len, u8 *icv); + + int index; /* index for configuring */ +}; + +#define DEFAULT_MKA_ALG_INDEX 0 + +/* See IEEE Std 802.1X-2010, 9.16 MKA management */ +struct ieee802_1x_mka_participant { + /* used for active and potential participant */ + struct mka_key_name ckn; + struct mka_key cak; + Boolean cached; + + /* used by management to monitor and control activation */ + Boolean active; + Boolean participant; + Boolean retain; + + enum { DEFAULT, DISABLED, ON_OPER_UP, ALWAYS } activate; + + /* used for active participant */ + Boolean principal; + struct dl_list live_peers; + struct dl_list potential_peers; + + /* not defined in IEEE 802.1X */ + struct dl_list list; + + struct mka_key kek; + struct mka_key ick; + + struct ieee802_1x_mka_ki lki; + u8 lan; + Boolean ltx; + Boolean lrx; + + struct ieee802_1x_mka_ki oki; + u8 oan; + Boolean otx; + Boolean orx; + + Boolean is_key_server; + Boolean is_obliged_key_server; + Boolean can_be_key_server; + Boolean is_elected; + + struct dl_list sak_list; + struct dl_list rxsc_list; + + struct transmit_sc *txsc; + + u8 mi[MI_LEN]; + u32 mn; + + struct ieee802_1x_mka_peer_id current_peer_id; + struct ieee802_1x_mka_sci current_peer_sci; + time_t cak_life; + time_t mka_life; + Boolean to_dist_sak; + Boolean to_use_sak; + Boolean new_sak; + + Boolean advised_desired; + enum macsec_cap advised_capability; + + struct data_key *new_key; + u32 retry_count; + + struct ieee802_1x_kay *kay; +}; + +struct ieee802_1x_mka_hdr { + /* octet 1 */ + u32 type:8; + /* octet 2 */ + u32 reserve:8; + /* octet 3 */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + u32 length:4; + u32 reserve1:4; +#elif __BYTE_ORDER == __BIG_ENDIAN + u32 reserve1:4; + u32 length:4; +#else +#error "Please fix <bits/endian.h>" +#endif + /* octet 4 */ + u32 length1:8; +}; + +#define MKA_HDR_LEN sizeof(struct ieee802_1x_mka_hdr) + +struct ieee802_1x_mka_basic_body { + /* octet 1 */ + u32 version:8; + /* octet 2 */ + u32 priority:8; + /* octet 3 */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + u32 length:4; + u32 macsec_capbility:2; + u32 macsec_desired:1; + u32 key_server:1; +#elif __BYTE_ORDER == __BIG_ENDIAN + u32 key_server:1; + u32 macsec_desired:1; + u32 macsec_capbility:2; + u32 length:4; +#endif + /* octet 4 */ + u32 length1:8; + + struct ieee802_1x_mka_sci actor_sci; + u8 actor_mi[MI_LEN]; + u32 actor_mn; + u8 algo_agility[4]; + + /* followed by CAK Name*/ + u8 ckn[0]; +}; + +struct ieee802_1x_mka_peer_body { + /* octet 1 */ + u32 type:8; + /* octet 2 */ + u32 reserve:8; + /* octet 3 */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + u32 length:4; + u32 reserve1:4; +#elif __BYTE_ORDER == __BIG_ENDIAN + u32 reserve1:4; + u32 length:4; +#endif + /* octet 4 */ + u32 length1:8; + + u8 peer[0]; + /* followed by Peers */ +}; + +struct ieee802_1x_mka_sak_use_body { + /* octet 1 */ + u32 type:8; + /* octet 2 */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + u32 orx:1; + u32 otx:1; + u32 oan:2; + u32 lrx:1; + u32 ltx:1; + u32 lan:2; +#elif __BYTE_ORDER == __BIG_ENDIAN + u32 lan:2; + u32 ltx:1; + u32 lrx:1; + u32 oan:2; + u32 otx:1; + u32 orx:1; +#endif + + /* octet 3 */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + u32 length:4; + u32 delay_protect:1; + u32 reserve:1; + u32 prx:1; + u32 ptx:1; +#elif __BYTE_ORDER == __BIG_ENDIAN + u32 ptx:1; + u32 prx:1; + u32 reserve:1; + u32 delay_protect:1; + u32 length:4; +#endif + + /* octet 4 */ + u32 length1:8; + + /* octet 5 - 16 */ + u8 lsrv_mi[MI_LEN]; + /* octet 17 - 20 */ + u32 lkn; + /* octet 21 - 24 */ + u32 llpn; + + /* octet 25 - 36 */ + u8 osrv_mi[MI_LEN]; + /* octet 37 - 40 */ + u32 okn; + /* octet 41 - 44 */ + u32 olpn; +}; + + +struct ieee802_1x_mka_dist_sak_body { + /* octet 1 */ + u32 type:8; + /* octet 2 */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + u32 reserve:4; + u32 confid_offset:2; + u32 dan:2; +#elif __BYTE_ORDER == __BIG_ENDIAN + u32 dan:2; + u32 confid_offset:2; + u32 reserve:4; +#endif + /* octet 3 */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + u32 length:4; + u32 reserve1:4; +#elif __BYTE_ORDER == __BIG_ENDIAN + u32 reserve1:4; + u32 length:4; +#endif + /* octet 4 */ + u32 length1:8; + /* octet 5 - 8 */ + u32 kn; + + /* for GCM-AES-128: octet 9-32: SAK + * for other cipher suite: octet 9-16: cipher suite id, octet 17-: SAK + */ + u8 sak[0]; +}; + + +struct ieee802_1x_mka_icv_body { + /* octet 1 */ + u32 type:8; + /* octet 2 */ + u32 reserve:8; + /* octet 3 */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + u32 length:4; + u32 reserve1:4; +#elif __BYTE_ORDER == __BIG_ENDIAN + u32 reserve1:4; + u32 length:4; +#endif + /* octet 4 */ + u32 length1:8; + + /* octet 5 - */ + u8 icv[0]; +}; + +#endif /* IEEE802_1X_KAY_I_H */ diff --git a/src/pae/ieee802_1x_key.c b/src/pae/ieee802_1x_key.c new file mode 100644 index 0000000..9a8d923 --- /dev/null +++ b/src/pae/ieee802_1x_key.c @@ -0,0 +1,189 @@ +/* + * IEEE 802.1X-2010 Key Hierarchy + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + * + * SAK derivation specified in IEEE Std 802.1X-2010, Clause 6.2 +*/ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "crypto/md5.h" +#include "crypto/sha1.h" +#include "crypto/aes_wrap.h" +#include "crypto/crypto.h" +#include "ieee802_1x_key.h" + + +static void joint_two_mac(const u8 *mac1, const u8 *mac2, u8 *out) +{ + if (os_memcmp(mac1, mac2, ETH_ALEN) < 0) { + os_memcpy(out, mac1, ETH_ALEN); + os_memcpy(out + ETH_ALEN, mac2, ETH_ALEN); + } else { + os_memcpy(out, mac2, ETH_ALEN); + os_memcpy(out + ETH_ALEN, mac1, ETH_ALEN); + } +} + + +/* IEEE Std 802.1X-2010, 6.2.1 KDF */ +static int aes_kdf_128(const u8 *kdk, const char *label, const u8 *context, + int ctx_bits, int ret_bits, u8 *ret) +{ + const int h = 128; + const int r = 8; + int i, n; + int lab_len, ctx_len, ret_len, buf_len; + u8 *buf; + + lab_len = os_strlen(label); + ctx_len = (ctx_bits + 7) / 8; + ret_len = ((ret_bits & 0xffff) + 7) / 8; + buf_len = lab_len + ctx_len + 4; + + os_memset(ret, 0, ret_len); + + n = (ret_bits + h - 1) / h; + if (n > ((0x1 << r) - 1)) + return -1; + + buf = os_zalloc(buf_len); + if (buf == NULL) + return -1; + + os_memcpy(buf + 1, label, lab_len); + os_memcpy(buf + lab_len + 2, context, ctx_len); + WPA_PUT_BE16(&buf[buf_len - 2], ret_bits); + + for (i = 0; i < n; i++) { + buf[0] = (u8) (i + 1); + if (omac1_aes_128(kdk, buf, buf_len, ret)) { + os_free(buf); + return -1; + } + ret = ret + h / 8; + } + os_free(buf); + return 0; +} + + +/********** AES-CMAC-128 **********/ +/** + * ieee802_1x_cak_128bits_aes_cmac + * + * IEEE Std 802.1X-2010, 6.2.2 + * CAK = KDF(Key, Label, mac1 | mac2, CAKlength) + */ +int ieee802_1x_cak_128bits_aes_cmac(const u8 *msk, const u8 *mac1, + const u8 *mac2, u8 *cak) +{ + u8 context[2 * ETH_ALEN]; + + joint_two_mac(mac1, mac2, context); + return aes_kdf_128(msk, "IEEE8021 EAP CAK", + context, sizeof(context) * 8, 128, cak); +} + + +/** + * ieee802_1x_ckn_128bits_aes_cmac + * + * IEEE Std 802.1X-2010, 6.2.2 + * CKN = KDF(Key, Label, ID | mac1 | mac2, CKNlength) + */ +int ieee802_1x_ckn_128bits_aes_cmac(const u8 *msk, const u8 *mac1, + const u8 *mac2, const u8 *sid, + size_t sid_bytes, u8 *ckn) +{ + int res; + u8 *context; + size_t ctx_len = sid_bytes + ETH_ALEN * 2; + + context = os_zalloc(ctx_len); + if (!context) { + wpa_printf(MSG_ERROR, "MKA-%s: out of memory", __func__); + return -1; + } + os_memcpy(context, sid, sid_bytes); + joint_two_mac(mac1, mac2, context + sid_bytes); + + res = aes_kdf_128(msk, "IEEE8021 EAP CKN", context, ctx_len * 8, + 128, ckn); + os_free(context); + return res; +} + + +/** + * ieee802_1x_kek_128bits_aes_cmac + * + * IEEE Std 802.1X-2010, 9.3.3 + * KEK = KDF(Key, Label, Keyid, KEKLength) + */ +int ieee802_1x_kek_128bits_aes_cmac(const u8 *cak, const u8 *ckn, + size_t ckn_bytes, u8 *kek) +{ + u8 context[16]; + + /* First 16 octets of CKN, with null octets appended to pad if needed */ + os_memset(context, 0, sizeof(context)); + os_memcpy(context, ckn, (ckn_bytes < 16) ? ckn_bytes : 16); + + return aes_kdf_128(cak, "IEEE8021 KEK", context, sizeof(context) * 8, + 128, kek); +} + + +/** + * ieee802_1x_ick_128bits_aes_cmac + * + * IEEE Std 802.1X-2010, 9.3.3 + * ICK = KDF(Key, Label, Keyid, ICKLength) + */ +int ieee802_1x_ick_128bits_aes_cmac(const u8 *cak, const u8 *ckn, + size_t ckn_bytes, u8 *ick) +{ + u8 context[16]; + + /* First 16 octets of CKN, with null octets appended to pad if needed */ + os_memset(context, 0, sizeof(context)); + os_memcpy(context, ckn, (ckn_bytes < 16) ? ckn_bytes : 16); + + return aes_kdf_128(cak, "IEEE8021 ICK", context, sizeof(context) * 8, + 128, ick); +} + + +/** + * ieee802_1x_icv_128bits_aes_cmac + * + * IEEE Std 802.1X-2010, 9.4.1 + * ICV = AES-CMAC(ICK, M, 128) + */ +int ieee802_1x_icv_128bits_aes_cmac(const u8 *ick, const u8 *msg, + size_t msg_bytes, u8 *icv) +{ + if (omac1_aes_128(ick, msg, msg_bytes, icv)) { + wpa_printf(MSG_ERROR, "MKA: omac1_aes_128 failed"); + return -1; + } + return 0; +} + + +/** + * ieee802_1x_sak_128bits_aes_cmac + * + * IEEE Std 802.1X-2010, 9.8.1 + * SAK = KDF(Key, Label, KS-nonce | MI-value list | KN, SAKLength) + */ +int ieee802_1x_sak_128bits_aes_cmac(const u8 *cak, const u8 *ctx, + size_t ctx_bytes, u8 *sak) +{ + return aes_kdf_128(cak, "IEEE8021 SAK", ctx, ctx_bytes * 8, 128, sak); +} diff --git a/src/pae/ieee802_1x_key.h b/src/pae/ieee802_1x_key.h new file mode 100644 index 0000000..ea318ea --- /dev/null +++ b/src/pae/ieee802_1x_key.h @@ -0,0 +1,26 @@ +/* + * IEEE 802.1X-2010 Key Hierarchy + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef IEEE802_1X_KEY_H +#define IEEE802_1X_KEY_H + +int ieee802_1x_cak_128bits_aes_cmac(const u8 *msk, const u8 *mac1, + const u8 *mac2, u8 *cak); +int ieee802_1x_ckn_128bits_aes_cmac(const u8 *msk, const u8 *mac1, + const u8 *mac2, const u8 *sid, + size_t sid_bytes, u8 *ckn); +int ieee802_1x_kek_128bits_aes_cmac(const u8 *cak, const u8 *ckn, + size_t ckn_bytes, u8 *kek); +int ieee802_1x_ick_128bits_aes_cmac(const u8 *cak, const u8 *ckn, + size_t ckn_bytes, u8 *ick); +int ieee802_1x_icv_128bits_aes_cmac(const u8 *ick, const u8 *msg, + size_t msg_bytes, u8 *icv); +int ieee802_1x_sak_128bits_aes_cmac(const u8 *cak, const u8 *ctx, + size_t ctx_bytes, u8 *sak); + +#endif /* IEEE802_1X_KEY_H */ diff --git a/src/pae/ieee802_1x_secy_ops.c b/src/pae/ieee802_1x_secy_ops.c new file mode 100644 index 0000000..fbe05dc --- /dev/null +++ b/src/pae/ieee802_1x_secy_ops.c @@ -0,0 +1,492 @@ + /* + * SecY Operations + * 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 "utils/common.h" +#include "utils/eloop.h" +#include "common/defs.h" +#include "drivers/driver.h" +#include "pae/ieee802_1x_kay.h" +#include "pae/ieee802_1x_kay_i.h" +#include "pae/ieee802_1x_secy_ops.h" + + +int secy_cp_control_validate_frames(struct ieee802_1x_kay *kay, + enum validate_frames vf) +{ + kay->vf = vf; + return 0; +} + + +int secy_cp_control_protect_frames(struct ieee802_1x_kay *kay, Boolean enabled) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->enable_protect_frames) { + wpa_printf(MSG_ERROR, + "KaY: secy enable_protect_frames operation not supported"); + return -1; + } + + return ops->enable_protect_frames(ops->ctx, enabled); +} + + +int secy_cp_control_replay(struct ieee802_1x_kay *kay, Boolean enabled, u32 win) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->set_replay_protect) { + wpa_printf(MSG_ERROR, + "KaY: secy set_replay_protect operation not supported"); + return -1; + } + + return ops->set_replay_protect(ops->ctx, enabled, win); +} + + +int secy_cp_control_current_cipher_suite(struct ieee802_1x_kay *kay, + const u8 *cs, size_t cs_len) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->set_current_cipher_suite) { + wpa_printf(MSG_ERROR, + "KaY: secy set_current_cipher_suite operation not supported"); + return -1; + } + + return ops->set_current_cipher_suite(ops->ctx, cs, cs_len); +} + + +int secy_cp_control_confidentiality_offset(struct ieee802_1x_kay *kay, + enum confidentiality_offset co) +{ + kay->co = co; + return 0; +} + + +int secy_cp_control_enable_port(struct ieee802_1x_kay *kay, Boolean enabled) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->enable_controlled_port) { + wpa_printf(MSG_ERROR, + "KaY: secy enable_controlled_port operation not supported"); + return -1; + } + + return ops->enable_controlled_port(ops->ctx, enabled); +} + + +int secy_get_receive_lowest_pn(struct ieee802_1x_kay *kay, + struct receive_sa *rxsa) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !rxsa) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->get_receive_lowest_pn) { + wpa_printf(MSG_ERROR, + "KaY: secy get_receive_lowest_pn operation not supported"); + return -1; + } + + return ops->get_receive_lowest_pn(ops->ctx, + rxsa->sc->channel, + rxsa->an, + &rxsa->lowest_pn); +} + + +int secy_get_transmit_next_pn(struct ieee802_1x_kay *kay, + struct transmit_sa *txsa) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !txsa) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->get_transmit_next_pn) { + wpa_printf(MSG_ERROR, + "KaY: secy get_receive_lowest_pn operation not supported"); + return -1; + } + + return ops->get_transmit_next_pn(ops->ctx, + txsa->sc->channel, + txsa->an, + &txsa->next_pn); +} + + +int secy_set_transmit_next_pn(struct ieee802_1x_kay *kay, + struct transmit_sa *txsa) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !txsa) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->set_transmit_next_pn) { + wpa_printf(MSG_ERROR, + "KaY: secy get_receive_lowest_pn operation not supported"); + return -1; + } + + return ops->set_transmit_next_pn(ops->ctx, + txsa->sc->channel, + txsa->an, + txsa->next_pn); +} + + +int secy_get_available_receive_sc(struct ieee802_1x_kay *kay, u32 *channel) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->get_available_receive_sc) { + wpa_printf(MSG_ERROR, + "KaY: secy get_available_receive_sc operation not supported"); + return -1; + } + + return ops->get_available_receive_sc(ops->ctx, channel); +} + + +int secy_create_receive_sc(struct ieee802_1x_kay *kay, struct receive_sc *rxsc) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !rxsc) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->create_receive_sc) { + wpa_printf(MSG_ERROR, + "KaY: secy create_receive_sc operation not supported"); + return -1; + } + + return ops->create_receive_sc(ops->ctx, rxsc->channel, &rxsc->sci, + kay->vf, kay->co); +} + + +int secy_delete_receive_sc(struct ieee802_1x_kay *kay, struct receive_sc *rxsc) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !rxsc) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->delete_receive_sc) { + wpa_printf(MSG_ERROR, + "KaY: secy delete_receive_sc operation not supported"); + return -1; + } + + return ops->delete_receive_sc(ops->ctx, rxsc->channel); +} + + +int secy_create_receive_sa(struct ieee802_1x_kay *kay, struct receive_sa *rxsa) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !rxsa) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->create_receive_sa) { + wpa_printf(MSG_ERROR, + "KaY: secy create_receive_sa operation not supported"); + return -1; + } + + return ops->create_receive_sa(ops->ctx, rxsa->sc->channel, rxsa->an, + rxsa->lowest_pn, rxsa->pkey->key); +} + + +int secy_enable_receive_sa(struct ieee802_1x_kay *kay, struct receive_sa *rxsa) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !rxsa) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->enable_receive_sa) { + wpa_printf(MSG_ERROR, + "KaY: secy enable_receive_sa operation not supported"); + return -1; + } + + rxsa->enable_receive = TRUE; + + return ops->enable_receive_sa(ops->ctx, rxsa->sc->channel, rxsa->an); +} + + +int secy_disable_receive_sa(struct ieee802_1x_kay *kay, struct receive_sa *rxsa) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !rxsa) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->disable_receive_sa) { + wpa_printf(MSG_ERROR, + "KaY: secy disable_receive_sa operation not supported"); + return -1; + } + + rxsa->enable_receive = FALSE; + + return ops->disable_receive_sa(ops->ctx, rxsa->sc->channel, rxsa->an); +} + + +int secy_get_available_transmit_sc(struct ieee802_1x_kay *kay, u32 *channel) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->get_available_transmit_sc) { + wpa_printf(MSG_ERROR, + "KaY: secy get_available_transmit_sc operation not supported"); + return -1; + } + + return ops->get_available_transmit_sc(ops->ctx, channel); +} + + +int secy_create_transmit_sc(struct ieee802_1x_kay *kay, + struct transmit_sc *txsc) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !txsc) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->create_transmit_sc) { + wpa_printf(MSG_ERROR, + "KaY: secy create_transmit_sc operation not supported"); + return -1; + } + + return ops->create_transmit_sc(ops->ctx, txsc->channel, &txsc->sci, + kay->co); +} + + +int secy_delete_transmit_sc(struct ieee802_1x_kay *kay, + struct transmit_sc *txsc) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !txsc) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->delete_transmit_sc) { + wpa_printf(MSG_ERROR, + "KaY: secy delete_transmit_sc operation not supported"); + return -1; + } + + return ops->delete_transmit_sc(ops->ctx, txsc->channel); +} + + +int secy_create_transmit_sa(struct ieee802_1x_kay *kay, + struct transmit_sa *txsa) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !txsa) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->create_transmit_sa) { + wpa_printf(MSG_ERROR, + "KaY: secy create_transmit_sa operation not supported"); + return -1; + } + + return ops->create_transmit_sa(ops->ctx, txsa->sc->channel, txsa->an, + txsa->next_pn, txsa->confidentiality, + txsa->pkey->key); +} + + +int secy_enable_transmit_sa(struct ieee802_1x_kay *kay, + struct transmit_sa *txsa) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !txsa) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->enable_transmit_sa) { + wpa_printf(MSG_ERROR, + "KaY: secy enable_transmit_sa operation not supported"); + return -1; + } + + txsa->enable_transmit = TRUE; + + return ops->enable_transmit_sa(ops->ctx, txsa->sc->channel, txsa->an); +} + + +int secy_disable_transmit_sa(struct ieee802_1x_kay *kay, + struct transmit_sa *txsa) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay || !txsa) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->disable_transmit_sa) { + wpa_printf(MSG_ERROR, + "KaY: secy disable_transmit_sa operation not supported"); + return -1; + } + + txsa->enable_transmit = FALSE; + + return ops->disable_transmit_sa(ops->ctx, txsa->sc->channel, txsa->an); +} + + +int secy_init_macsec(struct ieee802_1x_kay *kay) +{ + int ret; + struct ieee802_1x_kay_ctx *ops; + struct macsec_init_params params; + + if (!kay) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->macsec_init) { + wpa_printf(MSG_ERROR, + "KaY: secy macsec_init operation not supported"); + return -1; + } + + params.use_es = FALSE; + params.use_scb = FALSE; + params.always_include_sci = TRUE; + + ret = ops->macsec_init(ops->ctx, ¶ms); + + return ret; +} + + +int secy_deinit_macsec(struct ieee802_1x_kay *kay) +{ + struct ieee802_1x_kay_ctx *ops; + + if (!kay) { + wpa_printf(MSG_ERROR, "KaY: %s params invalid", __func__); + return -1; + } + + ops = kay->ctx; + if (!ops || !ops->macsec_deinit) { + wpa_printf(MSG_ERROR, + "KaY: secy macsec_deinit operation not supported"); + return -1; + } + + return ops->macsec_deinit(ops->ctx); +} diff --git a/src/pae/ieee802_1x_secy_ops.h b/src/pae/ieee802_1x_secy_ops.h new file mode 100644 index 0000000..295b823 --- /dev/null +++ b/src/pae/ieee802_1x_secy_ops.h @@ -0,0 +1,62 @@ + /* + * SecY Operations + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef IEEE802_1X_SECY_OPS_H +#define IEEE802_1X_SECY_OPS_H + +#include "common/defs.h" +#include "common/ieee802_1x_defs.h" + +struct ieee802_1x_kay_conf; +struct receive_sa; +struct transmit_sa; +struct receive_sc; +struct transmit_sc; + +int secy_init_macsec(struct ieee802_1x_kay *kay); +int secy_deinit_macsec(struct ieee802_1x_kay *kay); + +/****** CP -> SecY ******/ +int secy_cp_control_validate_frames(struct ieee802_1x_kay *kay, + enum validate_frames vf); +int secy_cp_control_protect_frames(struct ieee802_1x_kay *kay, Boolean flag); +int secy_cp_control_replay(struct ieee802_1x_kay *kay, Boolean flag, u32 win); +int secy_cp_control_current_cipher_suite(struct ieee802_1x_kay *kay, + const u8 *cs, size_t cs_len); +int secy_cp_control_confidentiality_offset(struct ieee802_1x_kay *kay, + enum confidentiality_offset co); +int secy_cp_control_enable_port(struct ieee802_1x_kay *kay, Boolean flag); + +/****** KaY -> SecY *******/ +int secy_get_receive_lowest_pn(struct ieee802_1x_kay *kay, + struct receive_sa *rxsa); +int secy_get_transmit_next_pn(struct ieee802_1x_kay *kay, + struct transmit_sa *txsa); +int secy_set_transmit_next_pn(struct ieee802_1x_kay *kay, + struct transmit_sa *txsa); +int secy_get_available_receive_sc(struct ieee802_1x_kay *kay, u32 *channel); +int secy_create_receive_sc(struct ieee802_1x_kay *kay, struct receive_sc *rxsc); +int secy_delete_receive_sc(struct ieee802_1x_kay *kay, struct receive_sc *rxsc); +int secy_create_receive_sa(struct ieee802_1x_kay *kay, struct receive_sa *rxsa); +int secy_enable_receive_sa(struct ieee802_1x_kay *kay, struct receive_sa *rxsa); +int secy_disable_receive_sa(struct ieee802_1x_kay *kay, + struct receive_sa *rxsa); + +int secy_get_available_transmit_sc(struct ieee802_1x_kay *kay, u32 *channel); +int secy_create_transmit_sc(struct ieee802_1x_kay *kay, + struct transmit_sc *txsc); +int secy_delete_transmit_sc(struct ieee802_1x_kay *kay, + struct transmit_sc *txsc); +int secy_create_transmit_sa(struct ieee802_1x_kay *kay, + struct transmit_sa *txsa); +int secy_enable_transmit_sa(struct ieee802_1x_kay *kay, + struct transmit_sa *txsa); +int secy_disable_transmit_sa(struct ieee802_1x_kay *kay, + struct transmit_sa *txsa); + +#endif /* IEEE802_1X_SECY_OPS_H */ diff --git a/src/radius/radius.c b/src/radius/radius.c index 494f92d..e34d08b 100644 --- a/src/radius/radius.c +++ b/src/radius/radius.c @@ -1,6 +1,6 @@ /* * RADIUS message processing - * Copyright (c) 2002-2009, 2011-2012, Jouni Malinen <j@w1.fi> + * Copyright (c) 2002-2009, 2011-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -231,7 +231,8 @@ static struct radius_attr_type radius_attrs[] = { RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, "Chargeable-User-Identity", RADIUS_ATTR_TEXT }, { RADIUS_ATTR_NAS_IPV6_ADDRESS, "NAS-IPv6-Address", RADIUS_ATTR_IPV6 }, - { RADIUS_ATTR_ERROR_CAUSE, "Error-Cause", RADIUS_ATTR_INT32 } + { RADIUS_ATTR_ERROR_CAUSE, "Error-Cause", RADIUS_ATTR_INT32 }, + { RADIUS_ATTR_EAP_KEY_NAME, "EAP-Key-Name", RADIUS_ATTR_HEXDUMP }, }; #define RADIUS_ATTRS ARRAY_SIZE(radius_attrs) @@ -249,25 +250,17 @@ static struct radius_attr_type *radius_get_attr_type(u8 type) } -static void print_char(char c) -{ - if (c >= 32 && c < 127) - printf("%c", c); - else - printf("<%02x>", c); -} - - static void radius_msg_dump_attr(struct radius_attr_hdr *hdr) { struct radius_attr_type *attr; - int i, len; + int len; unsigned char *pos; + char buf[1000]; attr = radius_get_attr_type(hdr->type); - printf(" Attribute %d (%s) length=%d\n", - hdr->type, attr ? attr->name : "?Unknown?", hdr->length); + wpa_printf(MSG_INFO, " Attribute %d (%s) length=%d", + hdr->type, attr ? attr->name : "?Unknown?", hdr->length); if (attr == NULL || hdr->length < sizeof(struct radius_attr_hdr)) return; @@ -277,47 +270,50 @@ static void radius_msg_dump_attr(struct radius_attr_hdr *hdr) switch (attr->data_type) { case RADIUS_ATTR_TEXT: - printf(" Value: '"); - for (i = 0; i < len; i++) - print_char(pos[i]); - printf("'\n"); + printf_encode(buf, sizeof(buf), pos, len); + wpa_printf(MSG_INFO, " Value: '%s'", buf); break; case RADIUS_ATTR_IP: if (len == 4) { struct in_addr addr; os_memcpy(&addr, pos, 4); - printf(" Value: %s\n", inet_ntoa(addr)); - } else - printf(" Invalid IP address length %d\n", len); + wpa_printf(MSG_INFO, " Value: %s", + inet_ntoa(addr)); + } else { + wpa_printf(MSG_INFO, " Invalid IP address length %d", + len); + } break; #ifdef CONFIG_IPV6 case RADIUS_ATTR_IPV6: if (len == 16) { - char buf[128]; const char *atxt; struct in6_addr *addr = (struct in6_addr *) pos; atxt = inet_ntop(AF_INET6, addr, buf, sizeof(buf)); - printf(" Value: %s\n", atxt ? atxt : "?"); - } else - printf(" Invalid IPv6 address length %d\n", len); + wpa_printf(MSG_INFO, " Value: %s", + atxt ? atxt : "?"); + } else { + wpa_printf(MSG_INFO, " Invalid IPv6 address length %d", + len); + } break; #endif /* CONFIG_IPV6 */ case RADIUS_ATTR_HEXDUMP: case RADIUS_ATTR_UNDIST: - printf(" Value:"); - for (i = 0; i < len; i++) - printf(" %02x", pos[i]); - printf("\n"); + wpa_snprintf_hex(buf, sizeof(buf), pos, len); + wpa_printf(MSG_INFO, " Value: %s", buf); break; case RADIUS_ATTR_INT32: if (len == 4) - printf(" Value: %u\n", WPA_GET_BE32(pos)); + wpa_printf(MSG_INFO, " Value: %u", + WPA_GET_BE32(pos)); else - printf(" Invalid INT32 length %d\n", len); + wpa_printf(MSG_INFO, " Invalid INT32 length %d", + len); break; default: @@ -330,9 +326,9 @@ void radius_msg_dump(struct radius_msg *msg) { size_t i; - printf("RADIUS message: code=%d (%s) identifier=%d length=%d\n", - msg->hdr->code, radius_code_string(msg->hdr->code), - msg->hdr->identifier, be_to_host16(msg->hdr->length)); + wpa_printf(MSG_INFO, "RADIUS message: code=%d (%s) identifier=%d length=%d", + msg->hdr->code, radius_code_string(msg->hdr->code), + msg->hdr->identifier, be_to_host16(msg->hdr->length)); for (i = 0; i < msg->attr_used; i++) { struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i); @@ -384,7 +380,7 @@ int radius_msg_finish_srv(struct radius_msg *msg, const u8 *secret, attr = radius_msg_add_attr(msg, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, auth, MD5_MAC_LEN); if (attr == NULL) { - printf("WARNING: Could not add Message-Authenticator\n"); + wpa_printf(MSG_ERROR, "WARNING: Could not add Message-Authenticator"); return -1; } msg->hdr->length = host_to_be16(wpabuf_len(msg->buf)); @@ -473,6 +469,27 @@ void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret, } +void radius_msg_finish_acct_resp(struct radius_msg *msg, const u8 *secret, + size_t secret_len, const u8 *req_authenticator) +{ + const u8 *addr[2]; + size_t len[2]; + + msg->hdr->length = host_to_be16(wpabuf_len(msg->buf)); + os_memcpy(msg->hdr->authenticator, req_authenticator, MD5_MAC_LEN); + addr[0] = wpabuf_head(msg->buf); + len[0] = wpabuf_len(msg->buf); + addr[1] = secret; + len[1] = secret_len; + md5_vector(2, addr, len, msg->hdr->authenticator); + + if (wpabuf_len(msg->buf) > 0xffff) { + wpa_printf(MSG_WARNING, "RADIUS: Too long messages (%lu)", + (unsigned long) wpabuf_len(msg->buf)); + } +} + + int radius_msg_verify_acct_req(struct radius_msg *msg, const u8 *secret, size_t secret_len) { @@ -585,7 +602,7 @@ struct radius_attr_hdr *radius_msg_add_attr(struct radius_msg *msg, u8 type, struct radius_attr_hdr *attr; if (data_len > RADIUS_MAX_ATTR_LEN) { - printf("radius_msg_add_attr: too long attribute (%lu bytes)\n", + wpa_printf(MSG_ERROR, "radius_msg_add_attr: too long attribute (%lu bytes)", (unsigned long) data_len); return NULL; } @@ -756,8 +773,7 @@ int radius_msg_verify_msg_auth(struct radius_msg *msg, const u8 *secret, tmp = radius_get_attr_hdr(msg, i); if (tmp->type == RADIUS_ATTR_MESSAGE_AUTHENTICATOR) { if (attr != NULL) { - printf("Multiple Message-Authenticator " - "attributes in RADIUS message\n"); + wpa_printf(MSG_INFO, "Multiple Message-Authenticator attributes in RADIUS message"); return 1; } attr = tmp; @@ -765,7 +781,7 @@ int radius_msg_verify_msg_auth(struct radius_msg *msg, const u8 *secret, } if (attr == NULL) { - printf("No Message-Authenticator attribute found\n"); + wpa_printf(MSG_INFO, "No Message-Authenticator attribute found"); return 1; } @@ -786,7 +802,7 @@ int radius_msg_verify_msg_auth(struct radius_msg *msg, const u8 *secret, } if (os_memcmp(orig, auth, MD5_MAC_LEN) != 0) { - printf("Invalid Message-Authenticator!\n"); + wpa_printf(MSG_INFO, "Invalid Message-Authenticator!"); return 1; } @@ -802,7 +818,7 @@ int radius_msg_verify(struct radius_msg *msg, const u8 *secret, u8 hash[MD5_MAC_LEN]; if (sent_msg == NULL) { - printf("No matching Access-Request message found\n"); + wpa_printf(MSG_INFO, "No matching Access-Request message found"); return 1; } @@ -823,7 +839,7 @@ int radius_msg_verify(struct radius_msg *msg, const u8 *secret, len[3] = secret_len; md5_vector(4, addr, len, hash); if (os_memcmp(hash, msg->hdr->authenticator, MD5_MAC_LEN) != 0) { - printf("Response Authenticator invalid!\n"); + wpa_printf(MSG_INFO, "Response Authenticator invalid!"); return 1; } @@ -962,7 +978,8 @@ static u8 * decrypt_ms_key(const u8 *key, size_t len, pos = key + 2; left = len - 2; if (left % 16) { - printf("Invalid ms key len %lu\n", (unsigned long) left); + wpa_printf(MSG_INFO, "Invalid ms key len %lu", + (unsigned long) left); return NULL; } @@ -996,7 +1013,7 @@ static u8 * decrypt_ms_key(const u8 *key, size_t len, } if (plain[0] == 0 || plain[0] > plen - 1) { - printf("Failed to decrypt MPPE key\n"); + wpa_printf(MSG_INFO, "Failed to decrypt MPPE key"); os_free(plain); return NULL; } @@ -1204,30 +1221,55 @@ int radius_msg_add_mppe_keys(struct radius_msg *msg, } -/* Add User-Password attribute to a RADIUS message and encrypt it as specified - * in RFC 2865, Chap. 5.2 */ -struct radius_attr_hdr * -radius_msg_add_attr_user_password(struct radius_msg *msg, - const u8 *data, size_t data_len, - const u8 *secret, size_t secret_len) +int radius_msg_add_wfa(struct radius_msg *msg, u8 subtype, const u8 *data, + size_t len) { - u8 buf[128]; - size_t padlen, i, buf_len, pos; + struct radius_attr_hdr *attr; + u8 *buf, *pos; + size_t alen; + + alen = 4 + 2 + len; + buf = os_malloc(alen); + if (buf == NULL) + return 0; + pos = buf; + WPA_PUT_BE32(pos, RADIUS_VENDOR_ID_WFA); + pos += 4; + *pos++ = subtype; + *pos++ = 2 + len; + os_memcpy(pos, data, len); + attr = radius_msg_add_attr(msg, RADIUS_ATTR_VENDOR_SPECIFIC, + buf, alen); + os_free(buf); + if (attr == NULL) + return 0; + + return 1; +} + + +int radius_user_password_hide(struct radius_msg *msg, + const u8 *data, size_t data_len, + const u8 *secret, size_t secret_len, + u8 *buf, size_t buf_len) +{ + size_t padlen, i, pos; const u8 *addr[2]; size_t len[2]; u8 hash[16]; - if (data_len > 128) - return NULL; + if (data_len + 16 > buf_len) + return -1; os_memcpy(buf, data, data_len); - buf_len = data_len; padlen = data_len % 16; - if (padlen && data_len < sizeof(buf)) { + if (padlen && data_len < buf_len) { padlen = 16 - padlen; os_memset(buf + data_len, 0, padlen); - buf_len += padlen; + buf_len = data_len + padlen; + } else { + buf_len = data_len; } addr[0] = secret; @@ -1253,8 +1295,27 @@ radius_msg_add_attr_user_password(struct radius_msg *msg, pos += 16; } + return buf_len; +} + + +/* Add User-Password attribute to a RADIUS message and encrypt it as specified + * in RFC 2865, Chap. 5.2 */ +struct radius_attr_hdr * +radius_msg_add_attr_user_password(struct radius_msg *msg, + const u8 *data, size_t data_len, + const u8 *secret, size_t secret_len) +{ + u8 buf[128]; + int res; + + res = radius_user_password_hide(msg, data, data_len, + secret, secret_len, buf, sizeof(buf)); + if (res < 0) + return NULL; + return radius_msg_add_attr(msg, RADIUS_ATTR_USER_PASSWORD, - buf, buf_len); + buf, res); } diff --git a/src/radius/radius.h b/src/radius/radius.h index 2031054..34307f2 100644 --- a/src/radius/radius.h +++ b/src/radius/radius.h @@ -1,6 +1,6 @@ /* * RADIUS message processing - * Copyright (c) 2002-2009, 2012, Jouni Malinen <j@w1.fi> + * Copyright (c) 2002-2009, 2012, 2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -90,7 +90,8 @@ enum { RADIUS_ATTR_USER_NAME = 1, RADIUS_ATTR_ACCT_INTERIM_INTERVAL = 85, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY = 89, RADIUS_ATTR_NAS_IPV6_ADDRESS = 95, - RADIUS_ATTR_ERROR_CAUSE = 101 + RADIUS_ATTR_ERROR_CAUSE = 101, + RADIUS_ATTR_EAP_KEY_NAME = 102, }; @@ -163,6 +164,18 @@ enum { RADIUS_VENDOR_ATTR_MS_MPPE_SEND_KEY = 16, RADIUS_VENDOR_ATTR_MS_MPPE_RECV_KEY = 17 }; + +/* Hotspot 2.0 - WFA Vendor-specific RADIUS Attributes */ +#define RADIUS_VENDOR_ID_WFA 40808 + +enum { + RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION = 1, + RADIUS_VENDOR_ATTR_WFA_HS20_AP_VERSION = 2, + RADIUS_VENDOR_ATTR_WFA_HS20_STA_VERSION = 3, + RADIUS_VENDOR_ATTR_WFA_HS20_DEAUTH_REQ = 4, + RADIUS_VENDOR_ATTR_WFA_HS20_SESSION_INFO_URL = 5, +}; + #ifdef _MSC_VER #pragma pack(pop) #endif /* _MSC_VER */ @@ -204,6 +217,9 @@ int radius_msg_finish_das_resp(struct radius_msg *msg, const u8 *secret, const struct radius_hdr *req_hdr); void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret, size_t secret_len); +void radius_msg_finish_acct_resp(struct radius_msg *msg, const u8 *secret, + size_t secret_len, + const u8 *req_authenticator); int radius_msg_verify_acct_req(struct radius_msg *msg, const u8 *secret, size_t secret_len); int radius_msg_verify_das_req(struct radius_msg *msg, const u8 *secret, @@ -234,6 +250,12 @@ int radius_msg_add_mppe_keys(struct radius_msg *msg, const u8 *secret, size_t secret_len, const u8 *send_key, size_t send_key_len, const u8 *recv_key, size_t recv_key_len); +int radius_msg_add_wfa(struct radius_msg *msg, u8 subtype, const u8 *data, + size_t len); +int radius_user_password_hide(struct radius_msg *msg, + const u8 *data, size_t data_len, + const u8 *secret, size_t secret_len, + u8 *buf, size_t buf_len); struct radius_attr_hdr * radius_msg_add_attr_user_password(struct radius_msg *msg, const u8 *data, size_t data_len, diff --git a/src/radius/radius_client.c b/src/radius/radius_client.c index 7625996..10056a6 100644 --- a/src/radius/radius_client.c +++ b/src/radius/radius_client.c @@ -295,26 +295,34 @@ int radius_client_register(struct radius_client_data *radius, } -static void radius_client_handle_send_error(struct radius_client_data *radius, - int s, RadiusType msg_type) +/* + * Returns >0 if message queue was flushed (i.e., the message that triggered + * the error is not available anymore) + */ +static int radius_client_handle_send_error(struct radius_client_data *radius, + int s, RadiusType msg_type) { #ifndef CONFIG_NATIVE_WINDOWS int _errno = errno; wpa_printf(MSG_INFO, "send[RADIUS]: %s", strerror(errno)); if (_errno == ENOTCONN || _errno == EDESTADDRREQ || _errno == EINVAL || - _errno == EBADF) { + _errno == EBADF || _errno == ENETUNREACH) { hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, HOSTAPD_LEVEL_INFO, "Send failed - maybe interface status changed -" " try to connect again"); - eloop_unregister_read_sock(s); - close(s); - if (msg_type == RADIUS_ACCT || msg_type == RADIUS_ACCT_INTERIM) + if (msg_type == RADIUS_ACCT || + msg_type == RADIUS_ACCT_INTERIM) { radius_client_init_acct(radius); - else + return 0; + } else { radius_client_init_auth(radius); + return 1; + } } #endif /* CONFIG_NATIVE_WINDOWS */ + + return 0; } @@ -353,8 +361,11 @@ static int radius_client_retransmit(struct radius_client_data *radius, os_get_reltime(&entry->last_attempt); buf = radius_msg_get_buf(entry->msg); - if (send(s, wpabuf_head(buf), wpabuf_len(buf), 0) < 0) - radius_client_handle_send_error(radius, s, entry->msg_type); + if (send(s, wpabuf_head(buf), wpabuf_len(buf), 0) < 0) { + if (radius_client_handle_send_error(radius, s, entry->msg_type) + > 0) + return 0; + } entry->next_try = now + entry->next_wait; entry->next_wait *= 2; @@ -378,6 +389,8 @@ static void radius_client_timer(void *eloop_ctx, void *timeout_ctx) struct radius_msg_list *entry, *prev, *tmp; int auth_failover = 0, acct_failover = 0; char abuf[50]; + size_t prev_num_msgs; + int s; entry = radius->msgs; if (!entry) @@ -388,6 +401,7 @@ static void radius_client_timer(void *eloop_ctx, void *timeout_ctx) prev = NULL; while (entry) { + prev_num_msgs = radius->num_msgs; if (now.sec >= entry->next_try && radius_client_retransmit(radius, entry, now.sec)) { if (prev) @@ -402,7 +416,18 @@ static void radius_client_timer(void *eloop_ctx, void *timeout_ctx) continue; } - if (entry->attempts > RADIUS_CLIENT_NUM_FAILOVER) { + if (prev_num_msgs != radius->num_msgs) { + wpa_printf(MSG_DEBUG, + "RADIUS: Message removed from queue - restart from beginning"); + entry = radius->msgs; + prev = NULL; + continue; + } + + s = entry->msg_type == RADIUS_AUTH ? radius->auth_sock : + radius->acct_sock; + if (entry->attempts > RADIUS_CLIENT_NUM_FAILOVER || + (s < 0 && entry->attempts > 0)) { if (entry->msg_type == RADIUS_ACCT || entry->msg_type == RADIUS_ACCT_INTERIM) acct_failover++; @@ -633,7 +658,7 @@ int radius_client_send(struct radius_client_data *radius, } if (msg_type == RADIUS_ACCT || msg_type == RADIUS_ACCT_INTERIM) { - if (conf->acct_server == NULL) { + if (conf->acct_server == NULL || radius->acct_sock < 0) { hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, HOSTAPD_LEVEL_INFO, @@ -647,7 +672,7 @@ int radius_client_send(struct radius_client_data *radius, s = radius->acct_sock; conf->acct_server->requests++; } else { - if (conf->auth_server == NULL) { + if (conf->auth_server == NULL || radius->auth_sock < 0) { hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, HOSTAPD_LEVEL_INFO, @@ -951,9 +976,10 @@ radius_change_server(struct radius_client_data *radius, hostapd_ip_txt(&nserv->addr, abuf, sizeof(abuf)), nserv->port); - if (!oserv || nserv->shared_secret_len != oserv->shared_secret_len || - os_memcmp(nserv->shared_secret, oserv->shared_secret, - nserv->shared_secret_len) != 0) { + if (oserv && oserv != nserv && + (nserv->shared_secret_len != oserv->shared_secret_len || + os_memcmp(nserv->shared_secret, oserv->shared_secret, + nserv->shared_secret_len) != 0)) { /* Pending RADIUS packets used different shared secret, so * they need to be modified. Update accounting message * authenticators here. Authentication messages are removed @@ -971,7 +997,8 @@ radius_change_server(struct radius_client_data *radius, } /* Reset retry counters for the new server */ - for (entry = radius->msgs; entry; entry = entry->next) { + for (entry = radius->msgs; oserv && oserv != nserv && entry; + entry = entry->next) { if ((auth && entry->msg_type != RADIUS_AUTH) || (!auth && entry->msg_type != RADIUS_ACCT)) continue; @@ -1128,11 +1155,51 @@ static int radius_client_disable_pmtu_discovery(int s) } +static void radius_close_auth_sockets(struct radius_client_data *radius) +{ + radius->auth_sock = -1; + + if (radius->auth_serv_sock >= 0) { + eloop_unregister_read_sock(radius->auth_serv_sock); + close(radius->auth_serv_sock); + radius->auth_serv_sock = -1; + } +#ifdef CONFIG_IPV6 + if (radius->auth_serv_sock6 >= 0) { + eloop_unregister_read_sock(radius->auth_serv_sock6); + close(radius->auth_serv_sock6); + radius->auth_serv_sock6 = -1; + } +#endif /* CONFIG_IPV6 */ +} + + +static void radius_close_acct_sockets(struct radius_client_data *radius) +{ + radius->acct_sock = -1; + + if (radius->acct_serv_sock >= 0) { + eloop_unregister_read_sock(radius->acct_serv_sock); + close(radius->acct_serv_sock); + radius->acct_serv_sock = -1; + } +#ifdef CONFIG_IPV6 + if (radius->acct_serv_sock6 >= 0) { + eloop_unregister_read_sock(radius->acct_serv_sock6); + close(radius->acct_serv_sock6); + radius->acct_serv_sock6 = -1; + } +#endif /* CONFIG_IPV6 */ +} + + static int radius_client_init_auth(struct radius_client_data *radius) { struct hostapd_radius_servers *conf = radius->conf; int ok = 0; + radius_close_auth_sockets(radius); + radius->auth_serv_sock = socket(PF_INET, SOCK_DGRAM, 0); if (radius->auth_serv_sock < 0) wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET,SOCK_DGRAM]: %s", @@ -1163,6 +1230,7 @@ static int radius_client_init_auth(struct radius_client_data *radius) radius_client_receive, radius, (void *) RADIUS_AUTH)) { wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for authentication server"); + radius_close_auth_sockets(radius); return -1; } @@ -1172,6 +1240,7 @@ static int radius_client_init_auth(struct radius_client_data *radius) radius_client_receive, radius, (void *) RADIUS_AUTH)) { wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for authentication server"); + radius_close_auth_sockets(radius); return -1; } #endif /* CONFIG_IPV6 */ @@ -1185,6 +1254,8 @@ static int radius_client_init_acct(struct radius_client_data *radius) struct hostapd_radius_servers *conf = radius->conf; int ok = 0; + radius_close_acct_sockets(radius); + radius->acct_serv_sock = socket(PF_INET, SOCK_DGRAM, 0); if (radius->acct_serv_sock < 0) wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET,SOCK_DGRAM]: %s", @@ -1215,6 +1286,7 @@ static int radius_client_init_acct(struct radius_client_data *radius) radius_client_receive, radius, (void *) RADIUS_ACCT)) { wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for accounting server"); + radius_close_acct_sockets(radius); return -1; } @@ -1224,6 +1296,7 @@ static int radius_client_init_acct(struct radius_client_data *radius) radius_client_receive, radius, (void *) RADIUS_ACCT)) { wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for accounting server"); + radius_close_acct_sockets(radius); return -1; } #endif /* CONFIG_IPV6 */ @@ -1285,16 +1358,8 @@ void radius_client_deinit(struct radius_client_data *radius) if (!radius) return; - if (radius->auth_serv_sock >= 0) - eloop_unregister_read_sock(radius->auth_serv_sock); - if (radius->acct_serv_sock >= 0) - eloop_unregister_read_sock(radius->acct_serv_sock); -#ifdef CONFIG_IPV6 - if (radius->auth_serv_sock6 >= 0) - eloop_unregister_read_sock(radius->auth_serv_sock6); - if (radius->acct_serv_sock6 >= 0) - eloop_unregister_read_sock(radius->acct_serv_sock6); -#endif /* CONFIG_IPV6 */ + radius_close_auth_sockets(radius); + radius_close_acct_sockets(radius); eloop_cancel_timeout(radius_retry_primary_timer, radius, NULL); diff --git a/src/radius/radius_das.c b/src/radius/radius_das.c index b2a2773..9655f4c 100644 --- a/src/radius/radius_das.c +++ b/src/radius/radius_das.c @@ -38,11 +38,16 @@ static struct radius_msg * radius_das_disconnect(struct radius_das_data *das, struct radius_msg *reply; u8 allowed[] = { RADIUS_ATTR_USER_NAME, + RADIUS_ATTR_NAS_IP_ADDRESS, RADIUS_ATTR_CALLING_STATION_ID, + RADIUS_ATTR_NAS_IDENTIFIER, RADIUS_ATTR_ACCT_SESSION_ID, RADIUS_ATTR_EVENT_TIMESTAMP, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, +#ifdef CONFIG_IPV6 + RADIUS_ATTR_NAS_IPV6_ADDRESS, +#endif /* CONFIG_IPV6 */ 0 }; int error = 405; @@ -67,6 +72,36 @@ static struct radius_msg * radius_das_disconnect(struct radius_das_data *das, os_memset(&attrs, 0, sizeof(attrs)); + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IP_ADDRESS, + &buf, &len, NULL) == 0) { + if (len != 4) { + wpa_printf(MSG_INFO, "DAS: Invalid NAS-IP-Address from %s:%d", + abuf, from_port); + error = 407; + goto fail; + } + attrs.nas_ip_addr = buf; + } + +#ifdef CONFIG_IPV6 + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS, + &buf, &len, NULL) == 0) { + if (len != 16) { + wpa_printf(MSG_INFO, "DAS: Invalid NAS-IPv6-Address from %s:%d", + abuf, from_port); + error = 407; + goto fail; + } + attrs.nas_ipv6_addr = buf; + } +#endif /* CONFIG_IPV6 */ + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER, + &buf, &len, NULL) == 0) { + attrs.nas_identifier = buf; + attrs.nas_identifier_len = len; + } + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CALLING_STATION_ID, &buf, &len, NULL) == 0) { if (len >= sizeof(tmp)) diff --git a/src/radius/radius_das.h b/src/radius/radius_das.h index 738b18b..e3ed540 100644 --- a/src/radius/radius_das.h +++ b/src/radius/radius_das.h @@ -18,6 +18,13 @@ enum radius_das_res { }; struct radius_das_attrs { + /* NAS identification attributes */ + const u8 *nas_ip_addr; + const u8 *nas_identifier; + size_t nas_identifier_len; + const u8 *nas_ipv6_addr; + + /* Session identification attributes */ const u8 *sta_addr; const u8 *user_name; size_t user_name_len; diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c index 1063d65..c35ba55 100644 --- a/src/radius/radius_server.c +++ b/src/radius/radius_server.c @@ -1,6 +1,6 @@ /* * RADIUS authentication server - * Copyright (c) 2005-2009, 2011, Jouni Malinen <j@w1.fi> + * Copyright (c) 2005-2009, 2011-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -8,11 +8,16 @@ #include "includes.h" #include <net/if.h> +#ifdef CONFIG_SQLITE +#include <sqlite3.h> +#endif /* CONFIG_SQLITE */ #include "common.h" #include "radius.h" #include "eloop.h" #include "eap_server/eap.h" +#include "ap/ap_config.h" +#include "crypto/tls.h" #include "radius_server.h" /** @@ -49,6 +54,13 @@ struct radius_server_counters { u32 bad_authenticators; u32 packets_dropped; u32 unknown_types; + + u32 acct_requests; + u32 invalid_acct_requests; + u32 acct_responses; + u32 malformed_acct_requests; + u32 acct_bad_authenticators; + u32 unknown_acct_types; }; /** @@ -61,6 +73,8 @@ struct radius_session { unsigned int sess_id; struct eap_sm *eap; struct eap_eapol_interface *eap_if; + char *username; /* from User-Name attribute */ + char *nas_ip; struct radius_msg *last_msg; char *last_from_addr; @@ -70,6 +84,11 @@ struct radius_session { u8 last_identifier; struct radius_msg *last_reply; u8 last_authenticator[16]; + + unsigned int remediation:1; + unsigned int macacl:1; + + struct hostapd_radius_attr *accept_attr; }; /** @@ -99,6 +118,11 @@ struct radius_server_data { int auth_sock; /** + * acct_sock - Socket for RADIUS accounting messages + */ + int acct_sock; + + /** * clients - List of authorized RADIUS clients */ struct radius_client *clients; @@ -295,6 +319,13 @@ struct radius_server_data { #ifdef CONFIG_RADIUS_TEST char *dump_msk_file; #endif /* CONFIG_RADIUS_TEST */ + + char *subscr_remediation_url; + u8 subscr_remediation_method; + +#ifdef CONFIG_SQLITE + sqlite3 *db; +#endif /* CONFIG_SQLITE */ }; @@ -312,6 +343,52 @@ static void radius_server_session_timeout(void *eloop_ctx, void *timeout_ctx); static void radius_server_session_remove_timeout(void *eloop_ctx, void *timeout_ctx); +void srv_log(struct radius_session *sess, const char *fmt, ...) +PRINTF_FORMAT(2, 3); + +void srv_log(struct radius_session *sess, const char *fmt, ...) +{ + va_list ap; + char *buf; + int buflen; + + va_start(ap, fmt); + buflen = vsnprintf(NULL, 0, fmt, ap) + 1; + va_end(ap); + + buf = os_malloc(buflen); + if (buf == NULL) + return; + va_start(ap, fmt); + vsnprintf(buf, buflen, fmt, ap); + va_end(ap); + + RADIUS_DEBUG("[0x%x %s] %s", sess->sess_id, sess->nas_ip, buf); + +#ifdef CONFIG_SQLITE + if (sess->server->db) { + char *sql; + sql = sqlite3_mprintf("INSERT INTO authlog" + "(timestamp,session,nas_ip,username,note)" + " VALUES (" + "strftime('%%Y-%%m-%%d %%H:%%M:%%f'," + "'now'),%u,%Q,%Q,%Q)", + sess->sess_id, sess->nas_ip, + sess->username, buf); + if (sql) { + if (sqlite3_exec(sess->server->db, sql, NULL, NULL, + NULL) != SQLITE_OK) { + RADIUS_ERROR("Failed to add authlog entry into sqlite database: %s", + sqlite3_errmsg(sess->server->db)); + } + sqlite3_free(sql); + } + } +#endif /* CONFIG_SQLITE */ + + os_free(buf); +} + static struct radius_client * radius_server_get_client(struct radius_server_data *data, struct in_addr *addr, @@ -377,6 +454,8 @@ static void radius_server_session_free(struct radius_server_data *data, radius_msg_free(sess->last_msg); os_free(sess->last_from_addr); radius_msg_free(sess->last_reply); + os_free(sess->username); + os_free(sess->nas_ip); os_free(sess); data->num_sess--; } @@ -456,47 +535,125 @@ radius_server_new_session(struct radius_server_data *data, } +#ifdef CONFIG_TESTING_OPTIONS +static void radius_server_testing_options_tls(struct radius_session *sess, + const char *tls, + struct eap_config *eap_conf) +{ + int test = atoi(tls); + + switch (test) { + case 1: + srv_log(sess, "TLS test - break VerifyData"); + eap_conf->tls_test_flags = TLS_BREAK_VERIFY_DATA; + break; + case 2: + srv_log(sess, "TLS test - break ServerKeyExchange ServerParams hash"); + eap_conf->tls_test_flags = TLS_BREAK_SRV_KEY_X_HASH; + break; + case 3: + srv_log(sess, "TLS test - break ServerKeyExchange ServerParams Signature"); + eap_conf->tls_test_flags = TLS_BREAK_SRV_KEY_X_SIGNATURE; + break; + case 4: + srv_log(sess, "TLS test - RSA-DHE using a short 511-bit prime"); + eap_conf->tls_test_flags = TLS_DHE_PRIME_511B; + break; + case 5: + srv_log(sess, "TLS test - RSA-DHE using a short 767-bit prime"); + eap_conf->tls_test_flags = TLS_DHE_PRIME_767B; + break; + case 6: + srv_log(sess, "TLS test - RSA-DHE using a bogus 15 \"prime\""); + eap_conf->tls_test_flags = TLS_DHE_PRIME_15; + break; + case 7: + srv_log(sess, "TLS test - RSA-DHE using a short 58-bit prime in long container"); + eap_conf->tls_test_flags = TLS_DHE_PRIME_58B; + break; + case 8: + srv_log(sess, "TLS test - RSA-DHE using a non-prime"); + eap_conf->tls_test_flags = TLS_DHE_NON_PRIME; + break; + default: + srv_log(sess, "Unrecognized TLS test"); + break; + } +} +#endif /* CONFIG_TESTING_OPTIONS */ + +static void radius_server_testing_options(struct radius_session *sess, + struct eap_config *eap_conf) +{ +#ifdef CONFIG_TESTING_OPTIONS + const char *pos; + + pos = os_strstr(sess->username, "@test-"); + if (pos == NULL) + return; + pos += 6; + if (os_strncmp(pos, "tls-", 4) == 0) + radius_server_testing_options_tls(sess, pos + 4, eap_conf); + else + srv_log(sess, "Unrecognized test: %s", pos); +#endif /* CONFIG_TESTING_OPTIONS */ +} + + static struct radius_session * radius_server_get_new_session(struct radius_server_data *data, struct radius_client *client, - struct radius_msg *msg) + struct radius_msg *msg, const char *from_addr) { u8 *user; size_t user_len; int res; struct radius_session *sess; struct eap_config eap_conf; + struct eap_user tmp; RADIUS_DEBUG("Creating a new session"); - user = os_malloc(256); - if (user == NULL) { - return NULL; - } - res = radius_msg_get_attr(msg, RADIUS_ATTR_USER_NAME, user, 256); - if (res < 0 || res > 256) { + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME, &user, + &user_len, NULL) < 0) { RADIUS_DEBUG("Could not get User-Name"); - os_free(user); return NULL; } - user_len = res; RADIUS_DUMP_ASCII("User-Name", user, user_len); - res = data->get_eap_user(data->conf_ctx, user, user_len, 0, NULL); - os_free(user); + os_memset(&tmp, 0, sizeof(tmp)); + res = data->get_eap_user(data->conf_ctx, user, user_len, 0, &tmp); + os_free(tmp.password); - if (res == 0) { - RADIUS_DEBUG("Matching user entry found"); - sess = radius_server_new_session(data, client); - if (sess == NULL) { - RADIUS_DEBUG("Failed to create a new session"); - return NULL; - } - } else { + if (res != 0) { RADIUS_DEBUG("User-Name not found from user database"); return NULL; } + RADIUS_DEBUG("Matching user entry found"); + sess = radius_server_new_session(data, client); + if (sess == NULL) { + RADIUS_DEBUG("Failed to create a new session"); + return NULL; + } + sess->accept_attr = tmp.accept_attr; + sess->macacl = tmp.macacl; + + sess->username = os_malloc(user_len * 4 + 1); + if (sess->username == NULL) { + radius_server_session_free(data, sess); + return NULL; + } + printf_encode(sess->username, user_len * 4 + 1, user, user_len); + + sess->nas_ip = os_strdup(from_addr); + if (sess->nas_ip == NULL) { + radius_server_session_free(data, sess); + return NULL; + } + + srv_log(sess, "New session created"); + os_memset(&eap_conf, 0, sizeof(eap_conf)); eap_conf.ssl_ctx = data->ssl_ctx; eap_conf.msg_ctx = data->msg_ctx; @@ -516,6 +673,7 @@ radius_server_get_new_session(struct radius_server_data *data, eap_conf.pwd_group = data->pwd_group; eap_conf.server_id = (const u8 *) data->server_id; eap_conf.server_id_len = os_strlen(data->server_id); + radius_server_testing_options(sess, &eap_conf); sess->eap = eap_server_sm_init(sess, &radius_server_eapol_cb, &eap_conf); if (sess->eap == NULL) { @@ -610,12 +768,134 @@ radius_server_encapsulate_eap(struct radius_server_data *data, } } +#ifdef CONFIG_HS20 + if (code == RADIUS_CODE_ACCESS_ACCEPT && sess->remediation && + data->subscr_remediation_url) { + u8 *buf; + size_t url_len = os_strlen(data->subscr_remediation_url); + buf = os_malloc(1 + url_len); + if (buf == NULL) { + radius_msg_free(msg); + return NULL; + } + buf[0] = data->subscr_remediation_method; + os_memcpy(&buf[1], data->subscr_remediation_url, url_len); + if (!radius_msg_add_wfa( + msg, RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION, + buf, 1 + url_len)) { + RADIUS_DEBUG("Failed to add WFA-HS20-SubscrRem"); + } + os_free(buf); + } else if (code == RADIUS_CODE_ACCESS_ACCEPT && sess->remediation) { + u8 buf[1]; + if (!radius_msg_add_wfa( + msg, RADIUS_VENDOR_ATTR_WFA_HS20_SUBSCR_REMEDIATION, + buf, 0)) { + RADIUS_DEBUG("Failed to add WFA-HS20-SubscrRem"); + } + } +#endif /* CONFIG_HS20 */ + + if (radius_msg_copy_attr(msg, request, RADIUS_ATTR_PROXY_STATE) < 0) { + RADIUS_DEBUG("Failed to copy Proxy-State attribute(s)"); + radius_msg_free(msg); + return NULL; + } + + if (code == RADIUS_CODE_ACCESS_ACCEPT) { + struct hostapd_radius_attr *attr; + for (attr = sess->accept_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"); + radius_msg_free(msg); + return NULL; + } + } + } + + if (radius_msg_finish_srv(msg, (u8 *) client->shared_secret, + client->shared_secret_len, + hdr->authenticator) < 0) { + RADIUS_DEBUG("Failed to add Message-Authenticator attribute"); + } + + return msg; +} + + +static struct radius_msg * +radius_server_macacl(struct radius_server_data *data, + struct radius_client *client, + struct radius_session *sess, + struct radius_msg *request) +{ + struct radius_msg *msg; + int code; + struct radius_hdr *hdr = radius_msg_get_hdr(request); + u8 *pw; + size_t pw_len; + + code = RADIUS_CODE_ACCESS_ACCEPT; + + if (radius_msg_get_attr_ptr(request, RADIUS_ATTR_USER_PASSWORD, &pw, + &pw_len, NULL) < 0) { + RADIUS_DEBUG("Could not get User-Password"); + code = RADIUS_CODE_ACCESS_REJECT; + } else { + int res; + struct eap_user tmp; + + os_memset(&tmp, 0, sizeof(tmp)); + res = data->get_eap_user(data->conf_ctx, (u8 *) sess->username, + os_strlen(sess->username), 0, &tmp); + if (res || !tmp.macacl || tmp.password == NULL) { + RADIUS_DEBUG("No MAC ACL user entry"); + os_free(tmp.password); + code = RADIUS_CODE_ACCESS_REJECT; + } else { + u8 buf[128]; + res = radius_user_password_hide( + request, tmp.password, tmp.password_len, + (u8 *) client->shared_secret, + client->shared_secret_len, + buf, sizeof(buf)); + os_free(tmp.password); + + if (res < 0 || pw_len != (size_t) res || + os_memcmp(pw, buf, res) != 0) { + RADIUS_DEBUG("Incorrect User-Password"); + code = RADIUS_CODE_ACCESS_REJECT; + } + } + } + + msg = radius_msg_new(code, hdr->identifier); + if (msg == NULL) { + RADIUS_DEBUG("Failed to allocate reply message"); + return NULL; + } + if (radius_msg_copy_attr(msg, request, RADIUS_ATTR_PROXY_STATE) < 0) { RADIUS_DEBUG("Failed to copy Proxy-State attribute(s)"); radius_msg_free(msg); return NULL; } + if (code == RADIUS_CODE_ACCESS_ACCEPT) { + struct hostapd_radius_attr *attr; + for (attr = sess->accept_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"); + radius_msg_free(msg); + return NULL; + } + } + } + if (radius_msg_finish_srv(msg, (u8 *) client->shared_secret, client->shared_secret_len, hdr->authenticator) < 0) { @@ -724,7 +1004,8 @@ static int radius_server_request(struct radius_server_data *data, from_addr, from_port); return -1; } else { - sess = radius_server_get_new_session(data, client, msg); + sess = radius_server_get_new_session(data, client, msg, + from_addr); if (sess == NULL) { RADIUS_DEBUG("Could not create a new session"); radius_server_reject(data, client, msg, from, fromlen, @@ -760,6 +1041,12 @@ static int radius_server_request(struct radius_server_data *data, } eap = radius_msg_get_eap(msg); + if (eap == NULL && sess->macacl) { + reply = radius_server_macacl(data, client, sess, msg); + if (reply == NULL) + return -1; + goto send_reply; + } if (eap == NULL) { RADIUS_DEBUG("No EAP-Message in RADIUS packet from %s", from_addr); @@ -810,9 +1097,14 @@ static int radius_server_request(struct radius_server_data *data, if (sess->eap_if->eapSuccess || sess->eap_if->eapFail) is_complete = 1; + if (sess->eap_if->eapFail) + srv_log(sess, "EAP authentication failed"); + else if (sess->eap_if->eapSuccess) + srv_log(sess, "EAP authentication succeeded"); reply = radius_server_encapsulate_eap(data, client, sess, msg); +send_reply: if (reply) { struct wpabuf *buf; struct radius_hdr *hdr; @@ -824,10 +1116,12 @@ static int radius_server_request(struct radius_server_data *data, switch (radius_msg_get_hdr(reply)->code) { case RADIUS_CODE_ACCESS_ACCEPT: + srv_log(sess, "Sending Access-Accept"); data->counters.access_accepts++; client->counters.access_accepts++; break; case RADIUS_CODE_ACCESS_REJECT: + srv_log(sess, "Sending Access-Reject"); data->counters.access_rejects++; client->counters.access_rejects++; break; @@ -979,6 +1273,140 @@ fail: } +static void radius_server_receive_acct(int sock, void *eloop_ctx, + void *sock_ctx) +{ + struct radius_server_data *data = eloop_ctx; + u8 *buf = NULL; + union { + struct sockaddr_storage ss; + struct sockaddr_in sin; +#ifdef CONFIG_IPV6 + struct sockaddr_in6 sin6; +#endif /* CONFIG_IPV6 */ + } from; + socklen_t fromlen; + int len, res; + struct radius_client *client = NULL; + struct radius_msg *msg = NULL, *resp = NULL; + char abuf[50]; + int from_port = 0; + struct radius_hdr *hdr; + struct wpabuf *rbuf; + + buf = os_malloc(RADIUS_MAX_MSG_LEN); + if (buf == NULL) { + goto fail; + } + + fromlen = sizeof(from); + len = recvfrom(sock, buf, RADIUS_MAX_MSG_LEN, 0, + (struct sockaddr *) &from.ss, &fromlen); + if (len < 0) { + wpa_printf(MSG_INFO, "recvfrom[radius_server]: %s", + strerror(errno)); + goto fail; + } + +#ifdef CONFIG_IPV6 + if (data->ipv6) { + if (inet_ntop(AF_INET6, &from.sin6.sin6_addr, abuf, + sizeof(abuf)) == NULL) + abuf[0] = '\0'; + from_port = ntohs(from.sin6.sin6_port); + RADIUS_DEBUG("Received %d bytes from %s:%d", + len, abuf, from_port); + + client = radius_server_get_client(data, + (struct in_addr *) + &from.sin6.sin6_addr, 1); + } +#endif /* CONFIG_IPV6 */ + + if (!data->ipv6) { + os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf)); + from_port = ntohs(from.sin.sin_port); + RADIUS_DEBUG("Received %d bytes from %s:%d", + len, abuf, from_port); + + client = radius_server_get_client(data, &from.sin.sin_addr, 0); + } + + RADIUS_DUMP("Received data", buf, len); + + if (client == NULL) { + RADIUS_DEBUG("Unknown client %s - packet ignored", abuf); + data->counters.invalid_acct_requests++; + goto fail; + } + + msg = radius_msg_parse(buf, len); + if (msg == NULL) { + RADIUS_DEBUG("Parsing incoming RADIUS frame failed"); + data->counters.malformed_acct_requests++; + client->counters.malformed_acct_requests++; + goto fail; + } + + os_free(buf); + buf = NULL; + + if (wpa_debug_level <= MSG_MSGDUMP) { + radius_msg_dump(msg); + } + + if (radius_msg_get_hdr(msg)->code != RADIUS_CODE_ACCOUNTING_REQUEST) { + RADIUS_DEBUG("Unexpected RADIUS code %d", + radius_msg_get_hdr(msg)->code); + data->counters.unknown_acct_types++; + client->counters.unknown_acct_types++; + goto fail; + } + + data->counters.acct_requests++; + client->counters.acct_requests++; + + if (radius_msg_verify_acct_req(msg, (u8 *) client->shared_secret, + client->shared_secret_len)) { + RADIUS_DEBUG("Invalid Authenticator from %s", abuf); + data->counters.acct_bad_authenticators++; + client->counters.acct_bad_authenticators++; + goto fail; + } + + /* TODO: Write accounting information to a file or database */ + + hdr = radius_msg_get_hdr(msg); + + resp = radius_msg_new(RADIUS_CODE_ACCOUNTING_RESPONSE, hdr->identifier); + if (resp == NULL) + goto fail; + + radius_msg_finish_acct_resp(resp, (u8 *) client->shared_secret, + client->shared_secret_len, + hdr->authenticator); + + RADIUS_DEBUG("Reply to %s:%d", abuf, from_port); + if (wpa_debug_level <= MSG_MSGDUMP) { + radius_msg_dump(resp); + } + rbuf = radius_msg_get_buf(resp); + data->counters.acct_responses++; + client->counters.acct_responses++; + res = sendto(data->acct_sock, wpabuf_head(rbuf), wpabuf_len(rbuf), 0, + (struct sockaddr *) &from.ss, fromlen); + if (res < 0) { + wpa_printf(MSG_INFO, "sendto[RADIUS SRV]: %s", + strerror(errno)); + } + +fail: + radius_msg_free(resp); + radius_msg_free(msg); + os_free(buf); +} + + static int radius_server_disable_pmtu_discovery(int s) { int r = -1; @@ -1192,8 +1620,8 @@ radius_server_read_clients(const char *client_file, int ipv6) break; } entry->shared_secret_len = os_strlen(entry->shared_secret); - entry->addr.s_addr = addr.s_addr; if (!ipv6) { + entry->addr.s_addr = addr.s_addr; val = 0; for (i = 0; i < mask; i++) val |= 1 << (31 - i); @@ -1298,6 +1726,23 @@ radius_server_init(struct radius_server_conf *conf) } } + if (conf->subscr_remediation_url) { + data->subscr_remediation_url = + os_strdup(conf->subscr_remediation_url); + } + data->subscr_remediation_method = conf->subscr_remediation_method; + +#ifdef CONFIG_SQLITE + if (conf->sqlite_file) { + if (sqlite3_open(conf->sqlite_file, &data->db)) { + RADIUS_ERROR("Could not open SQLite file '%s'", + conf->sqlite_file); + radius_server_deinit(data); + return NULL; + } + } +#endif /* CONFIG_SQLITE */ + #ifdef CONFIG_RADIUS_TEST if (conf->dump_msk_file) data->dump_msk_file = os_strdup(conf->dump_msk_file); @@ -1329,6 +1774,29 @@ radius_server_init(struct radius_server_conf *conf) return NULL; } + if (conf->acct_port) { +#ifdef CONFIG_IPV6 + if (conf->ipv6) + data->acct_sock = radius_server_open_socket6( + conf->acct_port); + else +#endif /* CONFIG_IPV6 */ + data->acct_sock = radius_server_open_socket(conf->acct_port); + if (data->acct_sock < 0) { + wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS accounting server"); + radius_server_deinit(data); + return NULL; + } + if (eloop_register_read_sock(data->acct_sock, + radius_server_receive_acct, + data, NULL)) { + radius_server_deinit(data); + return NULL; + } + } else { + data->acct_sock = -1; + } + return data; } @@ -1347,6 +1815,11 @@ void radius_server_deinit(struct radius_server_data *data) close(data->auth_sock); } + if (data->acct_sock >= 0) { + eloop_unregister_read_sock(data->acct_sock); + close(data->acct_sock); + } + radius_server_free_clients(data, data->clients); os_free(data->pac_opaque_encr_key); @@ -1356,6 +1829,13 @@ void radius_server_deinit(struct radius_server_data *data) #ifdef CONFIG_RADIUS_TEST os_free(data->dump_msk_file); #endif /* CONFIG_RADIUS_TEST */ + os_free(data->subscr_remediation_url); + +#ifdef CONFIG_SQLITE + if (data->db) + sqlite3_close(data->db); +#endif /* CONFIG_SQLITE */ + os_free(data); } @@ -1410,7 +1890,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf, "radiusAuthServTotalMalformedAccessRequests=%u\n" "radiusAuthServTotalBadAuthenticators=%u\n" "radiusAuthServTotalPacketsDropped=%u\n" - "radiusAuthServTotalUnknownTypes=%u\n", + "radiusAuthServTotalUnknownTypes=%u\n" + "radiusAccServTotalRequests=%u\n" + "radiusAccServTotalInvalidRequests=%u\n" + "radiusAccServTotalResponses=%u\n" + "radiusAccServTotalMalformedRequests=%u\n" + "radiusAccServTotalBadAuthenticators=%u\n" + "radiusAccServTotalUnknownTypes=%u\n", data->counters.access_requests, data->counters.invalid_requests, data->counters.dup_access_requests, @@ -1420,7 +1906,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf, data->counters.malformed_access_requests, data->counters.bad_authenticators, data->counters.packets_dropped, - data->counters.unknown_types); + data->counters.unknown_types, + data->counters.acct_requests, + data->counters.invalid_acct_requests, + data->counters.acct_responses, + data->counters.malformed_acct_requests, + data->counters.acct_bad_authenticators, + data->counters.unknown_acct_types); if (ret < 0 || ret >= end - pos) { *pos = '\0'; return pos - buf; @@ -1455,7 +1947,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf, "radiusAuthServMalformedAccessRequests=%u\n" "radiusAuthServBadAuthenticators=%u\n" "radiusAuthServPacketsDropped=%u\n" - "radiusAuthServUnknownTypes=%u\n", + "radiusAuthServUnknownTypes=%u\n" + "radiusAccServTotalRequests=%u\n" + "radiusAccServTotalInvalidRequests=%u\n" + "radiusAccServTotalResponses=%u\n" + "radiusAccServTotalMalformedRequests=%u\n" + "radiusAccServTotalBadAuthenticators=%u\n" + "radiusAccServTotalUnknownTypes=%u\n", idx, abuf, mbuf, cli->counters.access_requests, @@ -1466,7 +1964,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf, cli->counters.malformed_access_requests, cli->counters.bad_authenticators, cli->counters.packets_dropped, - cli->counters.unknown_types); + cli->counters.unknown_types, + cli->counters.acct_requests, + cli->counters.invalid_acct_requests, + cli->counters.acct_responses, + cli->counters.malformed_acct_requests, + cli->counters.acct_bad_authenticators, + cli->counters.unknown_acct_types); if (ret < 0 || ret >= end - pos) { *pos = '\0'; return pos - buf; @@ -1484,9 +1988,16 @@ static int radius_server_get_eap_user(void *ctx, const u8 *identity, { struct radius_session *sess = ctx; struct radius_server_data *data = sess->server; - - return data->get_eap_user(data->conf_ctx, identity, identity_len, - phase2, user); + int ret; + + ret = data->get_eap_user(data->conf_ctx, identity, identity_len, + phase2, user); + if (ret == 0 && user) { + sess->accept_attr = user->accept_attr; + sess->remediation = user->remediation; + sess->macacl = user->macacl; + } + return ret; } @@ -1499,10 +2010,18 @@ static const char * radius_server_get_eap_req_id_text(void *ctx, size_t *len) } +static void radius_server_log_msg(void *ctx, const char *msg) +{ + struct radius_session *sess = ctx; + srv_log(sess, "EAP: %s", msg); +} + + static struct eapol_callbacks radius_server_eapol_cb = { .get_eap_user = radius_server_get_eap_user, .get_eap_req_id_text = radius_server_get_eap_req_id_text, + .log_msg = radius_server_log_msg, }; diff --git a/src/radius/radius_server.h b/src/radius/radius_server.h index 284bd59..46ac312 100644 --- a/src/radius/radius_server.h +++ b/src/radius/radius_server.h @@ -22,6 +22,11 @@ struct radius_server_conf { int auth_port; /** + * acct_port - UDP port to listen to as an accounting server + */ + int acct_port; + + /** * client_file - RADIUS client configuration file * * This file contains the RADIUS clients and the shared secret to be @@ -35,6 +40,11 @@ struct radius_server_conf { char *client_file; /** + * sqlite_file - SQLite database for storing debug log information + */ + const char *sqlite_file; + + /** * conf_ctx - Context pointer for callbacks * * This is used as the ctx argument in get_eap_user() calls. @@ -204,6 +214,9 @@ struct radius_server_conf { #ifdef CONFIG_RADIUS_TEST const char *dump_msk_file; #endif /* CONFIG_RADIUS_TEST */ + + char *subscr_remediation_url; + u8 subscr_remediation_method; }; diff --git a/src/rsn_supp/peerkey.c b/src/rsn_supp/peerkey.c index cb86dfb..88550e4 100644 --- a/src/rsn_supp/peerkey.c +++ b/src/rsn_supp/peerkey.c @@ -858,7 +858,7 @@ static void wpa_supplicant_process_stk_3_of_4(struct wpa_sm *sm, if (wpa_supplicant_send_4_of_4(sm, peerkey->addr, key, ver, WPA_GET_BE16(key->key_info), - NULL, 0, &peerkey->stk)) + &peerkey->stk)) return; _key = (u8 *) peerkey->stk.tk1; diff --git a/src/rsn_supp/tdls.c b/src/rsn_supp/tdls.c index 8a978f7..3b14656 100644 --- a/src/rsn_supp/tdls.c +++ b/src/rsn_supp/tdls.c @@ -33,6 +33,7 @@ #define TDLS_TESTING_NO_TPK_EXPIRATION BIT(8) #define TDLS_TESTING_DECLINE_RESP BIT(9) #define TDLS_TESTING_IGNORE_AP_PROHIBIT BIT(10) +#define TDLS_TESTING_WRONG_MIC BIT(11) unsigned int tdls_testing = 0; #endif /* CONFIG_TDLS_TESTING */ @@ -83,6 +84,8 @@ static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx); static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer); static void wpa_tdls_disable_peer_link(struct wpa_sm *sm, struct wpa_tdls_peer *peer); +static int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr, + u16 reason_code); #define TDLS_MAX_IE_LEN 80 @@ -118,6 +121,7 @@ struct wpa_tdls_peer { u8 action_code; /* TDLS frame type */ u8 dialog_token; u16 status_code; + u32 peer_capab; int buf_len; /* length of TPK message for retransmission */ u8 *buf; /* buffer for TPK message */ } sm_tmr; @@ -142,6 +146,8 @@ struct wpa_tdls_peer { u8 *supp_oper_classes; size_t supp_oper_classes_len; + + u8 wmm_capable; }; @@ -211,26 +217,27 @@ static int wpa_tdls_set_key(struct wpa_sm *sm, struct wpa_tdls_peer *peer) static int wpa_tdls_send_tpk_msg(struct wpa_sm *sm, const u8 *dst, u8 action_code, u8 dialog_token, - u16 status_code, const u8 *buf, size_t len) + u16 status_code, u32 peer_capab, + const u8 *buf, size_t len) { return wpa_sm_send_tdls_mgmt(sm, dst, action_code, dialog_token, - status_code, buf, len); + status_code, peer_capab, buf, len); } static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code, - u8 dialog_token, u16 status_code, + u8 dialog_token, u16 status_code, u32 peer_capab, const u8 *msg, size_t msg_len) { struct wpa_tdls_peer *peer; wpa_printf(MSG_DEBUG, "TDLS: TPK send dest=" MACSTR " action_code=%u " - "dialog_token=%u status_code=%u msg_len=%u", + "dialog_token=%u status_code=%u peer_capab=%u msg_len=%u", MAC2STR(dest), action_code, dialog_token, status_code, - (unsigned int) msg_len); + peer_capab, (unsigned int) msg_len); if (wpa_tdls_send_tpk_msg(sm, dest, action_code, dialog_token, - status_code, msg, msg_len)) { + status_code, peer_capab, msg, msg_len)) { wpa_printf(MSG_INFO, "TDLS: Failed to send message " "(action_code=%u)", action_code); return -1; @@ -268,6 +275,7 @@ static int wpa_tdls_tpk_send(struct wpa_sm *sm, const u8 *dest, u8 action_code, peer->sm_tmr.action_code = action_code; peer->sm_tmr.dialog_token = dialog_token; peer->sm_tmr.status_code = status_code; + peer->sm_tmr.peer_capab = peer_capab; peer->sm_tmr.buf_len = msg_len; os_free(peer->sm_tmr.buf); peer->sm_tmr.buf = os_malloc(msg_len); @@ -324,6 +332,7 @@ static void wpa_tdls_tpk_retry_timeout(void *eloop_ctx, void *timeout_ctx) peer->sm_tmr.action_code, peer->sm_tmr.dialog_token, peer->sm_tmr.status_code, + peer->sm_tmr.peer_capab, peer->sm_tmr.buf, peer->sm_tmr.buf_len)) { wpa_printf(MSG_INFO, "TDLS: Failed to retry " @@ -645,6 +654,8 @@ static void wpa_tdls_peer_free(struct wpa_sm *sm, struct wpa_tdls_peer *peer) peer->supp_oper_classes = NULL; peer->rsnie_i_len = peer->rsnie_p_len = 0; peer->cipher = 0; + peer->qos_info = 0; + peer->wmm_capable = 0; peer->tpk_set = peer->tpk_success = 0; os_memset(&peer->tpk, 0, sizeof(peer->tpk)); os_memset(peer->inonce, 0, WPA_NONCE_LEN); @@ -668,7 +679,8 @@ static void wpa_tdls_linkid(struct wpa_sm *sm, struct wpa_tdls_peer *peer, } -int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr, u16 reason_code) +static int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr, + u16 reason_code) { struct wpa_tdls_peer *peer; struct wpa_tdls_ftie *ftie; @@ -747,12 +759,9 @@ skip_ies: /* request driver to send Teardown using this FTIE */ wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_TEARDOWN, 0, - reason_code, rbuf, pos - rbuf); + reason_code, 0, rbuf, pos - rbuf); os_free(rbuf); - /* clear the Peerkey statemachine */ - wpa_tdls_peer_free(sm, peer); - return 0; } @@ -918,7 +927,7 @@ static int wpa_tdls_send_error(struct wpa_sm *sm, const u8 *dst, " (action=%u status=%u)", MAC2STR(dst), tdls_action, status); return wpa_tdls_tpk_send(sm, dst, tdls_action, dialog_token, status, - NULL, 0); + 0, NULL, 0); } @@ -1124,7 +1133,7 @@ skip_ies: MAC2STR(peer->addr)); status = wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_SETUP_REQUEST, - 1, 0, rbuf, pos - rbuf); + 1, 0, 0, rbuf, pos - rbuf); os_free(rbuf); return status; @@ -1205,10 +1214,16 @@ static int wpa_tdls_send_tpk_m2(struct wpa_sm *sm, /* compute MIC before sending */ wpa_tdls_ftie_mic(peer->tpk.kck, 2, (u8 *) lnkid, peer->rsnie_p, (u8 *) &timeoutie, (u8 *) ftie, ftie->mic); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_WRONG_MIC) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use wrong MIC"); + ftie->mic[0] ^= 0x01; + } +#endif /* CONFIG_TDLS_TESTING */ skip_ies: status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_RESPONSE, - dtoken, 0, rbuf, pos - rbuf); + dtoken, 0, 0, rbuf, pos - rbuf); os_free(rbuf); return status; @@ -1226,6 +1241,7 @@ static int wpa_tdls_send_tpk_m3(struct wpa_sm *sm, struct wpa_tdls_timeoutie timeoutie; u32 lifetime; int status; + u32 peer_capab = 0; buf_len = 0; if (wpa_tdls_get_privacy(sm)) { @@ -1287,10 +1303,24 @@ static int wpa_tdls_send_tpk_m3(struct wpa_sm *sm, /* compute MIC before sending */ wpa_tdls_ftie_mic(peer->tpk.kck, 3, (u8 *) lnkid, peer->rsnie_p, (u8 *) &timeoutie, (u8 *) ftie, ftie->mic); +#ifdef CONFIG_TDLS_TESTING + if (tdls_testing & TDLS_TESTING_WRONG_MIC) { + wpa_printf(MSG_DEBUG, "TDLS: Testing - use wrong MIC"); + ftie->mic[0] ^= 0x01; + } +#endif /* CONFIG_TDLS_TESTING */ skip_ies: + + if (peer->vht_capabilities) + peer_capab |= TDLS_PEER_VHT; + if (peer->ht_capabilities) + peer_capab |= TDLS_PEER_HT; + if (peer->wmm_capable) + peer_capab |= TDLS_PEER_WMM; + status = wpa_tdls_tpk_send(sm, src_addr, WLAN_TDLS_SETUP_CONFIRM, - dtoken, 0, rbuf, pos - rbuf); + dtoken, 0, peer_capab, rbuf, pos - rbuf); os_free(rbuf); return status; @@ -1305,7 +1335,7 @@ static int wpa_tdls_send_discovery_response(struct wpa_sm *sm, "(peer " MACSTR ")", MAC2STR(peer->addr)); return wpa_tdls_tpk_send(sm, peer->addr, WLAN_TDLS_DISCOVERY_RESPONSE, - dialog_token, 0, NULL, 0); + dialog_token, 0, 0, NULL, 0); } @@ -1366,7 +1396,7 @@ int wpa_tdls_send_discovery_request(struct wpa_sm *sm, const u8 *addr) wpa_printf(MSG_DEBUG, "TDLS: Sending Discovery Request to peer " MACSTR, MAC2STR(addr)); return wpa_tdls_tpk_send(sm, addr, WLAN_TDLS_DISCOVERY_REQUEST, - 1, 0, NULL, 0); + 1, 0, 0, NULL, 0); } @@ -1466,6 +1496,31 @@ static int copy_peer_ext_capab(const struct wpa_eapol_ie_parse *kde, } +static int copy_peer_wmm_capab(const struct wpa_eapol_ie_parse *kde, + struct wpa_tdls_peer *peer) +{ + struct wmm_information_element *wmm; + + if (!kde->wmm) { + wpa_printf(MSG_DEBUG, "TDLS: No supported WMM capabilities received"); + return 0; + } + + if (kde->wmm_len < sizeof(struct wmm_information_element)) { + wpa_printf(MSG_DEBUG, "TDLS: Invalid supported WMM capabilities received"); + return -1; + } + + wmm = (struct wmm_information_element *) kde->wmm; + peer->qos_info = wmm->qos_info; + + peer->wmm_capable = 1; + + wpa_printf(MSG_DEBUG, "TDLS: Peer WMM QOS Info 0x%x", peer->qos_info); + return 0; +} + + static int copy_peer_supp_channels(const struct wpa_eapol_ie_parse *kde, struct wpa_tdls_peer *peer) { @@ -1638,6 +1693,10 @@ static int wpa_tdls_process_tpk_m1(struct wpa_sm *sm, const u8 *src_addr, peer->qos_info = kde.qosinfo; + /* Overwrite with the qos_info obtained in WMM IE */ + if (copy_peer_wmm_capab(&kde, peer) < 0) + goto error; + peer->aid = kde.aid; #ifdef CONFIG_TDLS_TESTING @@ -2018,6 +2077,10 @@ static int wpa_tdls_process_tpk_m2(struct wpa_sm *sm, const u8 *src_addr, peer->qos_info = kde.qosinfo; + /* Overwrite with the qos_info obtained in WMM IE */ + if (copy_peer_wmm_capab(&kde, peer) < 0) + goto error; + peer->aid = kde.aid; if (!wpa_tdls_get_privacy(sm)) { @@ -2400,7 +2463,8 @@ void wpa_tdls_remove(struct wpa_sm *sm, const u8 *addr) * Disable previous link to allow renegotiation to be completed * on AP path. */ - wpa_tdls_disable_peer_link(sm, peer); + wpa_tdls_do_teardown(sm, peer, + WLAN_REASON_TDLS_TEARDOWN_UNSPECIFIED); } } @@ -2526,8 +2590,8 @@ void wpa_tdls_teardown_peers(struct wpa_sm *sm) wpa_printf(MSG_DEBUG, "TDLS: Tear down peer " MACSTR, MAC2STR(peer->addr)); if (sm->tdls_external_setup) - wpa_tdls_send_teardown(sm, peer->addr, - WLAN_REASON_DEAUTH_LEAVING); + wpa_tdls_do_teardown(sm, peer, + WLAN_REASON_DEAUTH_LEAVING); else wpa_sm_tdls_oper(sm, TDLS_TEARDOWN, peer->addr); diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c index 4474c3b..ba2a8c8 100644 --- a/src/rsn_supp/wpa.c +++ b/src/rsn_supp/wpa.c @@ -89,7 +89,10 @@ void wpa_sm_key_request(struct wpa_sm *sm, int error, int pairwise) int key_info, ver; u8 bssid[ETH_ALEN], *rbuf; - if (wpa_key_mgmt_ft(sm->key_mgmt) || wpa_key_mgmt_sha256(sm->key_mgmt)) + if (sm->key_mgmt == WPA_KEY_MGMT_OSEN) + ver = WPA_KEY_INFO_TYPE_AKM_DEFINED; + else if (wpa_key_mgmt_ft(sm->key_mgmt) || + wpa_key_mgmt_sha256(sm->key_mgmt)) ver = WPA_KEY_INFO_TYPE_AES_128_CMAC; else if (sm->pairwise_cipher != WPA_CIPHER_TKIP) ver = WPA_KEY_INFO_TYPE_HMAC_SHA1_AES; @@ -107,7 +110,8 @@ void wpa_sm_key_request(struct wpa_sm *sm, int error, int pairwise) if (rbuf == NULL) return; - reply->type = sm->proto == WPA_PROTO_RSN ? + reply->type = (sm->proto == WPA_PROTO_RSN || + sm->proto == WPA_PROTO_OSEN) ? EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA; key_info = WPA_KEY_INFO_REQUEST | ver; if (sm->ptk_set) @@ -231,7 +235,8 @@ static int wpa_supplicant_get_pmk(struct wpa_sm *sm, } if (abort_cached && wpa_key_mgmt_wpa_ieee8021x(sm->key_mgmt) && - !wpa_key_mgmt_ft(sm->key_mgmt)) { + !wpa_key_mgmt_ft(sm->key_mgmt) && sm->key_mgmt != WPA_KEY_MGMT_OSEN) + { /* Send EAPOL-Start to trigger full EAP authentication. */ u8 *buf; size_t buflen; @@ -325,11 +330,12 @@ int wpa_supplicant_send_2_of_4(struct wpa_sm *sm, const unsigned char *dst, return -1; } - reply->type = sm->proto == WPA_PROTO_RSN ? + reply->type = (sm->proto == WPA_PROTO_RSN || + sm->proto == WPA_PROTO_OSEN) ? EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA; WPA_PUT_BE16(reply->key_info, ver | WPA_KEY_INFO_KEY_TYPE | WPA_KEY_INFO_MIC); - if (sm->proto == WPA_PROTO_RSN) + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) WPA_PUT_BE16(reply->key_length, 0); else os_memcpy(reply->key_length, key->key_length, 2); @@ -356,7 +362,7 @@ static int wpa_derive_ptk(struct wpa_sm *sm, const unsigned char *src_addr, const struct wpa_eapol_key *key, struct wpa_ptk *ptk) { - size_t ptk_len = sm->pairwise_cipher != WPA_CIPHER_TKIP ? 48 : 64; + size_t ptk_len = wpa_cipher_key_len(sm->pairwise_cipher) + 32; #ifdef CONFIG_IEEE80211R if (wpa_key_mgmt_ft(sm->key_mgmt)) return wpa_derive_ptk_ft(sm, src_addr, key, ptk, ptk_len); @@ -377,7 +383,6 @@ static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm, { struct wpa_eapol_ie_parse ie; struct wpa_ptk *ptk; - u8 buf[8]; int res; u8 *kde, *kde_buf = NULL; size_t kde_len; @@ -394,7 +399,7 @@ static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm, os_memset(&ie, 0, sizeof(ie)); - if (sm->proto == WPA_PROTO_RSN) { + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) { /* RSN: msg 1/4 should contain PMKID for the selected PMK */ const u8 *_buf = (const u8 *) (key + 1); size_t len = WPA_GET_BE16(key->key_data_length); @@ -431,10 +436,14 @@ static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm, * been verified when processing message 3/4. */ ptk = &sm->tptk; wpa_derive_ptk(sm, src_addr, key, ptk); - /* Supplicant: swap tx/rx Mic keys */ - os_memcpy(buf, ptk->u.auth.tx_mic_key, 8); - os_memcpy(ptk->u.auth.tx_mic_key, ptk->u.auth.rx_mic_key, 8); - os_memcpy(ptk->u.auth.rx_mic_key, buf, 8); + if (sm->pairwise_cipher == WPA_CIPHER_TKIP) { + u8 buf[8]; + /* Supplicant: swap tx/rx Mic keys */ + os_memcpy(buf, ptk->u.auth.tx_mic_key, 8); + os_memcpy(ptk->u.auth.tx_mic_key, ptk->u.auth.rx_mic_key, 8); + os_memcpy(ptk->u.auth.rx_mic_key, buf, 8); + os_memset(buf, 0, sizeof(buf)); + } sm->tptk_set = 1; kde = sm->assoc_wpa_ie; @@ -561,7 +570,7 @@ static int wpa_supplicant_install_ptk(struct wpa_sm *sm, keylen = wpa_cipher_key_len(sm->pairwise_cipher); rsclen = wpa_cipher_rsc_len(sm->pairwise_cipher); - if (sm->proto == WPA_PROTO_RSN) { + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) { key_rsc = null_rsc; } else { key_rsc = key->key_rsc; @@ -649,6 +658,7 @@ static int wpa_supplicant_install_gtk(struct wpa_sm *sm, wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, "WPA: Failed to set GTK to the driver " "(Group only)"); + os_memset(gtk_buf, 0, sizeof(gtk_buf)); return -1; } } else if (wpa_sm_set_key(sm, gd->alg, broadcast_ether_addr, @@ -658,8 +668,10 @@ static int wpa_supplicant_install_gtk(struct wpa_sm *sm, "WPA: Failed to set GTK to " "the driver (alg=%d keylen=%d keyidx=%d)", gd->alg, gd->gtk_len, gd->keyidx); + os_memset(gtk_buf, 0, sizeof(gtk_buf)); return -1; } + os_memset(gtk_buf, 0, sizeof(gtk_buf)); return 0; } @@ -721,8 +733,10 @@ static int wpa_supplicant_pairwise_gtk(struct wpa_sm *sm, wpa_supplicant_install_gtk(sm, &gd, key->key_rsc))) { wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "RSN: Failed to install GTK"); + os_memset(&gd, 0, sizeof(gd)); return -1; } + os_memset(&gd, 0, sizeof(gd)); wpa_supplicant_key_neg_complete(sm, sm->bssid, key_info & WPA_KEY_INFO_SECURE); @@ -734,13 +748,15 @@ static int ieee80211w_set_keys(struct wpa_sm *sm, struct wpa_eapol_ie_parse *ie) { #ifdef CONFIG_IEEE80211W - if (sm->mgmt_group_cipher != WPA_CIPHER_AES_128_CMAC) + if (!wpa_cipher_valid_mgmt_group(sm->mgmt_group_cipher)) return 0; if (ie->igtk) { + size_t len; const struct wpa_igtk_kde *igtk; u16 keyidx; - if (ie->igtk_len != sizeof(*igtk)) + len = wpa_cipher_key_len(sm->mgmt_group_cipher); + if (ie->igtk_len != WPA_IGTK_KDE_PREFIX_LEN + len) return -1; igtk = (const struct wpa_igtk_kde *) ie->igtk; keyidx = WPA_GET_LE16(igtk->keyid); @@ -748,15 +764,16 @@ static int ieee80211w_set_keys(struct wpa_sm *sm, "pn %02x%02x%02x%02x%02x%02x", keyidx, MAC2STR(igtk->pn)); wpa_hexdump_key(MSG_DEBUG, "WPA: IGTK", - igtk->igtk, WPA_IGTK_LEN); + igtk->igtk, len); if (keyidx > 4095) { wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, "WPA: Invalid IGTK KeyID %d", keyidx); return -1; } - if (wpa_sm_set_key(sm, WPA_ALG_IGTK, broadcast_ether_addr, + if (wpa_sm_set_key(sm, wpa_cipher_to_alg(sm->mgmt_group_cipher), + broadcast_ether_addr, keyidx, 0, igtk->pn, sizeof(igtk->pn), - igtk->igtk, WPA_IGTK_LEN) < 0) { + igtk->igtk, len) < 0) { wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, "WPA: Failed to configure IGTK to the driver"); return -1; @@ -1009,45 +1026,37 @@ static int wpa_supplicant_validate_ie(struct wpa_sm *sm, * @key: Pointer to the EAPOL-Key frame header * @ver: Version bits from EAPOL-Key Key Info * @key_info: Key Info - * @kde: KDEs to include the EAPOL-Key frame - * @kde_len: Length of KDEs * @ptk: PTK to use for keyed hash and encryption * Returns: 0 on success, -1 on failure */ int wpa_supplicant_send_4_of_4(struct wpa_sm *sm, const unsigned char *dst, const struct wpa_eapol_key *key, u16 ver, u16 key_info, - const u8 *kde, size_t kde_len, struct wpa_ptk *ptk) { size_t rlen; struct wpa_eapol_key *reply; u8 *rbuf; - if (kde) - wpa_hexdump(MSG_DEBUG, "WPA: KDE for msg 4/4", kde, kde_len); - rbuf = wpa_sm_alloc_eapol(sm, IEEE802_1X_TYPE_EAPOL_KEY, NULL, - sizeof(*reply) + kde_len, - &rlen, (void *) &reply); + sizeof(*reply), &rlen, (void *) &reply); if (rbuf == NULL) return -1; - reply->type = sm->proto == WPA_PROTO_RSN ? + reply->type = (sm->proto == WPA_PROTO_RSN || + sm->proto == WPA_PROTO_OSEN) ? EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA; key_info &= WPA_KEY_INFO_SECURE; key_info |= ver | WPA_KEY_INFO_KEY_TYPE | WPA_KEY_INFO_MIC; WPA_PUT_BE16(reply->key_info, key_info); - if (sm->proto == WPA_PROTO_RSN) + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) WPA_PUT_BE16(reply->key_length, 0); else os_memcpy(reply->key_length, key->key_length, 2); os_memcpy(reply->replay_counter, key->replay_counter, WPA_REPLAY_COUNTER_LEN); - WPA_PUT_BE16(reply->key_data_length, kde_len); - if (kde) - os_memcpy(reply + 1, kde, kde_len); + WPA_PUT_BE16(reply->key_data_length, 0); wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "WPA: Sending EAPOL-Key 4/4"); wpa_eapol_key_send(sm, ptk->kck, ver, dst, ETH_P_EAPOL, @@ -1088,7 +1097,10 @@ static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm, goto failed; } - if (ie.igtk && ie.igtk_len != sizeof(struct wpa_igtk_kde)) { + if (ie.igtk && + wpa_cipher_valid_mgmt_group(sm->mgmt_group_cipher) && + ie.igtk_len != WPA_IGTK_KDE_PREFIX_LEN + + (unsigned int) wpa_cipher_key_len(sm->mgmt_group_cipher)) { wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, "WPA: Invalid IGTK KDE length %lu", (unsigned long) ie.igtk_len); @@ -1125,7 +1137,7 @@ static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm, #endif /* CONFIG_P2P */ if (wpa_supplicant_send_4_of_4(sm, sm->bssid, key, ver, key_info, - NULL, 0, &sm->ptk)) { + &sm->ptk)) { goto failed; } @@ -1231,7 +1243,6 @@ static int wpa_supplicant_process_1_of_2_wpa(struct wpa_sm *sm, struct wpa_gtk_data *gd) { size_t maxkeylen; - u8 ek[32]; gd->gtk_len = WPA_GET_BE16(key->key_length); maxkeylen = keydatalen; @@ -1260,20 +1271,23 @@ static int wpa_supplicant_process_1_of_2_wpa(struct wpa_sm *sm, gd->keyidx = (key_info & WPA_KEY_INFO_KEY_INDEX_MASK) >> WPA_KEY_INFO_KEY_INDEX_SHIFT; if (ver == WPA_KEY_INFO_TYPE_HMAC_MD5_RC4) { - os_memcpy(ek, key->key_iv, 16); - os_memcpy(ek + 16, sm->ptk.kek, 16); + u8 ek[32]; if (keydatalen > sizeof(gd->gtk)) { wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, "WPA: RC4 key data too long (%lu)", (unsigned long) keydatalen); return -1; } + os_memcpy(ek, key->key_iv, 16); + os_memcpy(ek + 16, sm->ptk.kek, 16); os_memcpy(gd->gtk, key + 1, keydatalen); if (rc4_skip(ek, 32, 256, gd->gtk, keydatalen)) { + os_memset(ek, 0, sizeof(ek)); wpa_msg(sm->ctx->msg_ctx, MSG_ERROR, "WPA: RC4 failed"); return -1; } + os_memset(ek, 0, sizeof(ek)); } else if (ver == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) { if (keydatalen % 8) { wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, @@ -1320,12 +1334,13 @@ static int wpa_supplicant_send_2_of_2(struct wpa_sm *sm, if (rbuf == NULL) return -1; - reply->type = sm->proto == WPA_PROTO_RSN ? + reply->type = (sm->proto == WPA_PROTO_RSN || + sm->proto == WPA_PROTO_OSEN) ? EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA; key_info &= WPA_KEY_INFO_KEY_INDEX_MASK; key_info |= ver | WPA_KEY_INFO_MIC | WPA_KEY_INFO_SECURE; WPA_PUT_BE16(reply->key_info, key_info); - if (sm->proto == WPA_PROTO_RSN) + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) WPA_PUT_BE16(reply->key_length, 0); else os_memcpy(reply->key_length, key->key_length, 2); @@ -1360,7 +1375,7 @@ static void wpa_supplicant_process_1_of_2(struct wpa_sm *sm, key_info = WPA_GET_BE16(key->key_info); keydatalen = WPA_GET_BE16(key->key_data_length); - if (sm->proto == WPA_PROTO_RSN) { + if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) { ret = wpa_supplicant_process_1_of_2_rsn(sm, (const u8 *) (key + 1), keydatalen, key_info, @@ -1423,6 +1438,7 @@ static int wpa_supplicant_verify_eapol_key_mic(struct wpa_sm *sm, sm->tptk_set = 0; sm->ptk_set = 1; os_memcpy(&sm->ptk, &sm->tptk, sizeof(sm->ptk)); + os_memset(&sm->tptk, 0, sizeof(sm->tptk)); } } @@ -1475,12 +1491,15 @@ static int wpa_supplicant_decrypt_key_data(struct wpa_sm *sm, os_memcpy(ek, key->key_iv, 16); os_memcpy(ek + 16, sm->ptk.kek, 16); if (rc4_skip(ek, 32, 256, (u8 *) (key + 1), keydatalen)) { + os_memset(ek, 0, sizeof(ek)); wpa_msg(sm->ctx->msg_ctx, MSG_ERROR, "WPA: RC4 failed"); return -1; } + os_memset(ek, 0, sizeof(ek)); } else if (ver == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES || - ver == WPA_KEY_INFO_TYPE_AES_128_CMAC) { + ver == WPA_KEY_INFO_TYPE_AES_128_CMAC || + sm->key_mgmt == WPA_KEY_MGMT_OSEN) { u8 *buf; if (keydatalen % 8) { wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, @@ -1662,13 +1681,22 @@ int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr, #if defined(CONFIG_IEEE80211R) || defined(CONFIG_IEEE80211W) ver != WPA_KEY_INFO_TYPE_AES_128_CMAC && #endif /* CONFIG_IEEE80211R || CONFIG_IEEE80211W */ - ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) { + ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES && + sm->key_mgmt != WPA_KEY_MGMT_OSEN) { wpa_msg(sm->ctx->msg_ctx, MSG_INFO, "WPA: Unsupported EAPOL-Key descriptor version %d", ver); goto out; } + if (sm->key_mgmt == WPA_KEY_MGMT_OSEN && + ver != WPA_KEY_INFO_TYPE_AKM_DEFINED) { + wpa_msg(sm->ctx->msg_ctx, MSG_INFO, + "OSEN: Unsupported EAPOL-Key descriptor version %d", + ver); + goto out; + } + #ifdef CONFIG_IEEE80211R if (wpa_key_mgmt_ft(sm->key_mgmt)) { /* IEEE 802.11r uses a new key_info type (AES-128-CMAC). */ @@ -1681,7 +1709,8 @@ int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr, #endif /* CONFIG_IEEE80211R */ #ifdef CONFIG_IEEE80211W if (wpa_key_mgmt_sha256(sm->key_mgmt)) { - if (ver != WPA_KEY_INFO_TYPE_AES_128_CMAC) { + if (ver != WPA_KEY_INFO_TYPE_AES_128_CMAC && + sm->key_mgmt != WPA_KEY_MGMT_OSEN) { wpa_msg(sm->ctx->msg_ctx, MSG_INFO, "WPA: AP did not use the " "negotiated AES-128-CMAC"); @@ -1705,9 +1734,8 @@ int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr, "version for non-CCMP group keys"); } else goto out; - } - if (sm->pairwise_cipher == WPA_CIPHER_GCMP && - ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) { + } else if (sm->pairwise_cipher == WPA_CIPHER_GCMP && + ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) { wpa_msg(sm->ctx->msg_ctx, MSG_INFO, "WPA: GCMP is used, but EAPOL-Key " "descriptor version (%d) is not 2", ver); @@ -1797,7 +1825,7 @@ int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr, } extra_len = WPA_GET_BE16(key->key_data_length); - if (sm->proto == WPA_PROTO_RSN && + if ((sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) { if (wpa_supplicant_decrypt_key_data(sm, key, ver)) goto out; @@ -1851,7 +1879,8 @@ static u32 wpa_key_mgmt_suite(struct wpa_sm *sm) { switch (sm->key_mgmt) { case WPA_KEY_MGMT_IEEE8021X: - return (sm->proto == WPA_PROTO_RSN ? + return ((sm->proto == WPA_PROTO_RSN || + sm->proto == WPA_PROTO_OSEN) ? RSN_AUTH_KEY_MGMT_UNSPEC_802_1X : WPA_AUTH_KEY_MGMT_UNSPEC_802_1X); case WPA_KEY_MGMT_PSK: @@ -2114,7 +2143,9 @@ void wpa_sm_notify_assoc(struct wpa_sm *sm, const u8 *bssid) */ wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "WPA: Clear old PTK"); sm->ptk_set = 0; + os_memset(&sm->ptk, 0, sizeof(sm->ptk)); sm->tptk_set = 0; + os_memset(&sm->tptk, 0, sizeof(sm->tptk)); } #ifdef CONFIG_TDLS @@ -2368,44 +2399,6 @@ int wpa_sm_set_param(struct wpa_sm *sm, enum wpa_sm_conf_params param, /** - * wpa_sm_get_param - Get WPA state machine parameters - * @sm: Pointer to WPA state machine data from wpa_sm_init() - * @param: Parameter field - * Returns: Parameter value - */ -unsigned int wpa_sm_get_param(struct wpa_sm *sm, enum wpa_sm_conf_params param) -{ - if (sm == NULL) - return 0; - - switch (param) { - case RSNA_PMK_LIFETIME: - return sm->dot11RSNAConfigPMKLifetime; - case RSNA_PMK_REAUTH_THRESHOLD: - return sm->dot11RSNAConfigPMKReauthThreshold; - case RSNA_SA_TIMEOUT: - return sm->dot11RSNAConfigSATimeout; - case WPA_PARAM_PROTO: - return sm->proto; - case WPA_PARAM_PAIRWISE: - return sm->pairwise_cipher; - case WPA_PARAM_GROUP: - return sm->group_cipher; - case WPA_PARAM_KEY_MGMT: - return sm->key_mgmt; -#ifdef CONFIG_IEEE80211W - case WPA_PARAM_MGMT_GROUP: - return sm->mgmt_group_cipher; -#endif /* CONFIG_IEEE80211W */ - case WPA_PARAM_RSN_ENABLED: - return sm->rsn_enabled; - default: - return 0; - } -} - - -/** * wpa_sm_get_status - Get WPA state machine * @sm: Pointer to WPA state machine data from wpa_sm_init() * @buf: Buffer for status information @@ -2646,6 +2639,7 @@ int wpa_sm_pmksa_cache_list(struct wpa_sm *sm, char *buf, size_t len) } +#ifdef CONFIG_TESTING_OPTIONS void wpa_sm_drop_sa(struct wpa_sm *sm) { wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "WPA: Clear old PMK and PTK"); @@ -2655,6 +2649,7 @@ void wpa_sm_drop_sa(struct wpa_sm *sm) os_memset(&sm->ptk, 0, sizeof(sm->ptk)); os_memset(&sm->tptk, 0, sizeof(sm->tptk)); } +#endif /* CONFIG_TESTING_OPTIONS */ int wpa_sm_has_ptk(struct wpa_sm *sm) @@ -2680,29 +2675,22 @@ void wpa_sm_pmksa_cache_flush(struct wpa_sm *sm, void *network_ctx) #ifdef CONFIG_WNM int wpa_wnmsleep_install_key(struct wpa_sm *sm, u8 subelem_id, u8 *buf) { - struct wpa_gtk_data gd; -#ifdef CONFIG_IEEE80211W - struct wpa_igtk_kde igd; - u16 keyidx; -#endif /* CONFIG_IEEE80211W */ u16 keyinfo; u8 keylen; /* plaintext key len */ u8 *key_rsc; - os_memset(&gd, 0, sizeof(gd)); -#ifdef CONFIG_IEEE80211W - os_memset(&igd, 0, sizeof(igd)); -#endif /* CONFIG_IEEE80211W */ - - keylen = wpa_cipher_key_len(sm->group_cipher); - gd.key_rsc_len = wpa_cipher_rsc_len(sm->group_cipher); - gd.alg = wpa_cipher_to_alg(sm->group_cipher); - if (gd.alg == WPA_ALG_NONE) { - wpa_printf(MSG_DEBUG, "Unsupported group cipher suite"); - return -1; - } - if (subelem_id == WNM_SLEEP_SUBELEM_GTK) { + struct wpa_gtk_data gd; + + os_memset(&gd, 0, sizeof(gd)); + keylen = wpa_cipher_key_len(sm->group_cipher); + gd.key_rsc_len = wpa_cipher_rsc_len(sm->group_cipher); + gd.alg = wpa_cipher_to_alg(sm->group_cipher); + if (gd.alg == WPA_ALG_NONE) { + wpa_printf(MSG_DEBUG, "Unsupported group cipher suite"); + return -1; + } + key_rsc = buf + 5; keyinfo = WPA_GET_LE16(buf + 2); gd.gtk_len = keylen; @@ -2720,27 +2708,37 @@ int wpa_wnmsleep_install_key(struct wpa_sm *sm, u8 subelem_id, u8 *buf) wpa_hexdump_key(MSG_DEBUG, "Install GTK (WNM SLEEP)", gd.gtk, gd.gtk_len); if (wpa_supplicant_install_gtk(sm, &gd, key_rsc)) { + os_memset(&gd, 0, sizeof(gd)); wpa_printf(MSG_DEBUG, "Failed to install the GTK in " "WNM mode"); return -1; } + os_memset(&gd, 0, sizeof(gd)); #ifdef CONFIG_IEEE80211W } else if (subelem_id == WNM_SLEEP_SUBELEM_IGTK) { + struct wpa_igtk_kde igd; + u16 keyidx; + + os_memset(&igd, 0, sizeof(igd)); + keylen = wpa_cipher_key_len(sm->mgmt_group_cipher); os_memcpy(igd.keyid, buf + 2, 2); os_memcpy(igd.pn, buf + 4, 6); keyidx = WPA_GET_LE16(igd.keyid); - os_memcpy(igd.igtk, buf + 10, WPA_IGTK_LEN); + os_memcpy(igd.igtk, buf + 10, keylen); wpa_hexdump_key(MSG_DEBUG, "Install IGTK (WNM SLEEP)", - igd.igtk, WPA_IGTK_LEN); - if (wpa_sm_set_key(sm, WPA_ALG_IGTK, broadcast_ether_addr, + igd.igtk, keylen); + if (wpa_sm_set_key(sm, wpa_cipher_to_alg(sm->mgmt_group_cipher), + broadcast_ether_addr, keyidx, 0, igd.pn, sizeof(igd.pn), - igd.igtk, WPA_IGTK_LEN) < 0) { + igd.igtk, keylen) < 0) { wpa_printf(MSG_DEBUG, "Failed to install the IGTK in " "WNM mode"); + os_memset(&igd, 0, sizeof(igd)); return -1; } + os_memset(&igd, 0, sizeof(igd)); #endif /* CONFIG_IEEE80211W */ } else { wpa_printf(MSG_DEBUG, "Unknown element id"); diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h index 20b3f62..e98967c 100644 --- a/src/rsn_supp/wpa.h +++ b/src/rsn_supp/wpa.h @@ -54,7 +54,8 @@ struct wpa_sm_ctx { int *tdls_ext_setup); int (*send_tdls_mgmt)(void *ctx, const u8 *dst, u8 action_code, u8 dialog_token, - u16 status_code, const u8 *buf, size_t len); + u16 status_code, u32 peer_capab, + const u8 *buf, size_t len); int (*tdls_oper)(void *ctx, int oper, const u8 *peer); int (*tdls_peer_addset)(void *ctx, const u8 *addr, int add, u16 aid, u16 capability, const u8 *supp_rates, @@ -122,8 +123,6 @@ int wpa_sm_get_mib(struct wpa_sm *sm, char *buf, size_t buflen); int wpa_sm_set_param(struct wpa_sm *sm, enum wpa_sm_conf_params param, unsigned int value); -unsigned int wpa_sm_get_param(struct wpa_sm *sm, - enum wpa_sm_conf_params param); int wpa_sm_get_status(struct wpa_sm *sm, char *buf, size_t buflen, int verbose); @@ -239,12 +238,6 @@ static inline int wpa_sm_set_param(struct wpa_sm *sm, return -1; } -static inline unsigned int wpa_sm_get_param(struct wpa_sm *sm, - enum wpa_sm_conf_params param) -{ - return 0; -} - static inline int wpa_sm_get_status(struct wpa_sm *sm, char *buf, size_t buflen, int verbose) { @@ -386,7 +379,6 @@ void wpa_tdls_ap_ies(struct wpa_sm *sm, const u8 *ies, size_t len); void wpa_tdls_assoc_resp_ies(struct wpa_sm *sm, const u8 *ies, size_t len); int wpa_tdls_start(struct wpa_sm *sm, const u8 *addr); void wpa_tdls_remove(struct wpa_sm *sm, const u8 *addr); -int wpa_tdls_send_teardown(struct wpa_sm *sm, const u8 *addr, u16 reason_code); int wpa_tdls_teardown_link(struct wpa_sm *sm, const u8 *addr, u16 reason_code); int wpa_tdls_send_discovery_request(struct wpa_sm *sm, const u8 *addr); int wpa_tdls_init(struct wpa_sm *sm); diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h index 75cfb47..e20e9da 100644 --- a/src/rsn_supp/wpa_i.h +++ b/src/rsn_supp/wpa_i.h @@ -267,13 +267,13 @@ static inline int wpa_sm_tdls_get_capa(struct wpa_sm *sm, static inline int wpa_sm_send_tdls_mgmt(struct wpa_sm *sm, const u8 *dst, u8 action_code, u8 dialog_token, - u16 status_code, const u8 *buf, - size_t len) + u16 status_code, u32 peer_capab, + const u8 *buf, size_t len) { if (sm->ctx->send_tdls_mgmt) return sm->ctx->send_tdls_mgmt(sm->ctx->ctx, dst, action_code, dialog_token, status_code, - buf, len); + peer_capab, buf, len); return -1; } @@ -321,7 +321,6 @@ int wpa_supplicant_send_2_of_4(struct wpa_sm *sm, const unsigned char *dst, int wpa_supplicant_send_4_of_4(struct wpa_sm *sm, const unsigned char *dst, const struct wpa_eapol_key *key, u16 ver, u16 key_info, - const u8 *kde, size_t kde_len, struct wpa_ptk *ptk); int wpa_derive_ptk_ft(struct wpa_sm *sm, const unsigned char *src_addr, diff --git a/src/rsn_supp/wpa_ie.c b/src/rsn_supp/wpa_ie.c index e58bdc4..2329033 100644 --- a/src/rsn_supp/wpa_ie.c +++ b/src/rsn_supp/wpa_ie.c @@ -201,7 +201,7 @@ static int wpa_gen_wpa_ie_rsn(u8 *rsn_ie, size_t rsn_ie_len, } #ifdef CONFIG_IEEE80211W - if (mgmt_group_cipher == WPA_CIPHER_AES_128_CMAC) { + if (wpa_cipher_valid_mgmt_group(mgmt_group_cipher)) { if (!sm->cur_pmksa) { /* PMKID Count */ WPA_PUT_LE16(pos, 0); @@ -209,7 +209,8 @@ static int wpa_gen_wpa_ie_rsn(u8 *rsn_ie, size_t rsn_ie_len, } /* Management Group Cipher Suite */ - RSN_SELECTOR_PUT(pos, RSN_CIPHER_SUITE_AES_128_CMAC); + RSN_SELECTOR_PUT(pos, wpa_cipher_to_suite(WPA_PROTO_RSN, + mgmt_group_cipher)); pos += RSN_SELECTOR_LEN; } #endif /* CONFIG_IEEE80211W */ @@ -222,6 +223,64 @@ static int wpa_gen_wpa_ie_rsn(u8 *rsn_ie, size_t rsn_ie_len, } +#ifdef CONFIG_HS20 +static int wpa_gen_wpa_ie_osen(u8 *wpa_ie, size_t wpa_ie_len, + int pairwise_cipher, int group_cipher, + int key_mgmt) +{ + u8 *pos, *len; + u32 suite; + + if (wpa_ie_len < 2 + 4 + RSN_SELECTOR_LEN + + 2 + RSN_SELECTOR_LEN + 2 + RSN_SELECTOR_LEN) + return -1; + + pos = wpa_ie; + *pos++ = WLAN_EID_VENDOR_SPECIFIC; + len = pos++; /* to be filled */ + WPA_PUT_BE24(pos, OUI_WFA); + pos += 3; + *pos++ = HS20_OSEN_OUI_TYPE; + + /* Group Data Cipher Suite */ + suite = wpa_cipher_to_suite(WPA_PROTO_RSN, group_cipher); + if (suite == 0) { + wpa_printf(MSG_WARNING, "Invalid group cipher (%d).", + group_cipher); + return -1; + } + RSN_SELECTOR_PUT(pos, suite); + pos += RSN_SELECTOR_LEN; + + /* Pairwise Cipher Suite Count and List */ + WPA_PUT_LE16(pos, 1); + pos += 2; + suite = wpa_cipher_to_suite(WPA_PROTO_RSN, pairwise_cipher); + if (suite == 0 || + (!wpa_cipher_valid_pairwise(pairwise_cipher) && + pairwise_cipher != WPA_CIPHER_NONE)) { + wpa_printf(MSG_WARNING, "Invalid pairwise cipher (%d).", + pairwise_cipher); + return -1; + } + RSN_SELECTOR_PUT(pos, suite); + pos += RSN_SELECTOR_LEN; + + /* AKM Suite Count and List */ + WPA_PUT_LE16(pos, 1); + pos += 2; + RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_OSEN); + pos += RSN_SELECTOR_LEN; + + *len = pos - len - 1; + + WPA_ASSERT((size_t) (pos - wpa_ie) <= wpa_ie_len); + + return pos - wpa_ie; +} +#endif /* CONFIG_HS20 */ + + /** * wpa_gen_wpa_ie - Generate WPA/RSN IE based on current security policy * @sm: Pointer to WPA state machine data from wpa_sm_init() @@ -237,6 +296,13 @@ int wpa_gen_wpa_ie(struct wpa_sm *sm, u8 *wpa_ie, size_t wpa_ie_len) sm->group_cipher, sm->key_mgmt, sm->mgmt_group_cipher, sm); +#ifdef CONFIG_HS20 + else if (sm->proto == WPA_PROTO_OSEN) + return wpa_gen_wpa_ie_osen(wpa_ie, wpa_ie_len, + sm->pairwise_cipher, + sm->group_cipher, + sm->key_mgmt); +#endif /* CONFIG_HS20 */ else return wpa_gen_wpa_ie_wpa(wpa_ie, wpa_ie_len, sm->pairwise_cipher, @@ -246,6 +312,42 @@ int wpa_gen_wpa_ie(struct wpa_sm *sm, u8 *wpa_ie, size_t wpa_ie_len) /** + * wpa_parse_vendor_specific - Parse Vendor Specific IEs + * @pos: Pointer to the IE header + * @end: Pointer to the end of the Key Data buffer + * @ie: Pointer to parsed IE data + * Returns: 0 on success, 1 if end mark is found, -1 on failure + */ +static int wpa_parse_vendor_specific(const u8 *pos, const u8 *end, + struct wpa_eapol_ie_parse *ie) +{ + unsigned int oui; + + if (pos[1] < 4) { + wpa_printf(MSG_MSGDUMP, "Too short vendor specific IE ignored (len=%u)", + pos[1]); + return 1; + } + + oui = WPA_GET_BE24(&pos[2]); + if (oui == OUI_MICROSOFT && pos[5] == WMM_OUI_TYPE && pos[1] > 4) { + if (pos[6] == WMM_OUI_SUBTYPE_INFORMATION_ELEMENT) { + ie->wmm = &pos[2]; + ie->wmm_len = pos[1]; + wpa_hexdump(MSG_DEBUG, "WPA: WMM IE", + ie->wmm, ie->wmm_len); + } else if (pos[6] == WMM_OUI_SUBTYPE_PARAMETER_ELEMENT) { + ie->wmm = &pos[2]; + ie->wmm_len = pos[1]; + wpa_hexdump(MSG_DEBUG, "WPA: WMM Parameter Element", + ie->wmm, ie->wmm_len); + } + } + return 0; +} + + +/** * wpa_parse_generic - Parse EAPOL-Key Key Data Generic IEs * @pos: Pointer to the IE header * @end: Pointer to the end of the Key Data buffer @@ -457,8 +559,16 @@ int wpa_supplicant_parse_ies(const u8 *buf, size_t len, ie->supp_channels = pos + 2; ie->supp_channels_len = pos[1]; } else if (*pos == WLAN_EID_SUPPORTED_OPERATING_CLASSES) { - ie->supp_oper_classes = pos + 2; - ie->supp_oper_classes_len = pos[1]; + /* + * The value of the Length field of the Supported + * Operating Classes element is between 2 and 253. + * Silently skip invalid elements to avoid interop + * issues when trying to use the value. + */ + if (pos[1] >= 2 && pos[1] <= 253) { + ie->supp_oper_classes = pos + 2; + ie->supp_oper_classes_len = pos[1]; + } } else if (*pos == WLAN_EID_VENDOR_SPECIFIC) { ret = wpa_parse_generic(pos, end, ie); if (ret < 0) @@ -467,6 +577,14 @@ int wpa_supplicant_parse_ies(const u8 *buf, size_t len, ret = 0; break; } + + ret = wpa_parse_vendor_specific(pos, end, ie); + if (ret < 0) + break; + if (ret > 0) { + ret = 0; + break; + } } else { wpa_hexdump(MSG_DEBUG, "WPA: Unrecognized EAPOL-Key " "Key Data IE", pos, 2 + pos[1]); diff --git a/src/rsn_supp/wpa_ie.h b/src/rsn_supp/wpa_ie.h index 82b6fa3..0fc42cc 100644 --- a/src/rsn_supp/wpa_ie.h +++ b/src/rsn_supp/wpa_ie.h @@ -59,6 +59,8 @@ struct wpa_eapol_ie_parse { size_t supp_oper_classes_len; u8 qosinfo; u16 aid; + const u8 *wmm; + size_t wmm_len; #ifdef CONFIG_P2P const u8 *ip_addr_req; const u8 *ip_addr_alloc; diff --git a/src/tls/asn1.c b/src/tls/asn1.c index 53acd53..97462fa 100644 --- a/src/tls/asn1.c +++ b/src/tls/asn1.c @@ -1,6 +1,6 @@ /* * ASN.1 DER parsing - * Copyright (c) 2006, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -11,6 +11,17 @@ #include "common.h" #include "asn1.h" +struct asn1_oid asn1_sha1_oid = { + .oid = { 1, 3, 14, 3, 2, 26 }, + .len = 6 +}; + +struct asn1_oid asn1_sha256_oid = { + .oid = { 2, 16, 840, 1, 101, 3, 4, 2, 1 }, + .len = 9 +}; + + int asn1_get_next(const u8 *buf, size_t len, struct asn1_hdr *hdr) { const u8 *pos, *end; @@ -140,7 +151,7 @@ int asn1_get_oid(const u8 *buf, size_t len, struct asn1_oid *oid, } -void asn1_oid_to_str(struct asn1_oid *oid, char *buf, size_t len) +void asn1_oid_to_str(const struct asn1_oid *oid, char *buf, size_t len) { char *pos = buf; size_t i; @@ -204,3 +215,19 @@ unsigned long asn1_bit_string_to_long(const u8 *buf, size_t len) return val; } + + +int asn1_oid_equal(const struct asn1_oid *a, const struct asn1_oid *b) +{ + size_t i; + + if (a->len != b->len) + return 0; + + for (i = 0; i < a->len; i++) { + if (a->oid[i] != b->oid[i]) + return 0; + } + + return 1; +} diff --git a/src/tls/asn1.h b/src/tls/asn1.h index 6342c4c..7475007 100644 --- a/src/tls/asn1.h +++ b/src/tls/asn1.h @@ -60,7 +60,11 @@ int asn1_get_next(const u8 *buf, size_t len, struct asn1_hdr *hdr); int asn1_parse_oid(const u8 *buf, size_t len, struct asn1_oid *oid); int asn1_get_oid(const u8 *buf, size_t len, struct asn1_oid *oid, const u8 **next); -void asn1_oid_to_str(struct asn1_oid *oid, char *buf, size_t len); +void asn1_oid_to_str(const struct asn1_oid *oid, char *buf, size_t len); unsigned long asn1_bit_string_to_long(const u8 *buf, size_t len); +int asn1_oid_equal(const struct asn1_oid *a, const struct asn1_oid *b); + +extern struct asn1_oid asn1_sha1_oid; +extern struct asn1_oid asn1_sha256_oid; #endif /* ASN1_H */ diff --git a/src/tls/pkcs1.c b/src/tls/pkcs1.c index b6fde5e..381b7a0 100644 --- a/src/tls/pkcs1.c +++ b/src/tls/pkcs1.c @@ -1,6 +1,6 @@ /* * PKCS #1 (RSA Encryption) - * Copyright (c) 2006-2009, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -9,7 +9,9 @@ #include "includes.h" #include "common.h" +#include "crypto/crypto.h" #include "rsa.h" +#include "asn1.h" #include "pkcs1.h" @@ -113,6 +115,11 @@ int pkcs1_v15_private_key_decrypt(struct crypto_rsa_key *key, pos++; if (pos == end) return -1; + if (pos - out - 2 < 8) { + /* PKCS #1 v1.5, 8.1: At least eight octets long PS */ + wpa_printf(MSG_INFO, "LibTomCrypt: Too short padding"); + return -1; + } pos++; *outlen -= pos - out; @@ -142,35 +149,26 @@ int pkcs1_decrypt_public_key(struct crypto_rsa_key *key, * BT = 00 or 01 * PS = k-3-||D|| times (00 if BT=00) or (FF if BT=01) * k = length of modulus in octets + * + * Based on 10.1.3, "The block type shall be 01" for a signature. */ if (len < 3 + 8 + 16 /* min hash len */ || - plain[0] != 0x00 || (plain[1] != 0x00 && plain[1] != 0x01)) { + plain[0] != 0x00 || plain[1] != 0x01) { wpa_printf(MSG_INFO, "LibTomCrypt: Invalid signature EB " "structure"); return -1; } pos = plain + 3; - if (plain[1] == 0x00) { - /* BT = 00 */ - if (plain[2] != 0x00) { - wpa_printf(MSG_INFO, "LibTomCrypt: Invalid signature " - "PS (BT=00)"); - return -1; - } - while (pos + 1 < plain + len && *pos == 0x00 && pos[1] == 0x00) - pos++; - } else { - /* BT = 01 */ - if (plain[2] != 0xff) { - wpa_printf(MSG_INFO, "LibTomCrypt: Invalid signature " - "PS (BT=01)"); - return -1; - } - while (pos < plain + len && *pos == 0xff) - pos++; + /* BT = 01 */ + if (plain[2] != 0xff) { + wpa_printf(MSG_INFO, "LibTomCrypt: Invalid signature " + "PS (BT=01)"); + return -1; } + while (pos < plain + len && *pos == 0xff) + pos++; if (pos - plain - 2 < 8) { /* PKCS #1 v1.5, 8.1: At least eight octets long PS */ @@ -193,3 +191,130 @@ int pkcs1_decrypt_public_key(struct crypto_rsa_key *key, return 0; } + + +int pkcs1_v15_sig_ver(struct crypto_public_key *pk, + const u8 *s, size_t s_len, + const struct asn1_oid *hash_alg, + const u8 *hash, size_t hash_len) +{ + int res; + u8 *decrypted; + size_t decrypted_len; + const u8 *pos, *end, *next, *da_end; + struct asn1_hdr hdr; + struct asn1_oid oid; + + decrypted = os_malloc(s_len); + if (decrypted == NULL) + return -1; + decrypted_len = s_len; + res = crypto_public_key_decrypt_pkcs1(pk, s, s_len, decrypted, + &decrypted_len); + if (res < 0) { + wpa_printf(MSG_INFO, "PKCS #1: RSA decrypt failed"); + os_free(decrypted); + return -1; + } + wpa_hexdump(MSG_DEBUG, "Decrypted(S)", decrypted, decrypted_len); + + /* + * PKCS #1 v1.5, 10.1.2: + * + * DigestInfo ::= SEQUENCE { + * digestAlgorithm DigestAlgorithmIdentifier, + * digest Digest + * } + * + * DigestAlgorithmIdentifier ::= AlgorithmIdentifier + * + * Digest ::= OCTET STRING + * + */ + if (asn1_get_next(decrypted, decrypted_len, &hdr) < 0 || + hdr.class != ASN1_CLASS_UNIVERSAL || + hdr.tag != ASN1_TAG_SEQUENCE) { + wpa_printf(MSG_DEBUG, + "PKCS #1: Expected SEQUENCE (DigestInfo) - found class %d tag 0x%x", + hdr.class, hdr.tag); + os_free(decrypted); + return -1; + } + + pos = hdr.payload; + end = pos + hdr.length; + + /* + * X.509: + * AlgorithmIdentifier ::= SEQUENCE { + * algorithm OBJECT IDENTIFIER, + * parameters ANY DEFINED BY algorithm OPTIONAL + * } + */ + + if (asn1_get_next(pos, end - pos, &hdr) < 0 || + hdr.class != ASN1_CLASS_UNIVERSAL || + hdr.tag != ASN1_TAG_SEQUENCE) { + wpa_printf(MSG_DEBUG, + "PKCS #1: Expected SEQUENCE (AlgorithmIdentifier) - found class %d tag 0x%x", + hdr.class, hdr.tag); + os_free(decrypted); + return -1; + } + da_end = hdr.payload + hdr.length; + + if (asn1_get_oid(hdr.payload, hdr.length, &oid, &next)) { + wpa_printf(MSG_DEBUG, + "PKCS #1: Failed to parse digestAlgorithm"); + os_free(decrypted); + return -1; + } + + if (!asn1_oid_equal(&oid, hash_alg)) { + char txt[100], txt2[100]; + asn1_oid_to_str(&oid, txt, sizeof(txt)); + asn1_oid_to_str(hash_alg, txt2, sizeof(txt2)); + wpa_printf(MSG_DEBUG, + "PKCS #1: Hash alg OID mismatch: was %s, expected %s", + txt, txt2); + os_free(decrypted); + return -1; + } + + /* Digest ::= OCTET STRING */ + pos = da_end; + end = decrypted + decrypted_len; + + if (asn1_get_next(pos, end - pos, &hdr) < 0 || + hdr.class != ASN1_CLASS_UNIVERSAL || + hdr.tag != ASN1_TAG_OCTETSTRING) { + wpa_printf(MSG_DEBUG, + "PKCS #1: Expected OCTETSTRING (Digest) - found class %d tag 0x%x", + hdr.class, hdr.tag); + os_free(decrypted); + return -1; + } + wpa_hexdump(MSG_MSGDUMP, "PKCS #1: Decrypted Digest", + hdr.payload, hdr.length); + + if (hdr.length != hash_len || + os_memcmp(hdr.payload, hash, hdr.length) != 0) { + wpa_printf(MSG_INFO, "PKCS #1: Digest value does not match calculated hash"); + os_free(decrypted); + return -1; + } + + os_free(decrypted); + + if (hdr.payload + hdr.length != end) { + wpa_printf(MSG_INFO, + "PKCS #1: Extra data after signature - reject"); + + wpa_hexdump(MSG_DEBUG, "PKCS #1: Extra data", + hdr.payload + hdr.length, + end - hdr.payload - hdr.length); + return -1; + } + + return 0; +} diff --git a/src/tls/pkcs1.h b/src/tls/pkcs1.h index ed64def..f37ebf3 100644 --- a/src/tls/pkcs1.h +++ b/src/tls/pkcs1.h @@ -9,6 +9,9 @@ #ifndef PKCS1_H #define PKCS1_H +struct crypto_public_key; +struct asn1_oid; + int pkcs1_encrypt(int block_type, struct crypto_rsa_key *key, int use_private, const u8 *in, size_t inlen, u8 *out, size_t *outlen); @@ -18,5 +21,9 @@ int pkcs1_v15_private_key_decrypt(struct crypto_rsa_key *key, int pkcs1_decrypt_public_key(struct crypto_rsa_key *key, const u8 *crypt, size_t crypt_len, u8 *plain, size_t *plain_len); +int pkcs1_v15_sig_ver(struct crypto_public_key *pk, + const u8 *s, size_t s_len, + const struct asn1_oid *hash_alg, + const u8 *hash, size_t hash_len); #endif /* PKCS1_H */ diff --git a/src/tls/rsa.c b/src/tls/rsa.c index 125c420..0b7b530 100644 --- a/src/tls/rsa.c +++ b/src/tls/rsa.c @@ -1,6 +1,6 @@ /* * RSA - * Copyright (c) 2006, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -116,6 +116,29 @@ error: } +struct crypto_rsa_key * +crypto_rsa_import_public_key_parts(const u8 *n, size_t n_len, + const u8 *e, size_t e_len) +{ + struct crypto_rsa_key *key; + + key = os_zalloc(sizeof(*key)); + if (key == NULL) + return NULL; + + key->n = bignum_init(); + key->e = bignum_init(); + if (key->n == NULL || key->e == NULL || + bignum_set_unsigned_bin(key->n, n, n_len) < 0 || + bignum_set_unsigned_bin(key->e, e, e_len) < 0) { + crypto_rsa_free(key); + return NULL; + } + + return key; +} + + /** * crypto_rsa_import_private_key - Import an RSA private key * @buf: Key buffer (DER encoded RSA private key) diff --git a/src/tls/rsa.h b/src/tls/rsa.h index c236a9d..b65818e 100644 --- a/src/tls/rsa.h +++ b/src/tls/rsa.h @@ -14,6 +14,9 @@ struct crypto_rsa_key; struct crypto_rsa_key * crypto_rsa_import_public_key(const u8 *buf, size_t len); struct crypto_rsa_key * +crypto_rsa_import_public_key_parts(const u8 *n, size_t n_len, + const u8 *e, size_t e_len); +struct crypto_rsa_key * crypto_rsa_import_private_key(const u8 *buf, size_t len); size_t crypto_rsa_get_modulus_len(struct crypto_rsa_key *key); int crypto_rsa_exptmod(const u8 *in, size_t inlen, u8 *out, size_t *outlen, diff --git a/src/tls/tlsv1_client.c b/src/tls/tlsv1_client.c index 12148b6..4a4f0b6 100644 --- a/src/tls/tlsv1_client.c +++ b/src/tls/tlsv1_client.c @@ -1,6 +1,6 @@ /* * TLS v1.0/v1.1/v1.2 client (RFC 2246, RFC 4346, RFC 5246) - * Copyright (c) 2006-2011, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -459,10 +459,15 @@ struct tlsv1_client * tlsv1_client_init(void) count = 0; suites = conn->cipher_suites; + suites[count++] = TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; suites[count++] = TLS_RSA_WITH_AES_256_CBC_SHA256; + suites[count++] = TLS_DHE_RSA_WITH_AES_256_CBC_SHA; suites[count++] = TLS_RSA_WITH_AES_256_CBC_SHA; + suites[count++] = TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; suites[count++] = TLS_RSA_WITH_AES_128_CBC_SHA256; + suites[count++] = TLS_DHE_RSA_WITH_AES_128_CBC_SHA; suites[count++] = TLS_RSA_WITH_AES_128_CBC_SHA; + suites[count++] = TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; suites[count++] = TLS_RSA_WITH_3DES_EDE_CBC_SHA; suites[count++] = TLS_RSA_WITH_RC4_128_SHA; suites[count++] = TLS_RSA_WITH_RC4_128_MD5; diff --git a/src/tls/tlsv1_client_read.c b/src/tls/tlsv1_client_read.c index 3269ecf..f78921d 100644 --- a/src/tls/tlsv1_client_read.c +++ b/src/tls/tlsv1_client_read.c @@ -1,6 +1,6 @@ /* * TLSv1 client - read handshake message - * Copyright (c) 2006-2011, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -409,10 +409,37 @@ static int tls_process_certificate(struct tlsv1_client *conn, u8 ct, } +static unsigned int count_bits(const u8 *val, size_t len) +{ + size_t i; + unsigned int bits; + u8 tmp; + + for (i = 0; i < len; i++) { + if (val[i]) + break; + } + if (i == len) + return 0; + + bits = (len - i - 1) * 8; + tmp = val[i]; + while (tmp) { + bits++; + tmp >>= 1; + } + + return bits; +} + + static int tlsv1_process_diffie_hellman(struct tlsv1_client *conn, - const u8 *buf, size_t len) + const u8 *buf, size_t len, + tls_key_exchange key_exchange) { - const u8 *pos, *end; + const u8 *pos, *end, *server_params, *server_params_end; + u8 alert; + unsigned int bits; tlsv1_client_free_dh(conn); @@ -421,6 +448,7 @@ static int tlsv1_process_diffie_hellman(struct tlsv1_client *conn, if (end - pos < 3) goto fail; + server_params = pos; conn->dh_p_len = WPA_GET_BE16(pos); pos += 2; if (conn->dh_p_len == 0 || end - pos < (int) conn->dh_p_len) { @@ -428,6 +456,14 @@ static int tlsv1_process_diffie_hellman(struct tlsv1_client *conn, (unsigned long) conn->dh_p_len); goto fail; } + bits = count_bits(pos, conn->dh_p_len); + if (bits < 768) { + wpa_printf(MSG_INFO, "TLSv1: Reject under 768-bit DH prime (insecure; only %u bits)", + bits); + wpa_hexdump(MSG_DEBUG, "TLSv1: Rejected DH prime", + pos, conn->dh_p_len); + goto fail; + } conn->dh_p = os_malloc(conn->dh_p_len); if (conn->dh_p == NULL) goto fail; @@ -465,6 +501,59 @@ static int tlsv1_process_diffie_hellman(struct tlsv1_client *conn, pos += conn->dh_ys_len; wpa_hexdump(MSG_DEBUG, "TLSv1: DH Ys (server's public value)", conn->dh_ys, conn->dh_ys_len); + server_params_end = pos; + + if (key_exchange == TLS_KEY_X_DHE_RSA) { + u8 hash[MD5_MAC_LEN + SHA1_MAC_LEN]; + int hlen; + + if (conn->rl.tls_version == TLS_VERSION_1_2) { +#ifdef CONFIG_TLSV12 + /* + * RFC 5246, 4.7: + * TLS v1.2 adds explicit indication of the used + * signature and hash algorithms. + * + * struct { + * HashAlgorithm hash; + * SignatureAlgorithm signature; + * } SignatureAndHashAlgorithm; + */ + if (end - pos < 2) + goto fail; + if (pos[0] != TLS_HASH_ALG_SHA256 || + pos[1] != TLS_SIGN_ALG_RSA) { + wpa_printf(MSG_DEBUG, "TLSv1.2: Unsupported hash(%u)/signature(%u) algorithm", + pos[0], pos[1]); + goto fail; + } + pos += 2; + + hlen = tlsv12_key_x_server_params_hash( + conn->rl.tls_version, conn->client_random, + conn->server_random, server_params, + server_params_end - server_params, hash); +#else /* CONFIG_TLSV12 */ + goto fail; +#endif /* CONFIG_TLSV12 */ + } else { + hlen = tls_key_x_server_params_hash( + conn->rl.tls_version, conn->client_random, + conn->server_random, server_params, + server_params_end - server_params, hash); + } + + if (hlen < 0) + goto fail; + wpa_hexdump(MSG_MSGDUMP, "TLSv1: ServerKeyExchange hash", + hash, hlen); + + if (tls_verify_signature(conn->rl.tls_version, + conn->server_rsa_key, + hash, hlen, pos, end - pos, + &alert) < 0) + goto fail; + } return 0; @@ -543,8 +632,10 @@ static int tls_process_server_key_exchange(struct tlsv1_client *conn, u8 ct, wpa_hexdump(MSG_DEBUG, "TLSv1: ServerKeyExchange", pos, len); suite = tls_get_cipher_suite(conn->rl.cipher_suite); - if (suite && suite->key_exchange == TLS_KEY_X_DH_anon) { - if (tlsv1_process_diffie_hellman(conn, pos, len) < 0) { + if (suite && (suite->key_exchange == TLS_KEY_X_DH_anon || + suite->key_exchange == TLS_KEY_X_DHE_RSA)) { + if (tlsv1_process_diffie_hellman(conn, pos, len, + suite->key_exchange) < 0) { tls_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -873,6 +964,8 @@ static int tls_process_server_finished(struct tlsv1_client *conn, u8 ct, if (os_memcmp(pos, verify_data, TLS_VERIFY_DATA_LEN) != 0) { wpa_printf(MSG_INFO, "TLSv1: Mismatch in verify_data"); + tls_alert(conn, TLS_ALERT_LEVEL_FATAL, + TLS_ALERT_DECRYPT_ERROR); return -1; } diff --git a/src/tls/tlsv1_client_write.c b/src/tls/tlsv1_client_write.c index d789efb..839eb90 100644 --- a/src/tls/tlsv1_client_write.c +++ b/src/tls/tlsv1_client_write.c @@ -1,6 +1,6 @@ /* * TLSv1 client - write handshake message - * Copyright (c) 2006-2011, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -205,7 +205,7 @@ static int tls_write_client_certificate(struct tlsv1_client *conn, } -static int tlsv1_key_x_anon_dh(struct tlsv1_client *conn, u8 **pos, u8 *end) +static int tlsv1_key_x_dh(struct tlsv1_client *conn, u8 **pos, u8 *end) { /* ClientDiffieHellmanPublic */ u8 *csecret, *csecret_start, *dh_yc, *shared; @@ -399,8 +399,8 @@ static int tls_write_client_key_exchange(struct tlsv1_client *conn, hs_length = pos; pos += 3; /* body - ClientKeyExchange */ - if (keyx == TLS_KEY_X_DH_anon) { - if (tlsv1_key_x_anon_dh(conn, &pos, end) < 0) + if (keyx == TLS_KEY_X_DH_anon || keyx == TLS_KEY_X_DHE_RSA) { + if (tlsv1_key_x_dh(conn, &pos, end) < 0) return -1; } else { if (tlsv1_key_x_rsa(conn, &pos, end) < 0) diff --git a/src/tls/tlsv1_common.c b/src/tls/tlsv1_common.c index 4578b22..8a4645b 100644 --- a/src/tls/tlsv1_common.c +++ b/src/tls/tlsv1_common.c @@ -1,6 +1,6 @@ /* * TLSv1 common routines - * Copyright (c) 2006-2011, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -9,6 +9,7 @@ #include "includes.h" #include "common.h" +#include "crypto/md5.h" #include "crypto/sha1.h" #include "crypto/sha256.h" #include "x509v3.h" @@ -33,6 +34,10 @@ static const struct tls_cipher_suite tls_cipher_suites[] = { TLS_HASH_SHA }, { TLS_RSA_WITH_3DES_EDE_CBC_SHA, TLS_KEY_X_RSA, TLS_CIPHER_3DES_EDE_CBC, TLS_HASH_SHA }, + { TLS_DHE_RSA_WITH_DES_CBC_SHA, TLS_KEY_X_DHE_RSA, TLS_CIPHER_DES_CBC, + TLS_HASH_SHA}, + { TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_KEY_X_DHE_RSA, + TLS_CIPHER_3DES_EDE_CBC, TLS_HASH_SHA }, { TLS_DH_anon_WITH_RC4_128_MD5, TLS_KEY_X_DH_anon, TLS_CIPHER_RC4_128, TLS_HASH_MD5 }, { TLS_DH_anon_WITH_DES_CBC_SHA, TLS_KEY_X_DH_anon, @@ -41,16 +46,24 @@ static const struct tls_cipher_suite tls_cipher_suites[] = { TLS_CIPHER_3DES_EDE_CBC, TLS_HASH_SHA }, { TLS_RSA_WITH_AES_128_CBC_SHA, TLS_KEY_X_RSA, TLS_CIPHER_AES_128_CBC, TLS_HASH_SHA }, + { TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_KEY_X_DHE_RSA, + TLS_CIPHER_AES_128_CBC, TLS_HASH_SHA }, { TLS_DH_anon_WITH_AES_128_CBC_SHA, TLS_KEY_X_DH_anon, TLS_CIPHER_AES_128_CBC, TLS_HASH_SHA }, { TLS_RSA_WITH_AES_256_CBC_SHA, TLS_KEY_X_RSA, TLS_CIPHER_AES_256_CBC, TLS_HASH_SHA }, + { TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_KEY_X_DHE_RSA, + TLS_CIPHER_AES_256_CBC, TLS_HASH_SHA }, { TLS_DH_anon_WITH_AES_256_CBC_SHA, TLS_KEY_X_DH_anon, TLS_CIPHER_AES_256_CBC, TLS_HASH_SHA }, { TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_KEY_X_RSA, TLS_CIPHER_AES_128_CBC, TLS_HASH_SHA256 }, { TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_KEY_X_RSA, TLS_CIPHER_AES_256_CBC, TLS_HASH_SHA256 }, + { TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_KEY_X_DHE_RSA, + TLS_CIPHER_AES_128_CBC, TLS_HASH_SHA256 }, + { TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_KEY_X_DHE_RSA, + TLS_CIPHER_AES_256_CBC, TLS_HASH_SHA256 }, { TLS_DH_anon_WITH_AES_128_CBC_SHA256, TLS_KEY_X_DH_anon, TLS_CIPHER_AES_128_CBC, TLS_HASH_SHA256 }, { TLS_DH_anon_WITH_AES_256_CBC_SHA256, TLS_KEY_X_DH_anon, @@ -319,3 +332,163 @@ int tls_prf(u16 ver, const u8 *secret, size_t secret_len, const char *label, return tls_prf_sha1_md5(secret, secret_len, label, seed, seed_len, out, outlen); } + + +#ifdef CONFIG_TLSV12 +int tlsv12_key_x_server_params_hash(u16 tls_version, + const u8 *client_random, + const u8 *server_random, + const u8 *server_params, + size_t server_params_len, u8 *hash) +{ + size_t hlen; + struct crypto_hash *ctx; + + ctx = crypto_hash_init(CRYPTO_HASH_ALG_SHA256, NULL, 0); + if (ctx == NULL) + return -1; + crypto_hash_update(ctx, client_random, TLS_RANDOM_LEN); + crypto_hash_update(ctx, server_random, TLS_RANDOM_LEN); + crypto_hash_update(ctx, server_params, server_params_len); + hlen = SHA256_MAC_LEN; + if (crypto_hash_finish(ctx, hash, &hlen) < 0) + return -1; + + return hlen; +} +#endif /* CONFIG_TLSV12 */ + + +int tls_key_x_server_params_hash(u16 tls_version, const u8 *client_random, + const u8 *server_random, + const u8 *server_params, + size_t server_params_len, u8 *hash) +{ + u8 *hpos; + size_t hlen; + enum { SIGN_ALG_RSA, SIGN_ALG_DSA } alg = SIGN_ALG_RSA; + struct crypto_hash *ctx; + + hpos = hash; + + if (alg == SIGN_ALG_RSA) { + ctx = crypto_hash_init(CRYPTO_HASH_ALG_MD5, NULL, 0); + if (ctx == NULL) + return -1; + crypto_hash_update(ctx, client_random, TLS_RANDOM_LEN); + crypto_hash_update(ctx, server_random, TLS_RANDOM_LEN); + crypto_hash_update(ctx, server_params, server_params_len); + hlen = MD5_MAC_LEN; + if (crypto_hash_finish(ctx, hash, &hlen) < 0) + return -1; + hpos += hlen; + } + + ctx = crypto_hash_init(CRYPTO_HASH_ALG_SHA1, NULL, 0); + if (ctx == NULL) + return -1; + crypto_hash_update(ctx, client_random, TLS_RANDOM_LEN); + crypto_hash_update(ctx, server_random, TLS_RANDOM_LEN); + crypto_hash_update(ctx, server_params, server_params_len); + hlen = hash + sizeof(hash) - hpos; + if (crypto_hash_finish(ctx, hpos, &hlen) < 0) + return -1; + hpos += hlen; + return hpos - hash; +} + + +int tls_verify_signature(u16 tls_version, struct crypto_public_key *pk, + const u8 *data, size_t data_len, + const u8 *pos, size_t len, u8 *alert) +{ + u8 *buf; + const u8 *end = pos + len; + const u8 *decrypted; + u16 slen; + size_t buflen; + + if (end - pos < 2) { + *alert = TLS_ALERT_DECODE_ERROR; + return -1; + } + slen = WPA_GET_BE16(pos); + pos += 2; + if (end - pos < slen) { + *alert = TLS_ALERT_DECODE_ERROR; + return -1; + } + if (end - pos > slen) { + wpa_hexdump(MSG_MSGDUMP, "Additional data after Signature", + pos + slen, end - pos - slen); + end = pos + slen; + } + + wpa_hexdump(MSG_MSGDUMP, "TLSv1: Signature", pos, end - pos); + if (pk == NULL) { + wpa_printf(MSG_DEBUG, "TLSv1: No public key to verify signature"); + *alert = TLS_ALERT_INTERNAL_ERROR; + return -1; + } + + buflen = end - pos; + buf = os_malloc(end - pos); + if (buf == NULL) { + *alert = TLS_ALERT_INTERNAL_ERROR; + return -1; + } + if (crypto_public_key_decrypt_pkcs1(pk, pos, end - pos, buf, &buflen) < + 0) { + wpa_printf(MSG_DEBUG, "TLSv1: Failed to decrypt signature"); + os_free(buf); + *alert = TLS_ALERT_DECRYPT_ERROR; + return -1; + } + decrypted = buf; + + wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: Decrypted Signature", + decrypted, buflen); + +#ifdef CONFIG_TLSV12 + if (tls_version >= TLS_VERSION_1_2) { + /* + * RFC 3447, A.2.4 RSASSA-PKCS1-v1_5 + * + * DigestInfo ::= SEQUENCE { + * digestAlgorithm DigestAlgorithm, + * digest OCTET STRING + * } + * + * SHA-256 OID: sha256WithRSAEncryption ::= {pkcs-1 11} + * + * DER encoded DigestInfo for SHA256 per RFC 3447: + * 30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 || + * H + */ + if (buflen >= 19 + 32 && + os_memcmp(buf, "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01" + "\x65\x03\x04\x02\x01\x05\x00\x04\x20", 19) == 0) + { + wpa_printf(MSG_DEBUG, "TLSv1.2: DigestAlgorithn = SHA-256"); + decrypted = buf + 19; + buflen -= 19; + } else { + wpa_printf(MSG_DEBUG, "TLSv1.2: Unrecognized DigestInfo"); + os_free(buf); + *alert = TLS_ALERT_DECRYPT_ERROR; + return -1; + } + } +#endif /* CONFIG_TLSV12 */ + + if (buflen != data_len || os_memcmp(decrypted, data, data_len) != 0) { + wpa_printf(MSG_DEBUG, "TLSv1: Invalid Signature in CertificateVerify - did not match calculated hash"); + os_free(buf); + *alert = TLS_ALERT_DECRYPT_ERROR; + return -1; + } + + os_free(buf); + + return 0; +} diff --git a/src/tls/tlsv1_common.h b/src/tls/tlsv1_common.h index f28c0cd..26e68af 100644 --- a/src/tls/tlsv1_common.h +++ b/src/tls/tlsv1_common.h @@ -1,6 +1,6 @@ /* * TLSv1 common definitions - * Copyright (c) 2006-2011, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -257,5 +257,16 @@ int tls_version_ok(u16 ver); const char * tls_version_str(u16 ver); int tls_prf(u16 ver, const u8 *secret, size_t secret_len, const char *label, const u8 *seed, size_t seed_len, u8 *out, size_t outlen); +int tlsv12_key_x_server_params_hash(u16 tls_version, const u8 *client_random, + const u8 *server_random, + const u8 *server_params, + size_t server_params_len, u8 *hash); +int tls_key_x_server_params_hash(u16 tls_version, const u8 *client_random, + const u8 *server_random, + const u8 *server_params, + size_t server_params_len, u8 *hash); +int tls_verify_signature(u16 tls_version, struct crypto_public_key *pk, + const u8 *data, size_t data_len, + const u8 *pos, size_t len, u8 *alert); #endif /* TLSV1_COMMON_H */ diff --git a/src/tls/tlsv1_server.c b/src/tls/tlsv1_server.c index 2880309..23d0b81 100644 --- a/src/tls/tlsv1_server.c +++ b/src/tls/tlsv1_server.c @@ -1,6 +1,6 @@ /* * TLS v1.0/v1.1/v1.2 server (RFC 2246, RFC 4346, RFC 5246) - * Copyright (c) 2006-2011, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -21,6 +21,31 @@ */ +void tlsv1_server_log(struct tlsv1_server *conn, const char *fmt, ...) +{ + va_list ap; + char *buf; + int buflen; + + va_start(ap, fmt); + buflen = vsnprintf(NULL, 0, fmt, ap) + 1; + va_end(ap); + + buf = os_malloc(buflen); + if (buf == NULL) + return; + va_start(ap, fmt); + vsnprintf(buf, buflen, fmt, ap); + va_end(ap); + + wpa_printf(MSG_DEBUG, "TLSv1: %s", buf); + if (conn->log_cb) + conn->log_cb(conn->log_cb_ctx, buf); + + os_free(buf); +} + + void tlsv1_server_alert(struct tlsv1_server *conn, u8 level, u8 description) { conn->alert_level = level; @@ -250,8 +275,7 @@ int tlsv1_server_decrypt(struct tlsv1_server *conn, used = tlsv1_record_receive(&conn->rl, pos, in_end - pos, out_pos, &olen, &alert); if (used < 0) { - wpa_printf(MSG_DEBUG, "TLSv1: Record layer processing " - "failed"); + tlsv1_server_log(conn, "Record layer processing failed"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, alert); return -1; } @@ -265,14 +289,13 @@ int tlsv1_server_decrypt(struct tlsv1_server *conn, if (ct == TLS_CONTENT_TYPE_ALERT) { if (olen < 2) { - wpa_printf(MSG_DEBUG, "TLSv1: Alert " - "underflow"); + tlsv1_server_log(conn, "Alert underflow"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; } - wpa_printf(MSG_DEBUG, "TLSv1: Received alert %d:%d", - out_pos[0], out_pos[1]); + tlsv1_server_log(conn, "Received alert %d:%d", + out_pos[0], out_pos[1]); if (out_pos[0] == TLS_ALERT_LEVEL_WARNING) { /* Continue processing */ pos += used; @@ -285,13 +308,23 @@ int tlsv1_server_decrypt(struct tlsv1_server *conn, } if (ct != TLS_CONTENT_TYPE_APPLICATION_DATA) { - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected content type " - "0x%x", pos[0]); + tlsv1_server_log(conn, "Unexpected content type 0x%x", + pos[0]); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; } +#ifdef CONFIG_TESTING_OPTIONS + if ((conn->test_flags & + (TLS_BREAK_VERIFY_DATA | TLS_BREAK_SRV_KEY_X_HASH | + TLS_BREAK_SRV_KEY_X_SIGNATURE)) && + !conn->test_failure_reported) { + tlsv1_server_log(conn, "TEST-FAILURE: Client ApplData received after invalid handshake"); + conn->test_failure_reported = 1; + } +#endif /* CONFIG_TESTING_OPTIONS */ + out_pos += olen; if (out_pos > out_end) { wpa_printf(MSG_DEBUG, "TLSv1: Buffer not large enough " @@ -361,8 +394,15 @@ struct tlsv1_server * tlsv1_server_init(struct tlsv1_credentials *cred) count = 0; suites = conn->cipher_suites; + suites[count++] = TLS_DHE_RSA_WITH_AES_256_CBC_SHA256; + suites[count++] = TLS_RSA_WITH_AES_256_CBC_SHA256; + suites[count++] = TLS_DHE_RSA_WITH_AES_256_CBC_SHA; suites[count++] = TLS_RSA_WITH_AES_256_CBC_SHA; + suites[count++] = TLS_DHE_RSA_WITH_AES_128_CBC_SHA256; + suites[count++] = TLS_RSA_WITH_AES_128_CBC_SHA256; + suites[count++] = TLS_DHE_RSA_WITH_AES_128_CBC_SHA; suites[count++] = TLS_RSA_WITH_AES_128_CBC_SHA; + suites[count++] = TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA; suites[count++] = TLS_RSA_WITH_3DES_EDE_CBC_SHA; suites[count++] = TLS_RSA_WITH_RC4_128_SHA; suites[count++] = TLS_RSA_WITH_RC4_128_MD5; @@ -618,3 +658,125 @@ void tlsv1_server_set_session_ticket_cb(struct tlsv1_server *conn, conn->session_ticket_cb = cb; conn->session_ticket_cb_ctx = ctx; } + + +void tlsv1_server_set_log_cb(struct tlsv1_server *conn, + void (*cb)(void *ctx, const char *msg), void *ctx) +{ + conn->log_cb = cb; + conn->log_cb_ctx = ctx; +} + + +#ifdef CONFIG_TESTING_OPTIONS +void tlsv1_server_set_test_flags(struct tlsv1_server *conn, u32 flags) +{ + conn->test_flags = flags; +} + + +static const u8 test_tls_prime15[1] = { + 15 +}; + +static const u8 test_tls_prime511b[64] = { + 0x50, 0xfb, 0xf1, 0xae, 0x01, 0xf1, 0xfe, 0xe6, + 0xe1, 0xae, 0xdc, 0x1e, 0xbe, 0xfb, 0x9e, 0x58, + 0x9a, 0xd7, 0x54, 0x9d, 0x6b, 0xb3, 0x78, 0xe2, + 0x39, 0x7f, 0x30, 0x01, 0x25, 0xa1, 0xf9, 0x7c, + 0x55, 0x0e, 0xa1, 0x15, 0xcc, 0x36, 0x34, 0xbb, + 0x6c, 0x8b, 0x64, 0x45, 0x15, 0x7f, 0xd3, 0xe7, + 0x31, 0xc8, 0x8e, 0x56, 0x8e, 0x95, 0xdc, 0xea, + 0x9e, 0xdf, 0xf7, 0x56, 0xdd, 0xb0, 0x34, 0xdb +}; + +static const u8 test_tls_prime767b[96] = { + 0x4c, 0xdc, 0xb8, 0x21, 0x20, 0x9d, 0xe8, 0xa3, + 0x53, 0xd9, 0x1c, 0x18, 0xc1, 0x3a, 0x58, 0x67, + 0xa7, 0x85, 0xf9, 0x28, 0x9b, 0xce, 0xc0, 0xd1, + 0x05, 0x84, 0x61, 0x97, 0xb2, 0x86, 0x1c, 0xd0, + 0xd1, 0x96, 0x23, 0x29, 0x8c, 0xc5, 0x30, 0x68, + 0x3e, 0xf9, 0x05, 0xba, 0x60, 0xeb, 0xdb, 0xee, + 0x2d, 0xdf, 0x84, 0x65, 0x49, 0x87, 0x90, 0x2a, + 0xc9, 0x8e, 0x34, 0x63, 0x6d, 0x9a, 0x2d, 0x32, + 0x1c, 0x46, 0xd5, 0x4e, 0x20, 0x20, 0x90, 0xac, + 0xd5, 0x48, 0x79, 0x99, 0x0c, 0xe6, 0xed, 0xbf, + 0x79, 0xc2, 0x47, 0x50, 0x95, 0x38, 0x38, 0xbc, + 0xde, 0xb0, 0xd2, 0xe8, 0x97, 0xcb, 0x22, 0xbb +}; + +static const u8 test_tls_prime58[128] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xc1, 0xba, 0xc8, 0x25, 0xbe, 0x2d, 0xf3 +}; + +static const u8 test_tls_non_prime[] = { + /* + * This is not a prime and the value has the following factors: + * 13736783488716579923 * 16254860191773456563 * 18229434976173670763 * + * 11112313018289079419 * 10260802278580253339 * 12394009491575311499 * + * 12419059668711064739 * 14317973192687985827 * 10498605410533203179 * + * 16338688760390249003 * 11128963991123878883 * 12990532258280301419 * + * 3 + */ + 0x0C, 0x8C, 0x36, 0x9C, 0x6F, 0x71, 0x2E, 0xA7, + 0xAB, 0x32, 0xD3, 0x0F, 0x68, 0x3D, 0xB2, 0x6D, + 0x81, 0xDD, 0xC4, 0x84, 0x0D, 0x9C, 0x6E, 0x36, + 0x29, 0x70, 0xF3, 0x1E, 0x9A, 0x42, 0x0B, 0x67, + 0x82, 0x6B, 0xB1, 0xF2, 0xAF, 0x55, 0x28, 0xE7, + 0xDB, 0x67, 0x6C, 0xF7, 0x6B, 0xAC, 0xAC, 0xE5, + 0xF7, 0x9F, 0xD4, 0x63, 0x55, 0x70, 0x32, 0x7C, + 0x70, 0xFB, 0xAF, 0xB8, 0xEB, 0x37, 0xCF, 0x3F, + 0xFE, 0x94, 0x73, 0xF9, 0x7A, 0xC7, 0x12, 0x2E, + 0x9B, 0xB4, 0x7D, 0x08, 0x60, 0x83, 0x43, 0x52, + 0x83, 0x1E, 0xA5, 0xFC, 0xFA, 0x87, 0x12, 0xF4, + 0x64, 0xE2, 0xCE, 0x71, 0x17, 0x72, 0xB6, 0xAB +}; + +#endif /* CONFIG_TESTING_OPTIONS */ + + +void tlsv1_server_get_dh_p(struct tlsv1_server *conn, const u8 **dh_p, + size_t *dh_p_len) +{ + *dh_p = conn->cred->dh_p; + *dh_p_len = conn->cred->dh_p_len; + +#ifdef CONFIG_TESTING_OPTIONS + if (conn->test_flags & TLS_DHE_PRIME_511B) { + tlsv1_server_log(conn, "TESTING: Use short 511-bit prime with DHE"); + *dh_p = test_tls_prime511b; + *dh_p_len = sizeof(test_tls_prime511b); + } else if (conn->test_flags & TLS_DHE_PRIME_767B) { + tlsv1_server_log(conn, "TESTING: Use short 767-bit prime with DHE"); + *dh_p = test_tls_prime767b; + *dh_p_len = sizeof(test_tls_prime767b); + } else if (conn->test_flags & TLS_DHE_PRIME_15) { + tlsv1_server_log(conn, "TESTING: Use bogus 15 \"prime\" with DHE"); + *dh_p = test_tls_prime15; + *dh_p_len = sizeof(test_tls_prime15); + } else if (conn->test_flags & TLS_DHE_PRIME_58B) { + tlsv1_server_log(conn, "TESTING: Use short 58-bit prime in long container with DHE"); + *dh_p = test_tls_prime58; + *dh_p_len = sizeof(test_tls_prime58); + } else if (conn->test_flags & TLS_DHE_NON_PRIME) { + tlsv1_server_log(conn, "TESTING: Use claim non-prime as the DHE prime"); + *dh_p = test_tls_non_prime; + *dh_p_len = sizeof(test_tls_non_prime); + } +#endif /* CONFIG_TESTING_OPTIONS */ +} diff --git a/src/tls/tlsv1_server.h b/src/tls/tlsv1_server.h index a18c69e..b2b28d1 100644 --- a/src/tls/tlsv1_server.h +++ b/src/tls/tlsv1_server.h @@ -45,4 +45,9 @@ void tlsv1_server_set_session_ticket_cb(struct tlsv1_server *conn, tlsv1_server_session_ticket_cb cb, void *ctx); +void tlsv1_server_set_log_cb(struct tlsv1_server *conn, + void (*cb)(void *ctx, const char *msg), void *ctx); + +void tlsv1_server_set_test_flags(struct tlsv1_server *conn, u32 flags); + #endif /* TLSV1_SERVER_H */ diff --git a/src/tls/tlsv1_server_i.h b/src/tls/tlsv1_server_i.h index 1f61533..96d79b3 100644 --- a/src/tls/tlsv1_server_i.h +++ b/src/tls/tlsv1_server_i.h @@ -51,13 +51,24 @@ struct tlsv1_server { tlsv1_server_session_ticket_cb session_ticket_cb; void *session_ticket_cb_ctx; + void (*log_cb)(void *ctx, const char *msg); + void *log_cb_ctx; + int use_session_ticket; u8 *dh_secret; size_t dh_secret_len; + +#ifdef CONFIG_TESTING_OPTIONS + u32 test_flags; + int test_failure_reported; +#endif /* CONFIG_TESTING_OPTIONS */ }; +void tlsv1_server_log(struct tlsv1_server *conn, const char *fmt, ...) +PRINTF_FORMAT(2, 3); + void tlsv1_server_alert(struct tlsv1_server *conn, u8 level, u8 description); int tlsv1_server_derive_keys(struct tlsv1_server *conn, const u8 *pre_master_secret, @@ -67,5 +78,7 @@ u8 * tlsv1_server_send_alert(struct tlsv1_server *conn, u8 level, u8 description, size_t *out_len); int tlsv1_server_process_handshake(struct tlsv1_server *conn, u8 ct, const u8 *buf, size_t *len); +void tlsv1_server_get_dh_p(struct tlsv1_server *conn, const u8 **dh_p, + size_t *dh_p_len); #endif /* TLSV1_SERVER_I_H */ diff --git a/src/tls/tlsv1_server_read.c b/src/tls/tlsv1_server_read.c index 6f6539b..c34545e 100644 --- a/src/tls/tlsv1_server_read.c +++ b/src/tls/tlsv1_server_read.c @@ -1,6 +1,6 @@ /* * TLSv1 server - read handshake message - * Copyright (c) 2006-2011, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -27,6 +27,25 @@ static int tls_process_change_cipher_spec(struct tlsv1_server *conn, size_t *in_len); +static int testing_cipher_suite_filter(struct tlsv1_server *conn, u16 suite) +{ +#ifdef CONFIG_TESTING_OPTIONS + if ((conn->test_flags & + (TLS_BREAK_SRV_KEY_X_HASH | TLS_BREAK_SRV_KEY_X_SIGNATURE | + TLS_DHE_PRIME_511B | TLS_DHE_PRIME_767B | TLS_DHE_PRIME_15 | + TLS_DHE_PRIME_58B | TLS_DHE_NON_PRIME)) && + suite != TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 && + suite != TLS_DHE_RSA_WITH_AES_256_CBC_SHA && + suite != TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 && + suite != TLS_DHE_RSA_WITH_AES_128_CBC_SHA && + suite != TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) + return 1; +#endif /* CONFIG_TESTING_OPTIONS */ + + return 0; +} + + static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, const u8 *in_data, size_t *in_len) { @@ -38,8 +57,8 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, u16 ext_type, ext_len; if (ct != TLS_CONTENT_TYPE_HANDSHAKE) { - wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; " - "received content type 0x%x", ct); + tlsv1_server_log(conn, "Expected Handshake; received content type 0x%x", + ct); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; @@ -53,13 +72,13 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, /* HandshakeType msg_type */ if (*pos != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) { - wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake " - "message %d (expected ClientHello)", *pos); + tlsv1_server_log(conn, "Received unexpected handshake message %d (expected ClientHello)", + *pos); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; } - wpa_printf(MSG_DEBUG, "TLSv1: Received ClientHello"); + tlsv1_server_log(conn, "Received ClientHello"); pos++; /* uint24 length */ len = WPA_GET_BE24(pos); @@ -78,13 +97,13 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, if (end - pos < 2) goto decode_error; conn->client_version = WPA_GET_BE16(pos); - wpa_printf(MSG_DEBUG, "TLSv1: Client version %d.%d", - conn->client_version >> 8, conn->client_version & 0xff); + tlsv1_server_log(conn, "Client version %d.%d", + conn->client_version >> 8, + conn->client_version & 0xff); if (conn->client_version < TLS_VERSION_1) { - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected protocol version in " - "ClientHello %u.%u", - conn->client_version >> 8, - conn->client_version & 0xff); + tlsv1_server_log(conn, "Unexpected protocol version in ClientHello %u.%u", + conn->client_version >> 8, + conn->client_version & 0xff); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_PROTOCOL_VERSION); return -1; @@ -101,8 +120,8 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, conn->rl.tls_version = TLS_VERSION_1_1; else conn->rl.tls_version = conn->client_version; - wpa_printf(MSG_DEBUG, "TLSv1: Using TLS v%s", - tls_version_str(conn->rl.tls_version)); + tlsv1_server_log(conn, "Using TLS v%s", + tls_version_str(conn->rl.tls_version)); /* Random random */ if (end - pos < TLS_RANDOM_LEN) @@ -137,6 +156,8 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, cipher_suite = 0; for (i = 0; !cipher_suite && i < conn->num_cipher_suites; i++) { + if (testing_cipher_suite_filter(conn, conn->cipher_suites[i])) + continue; c = pos; for (j = 0; j < num_suites; j++) { u16 tmp = WPA_GET_BE16(c); @@ -149,8 +170,7 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, } pos += num_suites * 2; if (!cipher_suite) { - wpa_printf(MSG_INFO, "TLSv1: No supported cipher suite " - "available"); + tlsv1_server_log(conn, "No supported cipher suite available"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_ILLEGAL_PARAMETER); return -1; @@ -180,16 +200,15 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, compr_null_found = 1; } if (!compr_null_found) { - wpa_printf(MSG_INFO, "TLSv1: Client does not accept NULL " - "compression"); + tlsv1_server_log(conn, "Client does not accept NULL compression"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_ILLEGAL_PARAMETER); return -1; } if (end - pos == 1) { - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected extra octet in the " - "end of ClientHello: 0x%02x", *pos); + tlsv1_server_log(conn, "Unexpected extra octet in the end of ClientHello: 0x%02x", + *pos); goto decode_error; } @@ -198,12 +217,11 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, ext_len = WPA_GET_BE16(pos); pos += 2; - wpa_printf(MSG_DEBUG, "TLSv1: %u bytes of ClientHello " - "extensions", ext_len); + tlsv1_server_log(conn, "%u bytes of ClientHello extensions", + ext_len); if (end - pos != ext_len) { - wpa_printf(MSG_DEBUG, "TLSv1: Invalid ClientHello " - "extension list length %u (expected %u)", - ext_len, (unsigned int) (end - pos)); + tlsv1_server_log(conn, "Invalid ClientHello extension list length %u (expected %u)", + ext_len, (unsigned int) (end - pos)); goto decode_error; } @@ -216,8 +234,7 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, while (pos < end) { if (end - pos < 2) { - wpa_printf(MSG_DEBUG, "TLSv1: Invalid " - "extension_type field"); + tlsv1_server_log(conn, "Invalid extension_type field"); goto decode_error; } @@ -225,8 +242,7 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, pos += 2; if (end - pos < 2) { - wpa_printf(MSG_DEBUG, "TLSv1: Invalid " - "extension_data length field"); + tlsv1_server_log(conn, "Invalid extension_data length field"); goto decode_error; } @@ -234,13 +250,12 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, pos += 2; if (end - pos < ext_len) { - wpa_printf(MSG_DEBUG, "TLSv1: Invalid " - "extension_data field"); + tlsv1_server_log(conn, "Invalid extension_data field"); goto decode_error; } - wpa_printf(MSG_DEBUG, "TLSv1: ClientHello Extension " - "type %u", ext_type); + tlsv1_server_log(conn, "ClientHello Extension type %u", + ext_type); wpa_hexdump(MSG_MSGDUMP, "TLSv1: ClientHello " "Extension data", pos, ext_len); @@ -260,14 +275,13 @@ static int tls_process_client_hello(struct tlsv1_server *conn, u8 ct, *in_len = end - in_data; - wpa_printf(MSG_DEBUG, "TLSv1: ClientHello OK - proceed to " - "ServerHello"); + tlsv1_server_log(conn, "ClientHello OK - proceed to ServerHello"); conn->state = SERVER_HELLO; return 0; decode_error: - wpa_printf(MSG_DEBUG, "TLSv1: Failed to decode ClientHello"); + tlsv1_server_log(conn, "Failed to decode ClientHello"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -284,8 +298,8 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, int reason; if (ct != TLS_CONTENT_TYPE_HANDSHAKE) { - wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; " - "received content type 0x%x", ct); + tlsv1_server_log(conn, "Expected Handshake; received content type 0x%x", + ct); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; @@ -295,8 +309,8 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, left = *in_len; if (left < 4) { - wpa_printf(MSG_DEBUG, "TLSv1: Too short Certificate message " - "(len=%lu)", (unsigned long) left); + tlsv1_server_log(conn, "Too short Certificate message (len=%lu)", + (unsigned long) left); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -308,9 +322,8 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, left -= 4; if (len > left) { - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected Certificate message " - "length (len=%lu != left=%lu)", - (unsigned long) len, (unsigned long) left); + tlsv1_server_log(conn, "Unexpected Certificate message length (len=%lu != left=%lu)", + (unsigned long) len, (unsigned long) left); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -318,8 +331,7 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, if (type == TLS_HANDSHAKE_TYPE_CLIENT_KEY_EXCHANGE) { if (conn->verify_peer) { - wpa_printf(MSG_DEBUG, "TLSv1: Client did not include " - "Certificate"); + tlsv1_server_log(conn, "Client did not include Certificate"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; @@ -329,17 +341,15 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, in_len); } if (type != TLS_HANDSHAKE_TYPE_CERTIFICATE) { - wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake " - "message %d (expected Certificate/" - "ClientKeyExchange)", type); + tlsv1_server_log(conn, "Received unexpected handshake message %d (expected Certificate/ClientKeyExchange)", + type); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; } - wpa_printf(MSG_DEBUG, - "TLSv1: Received Certificate (certificate_list len %lu)", - (unsigned long) len); + tlsv1_server_log(conn, "Received Certificate (certificate_list len %lu)", + (unsigned long) len); /* * opaque ASN.1Cert<2^24-1>; @@ -352,8 +362,8 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, end = pos + len; if (end - pos < 3) { - wpa_printf(MSG_DEBUG, "TLSv1: Too short Certificate " - "(left=%lu)", (unsigned long) left); + tlsv1_server_log(conn, "Too short Certificate (left=%lu)", + (unsigned long) left); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -363,10 +373,9 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, pos += 3; if ((size_t) (end - pos) != list_len) { - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected certificate_list " - "length (len=%lu left=%lu)", - (unsigned long) list_len, - (unsigned long) (end - pos)); + tlsv1_server_log(conn, "Unexpected certificate_list length (len=%lu left=%lu)", + (unsigned long) list_len, + (unsigned long) (end - pos)); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -375,8 +384,7 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, idx = 0; while (pos < end) { if (end - pos < 3) { - wpa_printf(MSG_DEBUG, "TLSv1: Failed to parse " - "certificate_list"); + tlsv1_server_log(conn, "Failed to parse certificate_list"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); x509_certificate_chain_free(chain); @@ -387,25 +395,23 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, pos += 3; if ((size_t) (end - pos) < cert_len) { - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected certificate " - "length (len=%lu left=%lu)", - (unsigned long) cert_len, - (unsigned long) (end - pos)); + tlsv1_server_log(conn, "Unexpected certificate length (len=%lu left=%lu)", + (unsigned long) cert_len, + (unsigned long) (end - pos)); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); x509_certificate_chain_free(chain); return -1; } - wpa_printf(MSG_DEBUG, "TLSv1: Certificate %lu (len %lu)", - (unsigned long) idx, (unsigned long) cert_len); + tlsv1_server_log(conn, "Certificate %lu (len %lu)", + (unsigned long) idx, (unsigned long) cert_len); if (idx == 0) { crypto_public_key_free(conn->client_rsa_key); if (tls_parse_cert(pos, cert_len, &conn->client_rsa_key)) { - wpa_printf(MSG_DEBUG, "TLSv1: Failed to parse " - "the certificate"); + tlsv1_server_log(conn, "Failed to parse the certificate"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_BAD_CERTIFICATE); x509_certificate_chain_free(chain); @@ -415,8 +421,7 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, cert = x509_certificate_parse(pos, cert_len); if (cert == NULL) { - wpa_printf(MSG_DEBUG, "TLSv1: Failed to parse " - "the certificate"); + tlsv1_server_log(conn, "Failed to parse the certificate"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_BAD_CERTIFICATE); x509_certificate_chain_free(chain); @@ -436,8 +441,8 @@ static int tls_process_certificate(struct tlsv1_server *conn, u8 ct, if (x509_certificate_chain_validate(conn->cred->trusted_certs, chain, &reason, 0) < 0) { int tls_reason; - wpa_printf(MSG_DEBUG, "TLSv1: Server certificate chain " - "validation failed (reason=%d)", reason); + tlsv1_server_log(conn, "Server certificate chain validation failed (reason=%d)", + reason); switch (reason) { case X509_VALIDATE_BAD_CERTIFICATE: tls_reason = TLS_ALERT_BAD_CERTIFICATE; @@ -494,9 +499,8 @@ static int tls_process_client_key_exchange_rsa( encr_len = WPA_GET_BE16(pos); pos += 2; if (pos + encr_len > end) { - wpa_printf(MSG_DEBUG, "TLSv1: Invalid ClientKeyExchange " - "format: encr_len=%u left=%u", - encr_len, (unsigned int) (end - pos)); + tlsv1_server_log(conn, "Invalid ClientKeyExchange format: encr_len=%u left=%u", + encr_len, (unsigned int) (end - pos)); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -539,15 +543,13 @@ static int tls_process_client_key_exchange_rsa( } if (!use_random && outlen != TLS_PRE_MASTER_SECRET_LEN) { - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected PreMasterSecret " - "length %lu", (unsigned long) outlen); + tlsv1_server_log(conn, "Unexpected PreMasterSecret length %lu", + (unsigned long) outlen); use_random = 1; } if (!use_random && WPA_GET_BE16(out) != conn->client_version) { - wpa_printf(MSG_DEBUG, "TLSv1: Client version in " - "ClientKeyExchange does not match with version in " - "ClientHello"); + tlsv1_server_log(conn, "Client version in ClientKeyExchange does not match with version in ClientHello"); use_random = 1; } @@ -582,7 +584,7 @@ static int tls_process_client_key_exchange_rsa( } -static int tls_process_client_key_exchange_dh_anon( +static int tls_process_client_key_exchange_dh( struct tlsv1_server *conn, const u8 *pos, const u8 *end) { const u8 *dh_yc; @@ -590,6 +592,8 @@ static int tls_process_client_key_exchange_dh_anon( u8 *shared; size_t shared_len; int res; + const u8 *dh_p; + size_t dh_p_len; /* * struct { @@ -600,6 +604,7 @@ static int tls_process_client_key_exchange_dh_anon( * } ClientDiffieHellmanPublic; */ + tlsv1_server_log(conn, "ClientDiffieHellmanPublic received"); wpa_hexdump(MSG_MSGDUMP, "TLSv1: ClientDiffieHellmanPublic", pos, end - pos); @@ -612,8 +617,7 @@ static int tls_process_client_key_exchange_dh_anon( } if (end - pos < 3) { - wpa_printf(MSG_DEBUG, "TLSv1: Invalid client public value " - "length"); + tlsv1_server_log(conn, "Invalid client public value length"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -623,8 +627,8 @@ static int tls_process_client_key_exchange_dh_anon( dh_yc = pos + 2; if (dh_yc + dh_yc_len > end) { - wpa_printf(MSG_DEBUG, "TLSv1: Client public value overflow " - "(length %d)", dh_yc_len); + tlsv1_server_log(conn, "Client public value overflow (length %d)", + dh_yc_len); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -641,7 +645,9 @@ static int tls_process_client_key_exchange_dh_anon( return -1; } - shared_len = conn->cred->dh_p_len; + tlsv1_server_get_dh_p(conn, &dh_p, &dh_p_len); + + shared_len = dh_p_len; shared = os_malloc(shared_len); if (shared == NULL) { wpa_printf(MSG_DEBUG, "TLSv1: Could not allocate memory for " @@ -653,8 +659,7 @@ static int tls_process_client_key_exchange_dh_anon( /* shared = Yc^secret mod p */ if (crypto_mod_exp(dh_yc, dh_yc_len, conn->dh_secret, - conn->dh_secret_len, - conn->cred->dh_p, conn->cred->dh_p_len, + conn->dh_secret_len, dh_p, dh_p_len, shared, &shared_len)) { os_free(shared); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, @@ -695,8 +700,8 @@ static int tls_process_client_key_exchange(struct tlsv1_server *conn, u8 ct, const struct tls_cipher_suite *suite; if (ct != TLS_CONTENT_TYPE_HANDSHAKE) { - wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; " - "received content type 0x%x", ct); + tlsv1_server_log(conn, "Expected Handshake; received content type 0x%x", + ct); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; @@ -706,8 +711,8 @@ static int tls_process_client_key_exchange(struct tlsv1_server *conn, u8 ct, left = *in_len; if (left < 4) { - wpa_printf(MSG_DEBUG, "TLSv1: Too short ClientKeyExchange " - "(Left=%lu)", (unsigned long) left); + tlsv1_server_log(conn, "Too short ClientKeyExchange (Left=%lu)", + (unsigned long) left); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -719,9 +724,8 @@ static int tls_process_client_key_exchange(struct tlsv1_server *conn, u8 ct, left -= 4; if (len > left) { - wpa_printf(MSG_DEBUG, "TLSv1: Mismatch in ClientKeyExchange " - "length (len=%lu != left=%lu)", - (unsigned long) len, (unsigned long) left); + tlsv1_server_log(conn, "Mismatch in ClientKeyExchange length (len=%lu != left=%lu)", + (unsigned long) len, (unsigned long) left); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -730,14 +734,14 @@ static int tls_process_client_key_exchange(struct tlsv1_server *conn, u8 ct, end = pos + len; if (type != TLS_HANDSHAKE_TYPE_CLIENT_KEY_EXCHANGE) { - wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake " - "message %d (expected ClientKeyExchange)", type); + tlsv1_server_log(conn, "Received unexpected handshake message %d (expected ClientKeyExchange)", + type); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; } - wpa_printf(MSG_DEBUG, "TLSv1: Received ClientKeyExchange"); + tlsv1_server_log(conn, "Received ClientKeyExchange"); wpa_hexdump(MSG_DEBUG, "TLSv1: ClientKeyExchange", pos, len); @@ -747,11 +751,11 @@ static int tls_process_client_key_exchange(struct tlsv1_server *conn, u8 ct, else keyx = suite->key_exchange; - if (keyx == TLS_KEY_X_DH_anon && - tls_process_client_key_exchange_dh_anon(conn, pos, end) < 0) + if ((keyx == TLS_KEY_X_DH_anon || keyx == TLS_KEY_X_DHE_RSA) && + tls_process_client_key_exchange_dh(conn, pos, end) < 0) return -1; - if (keyx != TLS_KEY_X_DH_anon && + if (keyx != TLS_KEY_X_DH_anon && keyx != TLS_KEY_X_DHE_RSA && tls_process_client_key_exchange_rsa(conn, pos, end) < 0) return -1; @@ -769,15 +773,14 @@ static int tls_process_certificate_verify(struct tlsv1_server *conn, u8 ct, const u8 *pos, *end; size_t left, len; u8 type; - size_t hlen, buflen; - u8 hash[MD5_MAC_LEN + SHA1_MAC_LEN], *hpos, *buf; + size_t hlen; + u8 hash[MD5_MAC_LEN + SHA1_MAC_LEN], *hpos; enum { SIGN_ALG_RSA, SIGN_ALG_DSA } alg = SIGN_ALG_RSA; - u16 slen; + u8 alert; if (ct == TLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC) { if (conn->verify_peer) { - wpa_printf(MSG_DEBUG, "TLSv1: Client did not include " - "CertificateVerify"); + tlsv1_server_log(conn, "Client did not include CertificateVerify"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; @@ -788,8 +791,8 @@ static int tls_process_certificate_verify(struct tlsv1_server *conn, u8 ct, } if (ct != TLS_CONTENT_TYPE_HANDSHAKE) { - wpa_printf(MSG_DEBUG, "TLSv1: Expected Handshake; " - "received content type 0x%x", ct); + tlsv1_server_log(conn, "Expected Handshake; received content type 0x%x", + ct); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; @@ -799,8 +802,8 @@ static int tls_process_certificate_verify(struct tlsv1_server *conn, u8 ct, left = *in_len; if (left < 4) { - wpa_printf(MSG_DEBUG, "TLSv1: Too short CertificateVerify " - "message (len=%lu)", (unsigned long) left); + tlsv1_server_log(conn, "Too short CertificateVerify message (len=%lu)", + (unsigned long) left); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -812,9 +815,8 @@ static int tls_process_certificate_verify(struct tlsv1_server *conn, u8 ct, left -= 4; if (len > left) { - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected CertificateVerify " - "message length (len=%lu != left=%lu)", - (unsigned long) len, (unsigned long) left); + tlsv1_server_log(conn, "Unexpected CertificateVerify message length (len=%lu != left=%lu)", + (unsigned long) len, (unsigned long) left); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -823,14 +825,14 @@ static int tls_process_certificate_verify(struct tlsv1_server *conn, u8 ct, end = pos + len; if (type != TLS_HANDSHAKE_TYPE_CERTIFICATE_VERIFY) { - wpa_printf(MSG_DEBUG, "TLSv1: Received unexpected handshake " - "message %d (expected CertificateVerify)", type); + tlsv1_server_log(conn, "Received unexpected handshake message %d (expected CertificateVerify)", + type); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; } - wpa_printf(MSG_DEBUG, "TLSv1: Received CertificateVerify"); + tlsv1_server_log(conn, "Received CertificateVerify"); /* * struct { @@ -917,90 +919,13 @@ static int tls_process_certificate_verify(struct tlsv1_server *conn, u8 ct, wpa_hexdump(MSG_MSGDUMP, "TLSv1: CertificateVerify hash", hash, hlen); - if (end - pos < 2) { - tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, - TLS_ALERT_DECODE_ERROR); - return -1; - } - slen = WPA_GET_BE16(pos); - pos += 2; - if (end - pos < slen) { - tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, - TLS_ALERT_DECODE_ERROR); - return -1; - } - - wpa_hexdump(MSG_MSGDUMP, "TLSv1: Signature", pos, end - pos); - if (conn->client_rsa_key == NULL) { - wpa_printf(MSG_DEBUG, "TLSv1: No client public key to verify " - "signature"); - tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, - TLS_ALERT_INTERNAL_ERROR); - return -1; - } - - buflen = end - pos; - buf = os_malloc(end - pos); - if (crypto_public_key_decrypt_pkcs1(conn->client_rsa_key, - pos, end - pos, buf, &buflen) < 0) - { - wpa_printf(MSG_DEBUG, "TLSv1: Failed to decrypt signature"); - os_free(buf); - tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, - TLS_ALERT_DECRYPT_ERROR); - return -1; - } - - wpa_hexdump_key(MSG_MSGDUMP, "TLSv1: Decrypted Signature", - buf, buflen); - -#ifdef CONFIG_TLSV12 - if (conn->rl.tls_version >= TLS_VERSION_1_2) { - /* - * RFC 3447, A.2.4 RSASSA-PKCS1-v1_5 - * - * DigestInfo ::= SEQUENCE { - * digestAlgorithm DigestAlgorithm, - * digest OCTET STRING - * } - * - * SHA-256 OID: sha256WithRSAEncryption ::= {pkcs-1 11} - * - * DER encoded DigestInfo for SHA256 per RFC 3447: - * 30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20 || - * H - */ - if (buflen >= 19 + 32 && - os_memcmp(buf, "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01" - "\x65\x03\x04\x02\x01\x05\x00\x04\x20", 19) == 0) - { - wpa_printf(MSG_DEBUG, "TLSv1.2: DigestAlgorithn = " - "SHA-256"); - os_memmove(buf, buf + 19, buflen - 19); - buflen -= 19; - } else { - wpa_printf(MSG_DEBUG, "TLSv1.2: Unrecognized " - "DigestInfo"); - os_free(buf); - tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, - TLS_ALERT_DECRYPT_ERROR); - return -1; - } - } -#endif /* CONFIG_TLSV12 */ - - if (buflen != hlen || os_memcmp(buf, hash, buflen) != 0) { - wpa_printf(MSG_DEBUG, "TLSv1: Invalid Signature in " - "CertificateVerify - did not match with calculated " - "hash"); - os_free(buf); - tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, - TLS_ALERT_DECRYPT_ERROR); + if (tls_verify_signature(conn->rl.tls_version, conn->client_rsa_key, + hash, hlen, pos, end - pos, &alert) < 0) { + tlsv1_server_log(conn, "Invalid Signature in CertificateVerify"); + tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, alert); return -1; } - os_free(buf); - *in_len = end - in_data; conn->state = CHANGE_CIPHER_SPEC; @@ -1017,8 +942,8 @@ static int tls_process_change_cipher_spec(struct tlsv1_server *conn, size_t left; if (ct != TLS_CONTENT_TYPE_CHANGE_CIPHER_SPEC) { - wpa_printf(MSG_DEBUG, "TLSv1: Expected ChangeCipherSpec; " - "received content type 0x%x", ct); + tlsv1_server_log(conn, "Expected ChangeCipherSpec; received content type 0x%x", + ct); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; @@ -1028,21 +953,21 @@ static int tls_process_change_cipher_spec(struct tlsv1_server *conn, left = *in_len; if (left < 1) { - wpa_printf(MSG_DEBUG, "TLSv1: Too short ChangeCipherSpec"); + tlsv1_server_log(conn, "Too short ChangeCipherSpec"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; } if (*pos != TLS_CHANGE_CIPHER_SPEC) { - wpa_printf(MSG_DEBUG, "TLSv1: Expected ChangeCipherSpec; " - "received data 0x%x", *pos); + tlsv1_server_log(conn, "Expected ChangeCipherSpec; received data 0x%x", + *pos); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; } - wpa_printf(MSG_DEBUG, "TLSv1: Received ChangeCipherSpec"); + tlsv1_server_log(conn, "Received ChangeCipherSpec"); if (tlsv1_record_change_read_cipher(&conn->rl) < 0) { wpa_printf(MSG_DEBUG, "TLSv1: Failed to change read cipher " "for record layer"); @@ -1067,9 +992,48 @@ static int tls_process_client_finished(struct tlsv1_server *conn, u8 ct, u8 verify_data[TLS_VERIFY_DATA_LEN]; u8 hash[MD5_MAC_LEN + SHA1_MAC_LEN]; +#ifdef CONFIG_TESTING_OPTIONS + if ((conn->test_flags & + (TLS_BREAK_SRV_KEY_X_HASH | TLS_BREAK_SRV_KEY_X_SIGNATURE)) && + !conn->test_failure_reported) { + tlsv1_server_log(conn, "TEST-FAILURE: Client Finished received after invalid ServerKeyExchange"); + conn->test_failure_reported = 1; + } + + if ((conn->test_flags & TLS_DHE_PRIME_15) && + !conn->test_failure_reported) { + tlsv1_server_log(conn, "TEST-FAILURE: Client Finished received after bogus DHE \"prime\" 15"); + conn->test_failure_reported = 1; + } + + if ((conn->test_flags & TLS_DHE_PRIME_58B) && + !conn->test_failure_reported) { + tlsv1_server_log(conn, "TEST-FAILURE: Client Finished received after short 58-bit DHE prime in long container"); + conn->test_failure_reported = 1; + } + + if ((conn->test_flags & TLS_DHE_PRIME_511B) && + !conn->test_failure_reported) { + tlsv1_server_log(conn, "TEST-WARNING: Client Finished received after short 511-bit DHE prime (insecure)"); + conn->test_failure_reported = 1; + } + + if ((conn->test_flags & TLS_DHE_PRIME_767B) && + !conn->test_failure_reported) { + tlsv1_server_log(conn, "TEST-NOTE: Client Finished received after 767-bit DHE prime (relatively insecure)"); + conn->test_failure_reported = 1; + } + + if ((conn->test_flags & TLS_DHE_NON_PRIME) && + !conn->test_failure_reported) { + tlsv1_server_log(conn, "TEST-NOTE: Client Finished received after non-prime claimed as DHE prime"); + conn->test_failure_reported = 1; + } +#endif /* CONFIG_TESTING_OPTIONS */ + if (ct != TLS_CONTENT_TYPE_HANDSHAKE) { - wpa_printf(MSG_DEBUG, "TLSv1: Expected Finished; " - "received content type 0x%x", ct); + tlsv1_server_log(conn, "Expected Finished; received content type 0x%x", + ct); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_UNEXPECTED_MESSAGE); return -1; @@ -1079,9 +1043,8 @@ static int tls_process_client_finished(struct tlsv1_server *conn, u8 ct, left = *in_len; if (left < 4) { - wpa_printf(MSG_DEBUG, "TLSv1: Too short record (left=%lu) for " - "Finished", - (unsigned long) left); + tlsv1_server_log(conn, "Too short record (left=%lu) forFinished", + (unsigned long) left); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -1101,18 +1064,16 @@ static int tls_process_client_finished(struct tlsv1_server *conn, u8 ct, left -= 4; if (len > left) { - wpa_printf(MSG_DEBUG, "TLSv1: Too short buffer for Finished " - "(len=%lu > left=%lu)", - (unsigned long) len, (unsigned long) left); + tlsv1_server_log(conn, "Too short buffer for Finished (len=%lu > left=%lu)", + (unsigned long) len, (unsigned long) left); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; } end = pos + len; if (len != TLS_VERIFY_DATA_LEN) { - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected verify_data length " - "in Finished: %lu (expected %d)", - (unsigned long) len, TLS_VERIFY_DATA_LEN); + tlsv1_server_log(conn, "Unexpected verify_data length in Finished: %lu (expected %d)", + (unsigned long) len, TLS_VERIFY_DATA_LEN); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; @@ -1175,18 +1136,17 @@ static int tls_process_client_finished(struct tlsv1_server *conn, u8 ct, verify_data, TLS_VERIFY_DATA_LEN); if (os_memcmp(pos, verify_data, TLS_VERIFY_DATA_LEN) != 0) { - wpa_printf(MSG_INFO, "TLSv1: Mismatch in verify_data"); + tlsv1_server_log(conn, "Mismatch in verify_data"); return -1; } - wpa_printf(MSG_DEBUG, "TLSv1: Received Finished"); + tlsv1_server_log(conn, "Received Finished"); *in_len = end - in_data; if (conn->use_session_ticket) { /* Abbreviated handshake using session ticket; RFC 4507 */ - wpa_printf(MSG_DEBUG, "TLSv1: Abbreviated handshake completed " - "successfully"); + tlsv1_server_log(conn, "Abbreviated handshake completed successfully"); conn->state = ESTABLISHED; } else { /* Full handshake */ @@ -1202,13 +1162,12 @@ int tlsv1_server_process_handshake(struct tlsv1_server *conn, u8 ct, { if (ct == TLS_CONTENT_TYPE_ALERT) { if (*len < 2) { - wpa_printf(MSG_DEBUG, "TLSv1: Alert underflow"); + tlsv1_server_log(conn, "Alert underflow"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DECODE_ERROR); return -1; } - wpa_printf(MSG_DEBUG, "TLSv1: Received alert %d:%d", - buf[0], buf[1]); + tlsv1_server_log(conn, "Received alert %d:%d", buf[0], buf[1]); *len = 2; conn->state = FAILED; return -1; @@ -1240,9 +1199,8 @@ int tlsv1_server_process_handshake(struct tlsv1_server *conn, u8 ct, return -1; break; default: - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected state %d " - "while processing received message", - conn->state); + tlsv1_server_log(conn, "Unexpected state %d while processing received message", + conn->state); return -1; } diff --git a/src/tls/tlsv1_server_write.c b/src/tls/tlsv1_server_write.c index 6d8e55e..15e6692 100644 --- a/src/tls/tlsv1_server_write.c +++ b/src/tls/tlsv1_server_write.c @@ -1,6 +1,6 @@ /* * TLSv1 server - write handshake message - * Copyright (c) 2006-2011, Jouni Malinen <j@w1.fi> + * Copyright (c) 2006-2014, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -48,7 +48,7 @@ static int tls_write_server_hello(struct tlsv1_server *conn, pos = *msgpos; - wpa_printf(MSG_DEBUG, "TLSv1: Send ServerHello"); + tlsv1_server_log(conn, "Send ServerHello"); rhdr = pos; pos += TLS_RECORD_HEADER_LEN; @@ -104,8 +104,7 @@ static int tls_write_server_hello(struct tlsv1_server *conn, conn->client_random, conn->server_random, conn->master_secret); if (res < 0) { - wpa_printf(MSG_DEBUG, "TLSv1: SessionTicket callback " - "indicated failure"); + tlsv1_server_log(conn, "SessionTicket callback indicated failure"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_HANDSHAKE_FAILURE); return -1; @@ -170,7 +169,7 @@ static int tls_write_server_certificate(struct tlsv1_server *conn, pos = *msgpos; - wpa_printf(MSG_DEBUG, "TLSv1: Send Certificate"); + tlsv1_server_log(conn, "Send Certificate"); rhdr = pos; pos += TLS_RECORD_HEADER_LEN; @@ -245,10 +244,12 @@ static int tls_write_server_key_exchange(struct tlsv1_server *conn, { tls_key_exchange keyx; const struct tls_cipher_suite *suite; - u8 *pos, *rhdr, *hs_start, *hs_length; + u8 *pos, *rhdr, *hs_start, *hs_length, *server_params; size_t rlen; u8 *dh_ys; size_t dh_ys_len; + const u8 *dh_p; + size_t dh_p_len; suite = tls_get_cipher_suite(conn->rl.cipher_suite); if (suite == NULL) @@ -261,8 +262,7 @@ static int tls_write_server_key_exchange(struct tlsv1_server *conn, return 0; } - if (keyx != TLS_KEY_X_DH_anon) { - /* TODO? */ + if (keyx != TLS_KEY_X_DH_anon && keyx != TLS_KEY_X_DHE_RSA) { wpa_printf(MSG_DEBUG, "TLSv1: ServerKeyExchange not yet " "supported with key exchange type %d", keyx); return -1; @@ -275,8 +275,10 @@ static int tls_write_server_key_exchange(struct tlsv1_server *conn, return -1; } + tlsv1_server_get_dh_p(conn, &dh_p, &dh_p_len); + os_free(conn->dh_secret); - conn->dh_secret_len = conn->cred->dh_p_len; + conn->dh_secret_len = dh_p_len; conn->dh_secret = os_malloc(conn->dh_secret_len); if (conn->dh_secret == NULL) { wpa_printf(MSG_DEBUG, "TLSv1: Failed to allocate " @@ -295,8 +297,7 @@ static int tls_write_server_key_exchange(struct tlsv1_server *conn, return -1; } - if (os_memcmp(conn->dh_secret, conn->cred->dh_p, conn->dh_secret_len) > - 0) + if (os_memcmp(conn->dh_secret, dh_p, conn->dh_secret_len) > 0) conn->dh_secret[0] = 0; /* make sure secret < p */ pos = conn->dh_secret; @@ -311,7 +312,7 @@ static int tls_write_server_key_exchange(struct tlsv1_server *conn, conn->dh_secret, conn->dh_secret_len); /* Ys = g^secret mod p */ - dh_ys_len = conn->cred->dh_p_len; + dh_ys_len = dh_p_len; dh_ys = os_malloc(dh_ys_len); if (dh_ys == NULL) { wpa_printf(MSG_DEBUG, "TLSv1: Failed to allocate memory for " @@ -322,8 +323,7 @@ static int tls_write_server_key_exchange(struct tlsv1_server *conn, } if (crypto_mod_exp(conn->cred->dh_g, conn->cred->dh_g_len, conn->dh_secret, conn->dh_secret_len, - conn->cred->dh_p, conn->cred->dh_p_len, - dh_ys, &dh_ys_len)) { + dh_p, dh_p_len, dh_ys, &dh_ys_len)) { tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_INTERNAL_ERROR); os_free(dh_ys); @@ -354,7 +354,7 @@ static int tls_write_server_key_exchange(struct tlsv1_server *conn, pos = *msgpos; - wpa_printf(MSG_DEBUG, "TLSv1: Send ServerKeyExchange"); + tlsv1_server_log(conn, "Send ServerKeyExchange"); rhdr = pos; pos += TLS_RECORD_HEADER_LEN; @@ -369,8 +369,9 @@ static int tls_write_server_key_exchange(struct tlsv1_server *conn, pos += 3; /* body - ServerDHParams */ + server_params = pos; /* dh_p */ - if (pos + 2 + conn->cred->dh_p_len > end) { + if (pos + 2 + dh_p_len > end) { wpa_printf(MSG_DEBUG, "TLSv1: Not enough buffer space for " "dh_p"); tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, @@ -378,10 +379,10 @@ static int tls_write_server_key_exchange(struct tlsv1_server *conn, os_free(dh_ys); return -1; } - WPA_PUT_BE16(pos, conn->cred->dh_p_len); + WPA_PUT_BE16(pos, dh_p_len); pos += 2; - os_memcpy(pos, conn->cred->dh_p, conn->cred->dh_p_len); - pos += conn->cred->dh_p_len; + os_memcpy(pos, dh_p, dh_p_len); + pos += dh_p_len; /* dh_g */ if (pos + 2 + conn->cred->dh_g_len > end) { @@ -412,6 +413,138 @@ static int tls_write_server_key_exchange(struct tlsv1_server *conn, pos += dh_ys_len; os_free(dh_ys); + /* + * select (SignatureAlgorithm) + * { case anonymous: struct { }; + * case rsa: + * digitally-signed struct { + * opaque md5_hash[16]; + * opaque sha_hash[20]; + * }; + * case dsa: + * digitally-signed struct { + * opaque sha_hash[20]; + * }; + * } Signature; + * + * md5_hash + * MD5(ClientHello.random + ServerHello.random + ServerParams); + * + * sha_hash + * SHA(ClientHello.random + ServerHello.random + ServerParams); + */ + + if (keyx == TLS_KEY_X_DHE_RSA) { + u8 hash[100]; + u8 *signed_start; + size_t clen; + int hlen; + + if (conn->rl.tls_version >= TLS_VERSION_1_2) { +#ifdef CONFIG_TLSV12 + hlen = tlsv12_key_x_server_params_hash( + conn->rl.tls_version, conn->client_random, + conn->server_random, server_params, + pos - server_params, hash + 19); + + /* + * RFC 5246, 4.7: + * TLS v1.2 adds explicit indication of the used + * signature and hash algorithms. + * + * struct { + * HashAlgorithm hash; + * SignatureAlgorithm signature; + * } SignatureAndHashAlgorithm; + */ + if (hlen < 0 || pos + 2 > end) { + tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, + TLS_ALERT_INTERNAL_ERROR); + return -1; + } + *pos++ = TLS_HASH_ALG_SHA256; + *pos++ = TLS_SIGN_ALG_RSA; + + /* + * RFC 3447, A.2.4 RSASSA-PKCS1-v1_5 + * + * DigestInfo ::= SEQUENCE { + * digestAlgorithm DigestAlgorithm, + * digest OCTET STRING + * } + * + * SHA-256 OID: sha256WithRSAEncryption ::= {pkcs-1 11} + * + * DER encoded DigestInfo for SHA256 per RFC 3447: + * 30 31 30 0d 06 09 60 86 48 01 65 03 04 02 01 05 00 + * 04 20 || H + */ + hlen += 19; + os_memcpy(hash, + "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65" + "\x03\x04\x02\x01\x05\x00\x04\x20", 19); + +#else /* CONFIG_TLSV12 */ + tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, + TLS_ALERT_INTERNAL_ERROR); + return -1; +#endif /* CONFIG_TLSV12 */ + } else { + hlen = tls_key_x_server_params_hash( + conn->rl.tls_version, conn->client_random, + conn->server_random, server_params, + pos - server_params, hash); + } + + if (hlen < 0) { + tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, + TLS_ALERT_INTERNAL_ERROR); + return -1; + } + + wpa_hexdump(MSG_MSGDUMP, "TLS: ServerKeyExchange signed_params hash", + hash, hlen); +#ifdef CONFIG_TESTING_OPTIONS + if (conn->test_flags & TLS_BREAK_SRV_KEY_X_HASH) { + tlsv1_server_log(conn, "TESTING: Break ServerKeyExchange signed params hash"); + hash[hlen - 1] ^= 0x80; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + /* + * RFC 2246, 4.7: + * In digital signing, one-way hash functions are used as input + * for a signing algorithm. A digitally-signed element is + * encoded as an opaque vector <0..2^16-1>, where the length is + * specified by the signing algorithm and key. + * + * In RSA signing, a 36-byte structure of two hashes (one SHA + * and one MD5) is signed (encrypted with the private key). It + * is encoded with PKCS #1 block type 0 or type 1 as described + * in [PKCS1]. + */ + signed_start = pos; /* length to be filled */ + pos += 2; + clen = end - pos; + if (conn->cred == NULL || + crypto_private_key_sign_pkcs1(conn->cred->key, hash, hlen, + pos, &clen) < 0) { + wpa_printf(MSG_DEBUG, "TLSv1: Failed to sign hash (PKCS #1)"); + tlsv1_server_alert(conn, TLS_ALERT_LEVEL_FATAL, + TLS_ALERT_INTERNAL_ERROR); + return -1; + } + WPA_PUT_BE16(signed_start, clen); +#ifdef CONFIG_TESTING_OPTIONS + if (conn->test_flags & TLS_BREAK_SRV_KEY_X_SIGNATURE) { + tlsv1_server_log(conn, "TESTING: Break ServerKeyExchange signed params signature"); + pos[clen - 1] ^= 0x80; + } +#endif /* CONFIG_TESTING_OPTIONS */ + + pos += clen; + } + WPA_PUT_BE24(hs_length, pos - hs_length - 3); if (tlsv1_record_send(&conn->rl, TLS_CONTENT_TYPE_HANDSHAKE, @@ -445,7 +578,7 @@ static int tls_write_server_certificate_request(struct tlsv1_server *conn, pos = *msgpos; - wpa_printf(MSG_DEBUG, "TLSv1: Send CertificateRequest"); + tlsv1_server_log(conn, "Send CertificateRequest"); rhdr = pos; pos += TLS_RECORD_HEADER_LEN; @@ -505,7 +638,7 @@ static int tls_write_server_hello_done(struct tlsv1_server *conn, size_t rlen; u8 payload[4]; - wpa_printf(MSG_DEBUG, "TLSv1: Send ServerHelloDone"); + tlsv1_server_log(conn, "Send ServerHelloDone"); /* opaque fragment[TLSPlaintext.length] */ @@ -541,7 +674,7 @@ static int tls_write_server_change_cipher_spec(struct tlsv1_server *conn, size_t rlen; u8 payload[1]; - wpa_printf(MSG_DEBUG, "TLSv1: Send ChangeCipherSpec"); + tlsv1_server_log(conn, "Send ChangeCipherSpec"); payload[0] = TLS_CHANGE_CIPHER_SPEC; @@ -578,7 +711,7 @@ static int tls_write_server_finished(struct tlsv1_server *conn, pos = *msgpos; - wpa_printf(MSG_DEBUG, "TLSv1: Send Finished"); + tlsv1_server_log(conn, "Send Finished"); /* Encrypted Handshake Message: Finished */ @@ -635,6 +768,12 @@ static int tls_write_server_finished(struct tlsv1_server *conn, } wpa_hexdump_key(MSG_DEBUG, "TLSv1: verify_data (server)", verify_data + 1 + 3, TLS_VERIFY_DATA_LEN); +#ifdef CONFIG_TESTING_OPTIONS + if (conn->test_flags & TLS_BREAK_VERIFY_DATA) { + tlsv1_server_log(conn, "TESTING: Break verify_data (server)"); + verify_data[1 + 3 + 1] ^= 0x80; + } +#endif /* CONFIG_TESTING_OPTIONS */ /* Handshake */ pos = hs_start = verify_data; @@ -736,7 +875,7 @@ static u8 * tls_send_change_cipher_spec(struct tlsv1_server *conn, *out_len = pos - msg; - wpa_printf(MSG_DEBUG, "TLSv1: Handshake completed successfully"); + tlsv1_server_log(conn, "Handshake completed successfully"); conn->state = ESTABLISHED; return msg; @@ -755,8 +894,8 @@ u8 * tlsv1_server_handshake_write(struct tlsv1_server *conn, size_t *out_len) /* Abbreviated handshake was already completed. */ return NULL; } - wpa_printf(MSG_DEBUG, "TLSv1: Unexpected state %d while " - "generating reply", conn->state); + tlsv1_server_log(conn, "Unexpected state %d while generating reply", + conn->state); return NULL; } } @@ -767,7 +906,7 @@ u8 * tlsv1_server_send_alert(struct tlsv1_server *conn, u8 level, { u8 *alert, *pos, *length; - wpa_printf(MSG_DEBUG, "TLSv1: Send Alert(%d:%d)", level, description); + tlsv1_server_log(conn, "Send Alert(%d:%d)", level, description); *out_len = 0; alert = os_malloc(10); diff --git a/src/tls/x509v3.c b/src/tls/x509v3.c index 06540bf..751a268 100644 --- a/src/tls/x509v3.c +++ b/src/tls/x509v3.c @@ -1348,7 +1348,8 @@ static int x509_parse_tbs_certificate(const u8 *buf, size_t len, wpa_printf(MSG_DEBUG, "X509: issuerUniqueID"); /* TODO: parse UniqueIdentifier ::= BIT STRING */ - if (hdr.payload + hdr.length == end) + pos = hdr.payload + hdr.length; + if (pos == end) return 0; if (asn1_get_next(pos, end - pos, &hdr) < 0 || @@ -1366,7 +1367,8 @@ static int x509_parse_tbs_certificate(const u8 *buf, size_t len, wpa_printf(MSG_DEBUG, "X509: subjectUniqueID"); /* TODO: parse UniqueIdentifier ::= BIT STRING */ - if (hdr.payload + hdr.length == end) + pos = hdr.payload + hdr.length; + if (pos == end) return 0; if (asn1_get_next(pos, end - pos, &hdr) < 0 || @@ -1781,6 +1783,15 @@ skip_digest_oid: return -1; } + if (hdr.payload + hdr.length < data + data_len) { + wpa_hexdump(MSG_INFO, + "X509: Extra data after certificate signature hash", + hdr.payload + hdr.length, + data + data_len - hdr.payload - hdr.length); + os_free(data); + return -1; + } + os_free(data); wpa_printf(MSG_DEBUG, "X509: Certificate Digest matches with " diff --git a/src/utils/browser-android.c b/src/utils/browser-android.c new file mode 100644 index 0000000..a066392 --- /dev/null +++ b/src/utils/browser-android.c @@ -0,0 +1,117 @@ +/* + * Hotspot 2.0 client - Web browser using Android browser + * Copyright (c) 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 "utils/eloop.h" +#include "wps/http_server.h" +#include "browser.h" + + +struct browser_data { + int success; +}; + + +static void browser_timeout(void *eloop_data, void *user_ctx) +{ + wpa_printf(MSG_INFO, "Timeout on waiting browser interaction to " + "complete"); + eloop_terminate(); +} + + +static void http_req(void *ctx, struct http_request *req) +{ + struct browser_data *data = ctx; + struct wpabuf *resp; + const char *url; + int done = 0; + + url = http_request_get_uri(req); + wpa_printf(MSG_INFO, "Browser response received: %s", url); + + if (os_strcmp(url, "/") == 0) { + data->success = 1; + done = 1; + } else if (os_strncmp(url, "/osu/", 5) == 0) { + data->success = atoi(url + 5); + done = 1; + } + + resp = wpabuf_alloc(1); + if (resp == NULL) { + http_request_deinit(req); + if (done) + eloop_terminate(); + return; + } + + if (done) { + eloop_cancel_timeout(browser_timeout, NULL, NULL); + eloop_register_timeout(0, 500000, browser_timeout, &data, NULL); + } + + http_request_send_and_deinit(req, resp); +} + + +int hs20_web_browser(const char *url) +{ + char cmd[2000]; + int ret; + struct http_server *http; + struct in_addr addr; + struct browser_data data; + + wpa_printf(MSG_INFO, "Launching Android browser to %s", url); + + os_memset(&data, 0, sizeof(data)); + + ret = os_snprintf(cmd, sizeof(cmd), + "am start -a android.intent.action.VIEW -d '%s' " + "-n com.android.browser/.BrowserActivity", url); + if (ret < 0 || (size_t) ret >= sizeof(cmd)) { + wpa_printf(MSG_ERROR, "Too long URL"); + return -1; + } + + if (eloop_init() < 0) { + wpa_printf(MSG_ERROR, "eloop_init failed"); + return -1; + } + addr.s_addr = htonl((127 << 24) | 1); + http = http_server_init(&addr, 12345, http_req, &data); + if (http == NULL) { + wpa_printf(MSG_ERROR, "http_server_init failed"); + eloop_destroy(); + return -1; + } + + if (system(cmd) != 0) { + wpa_printf(MSG_INFO, "Failed to launch Android browser"); + eloop_cancel_timeout(browser_timeout, NULL, NULL); + http_server_deinit(http); + eloop_destroy(); + return -1; + } + + eloop_register_timeout(30, 0, browser_timeout, &data, NULL); + eloop_run(); + eloop_cancel_timeout(browser_timeout, &data, NULL); + http_server_deinit(http); + eloop_destroy(); + + wpa_printf(MSG_INFO, "Closing Android browser"); + if (system("input keyevent 3") != 0) { + wpa_printf(MSG_INFO, "Failed to inject keyevent"); + } + + return data.success; +} diff --git a/src/utils/browser-system.c b/src/utils/browser-system.c new file mode 100644 index 0000000..2884d34 --- /dev/null +++ b/src/utils/browser-system.c @@ -0,0 +1,112 @@ +/* + * Hotspot 2.0 client - Web browser using system browser + * Copyright (c) 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 "utils/eloop.h" +#include "wps/http_server.h" +#include "browser.h" + + +struct browser_data { + int success; +}; + + +static void browser_timeout(void *eloop_data, void *user_ctx) +{ + wpa_printf(MSG_INFO, "Timeout on waiting browser interaction to " + "complete"); + eloop_terminate(); +} + + +static void http_req(void *ctx, struct http_request *req) +{ + struct browser_data *data = ctx; + struct wpabuf *resp; + const char *url; + int done = 0; + + url = http_request_get_uri(req); + wpa_printf(MSG_INFO, "Browser response received: %s", url); + + if (os_strcmp(url, "/") == 0) { + data->success = 1; + done = 1; + } else if (os_strncmp(url, "/osu/", 5) == 0) { + data->success = atoi(url + 5); + done = 1; + } + + resp = wpabuf_alloc(1); + if (resp == NULL) { + http_request_deinit(req); + if (done) + eloop_terminate(); + return; + } + + if (done) { + eloop_cancel_timeout(browser_timeout, NULL, NULL); + eloop_register_timeout(0, 500000, browser_timeout, &data, NULL); + } + + http_request_send_and_deinit(req, resp); +} + + +int hs20_web_browser(const char *url) +{ + char cmd[2000]; + int ret; + struct http_server *http; + struct in_addr addr; + struct browser_data data; + + wpa_printf(MSG_INFO, "Launching Android browser to %s", url); + + os_memset(&data, 0, sizeof(data)); + + ret = os_snprintf(cmd, sizeof(cmd), "x-www-browser '%s' &", url); + if (ret < 0 || (size_t) ret >= sizeof(cmd)) { + wpa_printf(MSG_ERROR, "Too long URL"); + return -1; + } + + if (eloop_init() < 0) { + wpa_printf(MSG_ERROR, "eloop_init failed"); + return -1; + } + addr.s_addr = htonl((127 << 24) | 1); + http = http_server_init(&addr, 12345, http_req, &data); + if (http == NULL) { + wpa_printf(MSG_ERROR, "http_server_init failed"); + eloop_destroy(); + return -1; + } + + if (system(cmd) != 0) { + wpa_printf(MSG_INFO, "Failed to launch browser"); + eloop_cancel_timeout(browser_timeout, NULL, NULL); + http_server_deinit(http); + eloop_destroy(); + return -1; + } + + eloop_register_timeout(120, 0, browser_timeout, &data, NULL); + eloop_run(); + eloop_cancel_timeout(browser_timeout, &data, NULL); + http_server_deinit(http); + eloop_destroy(); + + /* TODO: Close browser */ + + return data.success; +} diff --git a/src/utils/browser-wpadebug.c b/src/utils/browser-wpadebug.c new file mode 100644 index 0000000..eeb8f65 --- /dev/null +++ b/src/utils/browser-wpadebug.c @@ -0,0 +1,123 @@ +/* + * Hotspot 2.0 client - Web browser using wpadebug on Android + * Copyright (c) 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 "utils/eloop.h" +#include "wps/http_server.h" +#include "browser.h" + + +struct browser_data { + int success; +}; + + +static void browser_timeout(void *eloop_data, void *user_ctx) +{ + wpa_printf(MSG_INFO, "Timeout on waiting browser interaction to " + "complete"); + eloop_terminate(); +} + + +static void http_req(void *ctx, struct http_request *req) +{ + struct browser_data *data = ctx; + struct wpabuf *resp; + const char *url; + int done = 0; + + url = http_request_get_uri(req); + wpa_printf(MSG_INFO, "Browser response received: %s", url); + + if (os_strcmp(url, "/") == 0) { + data->success = 1; + done = 1; + } else if (os_strncmp(url, "/osu/", 5) == 0) { + data->success = atoi(url + 5); + done = 1; + } + + resp = wpabuf_alloc(100); + if (resp == NULL) { + http_request_deinit(req); + if (done) + eloop_terminate(); + return; + } + wpabuf_put_str(resp, "User input completed"); + + if (done) { + eloop_cancel_timeout(browser_timeout, NULL, NULL); + eloop_register_timeout(0, 500000, browser_timeout, &data, NULL); + } + + http_request_send_and_deinit(req, resp); +} + + +int hs20_web_browser(const char *url) +{ + char cmd[2000]; + int ret; + struct http_server *http; + struct in_addr addr; + struct browser_data data; + + wpa_printf(MSG_INFO, "Launching wpadebug browser to %s", url); + + os_memset(&data, 0, sizeof(data)); + + ret = os_snprintf(cmd, sizeof(cmd), + "am start -a android.action.MAIN " + "-c android.intent.category.LAUNCHER " + "-n w1.fi.wpadebug/.WpaWebViewActivity " + "-e w1.fi.wpadebug.URL '%s'", url); + if (ret < 0 || (size_t) ret >= sizeof(cmd)) { + wpa_printf(MSG_ERROR, "Too long URL"); + return -1; + } + + if (eloop_init() < 0) { + wpa_printf(MSG_ERROR, "eloop_init failed"); + return -1; + } + addr.s_addr = htonl((127 << 24) | 1); + http = http_server_init(&addr, 12345, http_req, &data); + if (http == NULL) { + wpa_printf(MSG_ERROR, "http_server_init failed"); + eloop_destroy(); + return -1; + } + + if (system(cmd) != 0) { + wpa_printf(MSG_INFO, "Failed to launch wpadebug browser"); + eloop_cancel_timeout(browser_timeout, NULL, NULL); + http_server_deinit(http); + eloop_destroy(); + return -1; + } + + eloop_register_timeout(300, 0, browser_timeout, &data, NULL); + eloop_run(); + eloop_cancel_timeout(browser_timeout, &data, NULL); + http_server_deinit(http); + eloop_destroy(); + + wpa_printf(MSG_INFO, "Closing Android browser"); + if (system("am start -a android.action.MAIN " + "-c android.intent.category.LAUNCHER " + "-n w1.fi.wpadebug/.WpaWebViewActivity " + "-e w1.fi.wpadebug.URL FINISH") != 0) { + wpa_printf(MSG_INFO, "Failed to close wpadebug browser"); + } + + return data.success; +} diff --git a/src/utils/browser.c b/src/utils/browser.c new file mode 100644 index 0000000..9cf6152 --- /dev/null +++ b/src/utils/browser.c @@ -0,0 +1,219 @@ +/* + * Hotspot 2.0 client - Web browser using WebKit + * Copyright (c) 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 <webkit/webkit.h> + +#include "common.h" +#include "browser.h" + + +struct browser_context { + GtkWidget *win; + int success; + int progress; + char *hover_link; + char *title; +}; + +static void win_cb_destroy(GtkWidget *win, struct browser_context *ctx) +{ + wpa_printf(MSG_DEBUG, "BROWSER:%s", __func__); + gtk_main_quit(); +} + + +static void browser_update_title(struct browser_context *ctx) +{ + char buf[100]; + + if (ctx->hover_link) { + gtk_window_set_title(GTK_WINDOW(ctx->win), ctx->hover_link); + return; + } + + if (ctx->progress == 100) { + gtk_window_set_title(GTK_WINDOW(ctx->win), + ctx->title ? ctx->title : + "Hotspot 2.0 client"); + return; + } + + snprintf(buf, sizeof(buf), "[%d%%] %s", ctx->progress, + ctx->title ? ctx->title : "Hotspot 2.0 client"); + gtk_window_set_title(GTK_WINDOW(ctx->win), buf); +} + + +static void view_cb_notify_progress(WebKitWebView *view, GParamSpec *pspec, + struct browser_context *ctx) +{ + ctx->progress = 100 * webkit_web_view_get_progress(view); + wpa_printf(MSG_DEBUG, "BROWSER:%s progress=%d", __func__, + ctx->progress); + browser_update_title(ctx); +} + + +static void view_cb_notify_load_status(WebKitWebView *view, GParamSpec *pspec, + struct browser_context *ctx) +{ + int status = webkit_web_view_get_load_status(view); + wpa_printf(MSG_DEBUG, "BROWSER:%s load-status=%d uri=%s", + __func__, status, webkit_web_view_get_uri(view)); +} + + +static void view_cb_resource_request_starting(WebKitWebView *view, + WebKitWebFrame *frame, + WebKitWebResource *res, + WebKitNetworkRequest *req, + WebKitNetworkResponse *resp, + struct browser_context *ctx) +{ + const gchar *uri = webkit_network_request_get_uri(req); + wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri); + if (g_str_has_suffix(uri, "/favicon.ico")) + webkit_network_request_set_uri(req, "about:blank"); + if (g_str_has_prefix(uri, "osu://")) { + ctx->success = atoi(uri + 6); + gtk_main_quit(); + } + if (g_str_has_prefix(uri, "http://localhost:12345")) { + /* + * This is used as a special trigger to indicate that the + * user exchange has been completed. + */ + ctx->success = 1; + gtk_main_quit(); + } +} + + +static gboolean view_cb_mime_type_policy_decision( + WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *req, + gchar *mime, WebKitWebPolicyDecision *policy, + struct browser_context *ctx) +{ + wpa_printf(MSG_DEBUG, "BROWSER:%s mime=%s", __func__, mime); + + if (!webkit_web_view_can_show_mime_type(view, mime)) { + webkit_web_policy_decision_download(policy); + return TRUE; + } + + return FALSE; +} + + +static gboolean view_cb_download_requested(WebKitWebView *view, + WebKitDownload *dl, + struct browser_context *ctx) +{ + const gchar *uri; + uri = webkit_download_get_uri(dl); + wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri); + return FALSE; +} + + +static void view_cb_hovering_over_link(WebKitWebView *view, gchar *title, + gchar *uri, struct browser_context *ctx) +{ + wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s uri=%s", __func__, title, + uri); + os_free(ctx->hover_link); + if (uri) + ctx->hover_link = os_strdup(uri); + else + ctx->hover_link = NULL; + + browser_update_title(ctx); +} + + +static void view_cb_title_changed(WebKitWebView *view, WebKitWebFrame *frame, + const char *title, + struct browser_context *ctx) +{ + wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s", __func__, title); + os_free(ctx->title); + ctx->title = os_strdup(title); + browser_update_title(ctx); +} + + +int hs20_web_browser(const char *url) +{ + GtkWidget *scroll; + SoupSession *s; + WebKitWebView *view; + WebKitWebSettings *settings; + struct browser_context ctx; + + memset(&ctx, 0, sizeof(ctx)); + if (!gtk_init_check(NULL, NULL)) + return -1; + + s = webkit_get_default_session(); + g_object_set(G_OBJECT(s), "ssl-ca-file", + "/etc/ssl/certs/ca-certificates.crt", NULL); + g_object_set(G_OBJECT(s), "ssl-strict", FALSE, NULL); + + ctx.win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_wmclass(GTK_WINDOW(ctx.win), "Hotspot 2.0 client", + "Hotspot 2.0 client"); + gtk_window_set_default_size(GTK_WINDOW(ctx.win), 800, 600); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + + g_signal_connect(G_OBJECT(ctx.win), "destroy", + G_CALLBACK(win_cb_destroy), &ctx); + + view = WEBKIT_WEB_VIEW(webkit_web_view_new()); + g_signal_connect(G_OBJECT(view), "notify::progress", + G_CALLBACK(view_cb_notify_progress), &ctx); + g_signal_connect(G_OBJECT(view), "notify::load-status", + G_CALLBACK(view_cb_notify_load_status), &ctx); + g_signal_connect(G_OBJECT(view), "resource-request-starting", + G_CALLBACK(view_cb_resource_request_starting), &ctx); + g_signal_connect(G_OBJECT(view), "mime-type-policy-decision-requested", + G_CALLBACK(view_cb_mime_type_policy_decision), &ctx); + g_signal_connect(G_OBJECT(view), "download-requested", + G_CALLBACK(view_cb_download_requested), &ctx); + g_signal_connect(G_OBJECT(view), "hovering-over-link", + G_CALLBACK(view_cb_hovering_over_link), &ctx); + g_signal_connect(G_OBJECT(view), "title-changed", + G_CALLBACK(view_cb_title_changed), &ctx); + + gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(ctx.win), GTK_WIDGET(scroll)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); + gtk_widget_show_all(ctx.win); + + settings = webkit_web_view_get_settings(view); + g_object_set(G_OBJECT(settings), "user-agent", + "Mozilla/5.0 (X11; U; Unix; en-US) " + "AppleWebKit/537.15 (KHTML, like Gecko) " + "hs20-client/1.0", NULL); + g_object_set(G_OBJECT(settings), "auto-load-images", TRUE, NULL); + + webkit_web_view_load_uri(view, url); + + gtk_main(); + gtk_widget_destroy(ctx.win); + while (gtk_events_pending()) + gtk_main_iteration(); + + free(ctx.hover_link); + free(ctx.title); + return ctx.success; +} diff --git a/src/utils/browser.h b/src/utils/browser.h new file mode 100644 index 0000000..aaa0eed --- /dev/null +++ b/src/utils/browser.h @@ -0,0 +1,21 @@ +/* + * Hotspot 2.0 client - Web browser + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef BROWSER_H +#define BROWSER_H + +#ifdef CONFIG_NO_BROWSER +static inline int hs20_web_browser(const char *url) +{ + return -1; +} +#else /* CONFIG_NO_BROWSER */ +int hs20_web_browser(const char *url); +#endif /* CONFIG_NO_BROWSER */ + +#endif /* BROWSER_H */ diff --git a/src/utils/common.c b/src/utils/common.c index 39751d4..7dc4797 100644 --- a/src/utils/common.c +++ b/src/utils/common.c @@ -350,7 +350,7 @@ void printf_encode(char *txt, size_t maxlen, const u8 *data, size_t len) size_t i; for (i = 0; i < len; i++) { - if (txt + 4 > end) + if (txt + 4 >= end) break; switch (data[i]) { diff --git a/src/utils/edit.c b/src/utils/edit.c index 177ecf4..d340bfa 100644 --- a/src/utils/edit.c +++ b/src/utils/edit.c @@ -14,7 +14,7 @@ #include "list.h" #include "edit.h" -#define CMD_BUF_LEN 256 +#define CMD_BUF_LEN 4096 static char cmdbuf[CMD_BUF_LEN]; static int cmdbuf_pos = 0; static int cmdbuf_len = 0; diff --git a/src/utils/edit_simple.c b/src/utils/edit_simple.c index a095ea6..13173cb 100644 --- a/src/utils/edit_simple.c +++ b/src/utils/edit_simple.c @@ -13,7 +13,7 @@ #include "edit.h" -#define CMD_BUF_LEN 256 +#define CMD_BUF_LEN 4096 static char cmdbuf[CMD_BUF_LEN]; static int cmdbuf_pos = 0; static const char *ps2 = NULL; diff --git a/src/utils/eloop.c b/src/utils/eloop.c index f83a232..0da6de4 100644 --- a/src/utils/eloop.c +++ b/src/utils/eloop.c @@ -7,17 +7,28 @@ */ #include "includes.h" +#include <assert.h> #include "common.h" #include "trace.h" #include "list.h" #include "eloop.h" +#if defined(CONFIG_ELOOP_POLL) && defined(CONFIG_ELOOP_EPOLL) +#error Do not define both of poll and epoll +#endif + +#if !defined(CONFIG_ELOOP_POLL) && !defined(CONFIG_ELOOP_EPOLL) +#define CONFIG_ELOOP_SELECT +#endif + #ifdef CONFIG_ELOOP_POLL -#include <assert.h> #include <poll.h> #endif /* CONFIG_ELOOP_POLL */ +#ifdef CONFIG_ELOOP_EPOLL +#include <sys/epoll.h> +#endif /* CONFIG_ELOOP_EPOLL */ struct eloop_sock { int sock; @@ -50,7 +61,11 @@ struct eloop_signal { struct eloop_sock_table { int count; struct eloop_sock *table; +#ifdef CONFIG_ELOOP_EPOLL + eloop_event_type type; +#else /* CONFIG_ELOOP_EPOLL */ int changed; +#endif /* CONFIG_ELOOP_EPOLL */ }; struct eloop_data { @@ -63,6 +78,13 @@ struct eloop_data { struct pollfd *pollfds; struct pollfd **pollfds_map; #endif /* CONFIG_ELOOP_POLL */ +#ifdef CONFIG_ELOOP_EPOLL + int epollfd; + int epoll_max_event_num; + int epoll_max_fd; + struct eloop_sock *epoll_table; + struct epoll_event *epoll_events; +#endif /* CONFIG_ELOOP_EPOLL */ struct eloop_sock_table readers; struct eloop_sock_table writers; struct eloop_sock_table exceptions; @@ -75,7 +97,6 @@ struct eloop_data { int pending_terminate; int terminate; - int reader_table_changed; }; static struct eloop_data eloop; @@ -128,6 +149,17 @@ int eloop_init(void) { os_memset(&eloop, 0, sizeof(eloop)); dl_list_init(&eloop.timeout); +#ifdef CONFIG_ELOOP_EPOLL + eloop.epollfd = epoll_create1(0); + if (eloop.epollfd < 0) { + wpa_printf(MSG_ERROR, "%s: epoll_create1 failed. %s\n", + __func__, strerror(errno)); + return -1; + } + eloop.readers.type = EVENT_TYPE_READ; + eloop.writers.type = EVENT_TYPE_WRITE; + eloop.exceptions.type = EVENT_TYPE_EXCEPTION; +#endif /* CONFIG_ELOOP_EPOLL */ #ifdef WPA_TRACE signal(SIGSEGV, eloop_sigsegv_handler); #endif /* WPA_TRACE */ @@ -139,6 +171,11 @@ static int eloop_sock_table_add_sock(struct eloop_sock_table *table, int sock, eloop_sock_handler handler, void *eloop_data, void *user_data) { +#ifdef CONFIG_ELOOP_EPOLL + struct eloop_sock *temp_table; + struct epoll_event ev, *temp_events; + int next; +#endif /* CONFIG_ELOOP_EPOLL */ struct eloop_sock *tmp; int new_max_sock; @@ -174,6 +211,33 @@ static int eloop_sock_table_add_sock(struct eloop_sock_table *table, eloop.pollfds = n; } #endif /* CONFIG_ELOOP_POLL */ +#ifdef CONFIG_ELOOP_EPOLL + if (new_max_sock >= eloop.epoll_max_fd) { + next = eloop.epoll_max_fd == 0 ? 16 : eloop.epoll_max_fd * 2; + temp_table = os_realloc_array(eloop.epoll_table, next, + sizeof(struct eloop_sock)); + if (temp_table == NULL) + return -1; + + eloop.epoll_max_fd = next; + eloop.epoll_table = temp_table; + } + + if (eloop.count + 1 > eloop.epoll_max_event_num) { + next = eloop.epoll_max_event_num == 0 ? 8 : + eloop.epoll_max_event_num * 2; + temp_events = os_realloc_array(eloop.epoll_events, next, + sizeof(struct epoll_event)); + if (temp_events == NULL) { + wpa_printf(MSG_ERROR, "%s: malloc for epoll failed. " + "%s\n", __func__, strerror(errno)); + return -1; + } + + eloop.epoll_max_event_num = next; + eloop.epoll_events = temp_events; + } +#endif /* CONFIG_ELOOP_EPOLL */ eloop_trace_sock_remove_ref(table); tmp = os_realloc_array(table->table, table->count + 1, @@ -190,9 +254,38 @@ static int eloop_sock_table_add_sock(struct eloop_sock_table *table, table->table = tmp; eloop.max_sock = new_max_sock; eloop.count++; +#ifndef CONFIG_ELOOP_EPOLL table->changed = 1; +#endif /* CONFIG_ELOOP_EPOLL */ eloop_trace_sock_add_ref(table); +#ifdef CONFIG_ELOOP_EPOLL + os_memset(&ev, 0, sizeof(ev)); + switch (table->type) { + case EVENT_TYPE_READ: + ev.events = EPOLLIN; + break; + case EVENT_TYPE_WRITE: + ev.events = EPOLLOUT; + break; + /* + * Exceptions are always checked when using epoll, but I suppose it's + * possible that someone registered a socket *only* for exception + * handling. + */ + case EVENT_TYPE_EXCEPTION: + ev.events = EPOLLERR | EPOLLHUP; + break; + } + ev.data.fd = sock; + if (epoll_ctl(eloop.epollfd, EPOLL_CTL_ADD, sock, &ev) < 0) { + wpa_printf(MSG_ERROR, "%s: epoll_ctl(ADD) for fd=%d " + "failed. %s\n", __func__, sock, strerror(errno)); + return -1; + } + os_memcpy(&eloop.epoll_table[sock], &table->table[table->count - 1], + sizeof(struct eloop_sock)); +#endif /* CONFIG_ELOOP_EPOLL */ return 0; } @@ -219,8 +312,18 @@ static void eloop_sock_table_remove_sock(struct eloop_sock_table *table, } table->count--; eloop.count--; +#ifndef CONFIG_ELOOP_EPOLL table->changed = 1; +#endif /* CONFIG_ELOOP_EPOLL */ eloop_trace_sock_add_ref(table); +#ifdef CONFIG_ELOOP_EPOLL + if (epoll_ctl(eloop.epollfd, EPOLL_CTL_DEL, sock, NULL) < 0) { + wpa_printf(MSG_ERROR, "%s: epoll_ctl(DEL) for fd=%d " + "failed. %s\n", __func__, sock, strerror(errno)); + return; + } + os_memset(&eloop.epoll_table[sock], 0, sizeof(struct eloop_sock)); +#endif /* CONFIG_ELOOP_EPOLL */ } @@ -362,7 +465,9 @@ static void eloop_sock_table_dispatch(struct eloop_sock_table *readers, max_pollfd_map, POLLERR | POLLHUP); } -#else /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_POLL */ + +#ifdef CONFIG_ELOOP_SELECT static void eloop_sock_table_set_fds(struct eloop_sock_table *table, fd_set *fds) @@ -374,8 +479,10 @@ static void eloop_sock_table_set_fds(struct eloop_sock_table *table, if (table->table == NULL) return; - for (i = 0; i < table->count; i++) + for (i = 0; i < table->count; i++) { + assert(table->table[i].sock >= 0); FD_SET(table->table[i].sock, fds); + } } @@ -399,7 +506,24 @@ static void eloop_sock_table_dispatch(struct eloop_sock_table *table, } } -#endif /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_SELECT */ + + +#ifdef CONFIG_ELOOP_EPOLL +static void eloop_sock_table_dispatch(struct epoll_event *events, int nfds) +{ + struct eloop_sock *table; + int i; + + for (i = 0; i < nfds; i++) { + table = &eloop.epoll_table[events[i].data.fd]; + if (table->handler == NULL) + continue; + table->handler(table->sock, table->eloop_data, + table->user_data); + } +} +#endif /* CONFIG_ELOOP_EPOLL */ static void eloop_sock_table_destroy(struct eloop_sock_table *table) @@ -459,6 +583,7 @@ int eloop_register_sock(int sock, eloop_event_type type, { struct eloop_sock_table *table; + assert(sock >= 0); table = eloop_get_sock_table(type); return eloop_sock_table_add_sock(table, sock, handler, eloop_data, user_data); @@ -773,20 +898,24 @@ void eloop_run(void) #ifdef CONFIG_ELOOP_POLL int num_poll_fds; int timeout_ms = 0; -#else /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_POLL */ +#ifdef CONFIG_ELOOP_SELECT fd_set *rfds, *wfds, *efds; struct timeval _tv; -#endif /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_SELECT */ +#ifdef CONFIG_ELOOP_EPOLL + int timeout_ms = -1; +#endif /* CONFIG_ELOOP_EPOLL */ int res; struct os_reltime tv, now; -#ifndef CONFIG_ELOOP_POLL +#ifdef CONFIG_ELOOP_SELECT rfds = os_malloc(sizeof(*rfds)); wfds = os_malloc(sizeof(*wfds)); efds = os_malloc(sizeof(*efds)); if (rfds == NULL || wfds == NULL || efds == NULL) goto out; -#endif /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_SELECT */ while (!eloop.terminate && (!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 || @@ -800,12 +929,13 @@ void eloop_run(void) os_reltime_sub(&timeout->time, &now, &tv); else tv.sec = tv.usec = 0; -#ifdef CONFIG_ELOOP_POLL +#if defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL) timeout_ms = tv.sec * 1000 + tv.usec / 1000; -#else /* CONFIG_ELOOP_POLL */ +#endif /* defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL) */ +#ifdef CONFIG_ELOOP_SELECT _tv.tv_sec = tv.sec; _tv.tv_usec = tv.usec; -#endif /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_SELECT */ } #ifdef CONFIG_ELOOP_POLL @@ -815,24 +945,36 @@ void eloop_run(void) eloop.max_pollfd_map); res = poll(eloop.pollfds, num_poll_fds, timeout ? timeout_ms : -1); - - if (res < 0 && errno != EINTR && errno != 0) { - wpa_printf(MSG_INFO, "eloop: poll: %s", - strerror(errno)); - goto out; - } -#else /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_POLL */ +#ifdef CONFIG_ELOOP_SELECT eloop_sock_table_set_fds(&eloop.readers, rfds); eloop_sock_table_set_fds(&eloop.writers, wfds); eloop_sock_table_set_fds(&eloop.exceptions, efds); res = select(eloop.max_sock + 1, rfds, wfds, efds, timeout ? &_tv : NULL); +#endif /* CONFIG_ELOOP_SELECT */ +#ifdef CONFIG_ELOOP_EPOLL + if (eloop.count == 0) { + res = 0; + } else { + res = epoll_wait(eloop.epollfd, eloop.epoll_events, + eloop.count, timeout_ms); + } +#endif /* CONFIG_ELOOP_EPOLL */ if (res < 0 && errno != EINTR && errno != 0) { - wpa_printf(MSG_INFO, "eloop: select: %s", - strerror(errno)); + wpa_printf(MSG_ERROR, "eloop: %s: %s", +#ifdef CONFIG_ELOOP_POLL + "poll" +#endif /* CONFIG_ELOOP_POLL */ +#ifdef CONFIG_ELOOP_SELECT + "select" +#endif /* CONFIG_ELOOP_SELECT */ +#ifdef CONFIG_ELOOP_EPOLL + "epoll" +#endif /* CONFIG_ELOOP_EPOLL */ + , strerror(errno)); goto out; } -#endif /* CONFIG_ELOOP_POLL */ eloop_process_pending_signals(); /* check if some registered timeouts have occurred */ @@ -858,20 +1000,24 @@ void eloop_run(void) eloop_sock_table_dispatch(&eloop.readers, &eloop.writers, &eloop.exceptions, eloop.pollfds_map, eloop.max_pollfd_map); -#else /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_POLL */ +#ifdef CONFIG_ELOOP_SELECT eloop_sock_table_dispatch(&eloop.readers, rfds); eloop_sock_table_dispatch(&eloop.writers, wfds); eloop_sock_table_dispatch(&eloop.exceptions, efds); -#endif /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_SELECT */ +#ifdef CONFIG_ELOOP_EPOLL + eloop_sock_table_dispatch(eloop.epoll_events, res); +#endif /* CONFIG_ELOOP_EPOLL */ } eloop.terminate = 0; out: -#ifndef CONFIG_ELOOP_POLL +#ifdef CONFIG_ELOOP_SELECT os_free(rfds); os_free(wfds); os_free(efds); -#endif /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_SELECT */ return; } @@ -915,6 +1061,11 @@ void eloop_destroy(void) os_free(eloop.pollfds); os_free(eloop.pollfds_map); #endif /* CONFIG_ELOOP_POLL */ +#ifdef CONFIG_ELOOP_EPOLL + os_free(eloop.epoll_table); + os_free(eloop.epoll_events); + close(eloop.epollfd); +#endif /* CONFIG_ELOOP_EPOLL */ } @@ -937,7 +1088,13 @@ void eloop_wait_for_read_sock(int sock) pfd.events = POLLIN; poll(&pfd, 1, -1); -#else /* CONFIG_ELOOP_POLL */ +#endif /* CONFIG_ELOOP_POLL */ +#if defined(CONFIG_ELOOP_SELECT) || defined(CONFIG_ELOOP_EPOLL) + /* + * We can use epoll() here. But epoll() requres 4 system calls. + * epoll_create1(), epoll_ctl() for ADD, epoll_wait, and close() for + * epoll fd. So select() is better for performance here. + */ fd_set rfds; if (sock < 0) @@ -946,5 +1103,9 @@ void eloop_wait_for_read_sock(int sock) FD_ZERO(&rfds); FD_SET(sock, &rfds); select(sock + 1, &rfds, NULL, NULL, NULL); -#endif /* CONFIG_ELOOP_POLL */ +#endif /* defined(CONFIG_ELOOP_SELECT) || defined(CONFIG_ELOOP_EPOLL) */ } + +#ifdef CONFIG_ELOOP_SELECT +#undef CONFIG_ELOOP_SELECT +#endif /* CONFIG_ELOOP_SELECT */ diff --git a/src/utils/http-utils.h b/src/utils/http-utils.h new file mode 100644 index 0000000..8d4399a --- /dev/null +++ b/src/utils/http-utils.h @@ -0,0 +1,63 @@ +/* + * HTTP wrapper + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef HTTP_UTILS_H +#define HTTP_UTILS_H + +struct http_ctx; + +struct http_othername { + char *oid; + u8 *data; + size_t len; +}; + +#define HTTP_MAX_CERT_LOGO_HASH 32 + +struct http_logo { + char *alg_oid; + u8 *hash; + size_t hash_len; + char *uri; +}; + +struct http_cert { + char **dnsname; + unsigned int num_dnsname; + struct http_othername *othername; + unsigned int num_othername; + struct http_logo *logo; + unsigned int num_logo; +}; + +int soap_init_client(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key); +int soap_reinit_client(struct http_ctx *ctx); +xml_node_t * soap_send_receive(struct http_ctx *ctx, xml_node_t *node); + +struct http_ctx * http_init_ctx(void *upper_ctx, struct xml_node_ctx *xml_ctx); +void http_ocsp_set(struct http_ctx *ctx, int val); +void http_deinit_ctx(struct http_ctx *ctx); + +int http_download_file(struct http_ctx *ctx, const char *url, + const char *fname, const char *ca_fname); +char * http_post(struct http_ctx *ctx, const char *url, const char *data, + const char *content_type, const char *ext_hdr, + const char *ca_fname, + const char *username, const char *password, + const char *client_cert, const char *client_key, + size_t *resp_len); +void http_set_cert_cb(struct http_ctx *ctx, + int (*cb)(void *ctx, struct http_cert *cert), + void *cb_ctx); +const char * http_get_err(struct http_ctx *ctx); +void http_parse_x509_certificate(struct http_ctx *ctx, const char *fname); + +#endif /* HTTP_UTILS_H */ diff --git a/src/utils/http_curl.c b/src/utils/http_curl.c new file mode 100644 index 0000000..07d9af0 --- /dev/null +++ b/src/utils/http_curl.c @@ -0,0 +1,1641 @@ +/* + * HTTP wrapper for libcurl + * 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 <curl/curl.h> +#ifdef EAP_TLS_OPENSSL +#include <openssl/ssl.h> +#include <openssl/asn1.h> +#include <openssl/asn1t.h> +#include <openssl/x509v3.h> + +#ifdef SSL_set_tlsext_status_type +#ifndef OPENSSL_NO_TLSEXT +#define HAVE_OCSP +#include <openssl/err.h> +#include <openssl/ocsp.h> +#endif /* OPENSSL_NO_TLSEXT */ +#endif /* SSL_set_tlsext_status_type */ +#endif /* EAP_TLS_OPENSSL */ + +#include "common.h" +#include "xml-utils.h" +#include "http-utils.h" + + +struct http_ctx { + void *ctx; + struct xml_node_ctx *xml; + CURL *curl; + struct curl_slist *curl_hdr; + char *svc_address; + char *svc_ca_fname; + char *svc_username; + char *svc_password; + char *svc_client_cert; + char *svc_client_key; + char *curl_buf; + size_t curl_buf_len; + + int (*cert_cb)(void *ctx, struct http_cert *cert); + void *cert_cb_ctx; + + enum { + NO_OCSP, OPTIONAL_OCSP, MANDATORY_OCSP + } ocsp; + X509 *peer_cert; + X509 *peer_issuer; + X509 *peer_issuer_issuer; + + const char *last_err; +}; + + +static void clear_curl(struct http_ctx *ctx) +{ + if (ctx->curl) { + curl_easy_cleanup(ctx->curl); + ctx->curl = NULL; + } + if (ctx->curl_hdr) { + curl_slist_free_all(ctx->curl_hdr); + ctx->curl_hdr = NULL; + } +} + + +static void clone_str(char **dst, const char *src) +{ + os_free(*dst); + if (src) + *dst = os_strdup(src); + else + *dst = NULL; +} + + +static void debug_dump(struct http_ctx *ctx, const char *title, + const char *buf, size_t len) +{ + char *txt; + size_t i; + + for (i = 0; i < len; i++) { + if (buf[i] < 32 && buf[i] != '\t' && buf[i] != '\n' && + buf[i] != '\r') { + wpa_hexdump_ascii(MSG_MSGDUMP, title, buf, len); + return; + } + } + + txt = os_malloc(len + 1); + if (txt == NULL) + return; + os_memcpy(txt, buf, len); + txt[len] = '\0'; + while (len > 0) { + len--; + if (txt[len] == '\n' || txt[len] == '\r') + txt[len] = '\0'; + else + break; + } + wpa_printf(MSG_MSGDUMP, "%s[%s]", title, txt); + os_free(txt); +} + + +static int curl_cb_debug(CURL *curl, curl_infotype info, char *buf, size_t len, + void *userdata) +{ + struct http_ctx *ctx = userdata; + switch (info) { + case CURLINFO_TEXT: + debug_dump(ctx, "CURLINFO_TEXT", buf, len); + break; + case CURLINFO_HEADER_IN: + debug_dump(ctx, "CURLINFO_HEADER_IN", buf, len); + break; + case CURLINFO_HEADER_OUT: + debug_dump(ctx, "CURLINFO_HEADER_OUT", buf, len); + break; + case CURLINFO_DATA_IN: + debug_dump(ctx, "CURLINFO_DATA_IN", buf, len); + break; + case CURLINFO_DATA_OUT: + debug_dump(ctx, "CURLINFO_DATA_OUT", buf, len); + break; + case CURLINFO_SSL_DATA_IN: + wpa_printf(MSG_DEBUG, "debug - CURLINFO_SSL_DATA_IN - %d", + (int) len); + break; + case CURLINFO_SSL_DATA_OUT: + wpa_printf(MSG_DEBUG, "debug - CURLINFO_SSL_DATA_OUT - %d", + (int) len); + break; + case CURLINFO_END: + wpa_printf(MSG_DEBUG, "debug - CURLINFO_END - %d", + (int) len); + break; + } + return 0; +} + + +static size_t curl_cb_write(void *ptr, size_t size, size_t nmemb, + void *userdata) +{ + struct http_ctx *ctx = userdata; + char *n; + n = os_realloc(ctx->curl_buf, ctx->curl_buf_len + size * nmemb + 1); + if (n == NULL) + return 0; + ctx->curl_buf = n; + os_memcpy(n + ctx->curl_buf_len, ptr, size * nmemb); + n[ctx->curl_buf_len + size * nmemb] = '\0'; + ctx->curl_buf_len += size * nmemb; + return size * nmemb; +} + + +#ifdef EAP_TLS_OPENSSL + +static void debug_dump_cert(const char *title, X509 *cert) +{ + BIO *out; + char *txt; + size_t rlen; + + out = BIO_new(BIO_s_mem()); + if (!out) + return; + + X509_print_ex(out, cert, XN_FLAG_COMPAT, X509_FLAG_COMPAT); + 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, "%s:\n%s", title, txt); + } + os_free(txt); + } + BIO_free(out); +} + + +static void add_alt_name_othername(struct http_ctx *ctx, struct http_cert *cert, + OTHERNAME *o) +{ + char txt[100]; + int res; + struct http_othername *on; + ASN1_TYPE *val; + + on = os_realloc_array(cert->othername, cert->num_othername + 1, + sizeof(struct http_othername)); + if (on == NULL) + return; + cert->othername = on; + on = &on[cert->num_othername]; + os_memset(on, 0, sizeof(*on)); + + res = OBJ_obj2txt(txt, sizeof(txt), o->type_id, 1); + if (res < 0 || res >= (int) sizeof(txt)) + return; + + on->oid = os_strdup(txt); + if (on->oid == NULL) + return; + + val = o->value; + on->data = val->value.octet_string->data; + on->len = val->value.octet_string->length; + + cert->num_othername++; +} + + +static void add_alt_name_dns(struct http_ctx *ctx, struct http_cert *cert, + ASN1_STRING *name) +{ + char *buf; + char **n; + + buf = NULL; + if (ASN1_STRING_to_UTF8((unsigned char **) &buf, name) < 0) + return; + + n = os_realloc_array(cert->dnsname, cert->num_dnsname + 1, + sizeof(char *)); + if (n == NULL) + return; + + cert->dnsname = n; + n[cert->num_dnsname] = buf; + cert->num_dnsname++; +} + + +static void add_alt_name(struct http_ctx *ctx, struct http_cert *cert, + const GENERAL_NAME *name) +{ + switch (name->type) { + case GEN_OTHERNAME: + add_alt_name_othername(ctx, cert, name->d.otherName); + break; + case GEN_DNS: + add_alt_name_dns(ctx, cert, name->d.dNSName); + break; + } +} + + +static void add_alt_names(struct http_ctx *ctx, struct http_cert *cert, + GENERAL_NAMES *names) +{ + int num, i; + + num = sk_GENERAL_NAME_num(names); + for (i = 0; i < num; i++) { + const GENERAL_NAME *name; + name = sk_GENERAL_NAME_value(names, i); + add_alt_name(ctx, cert, name); + } +} + + +/* RFC 3709 */ + +typedef struct { + X509_ALGOR *hashAlg; + ASN1_OCTET_STRING *hashValue; +} HashAlgAndValue; + +typedef struct { + STACK_OF(HashAlgAndValue) *refStructHash; + STACK_OF(ASN1_IA5STRING) *refStructURI; +} LogotypeReference; + +typedef struct { + ASN1_IA5STRING *mediaType; + STACK_OF(HashAlgAndValue) *logotypeHash; + STACK_OF(ASN1_IA5STRING) *logotypeURI; +} LogotypeDetails; + +typedef struct { + int type; + union { + ASN1_INTEGER *numBits; + ASN1_INTEGER *tableSize; + } d; +} LogotypeImageResolution; + +typedef struct { + ASN1_INTEGER *type; /* LogotypeImageType ::= INTEGER */ + ASN1_INTEGER *fileSize; + ASN1_INTEGER *xSize; + ASN1_INTEGER *ySize; + LogotypeImageResolution *resolution; + ASN1_IA5STRING *language; +} LogotypeImageInfo; + +typedef struct { + LogotypeDetails *imageDetails; + LogotypeImageInfo *imageInfo; +} LogotypeImage; + +typedef struct { + ASN1_INTEGER *fileSize; + ASN1_INTEGER *playTime; + ASN1_INTEGER *channels; + ASN1_INTEGER *sampleRate; + ASN1_IA5STRING *language; +} LogotypeAudioInfo; + +typedef struct { + LogotypeDetails *audioDetails; + LogotypeAudioInfo *audioInfo; +} LogotypeAudio; + +typedef struct { + STACK_OF(LogotypeImage) *image; + STACK_OF(LogotypeAudio) *audio; +} LogotypeData; + +typedef struct { + int type; + union { + LogotypeData *direct; + LogotypeReference *indirect; + } d; +} LogotypeInfo; + +typedef struct { + ASN1_OBJECT *logotypeType; + LogotypeInfo *info; +} OtherLogotypeInfo; + +typedef struct { + STACK_OF(LogotypeInfo) *communityLogos; + LogotypeInfo *issuerLogo; + LogotypeInfo *subjectLogo; + STACK_OF(OtherLogotypeInfo) *otherLogos; +} LogotypeExtn; + +ASN1_SEQUENCE(HashAlgAndValue) = { + ASN1_SIMPLE(HashAlgAndValue, hashAlg, X509_ALGOR), + ASN1_SIMPLE(HashAlgAndValue, hashValue, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(HashAlgAndValue); + +ASN1_SEQUENCE(LogotypeReference) = { + ASN1_SEQUENCE_OF(LogotypeReference, refStructHash, HashAlgAndValue), + ASN1_SEQUENCE_OF(LogotypeReference, refStructURI, ASN1_IA5STRING) +} ASN1_SEQUENCE_END(LogotypeReference); + +ASN1_SEQUENCE(LogotypeDetails) = { + ASN1_SIMPLE(LogotypeDetails, mediaType, ASN1_IA5STRING), + ASN1_SEQUENCE_OF(LogotypeDetails, logotypeHash, HashAlgAndValue), + ASN1_SEQUENCE_OF(LogotypeDetails, logotypeURI, ASN1_IA5STRING) +} ASN1_SEQUENCE_END(LogotypeDetails); + +ASN1_CHOICE(LogotypeImageResolution) = { + ASN1_IMP(LogotypeImageResolution, d.numBits, ASN1_INTEGER, 1), + ASN1_IMP(LogotypeImageResolution, d.tableSize, ASN1_INTEGER, 2) +} ASN1_CHOICE_END(LogotypeImageResolution); + +ASN1_SEQUENCE(LogotypeImageInfo) = { + ASN1_IMP_OPT(LogotypeImageInfo, type, ASN1_INTEGER, 0), + ASN1_SIMPLE(LogotypeImageInfo, fileSize, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeImageInfo, xSize, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeImageInfo, ySize, ASN1_INTEGER), + ASN1_OPT(LogotypeImageInfo, resolution, LogotypeImageResolution), + ASN1_IMP_OPT(LogotypeImageInfo, language, ASN1_IA5STRING, 4), +} ASN1_SEQUENCE_END(LogotypeImageInfo); + +ASN1_SEQUENCE(LogotypeImage) = { + ASN1_SIMPLE(LogotypeImage, imageDetails, LogotypeDetails), + ASN1_OPT(LogotypeImage, imageInfo, LogotypeImageInfo) +} ASN1_SEQUENCE_END(LogotypeImage); + +ASN1_SEQUENCE(LogotypeAudioInfo) = { + ASN1_SIMPLE(LogotypeAudioInfo, fileSize, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeAudioInfo, playTime, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeAudioInfo, channels, ASN1_INTEGER), + ASN1_IMP_OPT(LogotypeAudioInfo, sampleRate, ASN1_INTEGER, 3), + ASN1_IMP_OPT(LogotypeAudioInfo, language, ASN1_IA5STRING, 4) +} ASN1_SEQUENCE_END(LogotypeAudioInfo); + +ASN1_SEQUENCE(LogotypeAudio) = { + ASN1_SIMPLE(LogotypeAudio, audioDetails, LogotypeDetails), + ASN1_OPT(LogotypeAudio, audioInfo, LogotypeAudioInfo) +} ASN1_SEQUENCE_END(LogotypeAudio); + +ASN1_SEQUENCE(LogotypeData) = { + ASN1_SEQUENCE_OF_OPT(LogotypeData, image, LogotypeImage), + ASN1_IMP_SEQUENCE_OF_OPT(LogotypeData, audio, LogotypeAudio, 1) +} ASN1_SEQUENCE_END(LogotypeData); + +ASN1_CHOICE(LogotypeInfo) = { + ASN1_IMP(LogotypeInfo, d.direct, LogotypeData, 0), + ASN1_IMP(LogotypeInfo, d.indirect, LogotypeReference, 1) +} ASN1_CHOICE_END(LogotypeInfo); + +ASN1_SEQUENCE(OtherLogotypeInfo) = { + ASN1_SIMPLE(OtherLogotypeInfo, logotypeType, ASN1_OBJECT), + ASN1_SIMPLE(OtherLogotypeInfo, info, LogotypeInfo) +} ASN1_SEQUENCE_END(OtherLogotypeInfo); + +ASN1_SEQUENCE(LogotypeExtn) = { + ASN1_EXP_SEQUENCE_OF_OPT(LogotypeExtn, communityLogos, LogotypeInfo, 0), + ASN1_EXP_OPT(LogotypeExtn, issuerLogo, LogotypeInfo, 1), + ASN1_EXP_OPT(LogotypeExtn, issuerLogo, LogotypeInfo, 2), + ASN1_EXP_SEQUENCE_OF_OPT(LogotypeExtn, otherLogos, OtherLogotypeInfo, 3) +} ASN1_SEQUENCE_END(LogotypeExtn); + +IMPLEMENT_ASN1_FUNCTIONS(LogotypeExtn); + +#define sk_LogotypeInfo_num(st) SKM_sk_num(LogotypeInfo, (st)) +#define sk_LogotypeInfo_value(st, i) SKM_sk_value(LogotypeInfo, (st), (i)) +#define sk_LogotypeImage_num(st) SKM_sk_num(LogotypeImage, (st)) +#define sk_LogotypeImage_value(st, i) SKM_sk_value(LogotypeImage, (st), (i)) +#define sk_LogotypeAudio_num(st) SKM_sk_num(LogotypeAudio, (st)) +#define sk_LogotypeAudio_value(st, i) SKM_sk_value(LogotypeAudio, (st), (i)) +#define sk_HashAlgAndValue_num(st) SKM_sk_num(HashAlgAndValue, (st)) +#define sk_HashAlgAndValue_value(st, i) SKM_sk_value(HashAlgAndValue, (st), (i)) +#define sk_ASN1_IA5STRING_num(st) SKM_sk_num(ASN1_IA5STRING, (st)) +#define sk_ASN1_IA5STRING_value(st, i) SKM_sk_value(ASN1_IA5STRING, (st), (i)) + + +static void add_logo(struct http_ctx *ctx, struct http_cert *hcert, + HashAlgAndValue *hash, ASN1_IA5STRING *uri) +{ + char txt[100]; + int res, len; + struct http_logo *n; + + if (hash == NULL || uri == NULL) + return; + + res = OBJ_obj2txt(txt, sizeof(txt), hash->hashAlg->algorithm, 1); + if (res < 0 || res >= (int) sizeof(txt)) + return; + + n = os_realloc_array(hcert->logo, hcert->num_logo + 1, + sizeof(struct http_logo)); + if (n == NULL) + return; + hcert->logo = n; + n = &hcert->logo[hcert->num_logo]; + os_memset(n, 0, sizeof(*n)); + + n->alg_oid = os_strdup(txt); + if (n->alg_oid == NULL) + return; + + n->hash_len = ASN1_STRING_length(hash->hashValue); + n->hash = os_malloc(n->hash_len); + if (n->hash == NULL) { + os_free(n->alg_oid); + return; + } + os_memcpy(n->hash, ASN1_STRING_data(hash->hashValue), n->hash_len); + + len = ASN1_STRING_length(uri); + n->uri = os_malloc(len + 1); + if (n->uri == NULL) { + os_free(n->alg_oid); + os_free(n->hash); + return; + } + os_memcpy(n->uri, ASN1_STRING_data(uri), len); + n->uri[len] = '\0'; + + hcert->num_logo++; +} + + +static void add_logo_direct(struct http_ctx *ctx, struct http_cert *hcert, + LogotypeData *data) +{ + int i, num; + + if (data->image == NULL) + return; + + num = sk_LogotypeImage_num(data->image); + for (i = 0; i < num; i++) { + LogotypeImage *image; + LogotypeDetails *details; + int j, hash_num, uri_num; + HashAlgAndValue *found_hash = NULL; + + image = sk_LogotypeImage_value(data->image, i); + if (image == NULL) + continue; + + details = image->imageDetails; + if (details == NULL) + continue; + + hash_num = sk_HashAlgAndValue_num(details->logotypeHash); + for (j = 0; j < hash_num; j++) { + HashAlgAndValue *hash; + char txt[100]; + int res; + hash = sk_HashAlgAndValue_value(details->logotypeHash, + j); + if (hash == NULL) + continue; + res = OBJ_obj2txt(txt, sizeof(txt), + hash->hashAlg->algorithm, 1); + if (res < 0 || res >= (int) sizeof(txt)) + continue; + if (os_strcmp(txt, "2.16.840.1.101.3.4.2.1") == 0) { + found_hash = hash; + break; + } + } + + if (!found_hash) { + wpa_printf(MSG_DEBUG, "OpenSSL: No SHA256 hash found for the logo"); + continue; + } + + uri_num = sk_ASN1_IA5STRING_num(details->logotypeURI); + for (j = 0; j < uri_num; j++) { + ASN1_IA5STRING *uri; + uri = sk_ASN1_IA5STRING_value(details->logotypeURI, j); + add_logo(ctx, hcert, found_hash, uri); + } + } +} + + +static void add_logo_indirect(struct http_ctx *ctx, struct http_cert *hcert, + LogotypeReference *ref) +{ + int j, hash_num, uri_num; + + hash_num = sk_HashAlgAndValue_num(ref->refStructHash); + uri_num = sk_ASN1_IA5STRING_num(ref->refStructURI); + if (hash_num != uri_num) { + wpa_printf(MSG_INFO, "Unexpected LogotypeReference array size difference %d != %d", + hash_num, uri_num); + return; + } + + for (j = 0; j < hash_num; j++) { + HashAlgAndValue *hash; + ASN1_IA5STRING *uri; + hash = sk_HashAlgAndValue_value(ref->refStructHash, j); + uri = sk_ASN1_IA5STRING_value(ref->refStructURI, j); + add_logo(ctx, hcert, hash, uri); + } +} + + +static void i2r_HashAlgAndValue(HashAlgAndValue *hash, BIO *out, int indent) +{ + int i; + const unsigned char *data; + + BIO_printf(out, "%*shashAlg: ", indent, ""); + i2a_ASN1_OBJECT(out, hash->hashAlg->algorithm); + BIO_printf(out, "\n"); + + BIO_printf(out, "%*shashValue: ", indent, ""); + data = hash->hashValue->data; + for (i = 0; i < hash->hashValue->length; i++) + BIO_printf(out, "%s%02x", i > 0 ? ":" : "", data[i]); + BIO_printf(out, "\n"); +} + +static void i2r_LogotypeDetails(LogotypeDetails *details, BIO *out, int indent) +{ + int i, num; + + BIO_printf(out, "%*sLogotypeDetails\n", indent, ""); + if (details->mediaType) { + BIO_printf(out, "%*smediaType: ", indent, ""); + ASN1_STRING_print(out, details->mediaType); + BIO_printf(out, "\n"); + } + + num = details->logotypeHash ? + sk_HashAlgAndValue_num(details->logotypeHash) : 0; + for (i = 0; i < num; i++) { + HashAlgAndValue *hash; + hash = sk_HashAlgAndValue_value(details->logotypeHash, i); + i2r_HashAlgAndValue(hash, out, indent); + } + + num = details->logotypeURI ? + sk_ASN1_IA5STRING_num(details->logotypeURI) : 0; + for (i = 0; i < num; i++) { + ASN1_IA5STRING *uri; + uri = sk_ASN1_IA5STRING_value(details->logotypeURI, i); + BIO_printf(out, "%*slogotypeURI: ", indent, ""); + ASN1_STRING_print(out, uri); + BIO_printf(out, "\n"); + } +} + +static void i2r_LogotypeImageInfo(LogotypeImageInfo *info, BIO *out, int indent) +{ + long val; + + BIO_printf(out, "%*sLogotypeImageInfo\n", indent, ""); + if (info->type) { + val = ASN1_INTEGER_get(info->type); + BIO_printf(out, "%*stype: %ld\n", indent, "", val); + } else { + BIO_printf(out, "%*stype: default (1)\n", indent, ""); + } + val = ASN1_INTEGER_get(info->xSize); + BIO_printf(out, "%*sxSize: %ld\n", indent, "", val); + val = ASN1_INTEGER_get(info->ySize); + BIO_printf(out, "%*sySize: %ld\n", indent, "", val); + if (info->resolution) { + BIO_printf(out, "%*sresolution\n", indent, ""); + /* TODO */ + } + if (info->language) { + BIO_printf(out, "%*slanguage: ", indent, ""); + ASN1_STRING_print(out, info->language); + BIO_printf(out, "\n"); + } +} + +static void i2r_LogotypeImage(LogotypeImage *image, BIO *out, int indent) +{ + BIO_printf(out, "%*sLogotypeImage\n", indent, ""); + if (image->imageDetails) { + i2r_LogotypeDetails(image->imageDetails, out, indent + 4); + } + if (image->imageInfo) { + i2r_LogotypeImageInfo(image->imageInfo, out, indent + 4); + } +} + +static void i2r_LogotypeData(LogotypeData *data, const char *title, BIO *out, + int indent) +{ + int i, num; + + BIO_printf(out, "%*s%s - LogotypeData\n", indent, "", title); + + num = data->image ? sk_LogotypeImage_num(data->image) : 0; + for (i = 0; i < num; i++) { + LogotypeImage *image = sk_LogotypeImage_value(data->image, i); + i2r_LogotypeImage(image, out, indent + 4); + } + + num = data->audio ? sk_LogotypeAudio_num(data->audio) : 0; + for (i = 0; i < num; i++) { + BIO_printf(out, "%*saudio: TODO\n", indent, ""); + } +} + +static void i2r_LogotypeReference(LogotypeReference *ref, const char *title, + BIO *out, int indent) +{ + int i, hash_num, uri_num; + + BIO_printf(out, "%*s%s - LogotypeReference\n", indent, "", title); + + hash_num = ref->refStructHash ? + sk_HashAlgAndValue_num(ref->refStructHash) : 0; + uri_num = ref->refStructURI ? + sk_ASN1_IA5STRING_num(ref->refStructURI) : 0; + if (hash_num != uri_num) { + BIO_printf(out, "%*sUnexpected LogotypeReference array size difference %d != %d\n", + indent, "", hash_num, uri_num); + return; + } + + for (i = 0; i < hash_num; i++) { + HashAlgAndValue *hash; + ASN1_IA5STRING *uri; + + hash = sk_HashAlgAndValue_value(ref->refStructHash, i); + i2r_HashAlgAndValue(hash, out, indent); + + uri = sk_ASN1_IA5STRING_value(ref->refStructURI, i); + BIO_printf(out, "%*srefStructURI: ", indent, ""); + ASN1_STRING_print(out, uri); + BIO_printf(out, "\n"); + } +} + +static void i2r_LogotypeInfo(LogotypeInfo *info, const char *title, BIO *out, + int indent) +{ + switch (info->type) { + case 0: + i2r_LogotypeData(info->d.direct, title, out, indent); + break; + case 1: + i2r_LogotypeReference(info->d.indirect, title, out, indent); + break; + } +} + +static void debug_print_logotypeext(LogotypeExtn *logo) +{ + BIO *out; + int i, num; + int indent = 0; + + out = BIO_new_fp(stdout, BIO_NOCLOSE); + if (out == NULL) + return; + + if (logo->communityLogos) { + num = sk_LogotypeInfo_num(logo->communityLogos); + for (i = 0; i < num; i++) { + LogotypeInfo *info; + info = sk_LogotypeInfo_value(logo->communityLogos, i); + i2r_LogotypeInfo(info, "communityLogo", out, indent); + } + } + + if (logo->issuerLogo) { + i2r_LogotypeInfo(logo->issuerLogo, "issuerLogo", out, indent ); + } + + if (logo->subjectLogo) { + i2r_LogotypeInfo(logo->subjectLogo, "subjectLogo", out, indent); + } + + if (logo->otherLogos) { + BIO_printf(out, "%*sotherLogos - TODO\n", indent, ""); + } + + BIO_free(out); +} + + +static void add_logotype_ext(struct http_ctx *ctx, struct http_cert *hcert, + X509 *cert) +{ + ASN1_OBJECT *obj; + int pos; + X509_EXTENSION *ext; + ASN1_OCTET_STRING *os; + LogotypeExtn *logo; + const unsigned char *data; + int i, num; + + obj = OBJ_txt2obj("1.3.6.1.5.5.7.1.12", 0); + if (obj == NULL) + return; + + pos = X509_get_ext_by_OBJ(cert, obj, -1); + if (pos < 0) { + wpa_printf(MSG_INFO, "No logotype extension included"); + return; + } + + wpa_printf(MSG_INFO, "Parsing logotype extension"); + ext = X509_get_ext(cert, pos); + if (!ext) { + wpa_printf(MSG_INFO, "Could not get logotype extension"); + return; + } + + os = X509_EXTENSION_get_data(ext); + if (os == NULL) { + wpa_printf(MSG_INFO, "Could not get logotype extension data"); + return; + } + + wpa_hexdump(MSG_DEBUG, "logotypeExtn", + ASN1_STRING_data(os), ASN1_STRING_length(os)); + + data = ASN1_STRING_data(os); + logo = d2i_LogotypeExtn(NULL, &data, ASN1_STRING_length(os)); + if (logo == NULL) { + wpa_printf(MSG_INFO, "Failed to parse logotypeExtn"); + return; + } + + if (wpa_debug_level < MSG_INFO) + debug_print_logotypeext(logo); + + if (!logo->communityLogos) { + wpa_printf(MSG_INFO, "No communityLogos included"); + LogotypeExtn_free(logo); + return; + } + + num = sk_LogotypeInfo_num(logo->communityLogos); + for (i = 0; i < num; i++) { + LogotypeInfo *info; + info = sk_LogotypeInfo_value(logo->communityLogos, i); + switch (info->type) { + case 0: + add_logo_direct(ctx, hcert, info->d.direct); + break; + case 1: + add_logo_indirect(ctx, hcert, info->d.indirect); + break; + } + } + + LogotypeExtn_free(logo); +} + + +static void parse_cert(struct http_ctx *ctx, struct http_cert *hcert, + X509 *cert, GENERAL_NAMES **names) +{ + os_memset(hcert, 0, sizeof(*hcert)); + + *names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (*names) + add_alt_names(ctx, hcert, *names); + + add_logotype_ext(ctx, hcert, cert); +} + + +static void parse_cert_free(struct http_cert *hcert, GENERAL_NAMES *names) +{ + unsigned int i; + + for (i = 0; i < hcert->num_dnsname; i++) + OPENSSL_free(hcert->dnsname[i]); + os_free(hcert->dnsname); + + for (i = 0; i < hcert->num_othername; i++) + os_free(hcert->othername[i].oid); + os_free(hcert->othername); + + for (i = 0; i < hcert->num_logo; i++) { + os_free(hcert->logo[i].alg_oid); + os_free(hcert->logo[i].hash); + os_free(hcert->logo[i].uri); + } + os_free(hcert->logo); + + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); +} + + +static int validate_server_cert(struct http_ctx *ctx, X509 *cert) +{ + GENERAL_NAMES *names; + struct http_cert hcert; + int ret; + + if (ctx->cert_cb == NULL) + return 0; + + if (0) { + BIO *out; + out = BIO_new_fp(stdout, BIO_NOCLOSE); + X509_print_ex(out, cert, XN_FLAG_COMPAT, X509_FLAG_COMPAT); + BIO_free(out); + } + + parse_cert(ctx, &hcert, cert, &names); + ret = ctx->cert_cb(ctx->cert_cb_ctx, &hcert); + parse_cert_free(&hcert, names); + + return ret; +} + + +void http_parse_x509_certificate(struct http_ctx *ctx, const char *fname) +{ + BIO *in, *out; + X509 *cert; + GENERAL_NAMES *names; + struct http_cert hcert; + unsigned int i; + + in = BIO_new_file(fname, "r"); + if (in == NULL) { + wpa_printf(MSG_ERROR, "Could not read '%s'", fname); + return; + } + + cert = d2i_X509_bio(in, NULL); + BIO_free(in); + + if (cert == NULL) { + wpa_printf(MSG_ERROR, "Could not parse certificate"); + return; + } + + out = BIO_new_fp(stdout, BIO_NOCLOSE); + if (out) { + X509_print_ex(out, cert, XN_FLAG_COMPAT, + X509_FLAG_COMPAT); + BIO_free(out); + } + + wpa_printf(MSG_INFO, "Additional parsing information:"); + parse_cert(ctx, &hcert, cert, &names); + for (i = 0; i < hcert.num_othername; i++) { + if (os_strcmp(hcert.othername[i].oid, + "1.3.6.1.4.1.40808.1.1.1") == 0) { + char *name = os_zalloc(hcert.othername[i].len + 1); + if (name) { + os_memcpy(name, hcert.othername[i].data, + hcert.othername[i].len); + wpa_printf(MSG_INFO, + "id-wfa-hotspot-friendlyName: %s", + name); + os_free(name); + } + wpa_hexdump_ascii(MSG_INFO, + "id-wfa-hotspot-friendlyName", + hcert.othername[i].data, + hcert.othername[i].len); + } else { + wpa_printf(MSG_INFO, "subjAltName[othername]: oid=%s", + hcert.othername[i].oid); + wpa_hexdump_ascii(MSG_INFO, "unknown othername", + hcert.othername[i].data, + hcert.othername[i].len); + } + } + parse_cert_free(&hcert, names); + + X509_free(cert); +} + + +static int curl_cb_ssl_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + struct http_ctx *ctx; + X509 *cert; + int err, depth; + char buf[256]; + X509_NAME *name; + const char *err_str; + SSL *ssl; + SSL_CTX *ssl_ctx; + + ssl = X509_STORE_CTX_get_ex_data(x509_ctx, + SSL_get_ex_data_X509_STORE_CTX_idx()); + ssl_ctx = ssl->ctx; + ctx = SSL_CTX_get_app_data(ssl_ctx); + + wpa_printf(MSG_DEBUG, "curl_cb_ssl_verify"); + + err = X509_STORE_CTX_get_error(x509_ctx); + err_str = X509_verify_cert_error_string(err); + depth = X509_STORE_CTX_get_error_depth(x509_ctx); + cert = X509_STORE_CTX_get_current_cert(x509_ctx); + if (!cert) { + wpa_printf(MSG_INFO, "No server certificate available"); + ctx->last_err = "No server certificate available"; + return 0; + } + + if (depth == 0) + ctx->peer_cert = cert; + else if (depth == 1) + ctx->peer_issuer = cert; + else if (depth == 2) + ctx->peer_issuer_issuer = cert; + + name = X509_get_subject_name(cert); + X509_NAME_oneline(name, buf, sizeof(buf)); + wpa_printf(MSG_INFO, "Server certificate chain - depth=%d err=%d (%s) subject=%s", + depth, err, err_str, buf); + debug_dump_cert("Server certificate chain - certificate", cert); + + if (depth == 0 && preverify_ok && validate_server_cert(ctx, cert) < 0) + return 0; + + if (!preverify_ok) + ctx->last_err = "TLS validation failed"; + + return preverify_ok; +} + + +#ifdef HAVE_OCSP + +static void ocsp_debug_print_resp(OCSP_RESPONSE *rsp) +{ + BIO *out; + size_t rlen; + char *txt; + int res; + + out = BIO_new(BIO_s_mem()); + if (!out) + return; + + OCSP_RESPONSE_print(out, rsp, 0); + rlen = BIO_ctrl_pending(out); + txt = os_malloc(rlen + 1); + if (!txt) { + BIO_free(out); + return; + } + + res = BIO_read(out, txt, rlen); + if (res > 0) { + txt[res] = '\0'; + wpa_printf(MSG_MSGDUMP, "OpenSSL: OCSP Response\n%s", txt); + } + os_free(txt); + BIO_free(out); +} + + +static void tls_show_errors(const char *func, const char *txt) +{ + unsigned long err; + + wpa_printf(MSG_DEBUG, "OpenSSL: %s - %s %s", + func, txt, ERR_error_string(ERR_get_error(), NULL)); + + while ((err = ERR_get_error())) { + wpa_printf(MSG_DEBUG, "OpenSSL: pending error: %s", + ERR_error_string(err, NULL)); + } +} + + +static int ocsp_resp_cb(SSL *s, void *arg) +{ + struct http_ctx *ctx = arg; + const unsigned char *p; + int len, status, reason; + OCSP_RESPONSE *rsp; + OCSP_BASICRESP *basic; + OCSP_CERTID *id; + ASN1_GENERALIZEDTIME *produced_at, *this_update, *next_update; + X509_STORE *store; + STACK_OF(X509) *certs = NULL; + + len = SSL_get_tlsext_status_ocsp_resp(s, &p); + if (!p) { + wpa_printf(MSG_DEBUG, "OpenSSL: No OCSP response received"); + if (ctx->ocsp == MANDATORY_OCSP) + ctx->last_err = "No OCSP response received"; + return (ctx->ocsp == MANDATORY_OCSP) ? 0 : 1; + } + + wpa_hexdump(MSG_DEBUG, "OpenSSL: OCSP response", p, len); + + rsp = d2i_OCSP_RESPONSE(NULL, &p, len); + if (!rsp) { + wpa_printf(MSG_INFO, "OpenSSL: Failed to parse OCSP response"); + ctx->last_err = "Failed to parse OCSP response"; + return 0; + } + + ocsp_debug_print_resp(rsp); + + status = OCSP_response_status(rsp); + if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + wpa_printf(MSG_INFO, "OpenSSL: OCSP responder error %d (%s)", + status, OCSP_response_status_str(status)); + ctx->last_err = "OCSP responder error"; + return 0; + } + + basic = OCSP_response_get1_basic(rsp); + if (!basic) { + wpa_printf(MSG_INFO, "OpenSSL: Could not find BasicOCSPResponse"); + ctx->last_err = "Could not find BasicOCSPResponse"; + return 0; + } + + store = SSL_CTX_get_cert_store(s->ctx); + if (ctx->peer_issuer) { + wpa_printf(MSG_DEBUG, "OpenSSL: Add issuer"); + debug_dump_cert("OpenSSL: Issuer certificate", + ctx->peer_issuer); + + if (X509_STORE_add_cert(store, ctx->peer_issuer) != 1) { + tls_show_errors(__func__, + "OpenSSL: Could not add issuer to certificate store\n"); + } + certs = sk_X509_new_null(); + if (certs) { + X509 *cert; + cert = X509_dup(ctx->peer_issuer); + if (cert && !sk_X509_push(certs, cert)) { + tls_show_errors( + __func__, + "OpenSSL: Could not add issuer to OCSP responder trust store\n"); + X509_free(cert); + sk_X509_free(certs); + certs = NULL; + } + if (ctx->peer_issuer_issuer) { + X509 *cert; + cert = X509_dup(ctx->peer_issuer_issuer); + if (cert && !sk_X509_push(certs, cert)) { + tls_show_errors( + __func__, + "OpenSSL: Could not add issuer to OCSP responder trust store\n"); + X509_free(cert); + } + } + } + } + + status = OCSP_basic_verify(basic, certs, store, OCSP_TRUSTOTHER); + sk_X509_pop_free(certs, X509_free); + if (status <= 0) { + tls_show_errors(__func__, + "OpenSSL: OCSP response failed verification"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "OCSP response failed verification"; + return 0; + } + + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP response verification succeeded"); + + if (!ctx->peer_cert) { + wpa_printf(MSG_DEBUG, "OpenSSL: Peer certificate not available for OCSP status check"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "Peer certificate not available for OCSP status check"; + return 0; + } + + if (!ctx->peer_issuer) { + wpa_printf(MSG_DEBUG, "OpenSSL: Peer issuer certificate not available for OCSP status check"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "Peer issuer certificate not available for OCSP status check"; + return 0; + } + + id = OCSP_cert_to_id(NULL, ctx->peer_cert, ctx->peer_issuer); + if (!id) { + wpa_printf(MSG_DEBUG, "OpenSSL: Could not create OCSP certificate identifier"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "Could not create OCSP certificate identifier"; + return 0; + } + + if (!OCSP_resp_find_status(basic, id, &status, &reason, &produced_at, + &this_update, &next_update)) { + wpa_printf(MSG_INFO, "OpenSSL: Could not find current server certificate from OCSP response%s", + (ctx->ocsp == MANDATORY_OCSP) ? "" : + " (OCSP not required)"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + if (ctx->ocsp == MANDATORY_OCSP) + + ctx->last_err = "Could not find current server certificate from OCSP response"; + return (ctx->ocsp == MANDATORY_OCSP) ? 0 : 1; + } + + if (!OCSP_check_validity(this_update, next_update, 5 * 60, -1)) { + tls_show_errors(__func__, "OpenSSL: OCSP status times invalid"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "OCSP status times invalid"; + return 0; + } + + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status for server certificate: %s", + OCSP_cert_status_str(status)); + + if (status == V_OCSP_CERTSTATUS_GOOD) + return 1; + if (status == V_OCSP_CERTSTATUS_REVOKED) + ctx->last_err = "Server certificate has been revoked"; + return 0; + if (ctx->ocsp == MANDATORY_OCSP) { + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP required"); + ctx->last_err = "OCSP status unknown"; + return 0; + } + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP was not required, so allow connection to continue"); + return 1; +} + + +static SSL_METHOD patch_ssl_method; +static const SSL_METHOD *real_ssl_method; + +static int curl_patch_ssl_new(SSL *s) +{ + SSL_CTX *ssl = s->ctx; + int ret; + + ssl->method = real_ssl_method; + s->method = real_ssl_method; + + ret = s->method->ssl_new(s); + SSL_set_tlsext_status_type(s, TLSEXT_STATUSTYPE_ocsp); + + return ret; +} + +#endif /* HAVE_OCSP */ + + +static CURLcode curl_cb_ssl(CURL *curl, void *sslctx, void *parm) +{ + struct http_ctx *ctx = parm; + SSL_CTX *ssl = sslctx; + + wpa_printf(MSG_DEBUG, "curl_cb_ssl"); + SSL_CTX_set_app_data(ssl, ctx); + SSL_CTX_set_verify(ssl, SSL_VERIFY_PEER, curl_cb_ssl_verify); + +#ifdef HAVE_OCSP + if (ctx->ocsp != NO_OCSP) { + SSL_CTX_set_tlsext_status_cb(ssl, ocsp_resp_cb); + SSL_CTX_set_tlsext_status_arg(ssl, ctx); + + /* + * Use a temporary SSL_METHOD to get a callback on SSL_new() + * from libcurl since there is no proper callback registration + * available for this. + */ + os_memset(&patch_ssl_method, 0, sizeof(patch_ssl_method)); + patch_ssl_method.ssl_new = curl_patch_ssl_new; + real_ssl_method = ssl->method; + ssl->method = &patch_ssl_method; + } +#endif /* HAVE_OCSP */ + + return CURLE_OK; +} + +#endif /* EAP_TLS_OPENSSL */ + + +static CURL * setup_curl_post(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key) +{ + CURL *curl; + + wpa_printf(MSG_DEBUG, "Start HTTP client: address=%s ca_fname=%s " + "username=%s", address, ca_fname, username); + + curl = curl_easy_init(); + if (curl == NULL) + return NULL; + + curl_easy_setopt(curl, CURLOPT_URL, address); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + if (ca_fname) { + curl_easy_setopt(curl, CURLOPT_CAINFO, ca_fname); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); +#ifdef EAP_TLS_OPENSSL + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, curl_cb_ssl); + curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, ctx); +#endif /* EAP_TLS_OPENSSL */ + } else { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + } + if (client_cert && client_key) { + curl_easy_setopt(curl, CURLOPT_SSLCERT, client_cert); + curl_easy_setopt(curl, CURLOPT_SSLKEY, client_key); + } + /* TODO: use curl_easy_getinfo() with CURLINFO_CERTINFO to fetch + * information about the server certificate */ + curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_cb_debug); + curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_cb_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if (username) { + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE); + curl_easy_setopt(curl, CURLOPT_USERNAME, username); + curl_easy_setopt(curl, CURLOPT_PASSWORD, password); + } + + return curl; +} + + +static int post_init_client(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key) +{ + char *pos; + int count; + + clone_str(&ctx->svc_address, address); + clone_str(&ctx->svc_ca_fname, ca_fname); + clone_str(&ctx->svc_username, username); + clone_str(&ctx->svc_password, password); + clone_str(&ctx->svc_client_cert, client_cert); + clone_str(&ctx->svc_client_key, client_key); + + /* + * Workaround for Apache "Hostname 'FOO' provided via SNI and hostname + * 'foo' provided via HTTP are different. + */ + for (count = 0, pos = ctx->svc_address; count < 3 && pos && *pos; + pos++) { + if (*pos == '/') + count++; + *pos = tolower(*pos); + } + + ctx->curl = setup_curl_post(ctx, ctx->svc_address, ca_fname, username, + password, client_cert, client_key); + if (ctx->curl == NULL) + return -1; + + return 0; +} + + +int soap_init_client(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key) +{ + if (post_init_client(ctx, address, ca_fname, username, password, + client_cert, client_key) < 0) + return -1; + + ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, + "Content-Type: application/soap+xml"); + ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, "SOAPAction: "); + ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, "Expect:"); + curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, ctx->curl_hdr); + + return 0; +} + + +int soap_reinit_client(struct http_ctx *ctx) +{ + char *address = NULL; + char *ca_fname = NULL; + char *username = NULL; + char *password = NULL; + char *client_cert = NULL; + char *client_key = NULL; + int ret; + + clear_curl(ctx); + + clone_str(&address, ctx->svc_address); + clone_str(&ca_fname, ctx->svc_ca_fname); + clone_str(&username, ctx->svc_username); + clone_str(&password, ctx->svc_password); + clone_str(&client_cert, ctx->svc_client_cert); + clone_str(&client_key, ctx->svc_client_key); + + ret = soap_init_client(ctx, address, ca_fname, username, password, + client_cert, client_key); + os_free(address); + os_free(ca_fname); + os_free(username); + os_free(password); + os_free(client_cert); + os_free(client_key); + return ret; +} + + +static void free_curl_buf(struct http_ctx *ctx) +{ + os_free(ctx->curl_buf); + ctx->curl_buf = NULL; + ctx->curl_buf_len = 0; +} + + +xml_node_t * soap_send_receive(struct http_ctx *ctx, xml_node_t *node) +{ + char *str; + xml_node_t *envelope, *ret, *resp, *n; + CURLcode res; + long http = 0; + + ctx->last_err = NULL; + + wpa_printf(MSG_DEBUG, "SOAP: Sending message"); + envelope = soap_build_envelope(ctx->xml, node); + str = xml_node_to_str(ctx->xml, envelope); + xml_node_free(ctx->xml, envelope); + wpa_printf(MSG_MSGDUMP, "SOAP[%s]", str); + + curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDS, str); + free_curl_buf(ctx); + + res = curl_easy_perform(ctx->curl); + if (res != CURLE_OK) { + if (!ctx->last_err) + ctx->last_err = curl_easy_strerror(res); + wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s", + ctx->last_err); + os_free(str); + free_curl_buf(ctx); + return NULL; + } + os_free(str); + + curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &http); + wpa_printf(MSG_DEBUG, "SOAP: Server response code %ld", http); + if (http != 200) { + ctx->last_err = "HTTP download failed"; + wpa_printf(MSG_INFO, "HTTP download failed - code %ld", http); + free_curl_buf(ctx); + return NULL; + } + + if (ctx->curl_buf == NULL) + return NULL; + + wpa_printf(MSG_MSGDUMP, "Server response:\n%s", ctx->curl_buf); + resp = xml_node_from_buf(ctx->xml, ctx->curl_buf); + free_curl_buf(ctx); + if (resp == NULL) { + wpa_printf(MSG_INFO, "Could not parse SOAP response"); + ctx->last_err = "Could not parse SOAP response"; + return NULL; + } + + ret = soap_get_body(ctx->xml, resp); + if (ret == NULL) { + wpa_printf(MSG_INFO, "Could not get SOAP body"); + ctx->last_err = "Could not get SOAP body"; + return NULL; + } + + wpa_printf(MSG_DEBUG, "SOAP body localname: '%s'", + xml_node_get_localname(ctx->xml, ret)); + n = xml_node_copy(ctx->xml, ret); + xml_node_free(ctx->xml, resp); + + return n; +} + + +struct http_ctx * http_init_ctx(void *upper_ctx, struct xml_node_ctx *xml_ctx) +{ + struct http_ctx *ctx; + + ctx = os_zalloc(sizeof(*ctx)); + if (ctx == NULL) + return NULL; + ctx->ctx = upper_ctx; + ctx->xml = xml_ctx; + ctx->ocsp = OPTIONAL_OCSP; + + curl_global_init(CURL_GLOBAL_ALL); + + return ctx; +} + + +void http_ocsp_set(struct http_ctx *ctx, int val) +{ + if (val == 0) + ctx->ocsp = NO_OCSP; + else if (val == 1) + ctx->ocsp = OPTIONAL_OCSP; + if (val == 2) + ctx->ocsp = MANDATORY_OCSP; +} + + +void http_deinit_ctx(struct http_ctx *ctx) +{ + clear_curl(ctx); + os_free(ctx->curl_buf); + curl_global_cleanup(); + + os_free(ctx->svc_address); + os_free(ctx->svc_ca_fname); + os_free(ctx->svc_username); + os_free(ctx->svc_password); + os_free(ctx->svc_client_cert); + os_free(ctx->svc_client_key); + + os_free(ctx); +} + + +int http_download_file(struct http_ctx *ctx, const char *url, + const char *fname, const char *ca_fname) +{ + CURL *curl; + FILE *f; + CURLcode res; + long http = 0; + + ctx->last_err = NULL; + + wpa_printf(MSG_DEBUG, "curl: Download file from %s to %s (ca=%s)", + url, fname, ca_fname); + curl = curl_easy_init(); + if (curl == NULL) + return -1; + + f = fopen(fname, "wb"); + if (f == NULL) { + curl_easy_cleanup(curl); + return -1; + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + if (ca_fname) { + curl_easy_setopt(curl, CURLOPT_CAINFO, ca_fname); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); + } else { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + } + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_cb_debug); + curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, f); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + if (!ctx->last_err) + ctx->last_err = curl_easy_strerror(res); + wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s", + ctx->last_err); + curl_easy_cleanup(curl); + fclose(f); + return -1; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http); + wpa_printf(MSG_DEBUG, "curl: Server response code %ld", http); + if (http != 200) { + ctx->last_err = "HTTP download failed"; + wpa_printf(MSG_INFO, "HTTP download failed - code %ld", http); + curl_easy_cleanup(curl); + fclose(f); + return -1; + } + + curl_easy_cleanup(curl); + fclose(f); + + return 0; +} + + +char * http_post(struct http_ctx *ctx, const char *url, const char *data, + const char *content_type, const char *ext_hdr, + const char *ca_fname, + const char *username, const char *password, + const char *client_cert, const char *client_key, + size_t *resp_len) +{ + long http = 0; + CURLcode res; + char *ret; + CURL *curl; + struct curl_slist *curl_hdr = NULL; + + ctx->last_err = NULL; + wpa_printf(MSG_DEBUG, "curl: HTTP POST to %s", url); + curl = setup_curl_post(ctx, url, ca_fname, username, password, + client_cert, client_key); + if (curl == NULL) + return NULL; + + if (content_type) { + char ct[200]; + snprintf(ct, sizeof(ct), "Content-Type: %s", content_type); + curl_hdr = curl_slist_append(curl_hdr, ct); + } + if (ext_hdr) + curl_hdr = curl_slist_append(curl_hdr, ext_hdr); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_hdr); + + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); + free_curl_buf(ctx); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + if (!ctx->last_err) + ctx->last_err = curl_easy_strerror(res); + wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s", + ctx->last_err); + free_curl_buf(ctx); + return NULL; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http); + wpa_printf(MSG_DEBUG, "curl: Server response code %ld", http); + if (http != 200) { + ctx->last_err = "HTTP POST failed"; + wpa_printf(MSG_INFO, "HTTP POST failed - code %ld", http); + free_curl_buf(ctx); + return NULL; + } + + if (ctx->curl_buf == NULL) + return NULL; + + ret = ctx->curl_buf; + if (resp_len) + *resp_len = ctx->curl_buf_len; + ctx->curl_buf = NULL; + ctx->curl_buf_len = 0; + + wpa_printf(MSG_MSGDUMP, "Server response:\n%s", ret); + + return ret; +} + + +void http_set_cert_cb(struct http_ctx *ctx, + int (*cb)(void *ctx, struct http_cert *cert), + void *cb_ctx) +{ + ctx->cert_cb = cb; + ctx->cert_cb_ctx = cb_ctx; +} + + +const char * http_get_err(struct http_ctx *ctx) +{ + return ctx->last_err; +} diff --git a/src/utils/ip_addr.c b/src/utils/ip_addr.c index 3647c76..92a3590 100644 --- a/src/utils/ip_addr.c +++ b/src/utils/ip_addr.c @@ -33,30 +33,6 @@ const char * hostapd_ip_txt(const struct hostapd_ip_addr *addr, char *buf, } -int hostapd_ip_diff(struct hostapd_ip_addr *a, struct hostapd_ip_addr *b) -{ - if (a == NULL && b == NULL) - return 0; - if (a == NULL || b == NULL) - return 1; - - switch (a->af) { - case AF_INET: - if (a->u.v4.s_addr != b->u.v4.s_addr) - return 1; - break; -#ifdef CONFIG_IPV6 - case AF_INET6: - if (os_memcmp(&a->u.v6, &b->u.v6, sizeof(a->u.v6)) != 0) - return 1; - break; -#endif /* CONFIG_IPV6 */ - } - - return 0; -} - - int hostapd_parse_ip_addr(const char *txt, struct hostapd_ip_addr *addr) { #ifndef CONFIG_NATIVE_WINDOWS diff --git a/src/utils/ip_addr.h b/src/utils/ip_addr.h index 79ac20c..0670411 100644 --- a/src/utils/ip_addr.h +++ b/src/utils/ip_addr.h @@ -22,7 +22,6 @@ struct hostapd_ip_addr { const char * hostapd_ip_txt(const struct hostapd_ip_addr *addr, char *buf, size_t buflen); -int hostapd_ip_diff(struct hostapd_ip_addr *a, struct hostapd_ip_addr *b); int hostapd_parse_ip_addr(const char *txt, struct hostapd_ip_addr *addr); #endif /* IP_ADDR_H */ diff --git a/src/utils/os.h b/src/utils/os.h index 2e2350a..f019e26 100644 --- a/src/utils/os.h +++ b/src/utils/os.h @@ -240,6 +240,13 @@ int os_unsetenv(const char *name); char * os_readfile(const char *name, size_t *len); /** + * os_file_exists - Check whether the specified file exists + * @fname: Path and name of the file + * Returns: 1 if the file exists or 0 if not + */ +int os_file_exists(const char *fname); + +/** * os_zalloc - Allocate and zero memory * @size: Number of bytes to allocate * Returns: Pointer to allocated and zeroed memory or %NULL on failure @@ -549,6 +556,21 @@ static inline void * os_realloc_array(void *ptr, size_t nmemb, size_t size) return os_realloc(ptr, nmemb * size); } +/** + * os_remove_in_array - Remove a member from an array by index + * @ptr: Pointer to the array + * @nmemb: Current member count of the array + * @size: The size per member of the array + * @idx: Index of the member to be removed + */ +static inline void os_remove_in_array(void *ptr, size_t nmemb, size_t size, + size_t idx) +{ + if (idx < nmemb - 1) + os_memmove(((unsigned char *) ptr) + idx * size, + ((unsigned char *) ptr) + (idx + 1) * size, + (nmemb - idx - 1) * size); +} /** * os_strlcpy - Copy a string with size bound and NUL-termination diff --git a/src/utils/os_unix.c b/src/utils/os_unix.c index fa67fdf..008ec6b 100644 --- a/src/utils/os_unix.c +++ b/src/utils/os_unix.c @@ -407,6 +407,16 @@ char * os_readfile(const char *name, size_t *len) } +int os_file_exists(const char *fname) +{ + FILE *f = fopen(fname, "rb"); + if (f == NULL) + return 0; + fclose(f); + return 1; +} + + #ifndef WPA_TRACE void * os_zalloc(size_t size) { diff --git a/src/utils/pcsc_funcs.c b/src/utils/pcsc_funcs.c index ee90d25..ec06556 100644 --- a/src/utils/pcsc_funcs.c +++ b/src/utils/pcsc_funcs.c @@ -1237,6 +1237,7 @@ int scard_gsm_auth(struct scard_data *scard, const unsigned char *_rand, cmd[4] = 17; cmd[5] = 16; os_memcpy(cmd + 6, _rand, 16); + get_resp[0] = USIM_CLA; } len = sizeof(resp); ret = scard_transmit(scard, cmd, cmdlen, resp, &len); diff --git a/src/utils/platform.h b/src/utils/platform.h new file mode 100644 index 0000000..46cfe78 --- /dev/null +++ b/src/utils/platform.h @@ -0,0 +1,21 @@ +#ifndef PLATFORM_H +#define PLATFORM_H + +#include "includes.h" +#include "common.h" + +#define le16_to_cpu le_to_host16 +#define le32_to_cpu le_to_host32 + +#define get_unaligned(p) \ +({ \ + struct packed_dummy_struct { \ + typeof(*(p)) __val; \ + } __attribute__((packed)) *__ptr = (void *) (p); \ + \ + __ptr->__val; \ +}) +#define get_unaligned_le16(p) le16_to_cpu(get_unaligned((uint16_t *)(p))) +#define get_unaligned_le32(p) le32_to_cpu(get_unaligned((uint32_t *)(p))) + +#endif /* PLATFORM_H */ diff --git a/src/utils/radiotap.c b/src/utils/radiotap.c index 804473f..197a4af 100644 --- a/src/utils/radiotap.c +++ b/src/utils/radiotap.c @@ -2,6 +2,7 @@ * Radiotap parser * * Copyright 2007 Andy Green <andy@warmcat.com> + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -10,34 +11,44 @@ * Alternatively, this software may be distributed under the terms of BSD * license. * - * See README and COPYING for more details. - * - * - * Modified for userspace by Johannes Berg <johannes@sipsolutions.net> - * I only modified some things on top to ease syncing should bugs be found. + * See COPYING for more details. */ - -#include "includes.h" - -#include "common.h" #include "radiotap_iter.h" - -#define le16_to_cpu le_to_host16 -#define le32_to_cpu le_to_host32 -#define __le32 uint32_t -#define ulong unsigned long -#define unlikely(cond) (cond) -#define get_unaligned(p) \ -({ \ - struct packed_dummy_struct { \ - typeof(*(p)) __val; \ - } __attribute__((packed)) *__ptr = (void *) (p); \ - \ - __ptr->__val; \ -}) +#include "platform.h" /* function prototypes and related defs are in radiotap_iter.h */ +static const struct radiotap_align_size rtap_namespace_sizes[] = { + [IEEE80211_RADIOTAP_TSFT] = { .align = 8, .size = 8, }, + [IEEE80211_RADIOTAP_FLAGS] = { .align = 1, .size = 1, }, + [IEEE80211_RADIOTAP_RATE] = { .align = 1, .size = 1, }, + [IEEE80211_RADIOTAP_CHANNEL] = { .align = 2, .size = 4, }, + [IEEE80211_RADIOTAP_FHSS] = { .align = 2, .size = 2, }, + [IEEE80211_RADIOTAP_DBM_ANTSIGNAL] = { .align = 1, .size = 1, }, + [IEEE80211_RADIOTAP_DBM_ANTNOISE] = { .align = 1, .size = 1, }, + [IEEE80211_RADIOTAP_LOCK_QUALITY] = { .align = 2, .size = 2, }, + [IEEE80211_RADIOTAP_TX_ATTENUATION] = { .align = 2, .size = 2, }, + [IEEE80211_RADIOTAP_DB_TX_ATTENUATION] = { .align = 2, .size = 2, }, + [IEEE80211_RADIOTAP_DBM_TX_POWER] = { .align = 1, .size = 1, }, + [IEEE80211_RADIOTAP_ANTENNA] = { .align = 1, .size = 1, }, + [IEEE80211_RADIOTAP_DB_ANTSIGNAL] = { .align = 1, .size = 1, }, + [IEEE80211_RADIOTAP_DB_ANTNOISE] = { .align = 1, .size = 1, }, + [IEEE80211_RADIOTAP_RX_FLAGS] = { .align = 2, .size = 2, }, + [IEEE80211_RADIOTAP_TX_FLAGS] = { .align = 2, .size = 2, }, + [IEEE80211_RADIOTAP_RTS_RETRIES] = { .align = 1, .size = 1, }, + [IEEE80211_RADIOTAP_DATA_RETRIES] = { .align = 1, .size = 1, }, + [IEEE80211_RADIOTAP_MCS] = { .align = 1, .size = 3, }, + [IEEE80211_RADIOTAP_AMPDU_STATUS] = { .align = 4, .size = 8, }, + /* + * add more here as they are defined in radiotap.h + */ +}; + +static const struct ieee80211_radiotap_namespace radiotap_ns = { + .n_bits = sizeof(rtap_namespace_sizes) / sizeof(rtap_namespace_sizes[0]), + .align_size = rtap_namespace_sizes, +}; + /** * ieee80211_radiotap_iterator_init - radiotap parser iterator initialization * @iterator: radiotap_iterator to initialize @@ -73,38 +84,52 @@ * get_unaligned((type *)iterator.this_arg) to dereference * iterator.this_arg for type "type" safely on all arches. * - * Example code: - * See Documentation/networking/radiotap-headers.txt + * Example code: parse.c */ int ieee80211_radiotap_iterator_init( - struct ieee80211_radiotap_iterator *iterator, - struct ieee80211_radiotap_header *radiotap_header, - int max_length) + struct ieee80211_radiotap_iterator *iterator, + struct ieee80211_radiotap_header *radiotap_header, + int max_length, const struct ieee80211_radiotap_vendor_namespaces *vns) { + /* must at least have the radiotap header */ + if (max_length < (int)sizeof(struct ieee80211_radiotap_header)) + return -EINVAL; + /* Linux only supports version 0 radiotap format */ if (radiotap_header->it_version) return -EINVAL; /* sanity check for allowed length and radiotap length field */ - if (max_length < le16_to_cpu(get_unaligned(&radiotap_header->it_len))) + if (max_length < get_unaligned_le16(&radiotap_header->it_len)) return -EINVAL; - iterator->rtheader = radiotap_header; - iterator->max_length = le16_to_cpu(get_unaligned( - &radiotap_header->it_len)); - iterator->arg_index = 0; - iterator->bitmap_shifter = le32_to_cpu(get_unaligned( - &radiotap_header->it_present)); - iterator->arg = (u8 *)radiotap_header + sizeof(*radiotap_header); - iterator->this_arg = NULL; + iterator->_rtheader = radiotap_header; + iterator->_max_length = get_unaligned_le16(&radiotap_header->it_len); + iterator->_arg_index = 0; + iterator->_bitmap_shifter = get_unaligned_le32(&radiotap_header->it_present); + iterator->_arg = (uint8_t *)radiotap_header + sizeof(*radiotap_header); + iterator->_reset_on_ext = 0; + iterator->_next_bitmap = &radiotap_header->it_present; + iterator->_next_bitmap++; + iterator->_vns = vns; + iterator->current_namespace = &radiotap_ns; + iterator->is_radiotap_ns = 1; +#ifdef RADIOTAP_SUPPORT_OVERRIDES + iterator->n_overrides = 0; + iterator->overrides = NULL; +#endif /* find payload start allowing for extended bitmap(s) */ - if (unlikely(iterator->bitmap_shifter & (1<<IEEE80211_RADIOTAP_EXT))) { - while (le32_to_cpu(get_unaligned((__le32 *)iterator->arg)) & - (1<<IEEE80211_RADIOTAP_EXT)) { - iterator->arg += sizeof(u32); + if (iterator->_bitmap_shifter & (1<<IEEE80211_RADIOTAP_EXT)) { + if ((unsigned long)iterator->_arg - + (unsigned long)iterator->_rtheader + sizeof(uint32_t) > + (unsigned long)iterator->_max_length) + return -EINVAL; + while (get_unaligned_le32(iterator->_arg) & + (1 << IEEE80211_RADIOTAP_EXT)) { + iterator->_arg += sizeof(uint32_t); /* * check for insanity where the present bitmaps @@ -112,12 +137,14 @@ int ieee80211_radiotap_iterator_init( * stated radiotap header length */ - if (((ulong)iterator->arg - (ulong)iterator->rtheader) - > (ulong)iterator->max_length) + if ((unsigned long)iterator->_arg - + (unsigned long)iterator->_rtheader + + sizeof(uint32_t) > + (unsigned long)iterator->_max_length) return -EINVAL; } - iterator->arg += sizeof(u32); + iterator->_arg += sizeof(uint32_t); /* * no need to check again for blowing past stated radiotap @@ -126,11 +153,57 @@ int ieee80211_radiotap_iterator_init( */ } + iterator->this_arg = iterator->_arg; + /* we are all initialized happily */ return 0; } +static void find_ns(struct ieee80211_radiotap_iterator *iterator, + uint32_t oui, uint8_t subns) +{ + int i; + + iterator->current_namespace = NULL; + + if (!iterator->_vns) + return; + + for (i = 0; i < iterator->_vns->n_ns; i++) { + if (iterator->_vns->ns[i].oui != oui) + continue; + if (iterator->_vns->ns[i].subns != subns) + continue; + + iterator->current_namespace = &iterator->_vns->ns[i]; + break; + } +} + +#ifdef RADIOTAP_SUPPORT_OVERRIDES +static int find_override(struct ieee80211_radiotap_iterator *iterator, + int *align, int *size) +{ + int i; + + if (!iterator->overrides) + return 0; + + for (i = 0; i < iterator->n_overrides; i++) { + if (iterator->_arg_index == iterator->overrides[i].field) { + *align = iterator->overrides[i].align; + *size = iterator->overrides[i].size; + if (!*align) /* erroneous override */ + return 0; + return 1; + } + } + + return 0; +} +#endif + /** * ieee80211_radiotap_iterator_next - return next radiotap parser iterator arg @@ -156,99 +229,106 @@ int ieee80211_radiotap_iterator_init( */ int ieee80211_radiotap_iterator_next( - struct ieee80211_radiotap_iterator *iterator) + struct ieee80211_radiotap_iterator *iterator) { - - /* - * small length lookup table for all radiotap types we heard of - * starting from b0 in the bitmap, so we can walk the payload - * area of the radiotap header - * - * There is a requirement to pad args, so that args - * of a given length must begin at a boundary of that length - * -- but note that compound args are allowed (eg, 2 x u16 - * for IEEE80211_RADIOTAP_CHANNEL) so total arg length is not - * a reliable indicator of alignment requirement. - * - * upper nybble: content alignment for arg - * lower nybble: content length for arg - */ - - static const u8 rt_sizes[] = { - [IEEE80211_RADIOTAP_TSFT] = 0x88, - [IEEE80211_RADIOTAP_FLAGS] = 0x11, - [IEEE80211_RADIOTAP_RATE] = 0x11, - [IEEE80211_RADIOTAP_CHANNEL] = 0x24, - [IEEE80211_RADIOTAP_FHSS] = 0x22, - [IEEE80211_RADIOTAP_DBM_ANTSIGNAL] = 0x11, - [IEEE80211_RADIOTAP_DBM_ANTNOISE] = 0x11, - [IEEE80211_RADIOTAP_LOCK_QUALITY] = 0x22, - [IEEE80211_RADIOTAP_TX_ATTENUATION] = 0x22, - [IEEE80211_RADIOTAP_DB_TX_ATTENUATION] = 0x22, - [IEEE80211_RADIOTAP_DBM_TX_POWER] = 0x11, - [IEEE80211_RADIOTAP_ANTENNA] = 0x11, - [IEEE80211_RADIOTAP_DB_ANTSIGNAL] = 0x11, - [IEEE80211_RADIOTAP_DB_ANTNOISE] = 0x11, - [IEEE80211_RADIOTAP_RX_FLAGS] = 0x22, - [IEEE80211_RADIOTAP_TX_FLAGS] = 0x22, - [IEEE80211_RADIOTAP_RTS_RETRIES] = 0x11, - [IEEE80211_RADIOTAP_DATA_RETRIES] = 0x11, - /* - * add more here as they are defined in - * include/net/ieee80211_radiotap.h - */ - }; - - /* - * for every radiotap entry we can at - * least skip (by knowing the length)... - */ - - while (iterator->arg_index < (int) sizeof(rt_sizes)) { + while (1) { int hit = 0; - int pad; + int pad, align, size, subns; + uint32_t oui; + + /* if no more EXT bits, that's it */ + if ((iterator->_arg_index % 32) == IEEE80211_RADIOTAP_EXT && + !(iterator->_bitmap_shifter & 1)) + return -ENOENT; - if (!(iterator->bitmap_shifter & 1)) + if (!(iterator->_bitmap_shifter & 1)) goto next_entry; /* arg not present */ + /* get alignment/size of data */ + switch (iterator->_arg_index % 32) { + case IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE: + case IEEE80211_RADIOTAP_EXT: + align = 1; + size = 0; + break; + case IEEE80211_RADIOTAP_VENDOR_NAMESPACE: + align = 2; + size = 6; + break; + default: +#ifdef RADIOTAP_SUPPORT_OVERRIDES + if (find_override(iterator, &align, &size)) { + /* all set */ + } else +#endif + if (!iterator->current_namespace || + iterator->_arg_index >= iterator->current_namespace->n_bits) { + if (iterator->current_namespace == &radiotap_ns) + return -ENOENT; + align = 0; + } else { + align = iterator->current_namespace->align_size[iterator->_arg_index].align; + size = iterator->current_namespace->align_size[iterator->_arg_index].size; + } + if (!align) { + /* skip all subsequent data */ + iterator->_arg = iterator->_next_ns_data; + /* give up on this namespace */ + iterator->current_namespace = NULL; + goto next_entry; + } + break; + } + /* * arg is present, account for alignment padding - * 8-bit args can be at any alignment - * 16-bit args must start on 16-bit boundary - * 32-bit args must start on 32-bit boundary - * 64-bit args must start on 64-bit boundary - * - * note that total arg size can differ from alignment of - * elements inside arg, so we use upper nybble of length - * table to base alignment on * - * also note: these alignments are ** relative to the - * start of the radiotap header **. There is no guarantee + * Note that these alignments are relative to the start + * of the radiotap header. There is no guarantee * that the radiotap header itself is aligned on any * kind of boundary. * - * the above is why get_unaligned() is used to dereference - * multibyte elements from the radiotap area + * The above is why get_unaligned() is used to dereference + * multibyte elements from the radiotap area. */ - pad = (((ulong)iterator->arg) - - ((ulong)iterator->rtheader)) & - ((rt_sizes[iterator->arg_index] >> 4) - 1); + pad = ((unsigned long)iterator->_arg - + (unsigned long)iterator->_rtheader) & (align - 1); if (pad) - iterator->arg += - (rt_sizes[iterator->arg_index] >> 4) - pad; + iterator->_arg += align - pad; + + if (iterator->_arg_index % 32 == IEEE80211_RADIOTAP_VENDOR_NAMESPACE) { + int vnslen; + + if ((unsigned long)iterator->_arg + size - + (unsigned long)iterator->_rtheader > + (unsigned long)iterator->_max_length) + return -EINVAL; + + oui = (*iterator->_arg << 16) | + (*(iterator->_arg + 1) << 8) | + *(iterator->_arg + 2); + subns = *(iterator->_arg + 3); + + find_ns(iterator, oui, subns); + + vnslen = get_unaligned_le16(iterator->_arg + 4); + iterator->_next_ns_data = iterator->_arg + size + vnslen; + if (!iterator->current_namespace) + size += vnslen; + } /* * this is what we will return to user, but we need to * move on first so next call has something fresh to test */ - iterator->this_arg_index = iterator->arg_index; - iterator->this_arg = iterator->arg; - hit = 1; + iterator->this_arg_index = iterator->_arg_index; + iterator->this_arg = iterator->_arg; + iterator->this_arg_size = size; /* internally move on the size of this arg */ - iterator->arg += rt_sizes[iterator->arg_index] & 0x0f; + iterator->_arg += size; /* * check for insanity where we are given a bitmap that @@ -257,31 +337,57 @@ int ieee80211_radiotap_iterator_next( * max_length on the last arg, never exceeding it. */ - if (((ulong)iterator->arg - (ulong)iterator->rtheader) > - (ulong) iterator->max_length) + if ((unsigned long)iterator->_arg - + (unsigned long)iterator->_rtheader > + (unsigned long)iterator->_max_length) return -EINVAL; - next_entry: - iterator->arg_index++; - if (unlikely((iterator->arg_index & 31) == 0)) { - /* completed current u32 bitmap */ - if (iterator->bitmap_shifter & 1) { - /* b31 was set, there is more */ - /* move to next u32 bitmap */ - iterator->bitmap_shifter = le32_to_cpu( - get_unaligned(iterator->next_bitmap)); - iterator->next_bitmap++; - } else - /* no more bitmaps: end */ - iterator->arg_index = sizeof(rt_sizes); - } else /* just try the next bit */ - iterator->bitmap_shifter >>= 1; + /* these special ones are valid in each bitmap word */ + switch (iterator->_arg_index % 32) { + case IEEE80211_RADIOTAP_VENDOR_NAMESPACE: + iterator->_reset_on_ext = 1; + + iterator->is_radiotap_ns = 0; + /* + * If parser didn't register this vendor + * namespace with us, allow it to show it + * as 'raw. Do do that, set argument index + * to vendor namespace. + */ + iterator->this_arg_index = + IEEE80211_RADIOTAP_VENDOR_NAMESPACE; + if (!iterator->current_namespace) + hit = 1; + goto next_entry; + case IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE: + iterator->_reset_on_ext = 1; + iterator->current_namespace = &radiotap_ns; + iterator->is_radiotap_ns = 1; + goto next_entry; + case IEEE80211_RADIOTAP_EXT: + /* + * bit 31 was set, there is more + * -- move to next u32 bitmap + */ + iterator->_bitmap_shifter = + get_unaligned_le32(iterator->_next_bitmap); + iterator->_next_bitmap++; + if (iterator->_reset_on_ext) + iterator->_arg_index = 0; + else + iterator->_arg_index++; + iterator->_reset_on_ext = 0; + break; + default: + /* we've got a hit! */ + hit = 1; + next_entry: + iterator->_bitmap_shifter >>= 1; + iterator->_arg_index++; + } /* if we found a valid arg earlier, return it now */ if (hit) return 0; } - - /* we don't know how to handle any more args, we're done */ - return -ENOENT; } diff --git a/src/utils/radiotap.h b/src/utils/radiotap.h index 137288f..0572e7c 100644 --- a/src/utils/radiotap.h +++ b/src/utils/radiotap.h @@ -1,6 +1,3 @@ -/* $FreeBSD: src/sys/net80211/ieee80211_radiotap.h,v 1.5 2005/01/22 20:12:05 sam Exp $ */ -/* $NetBSD: ieee80211_radiotap.h,v 1.11 2005/06/22 06:16:02 dyoung Exp $ */ - /*- * Copyright (c) 2003, 2004 David Young. All rights reserved. * @@ -178,6 +175,14 @@ struct ieee80211_radiotap_header { * * Number of unicast retries a transmitted frame used. * + * IEEE80211_RADIOTAP_MCS u8, u8, u8 unitless + * + * Contains a bitmap of known fields/flags, the flags, and + * the MCS index. + * + * IEEE80211_RADIOTAP_AMPDU_STATUS u32, u16, u8, u8 unitlesss + * + * Contains the AMPDU information for the subframe. */ enum ieee80211_radiotap_type { IEEE80211_RADIOTAP_TSFT = 0, @@ -198,6 +203,13 @@ enum ieee80211_radiotap_type { IEEE80211_RADIOTAP_TX_FLAGS = 15, IEEE80211_RADIOTAP_RTS_RETRIES = 16, IEEE80211_RADIOTAP_DATA_RETRIES = 17, + + IEEE80211_RADIOTAP_MCS = 19, + IEEE80211_RADIOTAP_AMPDU_STATUS = 20, + + /* valid in every it_present bitmap, even vendor namespaces */ + IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE = 29, + IEEE80211_RADIOTAP_VENDOR_NAMESPACE = 30, IEEE80211_RADIOTAP_EXT = 31 }; @@ -230,8 +242,10 @@ enum ieee80211_radiotap_type { * 802.11 header and payload * (to 32-bit boundary) */ +#define IEEE80211_RADIOTAP_F_BADFCS 0x40 /* frame failed FCS check */ + /* For IEEE80211_RADIOTAP_RX_FLAGS */ -#define IEEE80211_RADIOTAP_F_RX_BADFCS 0x0001 /* frame failed crc check */ +#define IEEE80211_RADIOTAP_F_RX_BADPLCP 0x0002 /* bad PLCP */ /* For IEEE80211_RADIOTAP_TX_FLAGS */ #define IEEE80211_RADIOTAP_F_TX_FAIL 0x0001 /* failed due to excessive @@ -240,4 +254,38 @@ enum ieee80211_radiotap_type { #define IEEE80211_RADIOTAP_F_TX_RTS 0x0004 /* used rts/cts handshake */ #define IEEE80211_RADIOTAP_F_TX_NOACK 0x0008 /* don't expect an ACK */ +/* For IEEE80211_RADIOTAP_AMPDU_STATUS */ +#define IEEE80211_RADIOTAP_AMPDU_REPORT_ZEROLEN 0x0001 +#define IEEE80211_RADIOTAP_AMPDU_IS_ZEROLEN 0x0002 +#define IEEE80211_RADIOTAP_AMPDU_LAST_KNOWN 0x0004 +#define IEEE80211_RADIOTAP_AMPDU_IS_LAST 0x0008 +#define IEEE80211_RADIOTAP_AMPDU_DELIM_CRC_ERR 0x0010 +#define IEEE80211_RADIOTAP_AMPDU_DELIM_CRC_KNOWN 0x0020 + +/* For IEEE80211_RADIOTAP_MCS */ +#define IEEE80211_RADIOTAP_MCS_HAVE_BW 0x01 +#define IEEE80211_RADIOTAP_MCS_HAVE_MCS 0x02 +#define IEEE80211_RADIOTAP_MCS_HAVE_GI 0x04 +#define IEEE80211_RADIOTAP_MCS_HAVE_FMT 0x08 +#define IEEE80211_RADIOTAP_MCS_HAVE_FEC 0x10 +#define IEEE80211_RADIOTAP_MCS_HAVE_STBC 0x20 +#define IEEE80211_RADIOTAP_MCS_HAVE_NESS 0x40 +#define IEEE80211_RADIOTAP_MCS_NESS_BIT1 0x80 + + +#define IEEE80211_RADIOTAP_MCS_BW_MASK 0x03 +#define IEEE80211_RADIOTAP_MCS_BW_20 0 +#define IEEE80211_RADIOTAP_MCS_BW_40 1 +#define IEEE80211_RADIOTAP_MCS_BW_20L 2 +#define IEEE80211_RADIOTAP_MCS_BW_20U 3 +#define IEEE80211_RADIOTAP_MCS_SGI 0x04 +#define IEEE80211_RADIOTAP_MCS_FMT_GF 0x08 +#define IEEE80211_RADIOTAP_MCS_FEC_LDPC 0x10 +#define IEEE80211_RADIOTAP_MCS_STBC_MASK 0x60 +#define IEEE80211_RADIOTAP_MCS_STBC_SHIFT 5 +#define IEEE80211_RADIOTAP_MCS_STBC_1 1 +#define IEEE80211_RADIOTAP_MCS_STBC_2 2 +#define IEEE80211_RADIOTAP_MCS_STBC_3 3 +#define IEEE80211_RADIOTAP_MCS_NESS_BIT0 0x80 + #endif /* IEEE80211_RADIOTAP_H */ diff --git a/src/utils/radiotap_iter.h b/src/utils/radiotap_iter.h index 2e0e872..b768c85 100644 --- a/src/utils/radiotap_iter.h +++ b/src/utils/radiotap_iter.h @@ -1,56 +1,96 @@ -/* - * Radiotap parser - * - * Copyright 2007 Andy Green <andy@warmcat.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Alternatively, this software may be distributed under the terms of BSD - * license. - * - * See README and COPYING for more details. - */ - #ifndef __RADIOTAP_ITER_H #define __RADIOTAP_ITER_H +#include <stdint.h> #include "radiotap.h" /* Radiotap header iteration * implemented in radiotap.c */ + +struct radiotap_override { + uint8_t field; + uint8_t align:4, size:4; +}; + +struct radiotap_align_size { + uint8_t align:4, size:4; +}; + +struct ieee80211_radiotap_namespace { + const struct radiotap_align_size *align_size; + int n_bits; + uint32_t oui; + uint8_t subns; +}; + +struct ieee80211_radiotap_vendor_namespaces { + const struct ieee80211_radiotap_namespace *ns; + int n_ns; +}; + /** * struct ieee80211_radiotap_iterator - tracks walk thru present radiotap args - * @rtheader: pointer to the radiotap header we are walking through - * @max_length: length of radiotap header in cpu byte ordering - * @this_arg_index: IEEE80211_RADIOTAP_... index of current arg - * @this_arg: pointer to current radiotap arg - * @arg_index: internal next argument index - * @arg: internal next argument pointer - * @next_bitmap: internal pointer to next present u32 - * @bitmap_shifter: internal shifter for curr u32 bitmap, b0 set == arg present + * @this_arg_index: index of current arg, valid after each successful call + * to ieee80211_radiotap_iterator_next() + * @this_arg: pointer to current radiotap arg; it is valid after each + * call to ieee80211_radiotap_iterator_next() but also after + * ieee80211_radiotap_iterator_init() where it will point to + * the beginning of the actual data portion + * @this_arg_size: length of the current arg, for convenience + * @current_namespace: pointer to the current namespace definition + * (or internally %NULL if the current namespace is unknown) + * @is_radiotap_ns: indicates whether the current namespace is the default + * radiotap namespace or not + * + * @overrides: override standard radiotap fields + * @n_overrides: number of overrides + * + * @_rtheader: pointer to the radiotap header we are walking through + * @_max_length: length of radiotap header in cpu byte ordering + * @_arg_index: next argument index + * @_arg: next argument pointer + * @_next_bitmap: internal pointer to next present u32 + * @_bitmap_shifter: internal shifter for curr u32 bitmap, b0 set == arg present + * @_vns: vendor namespace definitions + * @_next_ns_data: beginning of the next namespace's data + * @_reset_on_ext: internal; reset the arg index to 0 when going to the + * next bitmap word + * + * Describes the radiotap parser state. Fields prefixed with an underscore + * must not be used by users of the parser, only by the parser internally. */ struct ieee80211_radiotap_iterator { - struct ieee80211_radiotap_header *rtheader; - int max_length; - int this_arg_index; + struct ieee80211_radiotap_header *_rtheader; + const struct ieee80211_radiotap_vendor_namespaces *_vns; + const struct ieee80211_radiotap_namespace *current_namespace; + + unsigned char *_arg, *_next_ns_data; + uint32_t *_next_bitmap; + unsigned char *this_arg; +#ifdef RADIOTAP_SUPPORT_OVERRIDES + const struct radiotap_override *overrides; + int n_overrides; +#endif + int this_arg_index; + int this_arg_size; + + int is_radiotap_ns; - int arg_index; - unsigned char *arg; - uint32_t *next_bitmap; - uint32_t bitmap_shifter; + int _max_length; + int _arg_index; + uint32_t _bitmap_shifter; + int _reset_on_ext; }; extern int ieee80211_radiotap_iterator_init( - struct ieee80211_radiotap_iterator *iterator, - struct ieee80211_radiotap_header *radiotap_header, - int max_length); + struct ieee80211_radiotap_iterator *iterator, + struct ieee80211_radiotap_header *radiotap_header, + int max_length, const struct ieee80211_radiotap_vendor_namespaces *vns); extern int ieee80211_radiotap_iterator_next( - struct ieee80211_radiotap_iterator *iterator); + struct ieee80211_radiotap_iterator *iterator); #endif /* __RADIOTAP_ITER_H */ diff --git a/src/utils/trace.c b/src/utils/trace.c index 6795d41..6044f5f 100644 --- a/src/utils/trace.c +++ b/src/utils/trace.c @@ -18,11 +18,9 @@ static struct dl_list active_references = #ifdef WPA_TRACE_BFD #include <bfd.h> -#ifdef __linux__ -#include <demangle.h> -#else /* __linux__ */ -#include <libiberty/demangle.h> -#endif /* __linux__ */ + +#define DMGL_PARAMS (1 << 0) +#define DMGL_ANSI (1 << 1) static char *prg_fname = NULL; static bfd *cached_abfd = NULL; @@ -187,6 +185,7 @@ static void wpa_trace_bfd_addr(void *pc) wpa_printf(MSG_INFO, " %s() %s:%u", name, filename, data.line); free(aname); + aname = NULL; data.found = bfd_find_inliner_info(abfd, &data.filename, &data.function, &data.line); diff --git a/src/utils/utils_module_tests.c b/src/utils/utils_module_tests.c new file mode 100644 index 0000000..9a9ec40 --- /dev/null +++ b/src/utils/utils_module_tests.c @@ -0,0 +1,266 @@ +/* + * utils module tests + * Copyright (c) 2014, Jouni Malinen <j@w1.fi> + * + * 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/bitfield.h" +#include "utils/ext_password.h" +#include "utils/trace.h" + + +struct printf_test_data { + u8 *data; + size_t len; + char *encoded; +}; + +static const struct printf_test_data printf_tests[] = { + { (u8 *) "abcde", 5, "abcde" }, + { (u8 *) "a\0b\nc\ed\re\tf\"\\", 13, "a\\0b\\nc\\ed\\re\\tf\\\"\\\\" }, + { (u8 *) "\x00\x31\x00\x32\x00\x39", 6, "\\x001\\0002\\09" }, + { (u8 *) "\n\n\n", 3, "\n\12\x0a" }, + { (u8 *) "\303\245\303\244\303\266\303\205\303\204\303\226", 12, + "\\xc3\\xa5\xc3\\xa4\\xc3\\xb6\\xc3\\x85\\xc3\\x84\\xc3\\x96" }, + { (u8 *) "\303\245\303\244\303\266\303\205\303\204\303\226", 12, + "\\303\\245\\303\\244\\303\\266\\303\\205\\303\\204\\303\\226" }, + { (u8 *) "\xe5\xe4\xf6\xc5\xc4\xd6", 6, + "\\xe5\\xe4\\xf6\\xc5\\xc4\\xd6" }, + { NULL, 0, NULL } +}; + + +static int printf_encode_decode_tests(void) +{ + int i; + size_t binlen; + char buf[100]; + u8 bin[100]; + int errors = 0; + + wpa_printf(MSG_INFO, "printf encode/decode tests"); + + for (i = 0; printf_tests[i].data; i++) { + const struct printf_test_data *test = &printf_tests[i]; + printf_encode(buf, sizeof(buf), test->data, test->len); + wpa_printf(MSG_INFO, "%d: -> \"%s\"", i, buf); + + binlen = printf_decode(bin, sizeof(bin), buf); + if (binlen != test->len || + os_memcmp(bin, test->data, binlen) != 0) { + wpa_hexdump(MSG_ERROR, "Error in decoding#1", + bin, binlen); + errors++; + } + + binlen = printf_decode(bin, sizeof(bin), test->encoded); + if (binlen != test->len || + os_memcmp(bin, test->data, binlen) != 0) { + wpa_hexdump(MSG_ERROR, "Error in decoding#2", + bin, binlen); + errors++; + } + } + + buf[5] = 'A'; + printf_encode(buf, 5, (const u8 *) "abcde", 5); + if (buf[5] != 'A') { + wpa_printf(MSG_ERROR, "Error in bounds checking#1"); + errors++; + } + + for (i = 5; i < 10; i++) { + buf[i] = 'A'; + printf_encode(buf, i, (const u8 *) "\xdd\xdd\xdd\xdd\xdd", 5); + if (buf[i] != 'A') { + wpa_printf(MSG_ERROR, "Error in bounds checking#2(%d)", + i); + errors++; + } + } + + if (errors) { + wpa_printf(MSG_ERROR, "%d printf test(s) failed", errors); + return -1; + } + + return 0; +} + + +static int bitfield_tests(void) +{ + struct bitfield *bf; + int i; + int errors = 0; + + wpa_printf(MSG_INFO, "bitfield tests"); + + bf = bitfield_alloc(123); + if (bf == NULL) + return -1; + + for (i = 0; i < 123; i++) { + if (bitfield_is_set(bf, i) || bitfield_is_set(bf, i + 1)) + errors++; + if (i > 0 && bitfield_is_set(bf, i - 1)) + errors++; + bitfield_set(bf, i); + if (!bitfield_is_set(bf, i)) + errors++; + bitfield_clear(bf, i); + if (bitfield_is_set(bf, i)) + errors++; + } + + for (i = 123; i < 200; i++) { + if (bitfield_is_set(bf, i) || bitfield_is_set(bf, i + 1)) + errors++; + if (i > 0 && bitfield_is_set(bf, i - 1)) + errors++; + bitfield_set(bf, i); + if (bitfield_is_set(bf, i)) + errors++; + bitfield_clear(bf, i); + if (bitfield_is_set(bf, i)) + errors++; + } + + for (i = 0; i < 123; i++) { + if (bitfield_is_set(bf, i) || bitfield_is_set(bf, i + 1)) + errors++; + bitfield_set(bf, i); + if (!bitfield_is_set(bf, i)) + errors++; + } + + for (i = 0; i < 123; i++) { + if (!bitfield_is_set(bf, i)) + errors++; + bitfield_clear(bf, i); + if (bitfield_is_set(bf, i)) + errors++; + } + + for (i = 0; i < 123; i++) { + if (bitfield_get_first_zero(bf) != i) + errors++; + bitfield_set(bf, i); + } + if (bitfield_get_first_zero(bf) != -1) + errors++; + for (i = 0; i < 123; i++) { + if (!bitfield_is_set(bf, i)) + errors++; + bitfield_clear(bf, i); + if (bitfield_get_first_zero(bf) != i) + errors++; + bitfield_set(bf, i); + } + if (bitfield_get_first_zero(bf) != -1) + errors++; + + bitfield_free(bf); + + if (errors) { + wpa_printf(MSG_ERROR, "%d bitfield test(s) failed", errors); + return -1; + } + + return 0; +} + + +static int int_array_tests(void) +{ + int test1[] = { 1, 2, 3, 4, 5, 6, 0 }; + int test2[] = { 1, -1, 0 }; + int test3[] = { 1, 1, 1, -1, 2, 3, 4, 1, 2, 0 }; + int test3_res[] = { -1, 1, 2, 3, 4, 0 }; + int errors = 0; + int len; + + wpa_printf(MSG_INFO, "int_array tests"); + + if (int_array_len(test1) != 6 || + int_array_len(test2) != 2) + errors++; + + int_array_sort_unique(test3); + len = int_array_len(test3_res); + if (int_array_len(test3) != len) + errors++; + else if (os_memcmp(test3, test3_res, len * sizeof(int)) != 0) + errors++; + + if (errors) { + wpa_printf(MSG_ERROR, "%d int_array test(s) failed", errors); + return -1; + } + + return 0; +} + + +static int ext_password_tests(void) +{ + struct ext_password_data *data; + int ret = 0; + struct wpabuf *pw; + + wpa_printf(MSG_INFO, "ext_password tests"); + + data = ext_password_init("unknown", "foo"); + if (data != NULL) + return -1; + + data = ext_password_init("test", NULL); + if (data == NULL) + return -1; + pw = ext_password_get(data, "foo"); + if (pw != NULL) + ret = -1; + ext_password_free(pw); + + ext_password_deinit(data); + + pw = ext_password_get(NULL, "foo"); + if (pw != NULL) + ret = -1; + ext_password_free(pw); + + return ret; +} + + +static int trace_tests(void) +{ + wpa_printf(MSG_INFO, "trace tests"); + + wpa_trace_show("test backtrace"); + wpa_trace_dump_funcname("test funcname", trace_tests); + + return 0; +} + + +int utils_module_tests(void) +{ + int ret = 0; + + wpa_printf(MSG_INFO, "utils module tests"); + + if (printf_encode_decode_tests() < 0 || + ext_password_tests() < 0 || + trace_tests() < 0 || + bitfield_tests() < 0 || + int_array_tests() < 0) + ret = -1; + + return ret; +} diff --git a/src/utils/wpa_debug.c b/src/utils/wpa_debug.c index 7846c1e..647f6b4 100644 --- a/src/utils/wpa_debug.c +++ b/src/utils/wpa_debug.c @@ -596,10 +596,14 @@ void wpa_msg(void *ctx, int level, const char *fmt, ...) { va_list ap; char *buf; - const int buflen = 2048; + int buflen; int len; char prefix[130]; + va_start(ap, fmt); + buflen = vsnprintf(NULL, 0, fmt, ap) + 1; + va_end(ap); + buf = os_malloc(buflen); if (buf == NULL) { wpa_printf(MSG_ERROR, "wpa_msg: Failed to allocate message " @@ -630,12 +634,16 @@ void wpa_msg_ctrl(void *ctx, int level, const char *fmt, ...) { va_list ap; char *buf; - const int buflen = 2048; + int buflen; int len; if (!wpa_msg_cb) return; + va_start(ap, fmt); + buflen = vsnprintf(NULL, 0, fmt, ap) + 1; + va_end(ap); + buf = os_malloc(buflen); if (buf == NULL) { wpa_printf(MSG_ERROR, "wpa_msg_ctrl: Failed to allocate " @@ -654,9 +662,13 @@ void wpa_msg_global(void *ctx, int level, const char *fmt, ...) { va_list ap; char *buf; - const int buflen = 2048; + int buflen; int len; + va_start(ap, fmt); + buflen = vsnprintf(NULL, 0, fmt, ap) + 1; + va_end(ap); + buf = os_malloc(buflen); if (buf == NULL) { wpa_printf(MSG_ERROR, "wpa_msg_global: Failed to allocate " @@ -677,9 +689,13 @@ void wpa_msg_no_global(void *ctx, int level, const char *fmt, ...) { va_list ap; char *buf; - const int buflen = 2048; + int buflen; int len; + va_start(ap, fmt); + buflen = vsnprintf(NULL, 0, fmt, ap) + 1; + va_end(ap); + buf = os_malloc(buflen); if (buf == NULL) { wpa_printf(MSG_ERROR, "wpa_msg_no_global: Failed to allocate " @@ -712,9 +728,13 @@ void hostapd_logger(void *ctx, const u8 *addr, unsigned int module, int level, { va_list ap; char *buf; - const int buflen = 2048; + int buflen; int len; + va_start(ap, fmt); + buflen = vsnprintf(NULL, 0, fmt, ap) + 1; + va_end(ap); + buf = os_malloc(buflen); if (buf == NULL) { wpa_printf(MSG_ERROR, "hostapd_logger: Failed to allocate " diff --git a/src/utils/xml-utils.c b/src/utils/xml-utils.c new file mode 100644 index 0000000..4916d29 --- /dev/null +++ b/src/utils/xml-utils.c @@ -0,0 +1,471 @@ +/* + * Generic XML helper functions + * Copyright (c) 2012-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 "xml-utils.h" + + +static xml_node_t * get_node_uri_iter(struct xml_node_ctx *ctx, + xml_node_t *root, char *uri) +{ + char *end; + xml_node_t *node; + const char *name; + + end = strchr(uri, '/'); + if (end) + *end++ = '\0'; + + node = root; + xml_node_for_each_sibling(ctx, node) { + xml_node_for_each_check(ctx, node); + name = xml_node_get_localname(ctx, node); + if (strcasecmp(name, uri) == 0) + break; + } + + if (node == NULL) + return NULL; + + if (end) { + return get_node_uri_iter(ctx, xml_node_first_child(ctx, node), + end); + } + + return node; +} + + +xml_node_t * get_node_uri(struct xml_node_ctx *ctx, xml_node_t *root, + const char *uri) +{ + char *search; + xml_node_t *node; + + search = os_strdup(uri); + if (search == NULL) + return NULL; + + node = get_node_uri_iter(ctx, root, search); + + os_free(search); + return node; +} + + +static xml_node_t * get_node_iter(struct xml_node_ctx *ctx, + xml_node_t *root, const char *path) +{ + char *end; + xml_node_t *node; + const char *name; + + end = os_strchr(path, '/'); + if (end) + *end++ = '\0'; + + xml_node_for_each_child(ctx, node, root) { + xml_node_for_each_check(ctx, node); + name = xml_node_get_localname(ctx, node); + if (os_strcasecmp(name, path) == 0) + break; + } + + if (node == NULL) + return NULL; + if (end) + return get_node_iter(ctx, node, end); + return node; +} + + +xml_node_t * get_node(struct xml_node_ctx *ctx, xml_node_t *root, + const char *path) +{ + char *search; + xml_node_t *node; + + search = os_strdup(path); + if (search == NULL) + return NULL; + + node = get_node_iter(ctx, root, search); + + os_free(search); + return node; +} + + +xml_node_t * get_child_node(struct xml_node_ctx *ctx, xml_node_t *root, + const char *path) +{ + xml_node_t *node; + xml_node_t *match; + + xml_node_for_each_child(ctx, node, root) { + xml_node_for_each_check(ctx, node); + match = get_node(ctx, node, path); + if (match) + return match; + } + + return NULL; +} + + +xml_node_t * node_from_file(struct xml_node_ctx *ctx, const char *name) +{ + xml_node_t *node; + char *buf, *buf2, *start; + size_t len; + + buf = os_readfile(name, &len); + if (buf == NULL) + return NULL; + buf2 = os_realloc(buf, len + 1); + if (buf2 == NULL) { + os_free(buf); + return NULL; + } + buf = buf2; + buf[len] = '\0'; + + start = os_strstr(buf, "<!DOCTYPE "); + if (start) { + char *pos = start + 1; + int count = 1; + while (*pos) { + if (*pos == '<') + count++; + else if (*pos == '>') { + count--; + if (count == 0) { + pos++; + break; + } + } + pos++; + } + if (count == 0) { + /* Remove DOCTYPE to allow the file to be parsed */ + os_memset(start, ' ', pos - start); + } + } + + node = xml_node_from_buf(ctx, buf); + os_free(buf); + + return node; +} + + +int node_to_file(struct xml_node_ctx *ctx, const char *fname, xml_node_t *node) +{ + FILE *f; + char *str; + + str = xml_node_to_str(ctx, node); + if (str == NULL) + return -1; + + f = fopen(fname, "w"); + if (!f) { + os_free(str); + return -1; + } + + fprintf(f, "%s\n", str); + os_free(str); + fclose(f); + + return 0; +} + + +static char * get_val(struct xml_node_ctx *ctx, xml_node_t *node) +{ + char *val, *pos; + + val = xml_node_get_text(ctx, node); + if (val == NULL) + return NULL; + pos = val; + while (*pos) { + if (*pos != ' ' && *pos != '\t' && *pos != '\r' && *pos != '\n') + return val; + pos++; + } + + return NULL; +} + + +static char * add_path(const char *prev, const char *leaf) +{ + size_t len; + char *new_uri; + + if (prev == NULL) + return NULL; + + len = os_strlen(prev) + 1 + os_strlen(leaf) + 1; + new_uri = os_malloc(len); + if (new_uri) + os_snprintf(new_uri, len, "%s/%s", prev, leaf); + + return new_uri; +} + + +static void node_to_tnds(struct xml_node_ctx *ctx, xml_node_t *out, + xml_node_t *in, const char *uri) +{ + xml_node_t *node; + xml_node_t *tnds; + const char *name; + char *val; + char *new_uri; + + xml_node_for_each_child(ctx, node, in) { + xml_node_for_each_check(ctx, node); + name = xml_node_get_localname(ctx, node); + + tnds = xml_node_create(ctx, out, NULL, "Node"); + if (tnds == NULL) + return; + xml_node_create_text(ctx, tnds, NULL, "NodeName", name); + + if (uri) + xml_node_create_text(ctx, tnds, NULL, "Path", uri); + + val = get_val(ctx, node); + if (val) { + xml_node_create_text(ctx, tnds, NULL, "Value", val); + xml_node_get_text_free(ctx, val); + } + + new_uri = add_path(uri, name); + node_to_tnds(ctx, new_uri ? out : tnds, node, new_uri); + os_free(new_uri); + } +} + + +static int add_ddfname(struct xml_node_ctx *ctx, xml_node_t *parent, + const char *urn) +{ + xml_node_t *node; + + node = xml_node_create(ctx, parent, NULL, "RTProperties"); + if (node == NULL) + return -1; + node = xml_node_create(ctx, node, NULL, "Type"); + if (node == NULL) + return -1; + xml_node_create_text(ctx, node, NULL, "DDFName", urn); + return 0; +} + + +xml_node_t * mo_to_tnds(struct xml_node_ctx *ctx, xml_node_t *mo, + int use_path, const char *urn, const char *ns_uri) +{ + xml_node_t *root; + xml_node_t *node; + const char *name; + + root = xml_node_create_root(ctx, ns_uri, NULL, NULL, "MgmtTree"); + if (root == NULL) + return NULL; + + xml_node_create_text(ctx, root, NULL, "VerDTD", "1.2"); + + name = xml_node_get_localname(ctx, mo); + + node = xml_node_create(ctx, root, NULL, "Node"); + if (node == NULL) + goto fail; + xml_node_create_text(ctx, node, NULL, "NodeName", name); + if (urn) + add_ddfname(ctx, node, urn); + + node_to_tnds(ctx, use_path ? root : node, mo, use_path ? name : NULL); + + return root; + +fail: + xml_node_free(ctx, root); + return NULL; +} + + +static xml_node_t * get_first_child_node(struct xml_node_ctx *ctx, + xml_node_t *node, + const char *name) +{ + const char *lname; + xml_node_t *child; + + xml_node_for_each_child(ctx, child, node) { + xml_node_for_each_check(ctx, child); + lname = xml_node_get_localname(ctx, child); + if (os_strcasecmp(lname, name) == 0) + return child; + } + + return NULL; +} + + +static char * get_node_text(struct xml_node_ctx *ctx, xml_node_t *node, + const char *node_name) +{ + node = get_first_child_node(ctx, node, node_name); + if (node == NULL) + return NULL; + return xml_node_get_text(ctx, node); +} + + +static xml_node_t * add_mo_node(struct xml_node_ctx *ctx, xml_node_t *root, + xml_node_t *node, const char *uri) +{ + char *nodename, *value, *path; + xml_node_t *parent; + + nodename = get_node_text(ctx, node, "NodeName"); + if (nodename == NULL) + return NULL; + value = get_node_text(ctx, node, "Value"); + + if (root == NULL) { + root = xml_node_create_root(ctx, NULL, NULL, NULL, + nodename); + if (root && value) + xml_node_set_text(ctx, root, value); + } else { + if (uri == NULL) { + xml_node_get_text_free(ctx, nodename); + xml_node_get_text_free(ctx, value); + return NULL; + } + path = get_node_text(ctx, node, "Path"); + if (path) + uri = path; + parent = get_node_uri(ctx, root, uri); + xml_node_get_text_free(ctx, path); + if (parent == NULL) { + printf("Could not find URI '%s'\n", uri); + xml_node_get_text_free(ctx, nodename); + xml_node_get_text_free(ctx, value); + return NULL; + } + if (value) + xml_node_create_text(ctx, parent, NULL, nodename, + value); + else + xml_node_create(ctx, parent, NULL, nodename); + } + + xml_node_get_text_free(ctx, nodename); + xml_node_get_text_free(ctx, value); + + return root; +} + + +static xml_node_t * tnds_to_mo_iter(struct xml_node_ctx *ctx, xml_node_t *root, + xml_node_t *node, const char *uri) +{ + xml_node_t *child; + const char *name; + char *nodename; + + xml_node_for_each_sibling(ctx, node) { + xml_node_for_each_check(ctx, node); + + nodename = get_node_text(ctx, node, "NodeName"); + if (nodename == NULL) + return NULL; + + name = xml_node_get_localname(ctx, node); + if (strcmp(name, "Node") == 0) { + if (root && !uri) { + printf("Invalid TNDS tree structure - " + "multiple top level nodes\n"); + xml_node_get_text_free(ctx, nodename); + return NULL; + } + root = add_mo_node(ctx, root, node, uri); + } + + child = get_first_child_node(ctx, node, "Node"); + if (child) { + if (uri == NULL) + tnds_to_mo_iter(ctx, root, child, nodename); + else { + char *new_uri; + new_uri = add_path(uri, nodename); + tnds_to_mo_iter(ctx, root, child, new_uri); + os_free(new_uri); + } + } + xml_node_get_text_free(ctx, nodename); + } + + return root; +} + + +xml_node_t * tnds_to_mo(struct xml_node_ctx *ctx, xml_node_t *tnds) +{ + const char *name; + xml_node_t *node; + + name = xml_node_get_localname(ctx, tnds); + if (name == NULL || os_strcmp(name, "MgmtTree") != 0) + return NULL; + + node = get_first_child_node(ctx, tnds, "Node"); + if (!node) + return NULL; + return tnds_to_mo_iter(ctx, NULL, node, NULL); +} + + +xml_node_t * soap_build_envelope(struct xml_node_ctx *ctx, xml_node_t *node) +{ + xml_node_t *envelope, *body; + xml_namespace_t *ns; + + envelope = xml_node_create_root( + ctx, "http://www.w3.org/2003/05/soap-envelope", "soap12", &ns, + "Envelope"); + if (envelope == NULL) + return NULL; + body = xml_node_create(ctx, envelope, ns, "Body"); + xml_node_add_child(ctx, body, node); + return envelope; +} + + +xml_node_t * soap_get_body(struct xml_node_ctx *ctx, xml_node_t *soap) +{ + xml_node_t *body, *child; + + body = get_node_uri(ctx, soap, "Envelope/Body"); + if (body == NULL) + return NULL; + xml_node_for_each_child(ctx, child, body) { + xml_node_for_each_check(ctx, child); + return child; + } + return NULL; +} diff --git a/src/utils/xml-utils.h b/src/utils/xml-utils.h new file mode 100644 index 0000000..fb6208c --- /dev/null +++ b/src/utils/xml-utils.h @@ -0,0 +1,97 @@ +/* + * Generic XML helper functions + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef XML_UTILS_H +#define XML_UTILS_H + +struct xml_node_ctx; +typedef struct xml_node xml_node_t; +typedef struct xml_namespace_foo xml_namespace_t; + +/* XML library wrappers */ + +int xml_validate(struct xml_node_ctx *ctx, xml_node_t *node, + const char *xml_schema_fname, char **ret_err); +int xml_validate_dtd(struct xml_node_ctx *ctx, xml_node_t *node, + const char *dtd_fname, char **ret_err); +void xml_node_free(struct xml_node_ctx *ctx, xml_node_t *node); +xml_node_t * xml_node_get_parent(struct xml_node_ctx *ctx, xml_node_t *node); +xml_node_t * xml_node_from_buf(struct xml_node_ctx *ctx, const char *buf); +const char * xml_node_get_localname(struct xml_node_ctx *ctx, + xml_node_t *node); +char * xml_node_to_str(struct xml_node_ctx *ctx, xml_node_t *node); +void xml_node_detach(struct xml_node_ctx *ctx, xml_node_t *node); +void xml_node_add_child(struct xml_node_ctx *ctx, xml_node_t *parent, + xml_node_t *child); +xml_node_t * xml_node_create_root(struct xml_node_ctx *ctx, const char *ns_uri, + const char *ns_prefix, + xml_namespace_t **ret_ns, const char *name); +xml_node_t * xml_node_create(struct xml_node_ctx *ctx, xml_node_t *parent, + xml_namespace_t *ns, const char *name); +xml_node_t * xml_node_create_text(struct xml_node_ctx *ctx, + xml_node_t *parent, xml_namespace_t *ns, + const char *name, const char *value); +xml_node_t * xml_node_create_text_ns(struct xml_node_ctx *ctx, + xml_node_t *parent, const char *ns_uri, + const char *name, const char *value); +void xml_node_set_text(struct xml_node_ctx *ctx, xml_node_t *node, + const char *value); +int xml_node_add_attr(struct xml_node_ctx *ctx, xml_node_t *node, + xml_namespace_t *ns, const char *name, const char *value); +char * xml_node_get_attr_value(struct xml_node_ctx *ctx, xml_node_t *node, + char *name); +char * xml_node_get_attr_value_ns(struct xml_node_ctx *ctx, xml_node_t *node, + const char *ns_uri, char *name); +void xml_node_get_attr_value_free(struct xml_node_ctx *ctx, char *val); +xml_node_t * xml_node_first_child(struct xml_node_ctx *ctx, + xml_node_t *parent); +xml_node_t * xml_node_next_sibling(struct xml_node_ctx *ctx, + xml_node_t *node); +int xml_node_is_element(struct xml_node_ctx *ctx, xml_node_t *node); +char * xml_node_get_text(struct xml_node_ctx *ctx, xml_node_t *node); +void xml_node_get_text_free(struct xml_node_ctx *ctx, char *val); +char * xml_node_get_base64_text(struct xml_node_ctx *ctx, xml_node_t *node, + int *ret_len); +xml_node_t * xml_node_copy(struct xml_node_ctx *ctx, xml_node_t *node); + +#define xml_node_for_each_child(ctx, child, parent) \ +for (child = xml_node_first_child(ctx, parent); \ + child; \ + child = xml_node_next_sibling(ctx, child)) + +#define xml_node_for_each_sibling(ctx, node) \ +for (; \ + node; \ + node = xml_node_next_sibling(ctx, node)) + +#define xml_node_for_each_check(ctx, child) \ +if (!xml_node_is_element(ctx, child)) \ + continue + + +struct xml_node_ctx * xml_node_init_ctx(void *upper_ctx, + const void *env); +void xml_node_deinit_ctx(struct xml_node_ctx *ctx); + + +xml_node_t * get_node_uri(struct xml_node_ctx *ctx, xml_node_t *root, + const char *uri); +xml_node_t * get_node(struct xml_node_ctx *ctx, xml_node_t *root, + const char *path); +xml_node_t * get_child_node(struct xml_node_ctx *ctx, xml_node_t *root, + const char *path); +xml_node_t * node_from_file(struct xml_node_ctx *ctx, const char *name); +int node_to_file(struct xml_node_ctx *ctx, const char *fname, xml_node_t *node); +xml_node_t * mo_to_tnds(struct xml_node_ctx *ctx, xml_node_t *mo, + int use_path, const char *urn, const char *ns_uri); +xml_node_t * tnds_to_mo(struct xml_node_ctx *ctx, xml_node_t *tnds); + +xml_node_t * soap_build_envelope(struct xml_node_ctx *ctx, xml_node_t *node); +xml_node_t * soap_get_body(struct xml_node_ctx *ctx, xml_node_t *soap); + +#endif /* XML_UTILS_H */ diff --git a/src/utils/xml_libxml2.c b/src/utils/xml_libxml2.c new file mode 100644 index 0000000..c928394 --- /dev/null +++ b/src/utils/xml_libxml2.c @@ -0,0 +1,457 @@ +/* + * XML wrapper for libxml2 + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" +#define LIBXML_VALID_ENABLED +#include <libxml/tree.h> +#include <libxml/xmlschemastypes.h> + +#include "common.h" +#include "base64.h" +#include "xml-utils.h" + + +struct xml_node_ctx { + void *ctx; +}; + + +struct str_buf { + char *buf; + size_t len; +}; + +#define MAX_STR 1000 + +static void add_str(void *ctx_ptr, const char *fmt, ...) +{ + struct str_buf *str = ctx_ptr; + va_list ap; + char *n; + int len; + + n = os_realloc(str->buf, str->len + MAX_STR + 2); + if (n == NULL) + return; + str->buf = n; + + va_start(ap, fmt); + len = vsnprintf(str->buf + str->len, MAX_STR, fmt, ap); + va_end(ap); + if (len >= MAX_STR) + len = MAX_STR - 1; + str->len += len; + str->buf[str->len] = '\0'; +} + + +int xml_validate(struct xml_node_ctx *ctx, xml_node_t *node, + const char *xml_schema_fname, char **ret_err) +{ + xmlDocPtr doc; + xmlNodePtr n; + xmlSchemaParserCtxtPtr pctx; + xmlSchemaValidCtxtPtr vctx; + xmlSchemaPtr schema; + int ret; + struct str_buf errors; + + if (ret_err) + *ret_err = NULL; + + doc = xmlNewDoc((xmlChar *) "1.0"); + if (doc == NULL) + return -1; + n = xmlDocCopyNode((xmlNodePtr) node, doc, 1); + if (n == NULL) { + xmlFreeDoc(doc); + return -1; + } + xmlDocSetRootElement(doc, n); + + os_memset(&errors, 0, sizeof(errors)); + + pctx = xmlSchemaNewParserCtxt(xml_schema_fname); + xmlSchemaSetParserErrors(pctx, (xmlSchemaValidityErrorFunc) add_str, + (xmlSchemaValidityWarningFunc) add_str, + &errors); + schema = xmlSchemaParse(pctx); + xmlSchemaFreeParserCtxt(pctx); + + vctx = xmlSchemaNewValidCtxt(schema); + xmlSchemaSetValidErrors(vctx, (xmlSchemaValidityErrorFunc) add_str, + (xmlSchemaValidityWarningFunc) add_str, + &errors); + + ret = xmlSchemaValidateDoc(vctx, doc); + xmlSchemaFreeValidCtxt(vctx); + xmlFreeDoc(doc); + xmlSchemaFree(schema); + + if (ret == 0) { + os_free(errors.buf); + return 0; + } else if (ret > 0) { + if (ret_err) + *ret_err = errors.buf; + else + os_free(errors.buf); + return -1; + } else { + if (ret_err) + *ret_err = errors.buf; + else + os_free(errors.buf); + return -1; + } +} + + +int xml_validate_dtd(struct xml_node_ctx *ctx, xml_node_t *node, + const char *dtd_fname, char **ret_err) +{ + xmlDocPtr doc; + xmlNodePtr n; + xmlValidCtxt vctx; + xmlDtdPtr dtd; + int ret; + struct str_buf errors; + + if (ret_err) + *ret_err = NULL; + + doc = xmlNewDoc((xmlChar *) "1.0"); + if (doc == NULL) + return -1; + n = xmlDocCopyNode((xmlNodePtr) node, doc, 1); + if (n == NULL) { + xmlFreeDoc(doc); + return -1; + } + xmlDocSetRootElement(doc, n); + + os_memset(&errors, 0, sizeof(errors)); + + dtd = xmlParseDTD(NULL, (const xmlChar *) dtd_fname); + if (dtd == NULL) { + xmlFreeDoc(doc); + return -1; + } + + os_memset(&vctx, 0, sizeof(vctx)); + vctx.userData = &errors; + vctx.error = add_str; + vctx.warning = add_str; + ret = xmlValidateDtd(&vctx, doc, dtd); + xmlFreeDoc(doc); + xmlFreeDtd(dtd); + + if (ret == 1) { + os_free(errors.buf); + return 0; + } else { + if (ret_err) + *ret_err = errors.buf; + else + os_free(errors.buf); + return -1; + } +} + + +void xml_node_free(struct xml_node_ctx *ctx, xml_node_t *node) +{ + xmlFreeNode((xmlNodePtr) node); +} + + +xml_node_t * xml_node_get_parent(struct xml_node_ctx *ctx, xml_node_t *node) +{ + return (xml_node_t *) ((xmlNodePtr) node)->parent; +} + + +xml_node_t * xml_node_from_buf(struct xml_node_ctx *ctx, const char *buf) +{ + xmlDocPtr doc; + xmlNodePtr node; + + doc = xmlParseMemory(buf, strlen(buf)); + if (doc == NULL) + return NULL; + node = xmlDocGetRootElement(doc); + node = xmlCopyNode(node, 1); + xmlFreeDoc(doc); + + return (xml_node_t *) node; +} + + +const char * xml_node_get_localname(struct xml_node_ctx *ctx, + xml_node_t *node) +{ + return (const char *) ((xmlNodePtr) node)->name; +} + + +char * xml_node_to_str(struct xml_node_ctx *ctx, xml_node_t *node) +{ + xmlChar *buf; + int bufsiz; + char *ret, *pos; + xmlNodePtr n = (xmlNodePtr) node; + xmlDocPtr doc; + + doc = xmlNewDoc((xmlChar *) "1.0"); + n = xmlDocCopyNode(n, doc, 1); + xmlDocSetRootElement(doc, n); + xmlDocDumpFormatMemory(doc, &buf, &bufsiz, 0); + xmlFreeDoc(doc); + pos = (char *) buf; + if (strncmp(pos, "<?xml", 5) == 0) { + pos = strchr(pos, '>'); + if (pos) + pos++; + while (pos && (*pos == '\r' || *pos == '\n')) + pos++; + } + if (pos) + ret = os_strdup(pos); + else + ret = NULL; + xmlFree(buf); + + if (ret) { + pos = ret; + if (pos[0]) { + while (pos[1]) + pos++; + } + while (pos >= ret && *pos == '\n') + *pos-- = '\0'; + } + + return ret; +} + + +void xml_node_detach(struct xml_node_ctx *ctx, xml_node_t *node) +{ + xmlUnlinkNode((xmlNodePtr) node); +} + + +void xml_node_add_child(struct xml_node_ctx *ctx, xml_node_t *parent, + xml_node_t *child) +{ + xmlAddChild((xmlNodePtr) parent, (xmlNodePtr) child); +} + + +xml_node_t * xml_node_create_root(struct xml_node_ctx *ctx, const char *ns_uri, + const char *ns_prefix, + xml_namespace_t **ret_ns, const char *name) +{ + xmlNodePtr node; + xmlNsPtr ns = NULL; + + node = xmlNewNode(NULL, (const xmlChar *) name); + if (node == NULL) + return NULL; + if (ns_uri) { + ns = xmlNewNs(node, (const xmlChar *) ns_uri, + (const xmlChar *) ns_prefix); + xmlSetNs(node, ns); + } + + if (ret_ns) + *ret_ns = (xml_namespace_t *) ns; + + return (xml_node_t *) node; +} + + +xml_node_t * xml_node_create(struct xml_node_ctx *ctx, xml_node_t *parent, + xml_namespace_t *ns, const char *name) +{ + xmlNodePtr node; + node = xmlNewChild((xmlNodePtr) parent, (xmlNsPtr) ns, + (const xmlChar *) name, NULL); + return (xml_node_t *) node; +} + + +xml_node_t * xml_node_create_text(struct xml_node_ctx *ctx, + xml_node_t *parent, xml_namespace_t *ns, + const char *name, const char *value) +{ + xmlNodePtr node; + node = xmlNewTextChild((xmlNodePtr) parent, (xmlNsPtr) ns, + (const xmlChar *) name, (const xmlChar *) value); + return (xml_node_t *) node; +} + + +xml_node_t * xml_node_create_text_ns(struct xml_node_ctx *ctx, + xml_node_t *parent, const char *ns_uri, + const char *name, const char *value) +{ + xmlNodePtr node; + xmlNsPtr ns; + + node = xmlNewTextChild((xmlNodePtr) parent, NULL, + (const xmlChar *) name, (const xmlChar *) value); + ns = xmlNewNs(node, (const xmlChar *) ns_uri, NULL); + xmlSetNs(node, ns); + return (xml_node_t *) node; +} + + +void xml_node_set_text(struct xml_node_ctx *ctx, xml_node_t *node, + const char *value) +{ + /* TODO: escape XML special chars in value */ + xmlNodeSetContent((xmlNodePtr) node, (xmlChar *) value); +} + + +int xml_node_add_attr(struct xml_node_ctx *ctx, xml_node_t *node, + xml_namespace_t *ns, const char *name, const char *value) +{ + xmlAttrPtr attr; + + if (ns) { + attr = xmlNewNsProp((xmlNodePtr) node, (xmlNsPtr) ns, + (const xmlChar *) name, + (const xmlChar *) value); + } else { + attr = xmlNewProp((xmlNodePtr) node, (const xmlChar *) name, + (const xmlChar *) value); + } + + return attr ? 0 : -1; +} + + +char * xml_node_get_attr_value(struct xml_node_ctx *ctx, xml_node_t *node, + char *name) +{ + return (char *) xmlGetNoNsProp((xmlNodePtr) node, + (const xmlChar *) name); +} + + +char * xml_node_get_attr_value_ns(struct xml_node_ctx *ctx, xml_node_t *node, + const char *ns_uri, char *name) +{ + return (char *) xmlGetNsProp((xmlNodePtr) node, (const xmlChar *) name, + (const xmlChar *) ns_uri); +} + + +void xml_node_get_attr_value_free(struct xml_node_ctx *ctx, char *val) +{ + if (val) + xmlFree((xmlChar *) val); +} + + +xml_node_t * xml_node_first_child(struct xml_node_ctx *ctx, + xml_node_t *parent) +{ + return (xml_node_t *) ((xmlNodePtr) parent)->children; +} + + +xml_node_t * xml_node_next_sibling(struct xml_node_ctx *ctx, + xml_node_t *node) +{ + return (xml_node_t *) ((xmlNodePtr) node)->next; +} + + +int xml_node_is_element(struct xml_node_ctx *ctx, xml_node_t *node) +{ + return ((xmlNodePtr) node)->type == XML_ELEMENT_NODE; +} + + +char * xml_node_get_text(struct xml_node_ctx *ctx, xml_node_t *node) +{ + if (xmlChildElementCount((xmlNodePtr) node) > 0) + return NULL; + return (char *) xmlNodeGetContent((xmlNodePtr) node); +} + + +void xml_node_get_text_free(struct xml_node_ctx *ctx, char *val) +{ + if (val) + xmlFree((xmlChar *) val); +} + + +char * xml_node_get_base64_text(struct xml_node_ctx *ctx, xml_node_t *node, + int *ret_len) +{ + char *txt; + unsigned char *ret; + size_t len; + + txt = xml_node_get_text(ctx, node); + if (txt == NULL) + return NULL; + + ret = base64_decode((unsigned char *) txt, strlen(txt), &len); + if (ret_len) + *ret_len = len; + xml_node_get_text_free(ctx, txt); + if (ret == NULL) + return NULL; + txt = os_malloc(len + 1); + if (txt == NULL) { + os_free(ret); + return NULL; + } + os_memcpy(txt, ret, len); + txt[len] = '\0'; + return txt; +} + + +xml_node_t * xml_node_copy(struct xml_node_ctx *ctx, xml_node_t *node) +{ + if (node == NULL) + return NULL; + return (xml_node_t *) xmlCopyNode((xmlNodePtr) node, 1); +} + + +struct xml_node_ctx * xml_node_init_ctx(void *upper_ctx, + const void *env) +{ + struct xml_node_ctx *xctx; + + xctx = os_zalloc(sizeof(*xctx)); + if (xctx == NULL) + return NULL; + xctx->ctx = upper_ctx; + + LIBXML_TEST_VERSION + + return xctx; +} + + +void xml_node_deinit_ctx(struct xml_node_ctx *ctx) +{ + xmlSchemaCleanupTypes(); + xmlCleanupParser(); + xmlMemoryDump(); + os_free(ctx); +} diff --git a/src/wps/http_server.c b/src/wps/http_server.c index 06c8bee..ac088c4 100644 --- a/src/wps/http_server.c +++ b/src/wps/http_server.c @@ -244,7 +244,13 @@ struct http_server * http_server_init(struct in_addr *addr, int port, if (srv->fd < 0) goto fail; - setsockopt(srv->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (setsockopt(srv->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + { + wpa_printf(MSG_DEBUG, + "HTTP: setsockopt(SO_REUSEADDR) failed: %s", + strerror(errno)); + /* try to continue anyway */ + } if (fcntl(srv->fd, F_SETFL, O_NONBLOCK) < 0) goto fail; diff --git a/src/wps/httpread.c b/src/wps/httpread.c index b51d975..6d2d11c 100644 --- a/src/wps/httpread.c +++ b/src/wps/httpread.c @@ -617,7 +617,6 @@ static void httpread_read_handler(int sd, void *eloop_ctx, void *sock_ctx) * We do NOT support trailers except to skip them -- * this is supported (generally) by the http spec. */ - bbp = h->body + h->body_nbytes; for (;;) { int c; if (nread <= 0) diff --git a/src/wps/ndef.c b/src/wps/ndef.c index 2b35064..d45dfc8 100644 --- a/src/wps/ndef.c +++ b/src/wps/ndef.c @@ -148,7 +148,8 @@ static struct wpabuf * ndef_build_record(u8 flags, void *type, static int wifi_filter(struct ndef_record *record) { - if (record->type_length != os_strlen(wifi_handover_type)) + if (record->type == NULL || + record->type_length != os_strlen(wifi_handover_type)) return 0; if (os_memcmp(record->type, wifi_handover_type, os_strlen(wifi_handover_type)) != 0) @@ -173,7 +174,8 @@ struct wpabuf * ndef_build_wifi(const struct wpabuf *buf) static int p2p_filter(struct ndef_record *record) { - if (record->type_length != os_strlen(p2p_handover_type)) + if (record->type == NULL || + record->type_length != os_strlen(p2p_handover_type)) return 0; if (os_memcmp(record->type, p2p_handover_type, os_strlen(p2p_handover_type)) != 0) diff --git a/src/wps/wps.c b/src/wps/wps.c index 3d019f1..648cfd1 100644 --- a/src/wps/wps.c +++ b/src/wps/wps.c @@ -511,13 +511,11 @@ struct wpabuf * wps_build_probe_req_ie(u16 pw_id, struct wps_device_data *dev, wps_build_assoc_state(NULL, ie) || wps_build_config_error(ie, WPS_CFG_NO_ERROR) || wps_build_dev_password_id(ie, pw_id) || -#ifdef CONFIG_WPS2 wps_build_manufacturer(dev, ie) || wps_build_model_name(dev, ie) || wps_build_model_number(dev, ie) || wps_build_dev_name(dev, ie) || wps_build_wfa_ext(ie, req_type == WPS_REQ_ENROLLEE, NULL, 0) || -#endif /* CONFIG_WPS2 */ wps_build_req_dev_type(dev, ie, num_req_dev_types, req_dev_types) || wps_build_secondary_dev_type(dev, ie) @@ -526,13 +524,6 @@ struct wpabuf * wps_build_probe_req_ie(u16 pw_id, struct wps_device_data *dev, return NULL; } -#ifndef CONFIG_WPS2 - if (dev->p2p && wps_build_dev_name(dev, ie)) { - wpabuf_free(ie); - return NULL; - } -#endif /* CONFIG_WPS2 */ - return wps_ie_encapsulate(ie); } diff --git a/src/wps/wps.h b/src/wps/wps.h index 6ccce1a..192d283 100644 --- a/src/wps/wps.h +++ b/src/wps/wps.h @@ -42,7 +42,6 @@ struct wps_parse_attr; * @cred_attr: Unparsed Credential attribute data (used only in cred_cb()); * this may be %NULL, if not used * @cred_attr_len: Length of cred_attr in octets - * @ap_channel: AP channel */ struct wps_credential { u8 ssid[32]; @@ -55,7 +54,6 @@ struct wps_credential { u8 mac_addr[ETH_ALEN]; const u8 *cred_attr; size_t cred_attr_len; - u16 ap_channel; }; #define WPS_DEV_TYPE_LEN 8 @@ -670,6 +668,16 @@ struct wps_context { u16 auth_types; /** + * encr_types - Current AP encryption type (WPS_ENCR_*) + */ + u16 ap_encr_type; + + /** + * ap_auth_type - Current AP authentication types (WPS_AUTH_*) + */ + u16 ap_auth_type; + + /** * network_key - The current Network Key (PSK) or %NULL to generate new * * If %NULL, Registrar will generate per-device PSK. In addition, AP diff --git a/src/wps/wps_attr_build.c b/src/wps/wps_attr_build.c index 62d0feb..b689357 100644 --- a/src/wps/wps_attr_build.c +++ b/src/wps/wps_attr_build.c @@ -205,7 +205,6 @@ int wps_build_version(struct wpabuf *msg) int wps_build_wfa_ext(struct wpabuf *msg, int req_to_enroll, const u8 *auth_macs, size_t auth_macs_count) { -#ifdef CONFIG_WPS2 u8 *len; #ifdef CONFIG_WPS_TESTING @@ -246,7 +245,6 @@ int wps_build_wfa_ext(struct wpabuf *msg, int req_to_enroll, } WPA_PUT_BE16(len, (u8 *) wpabuf_put(msg, 0) - len - 2); -#endif /* CONFIG_WPS2 */ #ifdef CONFIG_WPS_TESTING if (WPS_VERSION > 0x20) { @@ -296,9 +294,10 @@ int wps_build_registrar_nonce(struct wps_data *wps, struct wpabuf *msg) int wps_build_auth_type_flags(struct wps_data *wps, struct wpabuf *msg) { u16 auth_types = WPS_AUTH_TYPES; -#ifdef CONFIG_WPS2 + /* WPA/WPA2-Enterprise enrollment not supported through WPS */ + auth_types &= ~WPS_AUTH_WPA; + auth_types &= ~WPS_AUTH_WPA2; auth_types &= ~WPS_AUTH_SHARED; -#endif /* CONFIG_WPS2 */ wpa_printf(MSG_DEBUG, "WPS: * Authentication Type Flags"); wpabuf_put_be16(msg, ATTR_AUTH_TYPE_FLAGS); wpabuf_put_be16(msg, 2); @@ -310,9 +309,7 @@ int wps_build_auth_type_flags(struct wps_data *wps, struct wpabuf *msg) int wps_build_encr_type_flags(struct wps_data *wps, struct wpabuf *msg) { u16 encr_types = WPS_ENCR_TYPES; -#ifdef CONFIG_WPS2 encr_types &= ~WPS_ENCR_WEP; -#endif /* CONFIG_WPS2 */ wpa_printf(MSG_DEBUG, "WPS: * Encryption Type Flags"); wpabuf_put_be16(msg, ATTR_ENCR_TYPE_FLAGS); wpabuf_put_be16(msg, 2); diff --git a/src/wps/wps_attr_parse.c b/src/wps/wps_attr_parse.c index f4e2e38..40bc1ad 100644 --- a/src/wps/wps_attr_parse.c +++ b/src/wps/wps_attr_parse.c @@ -59,6 +59,14 @@ static int wps_set_vendor_ext_wfa_subelem(struct wps_parse_attr *attr, } attr->settings_delay_time = pos; break; + case WFA_ELEM_REGISTRAR_CONFIGURATION_METHODS: + if (len != 2) { + wpa_printf(MSG_DEBUG, "WPS: Invalid Registrar Configuration Methods length %u", + len); + return -1; + } + attr->registrar_configuration_methods = pos; + break; default: wpa_printf(MSG_MSGDUMP, "WPS: Skipped unknown WFA Vendor " "Extension subelement %u", id); @@ -75,7 +83,7 @@ static int wps_parse_vendor_ext_wfa(struct wps_parse_attr *attr, const u8 *pos, const u8 *end = pos + len; u8 id, elen; - while (pos + 2 < end) { + while (pos + 2 <= end) { id = *pos++; elen = *pos++; if (pos + elen > end) @@ -413,22 +421,6 @@ static int wps_set_attr(struct wps_parse_attr *attr, u16 type, } attr->mac_addr = pos; break; - case ATTR_KEY_PROVIDED_AUTO: - if (len != 1) { - wpa_printf(MSG_DEBUG, "WPS: Invalid Key Provided " - "Automatically length %u", len); - return -1; - } - attr->key_prov_auto = pos; - break; - case ATTR_802_1X_ENABLED: - if (len != 1) { - wpa_printf(MSG_DEBUG, "WPS: Invalid 802.1X Enabled " - "length %u", len); - return -1; - } - attr->dot1x_enabled = pos; - break; case ATTR_SELECTED_REGISTRAR: if (len != 1) { wpa_printf(MSG_DEBUG, "WPS: Invalid Selected Registrar" @@ -500,14 +492,6 @@ static int wps_set_attr(struct wps_parse_attr *attr, u16 type, attr->network_key = pos; attr->network_key_len = len; break; - case ATTR_EAP_TYPE: - attr->eap_type = pos; - attr->eap_type_len = len; - break; - case ATTR_EAP_IDENTITY: - attr->eap_identity = pos; - attr->eap_identity_len = len; - break; case ATTR_AP_SETUP_LOCKED: if (len != 1) { wpa_printf(MSG_DEBUG, "WPS: Invalid AP Setup Locked " diff --git a/src/wps/wps_attr_parse.h b/src/wps/wps_attr_parse.h index 88e51a4..82c4739 100644 --- a/src/wps/wps_attr_parse.h +++ b/src/wps/wps_attr_parse.h @@ -47,8 +47,6 @@ struct wps_parse_attr { const u8 *network_idx; /* 1 octet */ const u8 *network_key_idx; /* 1 octet */ const u8 *mac_addr; /* ETH_ALEN (6) octets */ - const u8 *key_prov_auto; /* 1 octet (Bool) */ - const u8 *dot1x_enabled; /* 1 octet (Bool) */ const u8 *selected_registrar; /* 1 octet (Bool) */ const u8 *request_type; /* 1 octet */ const u8 *response_type; /* 1 octet */ @@ -57,6 +55,7 @@ struct wps_parse_attr { const u8 *network_key_shareable; /* 1 octet (Bool) */ const u8 *request_to_enroll; /* 1 octet (Bool) */ const u8 *ap_channel; /* 2 octets */ + const u8 *registrar_configuration_methods; /* 2 octets */ /* variable length fields */ const u8 *manufacturer; @@ -77,10 +76,6 @@ struct wps_parse_attr { size_t ssid_len; const u8 *network_key; /* <= 64 octets */ size_t network_key_len; - const u8 *eap_type; /* <= 8 octets */ - size_t eap_type_len; - const u8 *eap_identity; /* <= 64 octets */ - size_t eap_identity_len; const u8 *authorized_macs; /* <= 30 octets */ size_t authorized_macs_len; const u8 *sec_dev_type_list; /* <= 128 octets */ diff --git a/src/wps/wps_attr_process.c b/src/wps/wps_attr_process.c index b81f106..5266620 100644 --- a/src/wps/wps_attr_process.c +++ b/src/wps/wps_attr_process.c @@ -207,70 +207,6 @@ static int wps_process_cred_mac_addr(struct wps_credential *cred, } -static int wps_process_cred_eap_type(struct wps_credential *cred, - const u8 *eap_type, size_t eap_type_len) -{ - if (eap_type == NULL) - return 0; /* optional attribute */ - - wpa_hexdump(MSG_DEBUG, "WPS: EAP Type", eap_type, eap_type_len); - - return 0; -} - - -static int wps_process_cred_eap_identity(struct wps_credential *cred, - const u8 *identity, - size_t identity_len) -{ - if (identity == NULL) - return 0; /* optional attribute */ - - wpa_hexdump_ascii(MSG_DEBUG, "WPS: EAP Identity", - identity, identity_len); - - return 0; -} - - -static int wps_process_cred_key_prov_auto(struct wps_credential *cred, - const u8 *key_prov_auto) -{ - if (key_prov_auto == NULL) - return 0; /* optional attribute */ - - wpa_printf(MSG_DEBUG, "WPS: Key Provided Automatically: %d", - *key_prov_auto); - - return 0; -} - - -static int wps_process_cred_802_1x_enabled(struct wps_credential *cred, - const u8 *dot1x_enabled) -{ - if (dot1x_enabled == NULL) - return 0; /* optional attribute */ - - wpa_printf(MSG_DEBUG, "WPS: 802.1X Enabled: %d", *dot1x_enabled); - - return 0; -} - - -static int wps_process_cred_ap_channel(struct wps_credential *cred, - const u8 *ap_channel) -{ - if (ap_channel == NULL) - return 0; /* optional attribute */ - - cred->ap_channel = WPA_GET_BE16(ap_channel); - wpa_printf(MSG_DEBUG, "WPS: AP Channel: %u", cred->ap_channel); - - return 0; -} - - static int wps_workaround_cred_key(struct wps_credential *cred) { if (cred->auth_type & (WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK) && @@ -310,14 +246,7 @@ int wps_process_cred(struct wps_parse_attr *attr, wps_process_cred_network_key_idx(cred, attr->network_key_idx) || wps_process_cred_network_key(cred, attr->network_key, attr->network_key_len) || - wps_process_cred_mac_addr(cred, attr->mac_addr) || - wps_process_cred_eap_type(cred, attr->eap_type, - attr->eap_type_len) || - wps_process_cred_eap_identity(cred, attr->eap_identity, - attr->eap_identity_len) || - wps_process_cred_key_prov_auto(cred, attr->key_prov_auto) || - wps_process_cred_802_1x_enabled(cred, attr->dot1x_enabled) || - wps_process_cred_ap_channel(cred, attr->ap_channel)) + wps_process_cred_mac_addr(cred, attr->mac_addr)) return -1; return wps_workaround_cred_key(cred); diff --git a/src/wps/wps_common.c b/src/wps/wps_common.c index abf3a4f..a282348 100644 --- a/src/wps/wps_common.c +++ b/src/wps/wps_common.c @@ -531,9 +531,7 @@ u16 wps_config_methods_str2bin(const char *str) if (str == NULL) { /* Default to enabling methods based on build configuration */ methods |= WPS_CONFIG_DISPLAY | WPS_CONFIG_KEYPAD; -#ifdef CONFIG_WPS2 methods |= WPS_CONFIG_VIRT_DISPLAY; -#endif /* CONFIG_WPS2 */ #ifdef CONFIG_WPS_NFC methods |= WPS_CONFIG_NFC_INTERFACE; #endif /* CONFIG_WPS_NFC */ @@ -554,7 +552,6 @@ u16 wps_config_methods_str2bin(const char *str) methods |= WPS_CONFIG_PUSHBUTTON; if (os_strstr(str, "keypad")) methods |= WPS_CONFIG_KEYPAD; -#ifdef CONFIG_WPS2 if (os_strstr(str, "virtual_display")) methods |= WPS_CONFIG_VIRT_DISPLAY; if (os_strstr(str, "physical_display")) @@ -563,7 +560,6 @@ u16 wps_config_methods_str2bin(const char *str) methods |= WPS_CONFIG_VIRT_PUSHBUTTON; if (os_strstr(str, "physical_push_button")) methods |= WPS_CONFIG_PHY_PUSHBUTTON; -#endif /* CONFIG_WPS2 */ } return methods; diff --git a/src/wps/wps_defs.h b/src/wps/wps_defs.h index 6f8a49f..f483e2e 100644 --- a/src/wps/wps_defs.h +++ b/src/wps/wps_defs.h @@ -18,11 +18,7 @@ extern int wps_corrupt_pkhash; #else /* CONFIG_WPS_TESTING */ -#ifdef CONFIG_WPS2 #define WPS_VERSION 0x20 -#else /* CONFIG_WPS2 */ -#define WPS_VERSION 0x10 -#endif /* CONFIG_WPS2 */ #endif /* CONFIG_WPS_TESTING */ @@ -146,7 +142,8 @@ enum { WFA_ELEM_AUTHORIZEDMACS = 0x01, WFA_ELEM_NETWORK_KEY_SHAREABLE = 0x02, WFA_ELEM_REQUEST_TO_ENROLL = 0x03, - WFA_ELEM_SETTINGS_DELAY_TIME = 0x04 + WFA_ELEM_SETTINGS_DELAY_TIME = 0x04, + WFA_ELEM_REGISTRAR_CONFIGURATION_METHODS = 0x05 }; /* Device Password ID */ @@ -182,7 +179,7 @@ enum wps_msg_type { /* Authentication Type Flags */ #define WPS_AUTH_OPEN 0x0001 #define WPS_AUTH_WPAPSK 0x0002 -#define WPS_AUTH_SHARED 0x0004 +#define WPS_AUTH_SHARED 0x0004 /* deprecated */ #define WPS_AUTH_WPA 0x0008 #define WPS_AUTH_WPA2 0x0010 #define WPS_AUTH_WPA2PSK 0x0020 @@ -191,7 +188,7 @@ enum wps_msg_type { /* Encryption Type Flags */ #define WPS_ENCR_NONE 0x0001 -#define WPS_ENCR_WEP 0x0002 +#define WPS_ENCR_WEP 0x0002 /* deprecated */ #define WPS_ENCR_TKIP 0x0004 #define WPS_ENCR_AES 0x0008 #define WPS_ENCR_TYPES (WPS_ENCR_NONE | WPS_ENCR_WEP | WPS_ENCR_TKIP | \ @@ -245,12 +242,10 @@ enum wps_error_indication { #define WPS_CONFIG_NFC_INTERFACE 0x0040 #define WPS_CONFIG_PUSHBUTTON 0x0080 #define WPS_CONFIG_KEYPAD 0x0100 -#ifdef CONFIG_WPS2 #define WPS_CONFIG_VIRT_PUSHBUTTON 0x0280 #define WPS_CONFIG_PHY_PUSHBUTTON 0x0480 #define WPS_CONFIG_VIRT_DISPLAY 0x2008 #define WPS_CONFIG_PHY_DISPLAY 0x4008 -#endif /* CONFIG_WPS2 */ /* Connection Type Flags */ #define WPS_CONN_ESS 0x01 diff --git a/src/wps/wps_enrollee.c b/src/wps/wps_enrollee.c index 9d48ca5..d072582 100644 --- a/src/wps/wps_enrollee.c +++ b/src/wps/wps_enrollee.c @@ -130,10 +130,8 @@ static struct wpabuf * wps_build_m1(struct wps_data *wps) * workaround. */ config_methods &= ~WPS_CONFIG_PUSHBUTTON; -#ifdef CONFIG_WPS2 config_methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON | WPS_CONFIG_PHY_PUSHBUTTON); -#endif /* CONFIG_WPS2 */ } if (wps_build_version(msg) || @@ -243,54 +241,53 @@ static int wps_build_cred_ssid(struct wps_data *wps, struct wpabuf *msg) static int wps_build_cred_auth_type(struct wps_data *wps, struct wpabuf *msg) { - u16 auth_type = wps->wps->auth_types; - - /* Select the best authentication type */ - if (auth_type & WPS_AUTH_WPA2PSK) - auth_type = WPS_AUTH_WPA2PSK; - else if (auth_type & WPS_AUTH_WPAPSK) - auth_type = WPS_AUTH_WPAPSK; - else if (auth_type & WPS_AUTH_OPEN) - auth_type = WPS_AUTH_OPEN; - else if (auth_type & WPS_AUTH_SHARED) - auth_type = WPS_AUTH_SHARED; - - wpa_printf(MSG_DEBUG, "WPS: * Authentication Type (0x%x)", auth_type); + wpa_printf(MSG_DEBUG, "WPS: * Authentication Type (0x%x)", + wps->wps->ap_auth_type); wpabuf_put_be16(msg, ATTR_AUTH_TYPE); wpabuf_put_be16(msg, 2); - wpabuf_put_be16(msg, auth_type); + wpabuf_put_be16(msg, wps->wps->ap_auth_type); return 0; } static int wps_build_cred_encr_type(struct wps_data *wps, struct wpabuf *msg) { - u16 encr_type = wps->wps->encr_types; - - /* Select the best encryption type */ - if (wps->wps->auth_types & (WPS_AUTH_WPA2PSK | WPS_AUTH_WPAPSK)) { - if (encr_type & WPS_ENCR_AES) - encr_type = WPS_ENCR_AES; - else if (encr_type & WPS_ENCR_TKIP) - encr_type = WPS_ENCR_TKIP; - } else { - if (encr_type & WPS_ENCR_WEP) - encr_type = WPS_ENCR_WEP; - else if (encr_type & WPS_ENCR_NONE) - encr_type = WPS_ENCR_NONE; - } - - wpa_printf(MSG_DEBUG, "WPS: * Encryption Type (0x%x)", encr_type); + wpa_printf(MSG_DEBUG, "WPS: * Encryption Type (0x%x)", + wps->wps->ap_encr_type); wpabuf_put_be16(msg, ATTR_ENCR_TYPE); wpabuf_put_be16(msg, 2); - wpabuf_put_be16(msg, encr_type); + wpabuf_put_be16(msg, wps->wps->ap_encr_type); return 0; } static int wps_build_cred_network_key(struct wps_data *wps, struct wpabuf *msg) { - wpa_printf(MSG_DEBUG, "WPS: * Network Key"); + if ((wps->wps->ap_auth_type & (WPS_AUTH_WPAPSK | WPS_AUTH_WPA2PSK)) && + wps->wps->network_key_len == 0) { + char hex[65]; + u8 psk[32]; + /* Generate a random per-device PSK */ + if (random_get_bytes(psk, sizeof(psk)) < 0) + return -1; + wpa_hexdump_key(MSG_DEBUG, "WPS: Generated per-device PSK", + psk, sizeof(psk)); + wpa_printf(MSG_DEBUG, "WPS: * Network Key (len=%u)", + (unsigned int) wps->new_psk_len * 2); + wpa_snprintf_hex(hex, sizeof(hex), psk, sizeof(psk)); + wpabuf_put_be16(msg, ATTR_NETWORK_KEY); + wpabuf_put_be16(msg, sizeof(psk) * 2); + wpabuf_put_data(msg, hex, sizeof(psk) * 2); + if (wps->wps->registrar) { + wps_cb_new_psk(wps->wps->registrar, + wps->peer_dev.mac_addr, + wps->p2p_dev_addr, psk, sizeof(psk)); + } + return 0; + } + + wpa_printf(MSG_DEBUG, "WPS: * Network Key (len=%u)", + (unsigned int) wps->wps->network_key_len); wpabuf_put_be16(msg, ATTR_NETWORK_KEY); wpabuf_put_be16(msg, wps->wps->network_key_len); wpabuf_put_data(msg, wps->wps->network_key, wps->wps->network_key_len); @@ -310,6 +307,9 @@ static int wps_build_cred_mac_addr(struct wps_data *wps, struct wpabuf *msg) static int wps_build_ap_settings(struct wps_data *wps, struct wpabuf *plain) { + const u8 *start, *end; + int ret; + if (wps->wps->ap_settings) { wpa_printf(MSG_DEBUG, "WPS: * AP Settings (pre-configured)"); wpabuf_put_data(plain, wps->wps->ap_settings, @@ -317,11 +317,19 @@ static int wps_build_ap_settings(struct wps_data *wps, struct wpabuf *plain) return 0; } - return wps_build_cred_ssid(wps, plain) || + wpa_printf(MSG_DEBUG, "WPS: * AP Settings based on current configuration"); + start = wpabuf_put(plain, 0); + ret = wps_build_cred_ssid(wps, plain) || wps_build_cred_mac_addr(wps, plain) || wps_build_cred_auth_type(wps, plain) || wps_build_cred_encr_type(wps, plain) || wps_build_cred_network_key(wps, plain); + end = wpabuf_put(plain, 0); + + wpa_hexdump_key(MSG_DEBUG, "WPS: Plaintext AP Settings", + start, end - start); + + return ret; } @@ -688,7 +696,6 @@ static int wps_process_cred_e(struct wps_data *wps, const u8 *cred, #endif /* CONFIG_WPS_STRICT */ } -#ifdef CONFIG_WPS2 if (!(wps->cred.encr_type & (WPS_ENCR_NONE | WPS_ENCR_TKIP | WPS_ENCR_AES))) { if (wps->cred.encr_type & WPS_ENCR_WEP) { @@ -702,7 +709,6 @@ static int wps_process_cred_e(struct wps_data *wps, const u8 *cred, "invalid encr_type 0x%x", wps->cred.encr_type); return -1; } -#endif /* CONFIG_WPS2 */ if (wps->wps->cred_cb) { wps->cred.cred_attr = cred - 4; @@ -789,7 +795,6 @@ static int wps_process_ap_settings_e(struct wps_data *wps, #endif /* CONFIG_WPS_STRICT */ } -#ifdef CONFIG_WPS2 if (!(cred.encr_type & (WPS_ENCR_NONE | WPS_ENCR_TKIP | WPS_ENCR_AES))) { if (cred.encr_type & WPS_ENCR_WEP) { @@ -803,7 +808,6 @@ static int wps_process_ap_settings_e(struct wps_data *wps, "invalid encr_type 0x%x", cred.encr_type); return -1; } -#endif /* CONFIG_WPS2 */ #ifdef CONFIG_WPS_STRICT if (wps2) { @@ -820,7 +824,6 @@ static int wps_process_ap_settings_e(struct wps_data *wps, } #endif /* CONFIG_WPS_STRICT */ -#ifdef CONFIG_WPS2 if ((cred.encr_type & (WPS_ENCR_TKIP | WPS_ENCR_AES)) == WPS_ENCR_TKIP) { wpa_printf(MSG_DEBUG, "WPS: Upgrade encr_type TKIP -> " @@ -834,7 +837,6 @@ static int wps_process_ap_settings_e(struct wps_data *wps, "WPAPSK+WPA2PSK"); cred.auth_type |= WPS_AUTH_WPA2PSK; } -#endif /* CONFIG_WPS2 */ if (wps->wps->cred_cb) { cred.cred_attr = wpabuf_head(attrs); diff --git a/src/wps/wps_er.c b/src/wps/wps_er.c index 8e9ee7a..8b2675e 100644 --- a/src/wps/wps_er.c +++ b/src/wps/wps_er.c @@ -1490,11 +1490,9 @@ static int wps_er_build_sel_reg_config_methods(struct wpabuf *msg, static int wps_er_build_uuid_r(struct wpabuf *msg, const u8 *uuid_r) { -#ifdef CONFIG_WPS2 wpabuf_put_be16(msg, ATTR_UUID_R); wpabuf_put_be16(msg, WPS_UUID_LEN); wpabuf_put_data(msg, uuid_r, WPS_UUID_LEN); -#endif /* CONFIG_WPS2 */ return 0; } @@ -1506,9 +1504,7 @@ void wps_er_set_sel_reg(struct wps_er *er, int sel_reg, u16 dev_passwd_id, struct wps_er_ap *ap; struct wps_registrar *reg = er->wps->registrar; const u8 *auth_macs; -#ifdef CONFIG_WPS2 u8 bcast[ETH_ALEN]; -#endif /* CONFIG_WPS2 */ size_t count; union wps_event_data data; @@ -1522,13 +1518,11 @@ void wps_er_set_sel_reg(struct wps_er *er, int sel_reg, u16 dev_passwd_id, return; auth_macs = wps_authorized_macs(reg, &count); -#ifdef CONFIG_WPS2 if (count == 0) { os_memset(bcast, 0xff, ETH_ALEN); auth_macs = bcast; count = 1; } -#endif /* CONFIG_WPS2 */ if (wps_build_version(msg) || wps_er_build_selected_registrar(msg, sel_reg) || diff --git a/src/wps/wps_i.h b/src/wps/wps_i.h index 22070db..f7154f8 100644 --- a/src/wps/wps_i.h +++ b/src/wps/wps_i.h @@ -212,5 +212,7 @@ int wps_registrar_pbc_overlap(struct wps_registrar *reg, const u8 *addr, const u8 *uuid_e); void wps_registrar_remove_nfc_pw_token(struct wps_registrar *reg, struct wps_nfc_pw_token *token); +int wps_cb_new_psk(struct wps_registrar *reg, const u8 *mac_addr, + const u8 *p2p_dev_addr, const u8 *psk, size_t psk_len); #endif /* WPS_I_H */ diff --git a/src/wps/wps_module_tests.c b/src/wps/wps_module_tests.c new file mode 100644 index 0000000..6800e86 --- /dev/null +++ b/src/wps/wps_module_tests.c @@ -0,0 +1,337 @@ +/* + * WPS module tests + * Copyright (c) 2014, Jouni Malinen <j@w1.fi> + * + * 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 "wps_attr_parse.h" + +struct wps_attr_parse_test { + const char *data; + int result; + int extra; +}; + +struct wps_attr_parse_test wps_attr_parse_test_cases[] = { + /* Empty message */ + { "", 0, 0 }, + /* Truncated attribute header */ + { "10", -1, 0 }, + { "1010", -1, 0 }, + { "101000", -1, 0 }, + /* Attribute overflow */ + { "10100001", -1, 0 }, +#ifdef CONFIG_WPS_STRICT + { "10270000001057000101", -1, 0 }, + { "1027000010570001010000000000", -1, 0 }, +#else /* CONFIG_WPS_STRICT */ + /* Network Key workaround */ + { "10270000001057000101", 0, 1 }, + { "10230000001057000101", -1, 0 }, + { "10270000101057000101", -1, 0 }, + /* Mac OS X 10.6 padding workaround */ + { "1027000010570001010000000000", 0, 1 }, + { "1027000010570001010000000000000001000000", -1, 0 }, +#endif /* CONFIG_WPS_STRICT */ + /* Version */ + { "104a000110", 0, 0 }, + { "104a0000", -1, 0 }, + /* Message Type */ + { "1022000101", 0, 0 }, + { "10220000", -1, 0 }, + /* Enrollee Nonce */ + { "101a001000112233445566778899aabbccddeeff", 0, 0 }, + { "101a00111122334455667788990011223344556677", -1, 0 }, + /* Registrar Nonce */ + { "1039001000112233445566778899aabbccddeeff", 0, 0 }, + { "103900111122334455667788990011223344556677", -1, 0 }, + /* UUID-E */ + { "1047001000112233445566778899aabbccddeeff", 0, 0 }, + { "10470000", -1, 0 }, + { "104700111122334455667788990011223344556677", -1, 0 }, + /* UUID-R */ + { "1048001000112233445566778899aabbccddeeff", 0, 0 }, + { "10480000", -1, 0 }, + { "104800111122334455667788990011223344556677", -1, 0 }, + /* Auth Type Flags */ + { "100400021122", 0, 0 }, + { "10040001ff", -1, 0 }, + /* Encr Type Flags */ + { "101000021122", 0, 0 }, + { "10100001ff", -1, 0 }, + /* Connection Type Flags */ + { "100d0001ff", 0, 0 }, + { "100d0002ffff", -1, 0 }, + /* Config Methods */ + { "10080002ffff", 0, 0 }, + { "10080001ff", -1, 0 }, + /* Selected Registrar Config Methods */ + { "10530002ffff", 0, 0 }, + { "10530001ff", -1, 0 }, + /* Primary Device Type */ + { "105400081122334455667788", 0, 0 }, + { "105400111122334455667788990011223344556677", -1, 0 }, + /* RF Bands */ + { "103c0001ff", 0, 0 }, + { "103c0002ffff", -1, 0 }, + /* Association State */ + { "10020002ffff", 0, 0 }, + { "10020001ff", -1, 0 }, + /* Config Error */ + { "100900020001", 0, 0 }, + { "10090001ff", -1, 0 }, + /* Device Password ID */ + { "101200020004", 0, 0 }, + { "10120001ff", -1, 0 }, + /* OOB Device Password */ + { "102c001611223344556677889900112233445566778899000007", 0, 0 }, + { "102c0036112233445566778899001122334455667788990011223344556677889900112233445566778899001122334455667788990011223344", 0, 0 }, + { "102c0001ff", -1, 0 }, + { "102c003711223344556677889900112233445566778899001122334455667788990011223344556677889900112233445566778899001122334455", -1, 0 }, + { "102c002511223344556677889900112233445566778899001122334455667788990011223344556677", -1, 0 }, + /* OS Version */ + { "102d000411223344", 0, 0 }, + { "102d00111122334455667788990011223344556677", -1, 0 }, + /* WPS State */ + { "1044000101", 0, 0 }, + { "10440002ffff", -1, 0 }, + /* Authenticator */ + { "100500081122334455667788", 0, 0 }, + { "10050000", -1, 0 }, + { "100500111122334455667788990011223344556677", -1, 0 }, + /* R-Hash1 */ + { "103d00201122334455667788990011223344556677889900112233445566778899001122", 0, 0 }, + { "103d0000", -1, 0 }, + { "103d0021112233445566778899001122334455667788990011223344556677889900112233", -1, 0 }, + /* R-Hash2 */ + { "103e00201122334455667788990011223344556677889900112233445566778899001122", 0, 0 }, + { "103e0000", -1, 0 }, + { "103e0021112233445566778899001122334455667788990011223344556677889900112233", -1, 0 }, + /* E-Hash1 */ + { "101400201122334455667788990011223344556677889900112233445566778899001122", 0, 0 }, + { "10140000", -1, 0 }, + { "10140021112233445566778899001122334455667788990011223344556677889900112233", -1, 0 }, + /* E-Hash2 */ + { "101500201122334455667788990011223344556677889900112233445566778899001122", 0, 0 }, + { "10150000", -1, 0 }, + { "10150021112233445566778899001122334455667788990011223344556677889900112233", -1, 0 }, + /* R-SNonce1 */ + { "103f001011223344556677889900112233445566", 0, 0 }, + { "103f0000", -1, 0 }, + { "103f00111122334455667788990011223344556677", -1, 0 }, + /* R-SNonce2 */ + { "1040001011223344556677889900112233445566", 0, 0 }, + { "10400000", -1, 0 }, + { "104000111122334455667788990011223344556677", -1, 0 }, + /* E-SNonce1 */ + { "1016001011223344556677889900112233445566", 0, 0 }, + { "10160000", -1, 0 }, + { "101600111122334455667788990011223344556677", -1, 0 }, + /* E-SNonce2 */ + { "1017001011223344556677889900112233445566", 0, 0 }, + { "10170000", -1, 0 }, + { "101700111122334455667788990011223344556677", -1, 0 }, + /* Key Wrap Authenticator */ + { "101e00081122334455667788", 0, 0 }, + { "101e0000", -1, 0 }, + { "101e0009112233445566778899", -1, 0 }, + /* Authentication Type */ + { "100300020001", 0, 0 }, + { "10030001ff", -1, 0 }, + /* Encryption Type */ + { "100f00020001", 0, 0 }, + { "100f0001ff", -1, 0 }, + /* Network Index */ + { "1026000101", 0, 0 }, + { "10260002ffff", -1, 0 }, + /* Network Key Index */ + { "1028000101", 0, 3 }, + { "10280002ffff", -1, 0 }, + /* MAC Address */ + { "10200006112233445566", 0, 0 }, + { "10200000", -1, 0 }, + { "1020000711223344556677", -1, 0 }, + /* Selected Registrar */ + { "1041000101", 0, 0 }, + { "10410002ffff", -1, 0 }, + /* Request Type */ + { "103a000101", 0, 0 }, + { "103a0002ffff", -1, 0 }, + /* Response Type */ + { "103b000101", 0, 0 }, + { "103b0002ffff", -1, 0 }, + /* Manufacturer */ + { "10210000", 0, 0 }, + /* Model Name */ + { "10230000", 0, 0 }, + /* Model Number */ + { "10240000", 0, 0 }, + /* Serial Number */ + { "10420000", 0, 0 }, + /* Device Name */ + { "10110000", 0, 0 }, + /* Public Key */ + { "10320000", 0, 0 }, + /* Enc Settings */ + { "10180000", 0, 0 }, + /* SSID */ + { "10450000", 0, 0 }, + /* AP Setup Locked */ + { "1057000101", 0, 0 }, + { "10570002ffff", -1, 0 }, + /* Requested Device Type */ + { "106a00081122334455667788", 0, 0 }, + { "106a0000", -1, 0 }, + { "106a0009112233445566778899", -1, 0 }, + /* More than maximum Requested Device Type attributes */ + { "106a00081122334455667788106a00081122334455667788106a00081122334455667788106a00081122334455667788106a00081122334455667788106a00081122334455667788106a00081122334455667788106a00081122334455667788106a00081122334455667788106a00081122334455667788106a00081122334455667788106a00081122334455667788", 0, 4 }, + /* Secondary Device Type List */ + { "105500081122334455667788", 0, 0 }, + { "1055000711223344556677", -1, 0 }, + { "1055008811223344556677889900112233445566778899001122334455667788990011223344556677889900112233445566778899001122334455667788990011223344556677889900112233445566778899001122334455667788990011223344556677889900112233445566778899001122334455667788990011223344556677889900112233445566", -1, 0 }, + /* AP Channel */ + { "100100020001", 0, 0 }, + { "1001000101", -1, 0 }, + /* Skip invalid Vendor Extension */ + { "10490000", 0, 0 }, + { "1049000100", 0, 0 }, + { "104900020000", 0, 0 }, + /* Too long unknown vendor extension */ + { "10490401" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "112233445566778899001122334455667788990011223344556677889900" + "1122334455", -1, 0 }, + /* Maximum unknown vendor extensions */ + { "10490003111111104900032222221049000333333310490003444444104900035555551049000366666610490003777777104900038888881049000399999910490003AAAAAA", 0, 5 }, + /* More than maximum unknown vendor extensions */ + { "10490003111111104900032222221049000333333310490003444444104900035555551049000366666610490003777777104900038888881049000399999910490003AAAAAA10490003BBBBBB", -1, 0 }, + /* WFA vendor extensions */ + { "1049000300372a", 0, 0 }, + { "1049000400372a00", 0, 0 }, + { "1049000500372a0001", 0, 0 }, + { "1049001600372a0001ff0100020101030101040101ff00fe0101", 0, 6 }, + /* Invalid Version2 length */ + { "1049000500372a0000", -1, 0 }, + /* Invalid Network Key Shareable length */ + { "1049000500372a0200", -1, 0 }, + /* Invalid Requedt To Enroll length */ + { "1049000500372a0300", -1, 0 }, + /* Invalid Settings Delay Time length */ + { "1049000500372a0400", -1, 0 }, + /* More than maximum Credential attributes */ + { "100e0000100e0000100e0000100e0000100e0000100e0000100e0000100e0000100e0000100e0000100e0000100e0000", 0, 2 }, +}; + + +static int wps_attr_parse_tests(void) +{ + struct wps_parse_attr attr; + unsigned int i; + int ret = 0; + + wpa_printf(MSG_INFO, "WPS attribute parsing tests"); + + for (i = 0; i < ARRAY_SIZE(wps_attr_parse_test_cases); i++) { + struct wpabuf *buf; + size_t len; + struct wps_attr_parse_test *test = + &wps_attr_parse_test_cases[i]; + + len = os_strlen(test->data) / 2; + buf = wpabuf_alloc(len); + if (buf == NULL) + return -1; + if (hexstr2bin(test->data, wpabuf_put(buf, len), len) < 0) { + wpabuf_free(buf); + return -1; + } + if (wps_parse_msg(buf, &attr) != test->result) { + wpa_printf(MSG_ERROR, "WPS attribute parsing test %u failed: %s", + i, test->data); + ret = -1; + } + switch (test->extra) { + case 1: + if (!attr.network_key || !attr.ap_setup_locked) + ret = -1; + break; + case 2: + if (attr.num_cred != MAX_CRED_COUNT) + ret = -1; + break; + case 3: + if (!attr.network_key_idx) + ret = -1; + break; + case 4: + if (attr.num_req_dev_type != MAX_REQ_DEV_TYPE_COUNT) + ret = -1; + break; + case 5: + if (attr.num_vendor_ext != MAX_WPS_PARSE_VENDOR_EXT) + ret = -1; + break; + case 6: + if (!attr.version2 || + !attr.authorized_macs || + !attr.network_key_shareable || + !attr.request_to_enroll || + !attr.settings_delay_time) + ret = -1; + break; + } + wpabuf_free(buf); + } + + return ret; +} + + +int wps_module_tests(void) +{ + int ret = 0; + + wpa_printf(MSG_INFO, "WPS module tests"); + + if (wps_attr_parse_tests() < 0) + ret = -1; + + return ret; +} diff --git a/src/wps/wps_registrar.c b/src/wps/wps_registrar.c index 6d879be..b917e6b 100644 --- a/src/wps/wps_registrar.c +++ b/src/wps/wps_registrar.c @@ -538,7 +538,6 @@ static int wps_build_sel_pbc_reg_uuid_e(struct wps_registrar *reg, static void wps_set_pushbutton(u16 *methods, u16 conf_methods) { *methods |= WPS_CONFIG_PUSHBUTTON; -#ifdef CONFIG_WPS2 if ((conf_methods & WPS_CONFIG_VIRT_PUSHBUTTON) == WPS_CONFIG_VIRT_PUSHBUTTON) *methods |= WPS_CONFIG_VIRT_PUSHBUTTON; @@ -556,7 +555,6 @@ static void wps_set_pushbutton(u16 *methods, u16 conf_methods) */ *methods |= WPS_CONFIG_PHY_PUSHBUTTON; } -#endif /* CONFIG_WPS2 */ } @@ -568,10 +566,8 @@ static int wps_build_sel_reg_config_methods(struct wps_registrar *reg, return 0; methods = reg->wps->config_methods; methods &= ~WPS_CONFIG_PUSHBUTTON; -#ifdef CONFIG_WPS2 methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON | WPS_CONFIG_PHY_PUSHBUTTON); -#endif /* CONFIG_WPS2 */ if (reg->pbc) wps_set_pushbutton(&methods, reg->wps->config_methods); if (reg->sel_reg_config_methods_override >= 0) @@ -594,10 +590,8 @@ static int wps_build_probe_config_methods(struct wps_registrar *reg, * external Registrars. */ methods = reg->wps->config_methods & ~WPS_CONFIG_PUSHBUTTON; -#ifdef CONFIG_WPS2 methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON | WPS_CONFIG_PHY_PUSHBUTTON); -#endif /* CONFIG_WPS2 */ wpa_printf(MSG_DEBUG, "WPS: * Config Methods (%x)", methods); wpabuf_put_be16(msg, ATTR_CONFIG_METHODS); wpabuf_put_be16(msg, 2); @@ -617,13 +611,11 @@ const u8 * wps_authorized_macs(struct wps_registrar *reg, size_t *count) { *count = 0; -#ifdef CONFIG_WPS2 while (*count < WPS_MAX_AUTHORIZED_MACS) { if (is_zero_ether_addr(reg->authorized_macs_union[*count])) break; (*count)++; } -#endif /* CONFIG_WPS2 */ return (const u8 *) reg->authorized_macs_union; } @@ -1170,8 +1162,8 @@ void wps_registrar_probe_req_rx(struct wps_registrar *reg, const u8 *addr, } -static int wps_cb_new_psk(struct wps_registrar *reg, const u8 *mac_addr, - const u8 *p2p_dev_addr, const u8 *psk, size_t psk_len) +int wps_cb_new_psk(struct wps_registrar *reg, const u8 *mac_addr, + const u8 *p2p_dev_addr, const u8 *psk, size_t psk_len) { if (reg->new_psk_cb == NULL) return 0; @@ -1217,10 +1209,8 @@ static void wps_cb_set_sel_reg(struct wps_registrar *reg) if (reg->selected_registrar) { methods = reg->wps->config_methods & ~WPS_CONFIG_PUSHBUTTON; -#ifdef CONFIG_WPS2 methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON | WPS_CONFIG_PHY_PUSHBUTTON); -#endif /* CONFIG_WPS2 */ if (reg->pbc) wps_set_pushbutton(&methods, reg->wps->config_methods); } @@ -1609,8 +1599,6 @@ int wps_build_cred(struct wps_data *wps, struct wpabuf *msg) wps->auth_type = WPS_AUTH_WPAPSK; else if (wps->auth_type & WPS_AUTH_OPEN) wps->auth_type = WPS_AUTH_OPEN; - else if (wps->auth_type & WPS_AUTH_SHARED) - wps->auth_type = WPS_AUTH_SHARED; else { wpa_printf(MSG_DEBUG, "WPS: Unsupported auth_type 0x%x", wps->auth_type); @@ -1630,10 +1618,12 @@ int wps_build_cred(struct wps_data *wps, struct wpabuf *msg) return -1; } } else { - if (wps->encr_type & WPS_ENCR_WEP) - wps->encr_type = WPS_ENCR_WEP; - else if (wps->encr_type & WPS_ENCR_NONE) + if (wps->encr_type & WPS_ENCR_NONE) wps->encr_type = WPS_ENCR_NONE; +#ifdef CONFIG_TESTING_OPTIONS + else if (wps->encr_type & WPS_ENCR_WEP) + wps->encr_type = WPS_ENCR_WEP; +#endif /* CONFIG_TESTING_OPTIONS */ else { wpa_printf(MSG_DEBUG, "WPS: No suitable encryption " "type for non-WPA/WPA2 mode"); @@ -3446,10 +3436,8 @@ void wps_registrar_selected_registrar_changed(struct wps_registrar *reg, u16 methods; methods = reg->wps->config_methods & ~WPS_CONFIG_PUSHBUTTON; -#ifdef CONFIG_WPS2 methods &= ~(WPS_CONFIG_VIRT_PUSHBUTTON | WPS_CONFIG_PHY_PUSHBUTTON); -#endif /* CONFIG_WPS2 */ if (reg->pbc) { reg->sel_reg_dev_password_id_override = DEV_PW_PUSHBUTTON; @@ -3510,7 +3498,6 @@ int wps_registrar_get_info(struct wps_registrar *reg, const u8 *addr, int wps_registrar_config_ap(struct wps_registrar *reg, struct wps_credential *cred) { -#ifdef CONFIG_WPS2 wpa_printf(MSG_DEBUG, "WPS: encr_type=0x%x", cred->encr_type); if (!(cred->encr_type & (WPS_ENCR_NONE | WPS_ENCR_TKIP | WPS_ENCR_AES))) { @@ -3538,7 +3525,6 @@ int wps_registrar_config_ap(struct wps_registrar *reg, "WPAPSK+WPA2PSK"); cred->auth_type |= WPS_AUTH_WPA2PSK; } -#endif /* CONFIG_WPS2 */ if (reg->wps->cred_cb) return reg->wps->cred_cb(reg->wps->cb_ctx, cred); diff --git a/src/wps/wps_upnp_ap.c b/src/wps/wps_upnp_ap.c index 4f1dd8f..2949f14 100644 --- a/src/wps/wps_upnp_ap.c +++ b/src/wps/wps_upnp_ap.c @@ -61,11 +61,9 @@ int upnp_er_set_selected_registrar(struct wps_registrar *reg, os_memcpy(s->authorized_macs, attr.authorized_macs, count * ETH_ALEN); } else if (!attr.version2) { -#ifdef CONFIG_WPS2 wpa_printf(MSG_DEBUG, "WPS: Add broadcast " "AuthorizedMACs for WPS 1.0 ER"); os_memset(s->authorized_macs, 0xff, ETH_ALEN); -#endif /* CONFIG_WPS2 */ } eloop_register_timeout(WPS_PBC_WALK_TIME, 0, upnp_er_set_selected_timeout, s, reg); diff --git a/src/wps/wps_upnp_ssdp.c b/src/wps/wps_upnp_ssdp.c index 416961c..098571c 100644 --- a/src/wps/wps_upnp_ssdp.c +++ b/src/wps/wps_upnp_ssdp.c @@ -134,6 +134,8 @@ next_advertisement(struct upnp_wps_device_sm *sm, *islast = 0; iface = dl_list_first(&sm->interfaces, struct upnp_wps_device_interface, list); + if (!iface) + return NULL; uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string)); msg = wpabuf_alloc(800); /* more than big enough */ if (msg == NULL) @@ -587,6 +589,8 @@ static void ssdp_parse_msearch(struct upnp_wps_device_sm *sm, &sm->interfaces, struct upnp_wps_device_interface, list); + if (!iface) + continue; data += os_strlen("uuid:"); uuid_bin2str(iface->wps->uuid, uuid_string, sizeof(uuid_string)); diff --git a/src/wps/wps_upnp_web.c b/src/wps/wps_upnp_web.c index 11386d8..b1cf571 100644 --- a/src/wps/wps_upnp_web.c +++ b/src/wps/wps_upnp_web.c @@ -179,15 +179,12 @@ static const char *wps_device_xml_postfix = /* format_wps_device_xml -- produce content of "file" wps_device.xml * (UPNP_WPS_DEVICE_XML_FILE) */ -static void format_wps_device_xml(struct upnp_wps_device_sm *sm, +static void format_wps_device_xml(struct upnp_wps_device_interface *iface, + struct upnp_wps_device_sm *sm, struct wpabuf *buf) { const char *s; char uuid_string[80]; - struct upnp_wps_device_interface *iface; - - iface = dl_list_first(&sm->interfaces, - struct upnp_wps_device_interface, list); wpabuf_put_str(buf, wps_device_xml_prefix); @@ -319,13 +316,15 @@ static void web_connection_parse_get(struct upnp_wps_device_sm *sm, iface = dl_list_first(&sm->interfaces, struct upnp_wps_device_interface, list); + if (iface == NULL) { + http_request_deinit(hreq); + return; + } /* * It is not required that filenames be case insensitive but it is * allowed and cannot hurt here. */ - if (filename == NULL) - filename = "(null)"; /* just in case */ if (os_strcasecmp(filename, UPNP_WPS_DEVICE_XML_FILE) == 0) { wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP GET for device XML"); req = GET_DEVICE_XML_FILE; @@ -393,7 +392,7 @@ static void web_connection_parse_get(struct upnp_wps_device_sm *sm, switch (req) { case GET_DEVICE_XML_FILE: - format_wps_device_xml(sm, buf); + format_wps_device_xml(iface, sm, buf); break; case GET_SCPD_XML_FILE: wpabuf_put_str(buf, wps_scpd_xml); @@ -421,13 +420,14 @@ web_process_get_device_info(struct upnp_wps_device_sm *sm, iface = dl_list_first(&sm->interfaces, struct upnp_wps_device_interface, list); - peer = &iface->peer; wpa_printf(MSG_DEBUG, "WPS UPnP: GetDeviceInfo"); - if (iface->ctx->ap_pin == NULL) + if (!iface || iface->ctx->ap_pin == NULL) return HTTP_INTERNAL_SERVER_ERROR; + peer = &iface->peer; + /* * Request for DeviceInfo, i.e., M1 TLVs. This is a start of WPS * registration over UPnP with the AP acting as an Enrollee. It should @@ -475,6 +475,8 @@ web_process_put_message(struct upnp_wps_device_sm *sm, char *data, iface = dl_list_first(&sm->interfaces, struct upnp_wps_device_interface, list); + if (!iface) + return HTTP_INTERNAL_SERVER_ERROR; /* * PutMessage is used by external UPnP-based Registrar to perform WPS @@ -948,7 +950,7 @@ static void web_connection_parse_subscribe(struct upnp_wps_device_sm *sm, wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP SUBSCRIBE for event"); end = os_strchr(h, '\n'); - for (; end != NULL; h = end + 1) { + while (end) { /* Option line by option line */ h = end + 1; end = os_strchr(h, '\n'); @@ -1155,7 +1157,7 @@ static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm, wpa_printf(MSG_DEBUG, "WPS UPnP: HTTP UNSUBSCRIBE for event"); end = os_strchr(h, '\n'); - for (; end != NULL; h = end + 1) { + while (end) { /* Option line by option line */ h = end + 1; end = os_strchr(h, '\n'); @@ -1173,7 +1175,6 @@ static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm, ..... } #endif - /* SID is only for renewal */ match = "SID:"; match_len = os_strlen(match); if (os_strncasecmp(h, match, match_len) == 0) { @@ -1196,6 +1197,20 @@ static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm, got_uuid = 1; continue; } + + match = "NT:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + ret = HTTP_BAD_REQUEST; + goto send_msg; + } + + match = "CALLBACK:"; + match_len = os_strlen(match); + if (os_strncasecmp(h, match, match_len) == 0) { + ret = HTTP_BAD_REQUEST; + goto send_msg; + } } if (got_uuid) { @@ -1209,6 +1224,10 @@ static void web_connection_parse_unsubscribe(struct upnp_wps_device_sm *sm, sa->domain_and_port : "-null-"); dl_list_del(&s->list); subscription_destroy(s); + } else { + wpa_printf(MSG_INFO, "WPS UPnP: Could not find matching subscription to unsubscribe"); + ret = HTTP_PRECONDITION_FAILED; + goto send_msg; } } else { wpa_printf(MSG_INFO, "WPS UPnP: Unsubscribe fails (not " diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk index 9b07460..eaf9705 100644 --- a/wpa_supplicant/Android.mk +++ b/wpa_supplicant/Android.mk @@ -24,24 +24,24 @@ L_CFLAGS += -DVERSION_STR_POSTFIX=\"-$(PLATFORM_VERSION)\" # Set Android log name L_CFLAGS += -DANDROID_LOG_NAME=\"wpa_supplicant\" +# Disable unused parameter warnings +L_CFLAGS += -Wno-unused-parameter + +# Set Android extended P2P functionality +L_CFLAGS += -DANDROID_P2P +ifeq ($(BOARD_WPA_SUPPLICANT_PRIVATE_LIB),) +L_CFLAGS += -DANDROID_P2P_STUB +endif + # Disable roaming in wpa_supplicant ifdef CONFIG_NO_ROAMING L_CFLAGS += -DCONFIG_NO_ROAMING endif ifeq ($(BOARD_WLAN_DEVICE), bcmdhd) -L_CFLAGS += -DANDROID_P2P L_CFLAGS += -DP2P_CONCURRENT_SEARCH_DELAY=0 endif -ifeq ($(BOARD_WLAN_DEVICE), qcwcn) -L_CFLAGS += -DANDROID_P2P -endif - -ifeq ($(BOARD_WLAN_DEVICE), mrvl) -L_CFLAGS += -DANDROID_P2P -endif - # Use Android specific directory for control interface sockets L_CFLAGS += -DCONFIG_CTRL_IFACE_CLIENT_DIR=\"/data/misc/wifi/sockets\" L_CFLAGS += -DCONFIG_CTRL_IFACE_DIR=\"/data/system/wpa_supplicant\" @@ -70,8 +70,12 @@ INCLUDES += $(LOCAL_PATH)/src/wps INCLUDES += external/openssl/include INCLUDES += system/security/keystore/include ifdef CONFIG_DRIVER_NL80211 +ifneq ($(wildcard external/libnl),) +INCLUDES += external/libnl/include +else INCLUDES += external/libnl-headers endif +endif ifdef CONFIG_FIPS CONFIG_NO_RANDOM_POOL= @@ -136,6 +140,10 @@ ifdef CONFIG_ELOOP_POLL L_CFLAGS += -DCONFIG_ELOOP_POLL endif +ifdef CONFIG_ELOOP_EPOLL +L_CFLAGS += -DCONFIG_ELOOP_EPOLL +endif + ifdef CONFIG_EAPOL_TEST L_CFLAGS += -Werror -DEAPOL_TEST endif @@ -276,6 +284,7 @@ ifdef CONFIG_HS20 OBJS += hs20_supplicant.c L_CFLAGS += -DCONFIG_HS20 CONFIG_INTERWORKING=y +NEED_AES_OMAC1=y endif ifdef CONFIG_INTERWORKING @@ -612,10 +621,6 @@ NEED_SHA256=y endif ifdef CONFIG_WPS -ifdef CONFIG_WPS2 -L_CFLAGS += -DCONFIG_WPS2 -endif - # EAP-WSC L_CFLAGS += -DCONFIG_WPS -DEAP_WSC OBJS += wps_supplicant.c @@ -957,7 +962,8 @@ endif OBJS += src/crypto/crypto_gnutls.c OBJS_p += src/crypto/crypto_gnutls.c ifdef NEED_FIPS186_2_PRF -OBJS += src/crypto/fips_prf_gnutls.c +OBJS += src/crypto/fips_prf_internal.c +OBJS += src/crypto/sha1-internal.c endif LIBS += -lgcrypt LIBS_p += -lgcrypt @@ -973,7 +979,8 @@ endif OBJS += src/crypto/crypto_cryptoapi.c OBJS_p += src/crypto/crypto_cryptoapi.c ifdef NEED_FIPS186_2_PRF -OBJS += src/crypto/fips_prf_cryptoapi.c +OBJS += src/crypto/fips_prf_internal.c +OBJS += src/crypto/sha1-internal.c endif CONFIG_INTERNAL_SHA256=y CONFIG_INTERNAL_RC4=y @@ -988,7 +995,8 @@ endif OBJS += src/crypto/crypto_nss.c OBJS_p += src/crypto/crypto_nss.c ifdef NEED_FIPS186_2_PRF -OBJS += src/crypto/fips_prf_nss.c +OBJS += src/crypto/fips_prf_internal.c +OBJS += src/crypto/sha1-internal.c endif LIBS += -lnss3 LIBS_p += -lnss3 @@ -1568,8 +1576,12 @@ ifeq ($(CONFIG_TLS), openssl) LOCAL_SHARED_LIBRARIES += libcrypto libssl libkeystore_binder endif ifdef CONFIG_DRIVER_NL80211 +ifneq ($(wildcard external/libnl),) +LOCAL_SHARED_LIBRARIES += libnl +else LOCAL_STATIC_LIBRARIES += libnl_2 endif +endif LOCAL_CFLAGS := $(L_CFLAGS) LOCAL_SRC_FILES := $(OBJS) LOCAL_C_INCLUDES := $(INCLUDES) @@ -1609,4 +1621,5 @@ LOCAL_C_INCLUDES = $(INCLUDES) LOCAL_SHARED_LIBRARIES := libcutils liblog LOCAL_COPY_HEADERS_TO := libwpa_client LOCAL_COPY_HEADERS := src/common/wpa_ctrl.h +LOCAL_COPY_HEADERS += src/common/qca-vendor.h include $(BUILD_SHARED_LIBRARY) diff --git a/wpa_supplicant/ChangeLog b/wpa_supplicant/ChangeLog index e40cf91..5558a5e 100644 --- a/wpa_supplicant/ChangeLog +++ b/wpa_supplicant/ChangeLog @@ -1,5 +1,125 @@ ChangeLog for wpa_supplicant +2014-06-04 - v2.2 + * added DFS indicator to get_capability freq + * added/fixed nl80211 functionality + - BSSID/frequency hint for driver-based BSS selection + - fix tearing down WDS STA interfaces + - support vendor specific driver command + (VENDOR <vendor id> <sub command id> [<hex formatted data>]) + - GO interface teardown optimization + - allow beacon interval to be configured for IBSS + - add SHA256-based AKM suites to CONNECT/ASSOCIATE commands + * removed unused NFC_RX_HANDOVER_REQ and NFC_RX_HANDOVER_SEL control + interface commands (the more generic NFC_REPORT_HANDOVER is now used) + * fixed MSCHAP UTF-8 to UCS-2 conversion for three-byte encoding; + this fixes password with include UTF-8 characters that use + three-byte encoding EAP methods that use NtPasswordHash + * fixed couple of sequencies where radio work items could get stuck, + e.g., when rfkill blocking happens during scanning or when + scan-for-auth workaround is used + * P2P enhancements/fixes + - enable enable U-APSD on GO automatically if the driver indicates + support for this + - fixed some service discovery cases with broadcast queries not being + sent to all stations + - fixed Probe Request frame triggering invitation to trigger only a + single invitation instance even if multiple Probe Request frames are + received + - fixed a potential NULL pointer dereference crash when processing an + invalid Invitation Request frame + - add optional configuration file for the P2P_DEVICE parameters + - optimize scan for GO during persistent group invocation + - fix possible segmentation fault when PBC overlap is detected while + using a separate P2P group interface + - improve GO Negotiation robustness by allowing GO Negotiation + Confirmation to be retransmitted + - do use freed memory on device found event when P2P NFC + * added phase1 network parameter options for disabling TLS v1.1 and v1.2 + to allow workarounds with misbehaving AAA servers + (tls_disable_tlsv1_1=1 and tls_disable_tlsv1_2=1) + * added support for OCSP stapling to validate AAA server certificate + during TLS exchange + * Interworking/Hotspot 2.0 enhancements + - prefer the last added network in Interworking connection to make the + behavior more consistent with likely user expectation + - roaming partner configuration (roaming_partner within a cred block) + - support Hotspot 2.0 Release 2 + * "hs20_anqp_get <BSSID> 8" to request OSU Providers list + * "hs20_icon_request <BSSID> <icon filename>" to request icon files + * "fetch_osu" and "cancel_osu_fetch" to start/stop full OSU provider + search (all suitable APs in scan results) + * OSEN network for online signup connection + * min_{dl,ul}_bandwidth_{home,roaming} cred parameters + * max_bss_load cred parameter + * req_conn_capab cred parameter + * sp_priority cred parameter + * ocsp cred parameter + * slow down automatic connection attempts on EAP failure to meet + required behavior (no more than 10 retries within a 10-minute + interval) + * sample implementation of online signup client (both SPP and + OMA-DM protocols) (hs20/client/*) + - fixed GAS indication for additional comeback delay with status + code 95 + - extend ANQP_GET to accept Hotspot 2.0 subtypes + ANQP_GET <addr> <info id>[,<info id>]... + [,hs20:<subtype>][...,hs20:<subtype>] + - add control interface events CRED-ADDED <id>, + CRED-MODIFIED <id> <field>, CRED-REMOVED <id> + - add "GET_CRED <id> <field>" command + - enable FT for the connection automatically if the AP advertises + support for this + - fix a case where auto_interworking=1 could end up stopping scanning + * fixed TDLS interoperability issues with supported operating class in + some deployed stations + * internal TLS implementation enhancements/fixes + - add SHA256-based cipher suites + - add DHE-RSA cipher suites + - fix X.509 validation of PKCS#1 signature to check for extra data + * fixed PTK derivation for CCMP-256 and GCMP-256 + * added "reattach" command for fast reassociate-back-to-same-BSS + * allow PMF to be enabled for AP mode operation with the ieee80211w + parameter + * added "get_capability tdls" command + * added option to set config blobs through control interface with + "SET blob <name> <hexdump>" + * D-Bus interface extensions/fixes + - make p2p_no_group_iface configurable + - declare ServiceDiscoveryRequest method properly + - export peer's device address as a property + - make reassociate command behave like the control interface one, + i.e., to allow connection from disconnected state + * added optional "freq=<channel ranges>" parameter to SET pno + * added optional "freq=<channel ranges>" parameter to SELECT_NETWORK + * fixed OBSS scan result processing for 20/40 MHz co-ex report + * remove WPS 1.0 only support, i.e., WSC 2.0 support is now enabled + whenever CONFIG_WPS=y is set + * fixed regression in parsing of WNM Sleep Mode exit key data + * fixed potential segmentation fault and memory leaks in WNM neighbor + report processing + * EAP-pwd fixes + - fragmentation of PWD-Confirm-Resp + - fix memory leak when fragmentation is used + - fix possible segmentation fault on EAP method deinit if an invalid + group is negotiated + * added MACsec/IEEE Std 802.1X-2010 PAE implementation (currently + available only with the macsec_qca driver wrapper) + * fixed EAP-SIM counter-too-small message + * added 'dup_network <id_s> <id_d> <name>' command; this can be used to + clone the psk field without having toextract it from wpa_supplicant + * fixed GSM authentication on USIM + * added support for usin epoll in eloop (CONFIG_ELOOP_EPOLL=y) + * fixed some concurrent virtual interface cases with dedicated P2P + management interface to not catch events from removed interface (this + could result in the management interface getting disabled) + * fixed a memory leak in SAE random number generation + * fixed off-by-one bounds checking in printf_encode() + - this could result in some control interface ATTACH command cases + terminating wpa_supplicant + * fixed EAPOL-Key exchange when GCMP is used with SHA256-based AKM + * various bug fixes + 2014-02-04 - v2.1 * added support for simultaneous authentication of equals (SAE) for stronger password-based authentication with WPA2-Personal diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index d1e11a3..817a69d 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -106,10 +106,10 @@ OBJS_priv += ../src/utils/trace.o LDFLAGS += -rdynamic CFLAGS += -funwind-tables ifdef CONFIG_WPA_TRACE_BFD -CFLAGS += -DWPA_TRACE_BFD -LIBS += -lbfd -LIBS_p += -lbfd -LIBS_c += -lbfd +CFLAGS += -DPACKAGE="wpa_supplicant" -DWPA_TRACE_BFD +LIBS += -lbfd -ldl -liberty -lz +LIBS_p += -lbfd -ldl -liberty -lz +LIBS_c += -lbfd -ldl -liberty -lz endif endif @@ -130,6 +130,9 @@ ifdef CONFIG_ELOOP_POLL CFLAGS += -DCONFIG_ELOOP_POLL endif +ifdef CONFIG_ELOOP_EPOLL +CFLAGS += -DCONFIG_ELOOP_EPOLL +endif ifdef CONFIG_EAPOL_TEST CFLAGS += -Werror -DEAPOL_TEST @@ -278,6 +281,7 @@ ifdef CONFIG_HS20 OBJS += hs20_supplicant.o CFLAGS += -DCONFIG_HS20 CONFIG_INTERWORKING=y +NEED_AES_OMAC1=y endif ifdef CONFIG_INTERWORKING @@ -506,7 +510,7 @@ endif ifdef CONFIG_EAP_PROXY CFLAGS += -DCONFIG_EAP_PROXY OBJS += ../src/eap_peer/eap_proxy_$(CONFIG_EAP_PROXY).o -include eap_proxy_$(CONFIG_EAP_PROXY).mk +include eap_proxy_$(CONFIG_EAP_PROXY).mak CONFIG_IEEE8021X_EAPOL=y endif @@ -613,10 +617,6 @@ NEED_SHA256=y endif ifdef CONFIG_WPS -ifdef CONFIG_WPS2 -CFLAGS += -DCONFIG_WPS2 -endif - # EAP-WSC CFLAGS += -DCONFIG_WPS -DEAP_WSC OBJS += wps_supplicant.o @@ -743,6 +743,19 @@ LIBS += -ldl -rdynamic endif endif +ifdef CONFIG_MACSEC +CFLAGS += -DCONFIG_MACSEC +NEED_AES_ENCBLOCK=y +NEED_AES_UNWRAP=y +NEED_AES_WRAP=y +NEED_AES_OMAC1=y +OBJS += wpas_kay.o +OBJS += ../src/pae/ieee802_1x_cp.o +OBJS += ../src/pae/ieee802_1x_kay.o +OBJS += ../src/pae/ieee802_1x_key.o +OBJS += ../src/pae/ieee802_1x_secy_ops.o +endif + ifdef CONFIG_AP NEED_80211_COMMON=y NEED_EAP_COMMON=y @@ -958,7 +971,8 @@ endif OBJS += ../src/crypto/crypto_gnutls.o OBJS_p += ../src/crypto/crypto_gnutls.o ifdef NEED_FIPS186_2_PRF -OBJS += ../src/crypto/fips_prf_gnutls.o +OBJS += ../src/crypto/fips_prf_internal.o +SHA1OBJS += ../src/crypto/sha1-internal.o endif LIBS += -lgcrypt LIBS_p += -lgcrypt @@ -974,7 +988,8 @@ endif OBJS += ../src/crypto/crypto_cryptoapi.o OBJS_p += ../src/crypto/crypto_cryptoapi.o ifdef NEED_FIPS186_2_PRF -OBJS += ../src/crypto/fips_prf_cryptoapi.o +OBJS += ../src/crypto/fips_prf_internal.o +SHA1OBJS += ../src/crypto/sha1-internal.o endif CONFIG_INTERNAL_SHA256=y CONFIG_INTERNAL_RC4=y @@ -989,7 +1004,8 @@ endif OBJS += ../src/crypto/crypto_nss.o OBJS_p += ../src/crypto/crypto_nss.o ifdef NEED_FIPS186_2_PRF -OBJS += ../src/crypto/fips_prf_nss.o +OBJS += ../src/crypto/fips_prf_internal.o +SHA1OBJS += ../src/crypto/sha1-internal.o endif LIBS += -lnss3 LIBS_p += -lnss3 @@ -1259,6 +1275,11 @@ endif ifeq ($(CONFIG_CTRL_IFACE), udp) CFLAGS += -DCONFIG_CTRL_IFACE_UDP endif +ifeq ($(CONFIG_CTRL_IFACE), udp6) +CONFIG_CTRL_IFACE=udp +CFLAGS += -DCONFIG_CTRL_IFACE_UDP +CFLAGS += -DCONFIG_CTRL_IFACE_UDP_IPV6 +endif ifeq ($(CONFIG_CTRL_IFACE), named_pipe) CFLAGS += -DCONFIG_CTRL_IFACE_NAMED_PIPE endif @@ -1267,6 +1288,12 @@ CONFIG_CTRL_IFACE=udp CFLAGS += -DCONFIG_CTRL_IFACE_UDP CFLAGS += -DCONFIG_CTRL_IFACE_UDP_REMOTE endif +ifeq ($(CONFIG_CTRL_IFACE), udp6-remote) +CONFIG_CTRL_IFACE=udp +CFLAGS += -DCONFIG_CTRL_IFACE_UDP +CFLAGS += -DCONFIG_CTRL_IFACE_UDP_REMOTE +CFLAGS += -DCONFIG_CTRL_IFACE_UDP_IPV6 +endif OBJS += ctrl_iface.o ctrl_iface_$(CONFIG_CTRL_IFACE).o endif @@ -1464,6 +1491,16 @@ OBJS += offchannel.o CFLAGS += -DCONFIG_OFFCHANNEL endif +ifdef CONFIG_MODULE_TESTS +CFLAGS += -DCONFIG_MODULE_TESTS +OBJS += wpas_module_tests.o +OBJS += ../src/utils/utils_module_tests.o +OBJS += ../src/common/common_module_tests.o +ifdef CONFIG_WPS +OBJS += ../src/wps/wps_module_tests.o +endif +endif + OBJS += ../src/drivers/driver_common.o OBJS_priv += ../src/drivers/driver_common.o diff --git a/wpa_supplicant/README b/wpa_supplicant/README index 7f88cd6..653848e 100644 --- a/wpa_supplicant/README +++ b/wpa_supplicant/README @@ -413,7 +413,7 @@ usage: [-G<group>] \ -i<ifname> -c<config file> [-C<ctrl>] [-D<driver>] [-p<driver_param>] \ [-b<br_ifname> [-N -i<ifname> -c<conf> [-C<ctrl>] [-D<driver>] \ - [-p<driver_param>] [-b<br_ifname>] ...] + [-p<driver_param>] [-b<br_ifname>] [-m<P2P Device config file>] ... options: -b = optional bridge interface name @@ -438,6 +438,7 @@ options: -w = wait for interface to be added, if needed -W = wait for a control interface monitor before starting -N = start describing new interface + -m = Configuration file for the P2P Device drivers: nl80211 = Linux nl80211/cfg80211 diff --git a/wpa_supplicant/README-HS20 b/wpa_supplicant/README-HS20 index ad29ef7..58c2475 100644 --- a/wpa_supplicant/README-HS20 +++ b/wpa_supplicant/README-HS20 @@ -213,6 +213,65 @@ Credentials can be pre-configured for automatic network selection: # matching with the network. Multiple entries can be used to specify more # than one SSID. # +# roaming_partner: Roaming partner information +# This optional field can be used to configure preferences between roaming +# partners. The field is a string in following format: +# <FQDN>,<0/1 exact match>,<priority>,<* or country code> +# (non-exact match means any subdomain matches the entry; priority is in +# 0..255 range with 0 being the highest priority) +# +# update_identifier: PPS MO ID +# (Hotspot 2.0 PerProviderSubscription/UpdateIdentifier) +# +# provisioning_sp: FQDN of the SP that provisioned the credential +# This optional field can be used to keep track of the SP that provisioned +# the credential to find the PPS MO (./Wi-Fi/<provisioning_sp>). +# +# sp_priority: Credential priority within a provisioning SP +# This is the priority of the credential among all credentials +# provisionined by the same SP (i.e., for entries that have identical +# provisioning_sp value). The range of this priority is 0-255 with 0 +# being the highest and 255 the lower priority. +# +# Minimum backhaul threshold (PPS/<X+>/Policy/MinBackhauldThreshold/*) +# These fields can be used to specify minimum download/upload backhaul +# bandwidth that is preferred for the credential. This constraint is +# ignored if the AP does not advertise WAN Metrics information or if the +# limit would prevent any connection. Values are in kilobits per second. +# min_dl_bandwidth_home +# min_ul_bandwidth_home +# min_dl_bandwidth_roaming +# min_ul_bandwidth_roaming +# +# max_bss_load: Maximum BSS Load Channel Utilization (1..255) +# (PPS/<X+>/Policy/MaximumBSSLoadValue) +# This value is used as the maximum channel utilization for network +# selection purposes for home networks. If the AP does not advertise +# BSS Load or if the limit would prevent any connection, this constraint +# will be ignored. +# +# req_conn_capab: Required connection capability +# (PPS/<X+>/Policy/RequiredProtoPortTuple) +# This value is used to configure set of required protocol/port pairs that +# a roaming network shall support (include explicitly in Connection +# Capability ANQP element). This constraint is ignored if the AP does not +# advertise Connection Capability or if this constraint would prevent any +# network connection. This policy is not used in home networks. +# Format: <protocol>[:<comma-separated list of ports] +# Multiple entries can be used to list multiple requirements. +# For example, number of common TCP protocols: +# req_conn_capab=6:22,80,443 +# For example, IPSec/IKE: +# req_conn_capab=17:500 +# req_conn_capab=50 +# +# ocsp: Whether to use/require OCSP to check server certificate +# 0 = do not use OCSP stapling (TLS certificate status extension) +# 1 = try to use OCSP stapling, but not require response +# 2 = require valid OCSP stapling response +# +# sim_num: Identifier for which SIM to use in multi-SIM devices +# # for example: # #cred={ diff --git a/wpa_supplicant/README-P2P b/wpa_supplicant/README-P2P index 7354bbf..bfad501 100644 --- a/wpa_supplicant/README-P2P +++ b/wpa_supplicant/README-P2P @@ -230,9 +230,8 @@ discovery protocols and requests this to be sent to all discovered peers (note: this can result in long response frames). The pending requests are sent during device discovery (see p2p_find). -Only a single pending wildcard query is supported, but there can be -multiple pending peer device specific queries (each will be sent in -sequence whenever the peer is found). +There can be multiple pending peer device specific queries (each will be +sent in sequence whenever the peer is found). This command returns an identifier for the pending query (e.g., "1f77628") that can be used to cancel the request. Directed requests diff --git a/wpa_supplicant/README-WPS b/wpa_supplicant/README-WPS index 18b0cca..b884f67 100644 --- a/wpa_supplicant/README-WPS +++ b/wpa_supplicant/README-WPS @@ -60,7 +60,6 @@ driver interface: CONFIG_DRIVER_NL80211=y CONFIG_WPS=y -CONFIG_WPS2=y If you want to enable WPS external registrar (ER) functionality, you will also need to add following line: @@ -382,17 +381,6 @@ UUID|BSSID argument is included, this is a request to build the handover message for the specified AP when wpa_supplicant is operating as a WPS ER. -"nfc_rx_handover_req <hexdump of payload>" is used to indicate receipt -of NFC connection handover request. The payload may include multiple -carriers the the applicable ones are matched based on the media -type. The reply data is contents for the Handover Select Message -(hexdump). - -"nfc_rx_handover_sel <hexdump of payload>" is used to indicate receipt -of NFC connection handover select. The payload may include multiple -carriers the the applicable ones are matched based on the media -type. - "nfc_report_handover <INIT/RESP> WPS <carrier from handover request> <carrier from handover select>" can be used as an alternative way for reporting completed NFC connection handover. The first parameter diff --git a/wpa_supplicant/android.config b/wpa_supplicant/android.config index 184b41e..3ed734d 100644 --- a/wpa_supplicant/android.config +++ b/wpa_supplicant/android.config @@ -123,7 +123,7 @@ CONFIG_EAP_AKA=y # EAP-AKA' (enable CONFIG_PCSC, if EAP-AKA' is used). # This requires CONFIG_EAP_AKA to be enabled, too. -#CONFIG_EAP_AKA_PRIME=y +CONFIG_EAP_AKA_PRIME=y # Enable USIM simulator (Milenage) for EAP-AKA #CONFIG_USIM_SIMULATOR=y @@ -141,8 +141,6 @@ CONFIG_EAP_AKA=y # Wi-Fi Protected Setup (WPS) CONFIG_WPS=y -# Enable WSC 2.0 support -CONFIG_WPS2=y # Enable WPS external registrar functionality CONFIG_WPS_ER=y # Disable credentials for an open network by default when acting as a WPS @@ -239,7 +237,7 @@ CONFIG_BACKEND=file # main_none = Very basic example (development use only) #CONFIG_MAIN=main -# Select wrapper for operatins system and C library specific functions +# Select wrapper for operating system and C library specific functions # unix = UNIX/POSIX like systems (default) # win32 = Windows systems # none = Empty template @@ -253,6 +251,9 @@ CONFIG_ELOOP=eloop # Should we use poll instead of select? Select is used by default. #CONFIG_ELOOP_POLL=y +# Should we use epoll instead of select? Select is used by default. +#CONFIG_ELOOP_EPOLL=y + # Select layer 2 packet implementation # linux = Linux packet socket (default) # pcap = libpcap/libdnet/WinPcap diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c index ce3efcb..b02c424 100644 --- a/wpa_supplicant/ap.c +++ b/wpa_supplicant/ap.c @@ -48,6 +48,7 @@ static void wpas_conf_ap_vht(struct wpa_supplicant *wpa_s, struct hostapd_config *conf, struct hostapd_hw_modes *mode) { +#ifdef CONFIG_P2P u8 center_chan = 0; u8 channel = conf->channel; @@ -66,6 +67,10 @@ static void wpas_conf_ap_vht(struct wpa_supplicant *wpa_s, no_vht: conf->vht_oper_centr_freq_seg0_idx = channel + conf->secondary_channel * 2; +#else /* CONFIG_P2P */ + conf->vht_oper_centr_freq_seg0_idx = + conf->channel + conf->secondary_channel * 2; +#endif /* CONFIG_P2P */ } #endif /* CONFIG_IEEE80211N */ @@ -297,6 +302,11 @@ static int wpa_supplicant_conf_ap(struct wpa_supplicant *wpa_s, bss->wpa_group_rekey = 86400; } +#ifdef CONFIG_IEEE80211W + if (ssid->ieee80211w != MGMT_FRAME_PROTECTION_DEFAULT) + bss->ieee80211w = ssid->ieee80211w; +#endif /* CONFIG_IEEE80211W */ + #ifdef CONFIG_WPS /* * Enable WPS by default for open and WPA/WPA2-Personal network, but @@ -306,12 +316,10 @@ static int wpa_supplicant_conf_ap(struct wpa_supplicant *wpa_s, if (bss->ssid.security_policy != SECURITY_WPA_PSK && bss->ssid.security_policy != SECURITY_PLAINTEXT) goto no_wps; -#ifdef CONFIG_WPS2 if (bss->ssid.security_policy == SECURITY_WPA_PSK && (!(bss->rsn_pairwise & WPA_CIPHER_CCMP) || !(bss->wpa & 2))) goto no_wps; /* WPS2 does not allow WPA/TKIP-only * configuration */ -#endif /* CONFIG_WPS2 */ bss->eap_server = 1; if (!ssid->ignore_broadcast_ssid) @@ -505,17 +513,13 @@ int wpa_supplicant_create_ap(struct wpa_supplicant *wpa_s, params.ssid = ssid->ssid; params.ssid_len = ssid->ssid_len; switch (ssid->mode) { - case WPAS_MODE_INFRA: - params.mode = IEEE80211_MODE_INFRA; - break; - case WPAS_MODE_IBSS: - params.mode = IEEE80211_MODE_IBSS; - break; case WPAS_MODE_AP: case WPAS_MODE_P2P_GO: case WPAS_MODE_P2P_GROUP_FORMATION: params.mode = IEEE80211_MODE_AP; break; + default: + return -1; } if (ssid->frequency == 0) ssid->frequency = 2462; /* default channel 11 */ @@ -546,6 +550,8 @@ int wpa_supplicant_create_ap(struct wpa_supplicant *wpa_s, if (wpa_s->parent->set_ap_uapsd) params.uapsd = wpa_s->parent->ap_uapsd; + else if (params.p2p && (wpa_s->drv_flags & WPA_DRIVER_FLAGS_AP_UAPSD)) + params.uapsd = 1; /* mandatory for P2P GO */ else params.uapsd = -1; @@ -671,6 +677,9 @@ void wpa_supplicant_ap_deinit(struct wpa_supplicant *wpa_s) wpa_s->ap_iface->bss[0]->p2p_group = NULL; wpas_p2p_group_deinit(wpa_s); #endif /* CONFIG_P2P */ + wpa_s->ap_iface->driver_ap_teardown = + !!(wpa_s->drv_flags & WPA_DRIVER_FLAGS_AP_TEARDOWN_SUPPORT); + hostapd_interface_deinit(wpa_s->ap_iface); hostapd_interface_free(wpa_s->ap_iface); wpa_s->ap_iface = NULL; diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c index 9ea6903..f99a8a7 100644 --- a/wpa_supplicant/bss.c +++ b/wpa_supplicant/bss.c @@ -98,6 +98,7 @@ static struct wpa_bss_anqp * wpa_bss_anqp_clone(struct wpa_bss_anqp *anqp) ANQP_DUP(hs20_wan_metrics); ANQP_DUP(hs20_connection_capability); ANQP_DUP(hs20_operating_class); + ANQP_DUP(hs20_osu_providers_list); #endif /* CONFIG_HS20 */ #undef ANQP_DUP @@ -166,6 +167,7 @@ static void wpa_bss_anqp_free(struct wpa_bss_anqp *anqp) wpabuf_free(anqp->hs20_wan_metrics); wpabuf_free(anqp->hs20_connection_capability); wpabuf_free(anqp->hs20_operating_class); + wpabuf_free(anqp->hs20_osu_providers_list); #endif /* CONFIG_HS20 */ os_free(anqp); @@ -672,7 +674,8 @@ void wpa_bss_update_scan_res(struct wpa_supplicant *wpa_s, wpa_s->last_scan_res_size = siz; } - wpa_s->last_scan_res[wpa_s->last_scan_res_used++] = bss; + if (wpa_s->last_scan_res) + wpa_s->last_scan_res[wpa_s->last_scan_res_used++] = bss; } diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h index 4deeb5f..4a624c5 100644 --- a/wpa_supplicant/bss.h +++ b/wpa_supplicant/bss.h @@ -39,6 +39,7 @@ struct wpa_bss_anqp { struct wpabuf *hs20_wan_metrics; struct wpabuf *hs20_connection_capability; struct wpabuf *hs20_operating_class; + struct wpabuf *hs20_osu_providers_list; #endif /* CONFIG_HS20 */ }; @@ -129,4 +130,9 @@ int wpa_bss_get_bit_rates(const struct wpa_bss *bss, u8 **rates); struct wpa_bss_anqp * wpa_bss_anqp_alloc(void); int wpa_bss_anqp_unshare_alloc(struct wpa_bss *bss); +static inline int bss_is_dmg(const struct wpa_bss *bss) +{ + return bss->freq > 45000; +} + #endif /* BSS_H */ diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c index 2dd7054..e60bc05 100644 --- a/wpa_supplicant/config.c +++ b/wpa_supplicant/config.c @@ -405,6 +405,8 @@ static int wpa_config_parse_proto(const struct parse_data *data, else if (os_strcmp(start, "RSN") == 0 || os_strcmp(start, "WPA2") == 0) val |= WPA_PROTO_RSN; + else if (os_strcmp(start, "OSEN") == 0) + val |= WPA_PROTO_OSEN; else { wpa_printf(MSG_ERROR, "Line %d: invalid proto '%s'", line, start); @@ -436,10 +438,10 @@ static char * wpa_config_write_proto(const struct parse_data *data, int first = 1, ret; char *buf, *pos, *end; - pos = buf = os_zalloc(10); + pos = buf = os_zalloc(20); if (buf == NULL) return NULL; - end = buf + 10; + end = buf + 20; if (ssid->proto & WPA_PROTO_WPA) { ret = os_snprintf(pos, end - pos, "%sWPA", first ? "" : " "); @@ -457,6 +459,14 @@ static char * wpa_config_write_proto(const struct parse_data *data, first = 0; } + if (ssid->proto & WPA_PROTO_OSEN) { + ret = os_snprintf(pos, end - pos, "%sOSEN", first ? "" : " "); + if (ret < 0 || ret >= end - pos) + return buf; + pos += ret; + first = 0; + } + return buf; } #endif /* NO_CONFIG_WRITE */ @@ -516,6 +526,10 @@ static int wpa_config_parse_key_mgmt(const struct parse_data *data, else if (os_strcmp(start, "FT-SAE") == 0) val |= WPA_KEY_MGMT_FT_SAE; #endif /* CONFIG_SAE */ +#ifdef CONFIG_HS20 + else if (os_strcmp(start, "OSEN") == 0) + val |= WPA_KEY_MGMT_OSEN; +#endif /* CONFIG_HS20 */ else { wpa_printf(MSG_ERROR, "Line %d: invalid key_mgmt '%s'", line, start); @@ -1652,6 +1666,7 @@ static const struct parse_data ssid_fields[] = { { INTe(engine) }, { INTe(engine2) }, { INT(eapol_flags) }, + { INTe(sim_num) }, #endif /* IEEE8021X_EAPOL */ { FUNC_KEY(wep_key0) }, { FUNC_KEY(wep_key1) }, @@ -1687,6 +1702,8 @@ static const struct parse_data ssid_fields[] = { { INT_RANGE(disable_ht, 0, 1) }, { INT_RANGE(disable_ht40, -1, 1) }, { INT_RANGE(disable_sgi, 0, 1) }, + { INT_RANGE(disable_ldpc, 0, 1) }, + { INT_RANGE(ht40_intolerant, 0, 1) }, { INT_RANGE(disable_max_amsdu, -1, 1) }, { INT_RANGE(ampdu_factor, -1, 3) }, { INT_RANGE(ampdu_density, -1, 7) }, @@ -1716,6 +1733,9 @@ static const struct parse_data ssid_fields[] = { { INT(ap_max_inactivity) }, { INT(dtim_period) }, { INT(beacon_int) }, +#ifdef CONFIG_MACSEC + { INT_RANGE(macsec_policy, 0, 1) }, +#endif /* CONFIG_MACSEC */ }; #undef OFFSET @@ -1923,6 +1943,12 @@ void wpa_config_free_cred(struct wpa_cred *cred) os_free(cred->phase1); os_free(cred->phase2); os_free(cred->excluded_ssid); + os_free(cred->roaming_partner); + os_free(cred->provisioning_sp); + for (i = 0; i < cred->num_req_conn_capab; i++) + os_free(cred->req_conn_capab_port[i]); + os_free(cred->req_conn_capab_port); + os_free(cred->req_conn_capab_proto); os_free(cred); } @@ -1998,6 +2024,8 @@ void wpa_config_free(struct wpa_config *config) os_free(config->ext_password_backend); os_free(config->sae_groups); wpabuf_free(config->ap_vendor_elements); + os_free(config->osu_dir); + os_free(config->wowlan_triggers); os_free(config); } @@ -2131,11 +2159,13 @@ void wpa_config_set_network_defaults(struct wpa_ssid *ssid) ssid->eapol_flags = DEFAULT_EAPOL_FLAGS; ssid->eap_workaround = DEFAULT_EAP_WORKAROUND; ssid->eap.fragment_size = DEFAULT_FRAGMENT_SIZE; + ssid->eap.sim_num = DEFAULT_USER_SELECTED_SIM; #endif /* IEEE8021X_EAPOL */ #ifdef CONFIG_HT_OVERRIDES ssid->disable_ht = DEFAULT_DISABLE_HT; ssid->disable_ht40 = DEFAULT_DISABLE_HT40; ssid->disable_sgi = DEFAULT_DISABLE_SGI; + ssid->disable_ldpc = DEFAULT_DISABLE_LDPC; ssid->disable_max_amsdu = DEFAULT_DISABLE_MAX_AMSDU; ssid->ampdu_factor = DEFAULT_AMPDU_FACTOR; ssid->ampdu_density = DEFAULT_AMPDU_DENSITY; @@ -2393,6 +2423,69 @@ void wpa_config_update_psk(struct wpa_ssid *ssid) } +static int wpa_config_set_cred_req_conn_capab(struct wpa_cred *cred, + const char *value) +{ + u8 *proto; + int **port; + int *ports, *nports; + const char *pos; + unsigned int num_ports; + + proto = os_realloc_array(cred->req_conn_capab_proto, + cred->num_req_conn_capab + 1, sizeof(u8)); + if (proto == NULL) + return -1; + cred->req_conn_capab_proto = proto; + + port = os_realloc_array(cred->req_conn_capab_port, + cred->num_req_conn_capab + 1, sizeof(int *)); + if (port == NULL) + return -1; + cred->req_conn_capab_port = port; + + proto[cred->num_req_conn_capab] = atoi(value); + + pos = os_strchr(value, ':'); + if (pos == NULL) { + port[cred->num_req_conn_capab] = NULL; + cred->num_req_conn_capab++; + return 0; + } + pos++; + + ports = NULL; + num_ports = 0; + + while (*pos) { + nports = os_realloc_array(ports, num_ports + 1, sizeof(int)); + if (nports == NULL) { + os_free(ports); + return -1; + } + ports = nports; + ports[num_ports++] = atoi(pos); + + pos = os_strchr(pos, ','); + if (pos == NULL) + break; + pos++; + } + + nports = os_realloc_array(ports, num_ports + 1, sizeof(int)); + if (nports == NULL) { + os_free(ports); + return -1; + } + ports = nports; + ports[num_ports] = -1; + + port[cred->num_req_conn_capab] = ports; + cred->num_req_conn_capab++; + return 0; +} + + int wpa_config_set_cred(struct wpa_cred *cred, const char *var, const char *value, int line) { @@ -2409,6 +2502,14 @@ int wpa_config_set_cred(struct wpa_cred *cred, const char *var, return 0; } + if (os_strcmp(var, "sp_priority") == 0) { + int prio = atoi(value); + if (prio < 0 || prio > 255) + return -1; + cred->sp_priority = prio; + return 0; + } + if (os_strcmp(var, "pcsc") == 0) { cred->pcsc = atoi(value); return 0; @@ -2439,6 +2540,49 @@ int wpa_config_set_cred(struct wpa_cred *cred, const char *var, return 0; } + if (os_strcmp(var, "update_identifier") == 0) { + cred->update_identifier = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_dl_bandwidth_home") == 0) { + cred->min_dl_bandwidth_home = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_ul_bandwidth_home") == 0) { + cred->min_ul_bandwidth_home = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_dl_bandwidth_roaming") == 0) { + cred->min_dl_bandwidth_roaming = atoi(value); + return 0; + } + + if (os_strcmp(var, "min_ul_bandwidth_roaming") == 0) { + cred->min_ul_bandwidth_roaming = atoi(value); + return 0; + } + + if (os_strcmp(var, "max_bss_load") == 0) { + cred->max_bss_load = atoi(value); + return 0; + } + + if (os_strcmp(var, "req_conn_capab") == 0) + return wpa_config_set_cred_req_conn_capab(cred, value); + + if (os_strcmp(var, "ocsp") == 0) { + cred->ocsp = atoi(value); + return 0; + } + + if (os_strcmp(var, "sim_num") == 0) { + cred->sim_num = atoi(value); + return 0; + } + val = wpa_config_parse_string(value, &len); if (val == NULL) { wpa_printf(MSG_ERROR, "Line %d: invalid field '%s' string " @@ -2590,6 +2734,69 @@ int wpa_config_set_cred(struct wpa_cred *cred, const char *var, return 0; } + if (os_strcmp(var, "roaming_partner") == 0) { + struct roaming_partner *p; + char *pos; + + p = os_realloc_array(cred->roaming_partner, + cred->num_roaming_partner + 1, + sizeof(struct roaming_partner)); + if (p == NULL) { + os_free(val); + return -1; + } + cred->roaming_partner = p; + + p = &cred->roaming_partner[cred->num_roaming_partner]; + + pos = os_strchr(val, ','); + if (pos == NULL) { + os_free(val); + return -1; + } + *pos++ = '\0'; + if (pos - val - 1 >= (int) sizeof(p->fqdn)) { + os_free(val); + return -1; + } + os_memcpy(p->fqdn, val, pos - val); + + p->exact_match = atoi(pos); + + pos = os_strchr(pos, ','); + if (pos == NULL) { + os_free(val); + return -1; + } + *pos++ = '\0'; + + p->priority = atoi(pos); + + pos = os_strchr(pos, ','); + if (pos == NULL) { + os_free(val); + return -1; + } + *pos++ = '\0'; + + if (os_strlen(pos) >= sizeof(p->country)) { + os_free(val); + return -1; + } + os_memcpy(p->country, pos, os_strlen(pos) + 1); + + cred->num_roaming_partner++; + os_free(val); + + return 0; + } + + if (os_strcmp(var, "provisioning_sp") == 0) { + os_free(cred->provisioning_sp); + cred->provisioning_sp = val; + return 0; + } + if (line) { wpa_printf(MSG_ERROR, "Line %d: unknown cred field '%s'.", line, var); @@ -2601,6 +2808,275 @@ int wpa_config_set_cred(struct wpa_cred *cred, const char *var, } +char * alloc_int_str(int val) +{ + char *buf; + + buf = os_malloc(20); + if (buf == NULL) + return NULL; + os_snprintf(buf, 20, "%d", val); + return buf; +} + + +char * alloc_strdup(const char *str) +{ + if (str == NULL) + return NULL; + return os_strdup(str); +} + + +char * wpa_config_get_cred_no_key(struct wpa_cred *cred, const char *var) +{ + if (os_strcmp(var, "temporary") == 0) + return alloc_int_str(cred->temporary); + + if (os_strcmp(var, "priority") == 0) + return alloc_int_str(cred->priority); + + if (os_strcmp(var, "sp_priority") == 0) + return alloc_int_str(cred->sp_priority); + + if (os_strcmp(var, "pcsc") == 0) + return alloc_int_str(cred->pcsc); + + if (os_strcmp(var, "eap") == 0) { + if (!cred->eap_method) + return NULL; + return alloc_strdup(eap_get_name(cred->eap_method[0].vendor, + cred->eap_method[0].method)); + } + + if (os_strcmp(var, "update_identifier") == 0) + return alloc_int_str(cred->update_identifier); + + if (os_strcmp(var, "min_dl_bandwidth_home") == 0) + return alloc_int_str(cred->min_dl_bandwidth_home); + + if (os_strcmp(var, "min_ul_bandwidth_home") == 0) + return alloc_int_str(cred->min_ul_bandwidth_home); + + if (os_strcmp(var, "min_dl_bandwidth_roaming") == 0) + return alloc_int_str(cred->min_dl_bandwidth_roaming); + + if (os_strcmp(var, "min_ul_bandwidth_roaming") == 0) + return alloc_int_str(cred->min_ul_bandwidth_roaming); + + if (os_strcmp(var, "max_bss_load") == 0) + return alloc_int_str(cred->max_bss_load); + + if (os_strcmp(var, "req_conn_capab") == 0) { + unsigned int i; + char *buf, *end, *pos; + int ret; + + if (!cred->num_req_conn_capab) + return NULL; + + buf = os_malloc(4000); + if (buf == NULL) + return NULL; + pos = buf; + end = pos + 4000; + for (i = 0; i < cred->num_req_conn_capab; i++) { + int *ports; + + ret = os_snprintf(pos, end - pos, "%s%u", + i > 0 ? "\n" : "", + cred->req_conn_capab_proto[i]); + if (ret < 0 || ret >= end - pos) + return buf; + pos += ret; + + ports = cred->req_conn_capab_port[i]; + if (ports) { + int j; + for (j = 0; ports[j] != -1; j++) { + ret = os_snprintf(pos, end - pos, + "%s%d", + j > 0 ? "," : ":", + ports[j]); + if (ret < 0 || ret >= end - pos) + return buf; + pos += ret; + } + } + } + + return buf; + } + + if (os_strcmp(var, "ocsp") == 0) + return alloc_int_str(cred->ocsp); + + if (os_strcmp(var, "realm") == 0) + return alloc_strdup(cred->realm); + + if (os_strcmp(var, "username") == 0) + return alloc_strdup(cred->username); + + if (os_strcmp(var, "password") == 0) { + if (!cred->password) + return NULL; + return alloc_strdup("*"); + } + + if (os_strcmp(var, "ca_cert") == 0) + return alloc_strdup(cred->ca_cert); + + if (os_strcmp(var, "client_cert") == 0) + return alloc_strdup(cred->client_cert); + + if (os_strcmp(var, "private_key") == 0) + return alloc_strdup(cred->private_key); + + if (os_strcmp(var, "private_key_passwd") == 0) { + if (!cred->private_key_passwd) + return NULL; + return alloc_strdup("*"); + } + + if (os_strcmp(var, "imsi") == 0) + return alloc_strdup(cred->imsi); + + if (os_strcmp(var, "milenage") == 0) { + if (!(cred->milenage)) + return NULL; + return alloc_strdup("*"); + } + + if (os_strcmp(var, "domain_suffix_match") == 0) + return alloc_strdup(cred->domain_suffix_match); + + if (os_strcmp(var, "domain") == 0) { + unsigned int i; + char *buf, *end, *pos; + int ret; + + if (!cred->num_domain) + return NULL; + + buf = os_malloc(4000); + if (buf == NULL) + return NULL; + pos = buf; + end = pos + 4000; + + for (i = 0; i < cred->num_domain; i++) { + ret = os_snprintf(pos, end - pos, "%s%s", + i > 0 ? "\n" : "", cred->domain[i]); + if (ret < 0 || ret >= end - pos) + return buf; + pos += ret; + } + + return buf; + } + + if (os_strcmp(var, "phase1") == 0) + return alloc_strdup(cred->phase1); + + if (os_strcmp(var, "phase2") == 0) + return alloc_strdup(cred->phase2); + + if (os_strcmp(var, "roaming_consortium") == 0) { + size_t buflen; + char *buf; + + if (!cred->roaming_consortium_len) + return NULL; + buflen = cred->roaming_consortium_len * 2 + 1; + buf = os_malloc(buflen); + if (buf == NULL) + return NULL; + wpa_snprintf_hex(buf, buflen, cred->roaming_consortium, + cred->roaming_consortium_len); + return buf; + } + + if (os_strcmp(var, "required_roaming_consortium") == 0) { + size_t buflen; + char *buf; + + if (!cred->required_roaming_consortium_len) + return NULL; + buflen = cred->required_roaming_consortium_len * 2 + 1; + buf = os_malloc(buflen); + if (buf == NULL) + return NULL; + wpa_snprintf_hex(buf, buflen, cred->required_roaming_consortium, + cred->required_roaming_consortium_len); + return buf; + } + + if (os_strcmp(var, "excluded_ssid") == 0) { + unsigned int i; + char *buf, *end, *pos; + + if (!cred->num_excluded_ssid) + return NULL; + + buf = os_malloc(4000); + if (buf == NULL) + return NULL; + pos = buf; + end = pos + 4000; + + for (i = 0; i < cred->num_excluded_ssid; i++) { + struct excluded_ssid *e; + int ret; + + e = &cred->excluded_ssid[i]; + ret = os_snprintf(pos, end - pos, "%s%s", + i > 0 ? "\n" : "", + wpa_ssid_txt(e->ssid, e->ssid_len)); + if (ret < 0 || ret >= end - pos) + return buf; + pos += ret; + } + + return buf; + } + + if (os_strcmp(var, "roaming_partner") == 0) { + unsigned int i; + char *buf, *end, *pos; + + if (!cred->num_roaming_partner) + return NULL; + + buf = os_malloc(4000); + if (buf == NULL) + return NULL; + pos = buf; + end = pos + 4000; + + for (i = 0; i < cred->num_roaming_partner; i++) { + struct roaming_partner *p; + int ret; + + p = &cred->roaming_partner[i]; + ret = os_snprintf(pos, end - pos, "%s%s,%d,%u,%s", + i > 0 ? "\n" : "", + p->fqdn, p->exact_match, p->priority, + p->country); + if (ret < 0 || ret >= end - pos) + return buf; + pos += ret; + } + + return buf; + } + + if (os_strcmp(var, "provisioning_sp") == 0) + return alloc_strdup(cred->provisioning_sp); + + return NULL; +} + + struct wpa_cred * wpa_config_get_cred(struct wpa_config *config, int id) { struct wpa_cred *cred; @@ -2635,6 +3111,7 @@ struct wpa_cred * wpa_config_add_cred(struct wpa_config *config) if (cred == NULL) return NULL; cred->id = id; + cred->sim_num = DEFAULT_USER_SELECTED_SIM; if (last) last->next = cred; else @@ -3310,7 +3787,11 @@ static const struct global_parse_data global_fields[] = { { FUNC_NO_VAR(no_ctrl_interface), 0 }, { STR(ctrl_interface_group), 0 } /* deprecated */, #endif /* CONFIG_CTRL_IFACE */ +#ifdef CONFIG_MACSEC + { INT_RANGE(eapol_version, 1, 3), 0 }, +#else /* CONFIG_MACSEC */ { INT_RANGE(eapol_version, 1, 2), 0 }, +#endif /* CONFIG_MACSEC */ { INT(ap_scan), 0 }, { FUNC(bgscan), 0 }, { INT(disable_scan_offload), 0 }, @@ -3401,6 +3882,8 @@ static const struct global_parse_data global_fields[] = { { INT(scan_cur_freq), 0 }, { INT(sched_scan_interval), 0 }, { INT(tdls_external_control), 0}, + { STR(osu_dir), 0 }, + { STR(wowlan_triggers), 0 }, }; #undef FUNC diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h index e7bdaa5..bf3f3f7 100644 --- a/wpa_supplicant/config.h +++ b/wpa_supplicant/config.h @@ -236,6 +236,66 @@ struct wpa_cred { size_t ssid_len; } *excluded_ssid; size_t num_excluded_ssid; + + struct roaming_partner { + char fqdn[128]; + int exact_match; + u8 priority; + char country[3]; + } *roaming_partner; + size_t num_roaming_partner; + + int update_identifier; + + /** + * provisioning_sp - FQDN of the SP that provisioned the credential + */ + char *provisioning_sp; + + /** + * sp_priority - Credential priority within a provisioning SP + * + * This is the priority of the credential among all credentials + * provisionined by the same SP (i.e., for entries that have identical + * provisioning_sp value). The range of this priority is 0-255 with 0 + * being the highest and 255 the lower priority. + */ + int sp_priority; + + unsigned int min_dl_bandwidth_home; + unsigned int min_ul_bandwidth_home; + unsigned int min_dl_bandwidth_roaming; + unsigned int min_ul_bandwidth_roaming; + + /** + * max_bss_load - Maximum BSS Load Channel Utilization (1..255) + * This value is used as the maximum channel utilization for network + * selection purposes for home networks. If the AP does not advertise + * BSS Load or if the limit would prevent any connection, this + * constraint will be ignored. + */ + unsigned int max_bss_load; + + unsigned int num_req_conn_capab; + u8 *req_conn_capab_proto; + int **req_conn_capab_port; + + /** + * ocsp - Whether to use/require OCSP to check server certificate + * + * 0 = do not use OCSP stapling (TLS certificate status extension) + * 1 = try to use OCSP stapling, but not require response + * 2 = require valid OCSP stapling response + */ + int ocsp; + + /** + * sim_num - User selected SIM identifier + * + * This variable is used for identifying which SIM is used if the system + * has more than one. + */ + int sim_num; }; @@ -953,6 +1013,22 @@ struct wpa_config { u8 ip_addr_mask[4]; u8 ip_addr_start[4]; u8 ip_addr_end[4]; + + /** + * osu_dir - OSU provider information directory + * + * If set, allow FETCH_OSU control interface command to be used to fetch + * OSU provider information into all APs and store the results in this + * directory. + */ + char *osu_dir; + + /** + * wowlan_triggers - Wake-on-WLAN triggers + * + * If set, these wowlan triggers will be configured. + */ + char *wowlan_triggers; }; @@ -992,6 +1068,7 @@ int wpa_config_remove_cred(struct wpa_config *config, int id); void wpa_config_free_cred(struct wpa_cred *cred); int wpa_config_set_cred(struct wpa_cred *cred, const char *var, const char *value, int line); +char * wpa_config_get_cred_no_key(struct wpa_cred *cred, const char *var); struct wpa_config * wpa_config_alloc_empty(const char *ctrl_interface, const char *driver_param); diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c index 6312a77..4dc4d12 100644 --- a/wpa_supplicant/config_file.c +++ b/wpa_supplicant/config_file.c @@ -219,6 +219,7 @@ static struct wpa_cred * wpa_config_read_cred(FILE *f, int *line, int id) if (cred == NULL) return NULL; cred->id = id; + cred->sim_num = DEFAULT_USER_SELECTED_SIM; while (wpa_config_get_line(buf, sizeof(buf), f, line, &pos)) { if (os_strcmp(pos, "}") == 0) { @@ -351,8 +352,8 @@ struct wpa_config * wpa_config_read(const char *name, struct wpa_config *cfgp) FILE *f; char buf[512], *pos; int errors = 0, line = 0; - struct wpa_ssid *ssid, *tail = NULL, *head = NULL; - struct wpa_cred *cred, *cred_tail = NULL, *cred_head = NULL; + struct wpa_ssid *ssid, *tail, *head; + struct wpa_cred *cred, *cred_tail, *cred_head; struct wpa_config *config; int id = 0; int cred_id = 0; @@ -368,8 +369,12 @@ struct wpa_config * wpa_config_read(const char *name, struct wpa_config *cfgp) "structure"); return NULL; } - head = config->ssid; - cred_head = config->cred; + tail = head = config->ssid; + while (tail && tail->next) + tail = tail->next; + cred_tail = cred_head = config->cred; + while (cred_tail && cred_tail->next) + cred_tail = cred_tail->next; wpa_printf(MSG_DEBUG, "Reading configuration file '%s'", name); f = fopen(name, "r"); @@ -711,6 +716,8 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid) INT_DEF(eap_workaround, DEFAULT_EAP_WORKAROUND); STR(pac_file); INT_DEFe(fragment_size, DEFAULT_FRAGMENT_SIZE); + INTe(ocsp); + INT_DEFe(sim_num, DEFAULT_USER_SELECTED_SIM); #endif /* IEEE8021X_EAPOL */ INT(mode); INT(frequency); @@ -729,6 +736,9 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid) #endif /* CONFIG_P2P */ INT(dtim_period); INT(beacon_int); +#ifdef CONFIG_MACSEC + INT(macsec_policy); +#endif /* CONFIG_MACSEC */ #undef STR #undef INT @@ -768,7 +778,7 @@ static void wpa_config_write_cred(FILE *f, struct wpa_cred *cred) for (i = 0; i < cred->num_domain; i++) fprintf(f, "\tdomain=\"%s\"\n", cred->domain[i]); if (cred->domain_suffix_match) - fprintf(f, "\tdomain_suffix_match=\"%s\"", + fprintf(f, "\tdomain_suffix_match=\"%s\"\n", cred->domain_suffix_match); if (cred->roaming_consortium_len) { fprintf(f, "\troaming_consortium="); @@ -796,6 +806,70 @@ static void wpa_config_write_cred(FILE *f, struct wpa_cred *cred) fprintf(f, "\n"); } } + if (cred->roaming_partner) { + for (i = 0; i < cred->num_roaming_partner; i++) { + struct roaming_partner *p = &cred->roaming_partner[i]; + fprintf(f, "\troaming_partner=\"%s,%d,%u,%s\"\n", + p->fqdn, p->exact_match, p->priority, + p->country); + } + } + if (cred->update_identifier) + fprintf(f, "\tupdate_identifier=%d\n", cred->update_identifier); + + if (cred->provisioning_sp) + fprintf(f, "\tprovisioning_sp=\"%s\"\n", cred->provisioning_sp); + if (cred->sp_priority) + fprintf(f, "\tsp_priority=%d\n", cred->sp_priority); + + if (cred->min_dl_bandwidth_home) + fprintf(f, "\tmin_dl_bandwidth_home=%u\n", + cred->min_dl_bandwidth_home); + if (cred->min_ul_bandwidth_home) + fprintf(f, "\tmin_ul_bandwidth_home=%u\n", + cred->min_ul_bandwidth_home); + if (cred->min_dl_bandwidth_roaming) + fprintf(f, "\tmin_dl_bandwidth_roaming=%u\n", + cred->min_dl_bandwidth_roaming); + if (cred->min_ul_bandwidth_roaming) + fprintf(f, "\tmin_ul_bandwidth_roaming=%u\n", + cred->min_ul_bandwidth_roaming); + + if (cred->max_bss_load) + fprintf(f, "\tmax_bss_load=%u\n", + cred->max_bss_load); + + if (cred->ocsp) + fprintf(f, "\tocsp=%d\n", cred->ocsp); + + if (cred->num_req_conn_capab) { + for (i = 0; i < cred->num_req_conn_capab; i++) { + int *ports; + + fprintf(f, "\treq_conn_capab=%u", + cred->req_conn_capab_proto[i]); + ports = cred->req_conn_capab_port[i]; + if (ports) { + int j; + for (j = 0; ports[j] != -1; j++) { + fprintf(f, "%s%d", j > 0 ? "," : ":", + ports[j]); + } + } + fprintf(f, "\n"); + } + } + + if (cred->required_roaming_consortium_len) { + fprintf(f, "\trequired_roaming_consortium="); + for (i = 0; i < cred->required_roaming_consortium_len; i++) + fprintf(f, "%02x", + cred->required_roaming_consortium[i]); + fprintf(f, "\n"); + } + + if (cred->sim_num != DEFAULT_USER_SELECTED_SIM) + fprintf(f, "\tsim_num=%d\n", cred->sim_num); } @@ -1083,6 +1157,13 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config) if (config->tdls_external_control) fprintf(f, "tdls_external_control=%d\n", config->tdls_external_control); + + if (config->wowlan_triggers) + fprintf(f, "wowlan_triggers=\"%s\"\n", + config->wowlan_triggers); + + if (config->bgscan) + fprintf(f, "bgscan=\"%s\"\n", config->bgscan); } #endif /* CONFIG_NO_CONFIG_WRITE */ diff --git a/wpa_supplicant/config_ssid.h b/wpa_supplicant/config_ssid.h index d515030..76b0632 100644 --- a/wpa_supplicant/config_ssid.h +++ b/wpa_supplicant/config_ssid.h @@ -30,9 +30,11 @@ #define DEFAULT_DISABLE_HT 0 #define DEFAULT_DISABLE_HT40 0 #define DEFAULT_DISABLE_SGI 0 +#define DEFAULT_DISABLE_LDPC 0 #define DEFAULT_DISABLE_MAX_AMSDU -1 /* no change */ #define DEFAULT_AMPDU_FACTOR -1 /* no change */ #define DEFAULT_AMPDU_DENSITY -1 /* no change */ +#define DEFAULT_USER_SELECTED_SIM 1 struct psk_list_entry { struct dl_list list; @@ -525,6 +527,19 @@ struct wpa_ssid { int disable_sgi; /** + * disable_ldpc - Disable LDPC for this network + * + * By default, use it if it is available, but this can be configured + * to 1 to have it disabled. + */ + int disable_ldpc; + + /** + * ht40_intolerant - Indicate 40 MHz intolerant for this network + */ + int ht40_intolerant; + + /** * disable_max_amsdu - Disable MAX A-MSDU * * A-MDSU will be 3839 bytes when disabled, or 7935 @@ -621,6 +636,17 @@ struct wpa_ssid { * dereferences since it may not be updated in all cases. */ void *parent_cred; + +#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; +#endif /* CONFIG_MACSEC */ }; #endif /* CONFIG_SSID_H */ diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 9a3cbea..53e23ff 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -40,113 +40,14 @@ #include "blacklist.h" #include "autoscan.h" #include "wnm_sta.h" +#include "offchannel.h" static int wpa_supplicant_global_iface_list(struct wpa_global *global, char *buf, int len); static int wpa_supplicant_global_iface_interfaces(struct wpa_global *global, char *buf, int len); - - -static int pno_start(struct wpa_supplicant *wpa_s) -{ - int ret, interval; - size_t i, num_ssid; - struct wpa_ssid *ssid; - struct wpa_driver_scan_params params; - - if (wpa_s->pno || wpa_s->pno_sched_pending) - return 0; - - if ((wpa_s->wpa_state > WPA_SCANNING) && - (wpa_s->wpa_state <= WPA_COMPLETED)) { - wpa_printf(MSG_ERROR, "PNO: In assoc process"); - return -EAGAIN; - } - - if (wpa_s->wpa_state == WPA_SCANNING) { - wpa_supplicant_cancel_scan(wpa_s); - if (wpa_s->sched_scanning) { - wpa_printf(MSG_DEBUG, "Schedule PNO on completion of " - "ongoing sched scan"); - wpa_supplicant_cancel_sched_scan(wpa_s); - wpa_s->pno_sched_pending = 1; - return 0; - } - } - - os_memset(¶ms, 0, sizeof(params)); - - num_ssid = 0; - ssid = wpa_s->conf->ssid; - while (ssid) { - if (!wpas_network_disabled(wpa_s, ssid)) - num_ssid++; - ssid = ssid->next; - } - if (num_ssid > WPAS_MAX_SCAN_SSIDS) { - wpa_printf(MSG_DEBUG, "PNO: Use only the first %u SSIDs from " - "%u", WPAS_MAX_SCAN_SSIDS, (unsigned int) num_ssid); - num_ssid = WPAS_MAX_SCAN_SSIDS; - } - - if (num_ssid == 0) { - wpa_printf(MSG_DEBUG, "PNO: No configured SSIDs"); - return -1; - } - - params.filter_ssids = os_malloc(sizeof(struct wpa_driver_scan_filter) * - num_ssid); - if (params.filter_ssids == NULL) - return -1; - i = 0; - ssid = wpa_s->conf->ssid; - while (ssid) { - if (!wpas_network_disabled(wpa_s, ssid)) { - params.ssids[i].ssid = ssid->ssid; - params.ssids[i].ssid_len = ssid->ssid_len; - params.num_ssids++; - os_memcpy(params.filter_ssids[i].ssid, ssid->ssid, - ssid->ssid_len); - params.filter_ssids[i].ssid_len = ssid->ssid_len; - params.num_filter_ssids++; - i++; - if (i == num_ssid) - break; - } - ssid = ssid->next; - } - - if (wpa_s->conf->filter_rssi) - params.filter_rssi = wpa_s->conf->filter_rssi; - - interval = wpa_s->conf->sched_scan_interval ? - wpa_s->conf->sched_scan_interval : 10; - - ret = wpa_supplicant_start_sched_scan(wpa_s, ¶ms, interval); - os_free(params.filter_ssids); - if (ret == 0) - wpa_s->pno = 1; - return ret; -} - - -static int pno_stop(struct wpa_supplicant *wpa_s) -{ - int ret = 0; - - if (wpa_s->pno || wpa_s->sched_scanning) { - wpa_s->pno = 0; - ret = wpa_supplicant_stop_sched_scan(wpa_s); - } - - wpa_s->pno_sched_pending = 0; - - if (wpa_s->wpa_state == WPA_SCANNING) - wpa_supplicant_req_scan(wpa_s, 0, 0); - - return ret; -} - +static int * freq_range_to_channel_list(struct wpa_supplicant *wpa_s, + char *val); static int set_bssid_filter(struct wpa_supplicant *wpa_s, char *val) { @@ -307,6 +208,72 @@ static int set_disallow_aps(struct wpa_supplicant *wpa_s, char *val) } +#ifndef CONFIG_NO_CONFIG_BLOBS +static int wpas_ctrl_set_blob(struct wpa_supplicant *wpa_s, char *pos) +{ + char *name = pos; + struct wpa_config_blob *blob; + size_t len; + + pos = os_strchr(pos, ' '); + if (pos == NULL) + return -1; + *pos++ = '\0'; + len = os_strlen(pos); + if (len & 1) + return -1; + + wpa_printf(MSG_DEBUG, "CTRL: Set blob '%s'", name); + blob = os_zalloc(sizeof(*blob)); + if (blob == NULL) + return -1; + blob->name = os_strdup(name); + blob->data = os_malloc(len / 2); + if (blob->name == NULL || blob->data == NULL) { + wpa_config_free_blob(blob); + return -1; + } + + if (hexstr2bin(pos, blob->data, len / 2) < 0) { + wpa_printf(MSG_DEBUG, "CTRL: Invalid blob hex data"); + wpa_config_free_blob(blob); + return -1; + } + blob->len = len / 2; + + wpa_config_set_blob(wpa_s->conf, blob); + + return 0; +} +#endif /* CONFIG_NO_CONFIG_BLOBS */ + + +static int wpas_ctrl_pno(struct wpa_supplicant *wpa_s, char *cmd) +{ + char *params; + char *pos; + int *freqs = NULL; + int ret; + + if (atoi(cmd)) { + params = os_strchr(cmd, ' '); + os_free(wpa_s->manual_sched_scan_freqs); + if (params) { + params++; + pos = os_strstr(params, "freq="); + if (pos) + freqs = freq_range_to_channel_list(wpa_s, + pos + 5); + } + wpa_s->manual_sched_scan_freqs = freqs; + ret = wpas_start_pno(wpa_s); + } else { + ret = wpas_stop_pno(wpa_s); + } + return ret; +} + + static int wpa_supplicant_ctrl_iface_set(struct wpa_supplicant *wpa_s, char *cmd) { @@ -390,10 +357,7 @@ static int wpa_supplicant_ctrl_iface_set(struct wpa_supplicant *wpa_s, wpa_tdls_enable(wpa_s->wpa, !disabled); #endif /* CONFIG_TDLS */ } else if (os_strcasecmp(cmd, "pno") == 0) { - if (atoi(value)) - ret = pno_start(wpa_s); - else - ret = pno_stop(wpa_s); + ret = wpas_ctrl_pno(wpa_s, value); } else if (os_strcasecmp(cmd, "radio_disabled") == 0) { int disabled = atoi(value); if (wpa_drv_radio_disable(wpa_s, disabled) < 0) @@ -440,7 +404,11 @@ static int wpa_supplicant_ctrl_iface_set(struct wpa_supplicant *wpa_s, ret = wpa_drv_set_p2p_powersave(wpa_s, atoi(value), -1, -1); #ifdef CONFIG_WIFI_DISPLAY } else if (os_strcasecmp(cmd, "wifi_display") == 0) { - wifi_display_enable(wpa_s->global, !!atoi(value)); + int enabled = !!atoi(value); + if (enabled && !wpa_s->global->p2p) + ret = -1; + else + wifi_display_enable(wpa_s->global, enabled); #endif /* CONFIG_WIFI_DISPLAY */ } else if (os_strcasecmp(cmd, "bssid_filter") == 0) { ret = set_bssid_filter(wpa_s, value); @@ -448,6 +416,14 @@ static int wpa_supplicant_ctrl_iface_set(struct wpa_supplicant *wpa_s, ret = set_disallow_aps(wpa_s, value); } else if (os_strcasecmp(cmd, "no_keep_alive") == 0) { wpa_s->no_keep_alive = !!atoi(value); +#ifdef CONFIG_TESTING_OPTIONS + } else if (os_strcasecmp(cmd, "ext_mgmt_frame_handling") == 0) { + wpa_s->ext_mgmt_frame_handling = !!atoi(value); +#endif /* CONFIG_TESTING_OPTIONS */ +#ifndef CONFIG_NO_CONFIG_BLOBS + } else if (os_strcmp(cmd, "blob") == 0) { + ret = wpas_ctrl_set_blob(wpa_s, value); +#endif /* CONFIG_NO_CONFIG_BLOBS */ } else { value[-1] = '='; ret = wpa_config_process_global(wpa_s->conf, cmd, -1); @@ -475,8 +451,13 @@ static int wpa_supplicant_ctrl_iface_get(struct wpa_supplicant *wpa_s, wpa_s->conf->country[1]); #ifdef CONFIG_WIFI_DISPLAY } else if (os_strcasecmp(cmd, "wifi_display") == 0) { - res = os_snprintf(buf, buflen, "%d", - wpa_s->global->wifi_display); + int enabled; + if (wpa_s->global->p2p == NULL || + wpa_s->global->p2p_disabled) + enabled = 0; + else + enabled = wpa_s->global->wifi_display; + res = os_snprintf(buf, buflen, "%d", enabled); if (res < 0 || (unsigned int) res >= buflen) return -1; return res; @@ -626,6 +607,22 @@ static int wpa_supplicant_ctrl_iface_tdls_teardown( return ret; } + +static int ctrl_iface_get_capability_tdls( + struct wpa_supplicant *wpa_s, char *buf, size_t buflen) +{ + int ret; + + ret = os_snprintf(buf, buflen, "%s\n", + wpa_s->drv_flags & WPA_DRIVER_FLAGS_TDLS_SUPPORT ? + (wpa_s->drv_flags & + WPA_DRIVER_FLAGS_TDLS_EXTERNAL_SETUP ? + "EXTERNAL" : "INTERNAL") : "UNSUPPORTED"); + if (ret < 0 || (size_t) ret > buflen) + return -1; + return ret; +} + #endif /* CONFIG_TDLS */ @@ -942,6 +939,7 @@ static int wpas_ctrl_nfc_get_handover_req_wps(struct wpa_supplicant *wpa_s, } +#ifdef CONFIG_P2P static int wpas_ctrl_nfc_get_handover_req_p2p(struct wpa_supplicant *wpa_s, char *reply, size_t max_len, int ndef) @@ -964,6 +962,7 @@ static int wpas_ctrl_nfc_get_handover_req_p2p(struct wpa_supplicant *wpa_s, return res; } +#endif /* CONFIG_P2P */ static int wpas_ctrl_nfc_get_handover_req(struct wpa_supplicant *wpa_s, @@ -992,10 +991,12 @@ static int wpas_ctrl_nfc_get_handover_req(struct wpa_supplicant *wpa_s, wpa_s, reply, max_len, ndef); } +#ifdef CONFIG_P2P if (os_strcmp(pos, "P2P-CR") == 0) { return wpas_ctrl_nfc_get_handover_req_p2p( wpa_s, reply, max_len, ndef); } +#endif /* CONFIG_P2P */ return -1; } @@ -1023,6 +1024,7 @@ static int wpas_ctrl_nfc_get_handover_sel_wps(struct wpa_supplicant *wpa_s, } +#ifdef CONFIG_P2P static int wpas_ctrl_nfc_get_handover_sel_p2p(struct wpa_supplicant *wpa_s, char *reply, size_t max_len, int ndef, int tag) @@ -1043,6 +1045,7 @@ static int wpas_ctrl_nfc_get_handover_sel_p2p(struct wpa_supplicant *wpa_s, return res; } +#endif /* CONFIG_P2P */ static int wpas_ctrl_nfc_get_handover_sel(struct wpa_supplicant *wpa_s, @@ -1075,6 +1078,7 @@ static int wpas_ctrl_nfc_get_handover_sel(struct wpa_supplicant *wpa_s, os_strcmp(pos, "WPS-CR") == 0, pos2); } +#ifdef CONFIG_P2P if (os_strcmp(pos, "P2P-CR") == 0) { return wpas_ctrl_nfc_get_handover_sel_p2p( wpa_s, reply, max_len, ndef, 0); @@ -1084,66 +1088,12 @@ static int wpas_ctrl_nfc_get_handover_sel(struct wpa_supplicant *wpa_s, return wpas_ctrl_nfc_get_handover_sel_p2p( wpa_s, reply, max_len, ndef, 1); } +#endif /* CONFIG_P2P */ return -1; } -static int wpas_ctrl_nfc_rx_handover_req(struct wpa_supplicant *wpa_s, - char *cmd, char *reply, - size_t max_len) -{ - size_t len; - struct wpabuf *buf; - int ret; - - len = os_strlen(cmd); - if (len & 0x01) - return -1; - len /= 2; - - buf = wpabuf_alloc(len); - if (buf == NULL) - return -1; - if (hexstr2bin(cmd, wpabuf_put(buf, len), len) < 0) { - wpabuf_free(buf); - return -1; - } - - ret = wpas_wps_nfc_rx_handover_req(wpa_s, buf); - wpabuf_free(buf); - - return ret; -} - - -static int wpas_ctrl_nfc_rx_handover_sel(struct wpa_supplicant *wpa_s, - char *cmd) -{ - size_t len; - struct wpabuf *buf; - int ret; - - len = os_strlen(cmd); - if (len & 0x01) - return -1; - len /= 2; - - buf = wpabuf_alloc(len); - if (buf == NULL) - return -1; - if (hexstr2bin(cmd, wpabuf_put(buf, len), len) < 0) { - wpabuf_free(buf); - return -1; - } - - ret = wpas_wps_nfc_rx_handover_sel(wpa_s, buf); - wpabuf_free(buf); - - return ret; -} - - static int wpas_ctrl_nfc_report_handover(struct wpa_supplicant *wpa_s, char *cmd) { @@ -1151,6 +1101,7 @@ static int wpas_ctrl_nfc_report_handover(struct wpa_supplicant *wpa_s, struct wpabuf *req, *sel; int ret; char *pos, *role, *type, *pos2; +#ifdef CONFIG_P2P char *freq; int forced_freq = 0; @@ -1160,6 +1111,7 @@ static int wpas_ctrl_nfc_report_handover(struct wpa_supplicant *wpa_s, freq += 6; forced_freq = atoi(freq); } +#endif /* CONFIG_P2P */ role = cmd; pos = os_strchr(role, ' '); @@ -1228,11 +1180,14 @@ static int wpas_ctrl_nfc_report_handover(struct wpa_supplicant *wpa_s, if (os_strcmp(role, "INIT") == 0 && os_strcmp(type, "WPS") == 0) { ret = wpas_wps_nfc_report_handover(wpa_s, req, sel); +#ifdef CONFIG_AP } else if (os_strcmp(role, "RESP") == 0 && os_strcmp(type, "WPS") == 0) { ret = wpas_ap_wps_nfc_report_handover(wpa_s, req, sel); if (ret < 0) ret = wpas_er_wps_nfc_report_handover(wpa_s, req, sel); +#endif /* CONFIG_AP */ +#ifdef CONFIG_P2P } else if (os_strcmp(role, "INIT") == 0 && os_strcmp(type, "P2P") == 0) { ret = wpas_p2p_nfc_report_handover(wpa_s, 1, req, sel, 0); @@ -1240,6 +1195,7 @@ static int wpas_ctrl_nfc_report_handover(struct wpa_supplicant *wpa_s, { ret = wpas_p2p_nfc_report_handover(wpa_s, 0, req, sel, forced_freq); +#endif /* CONFIG_P2P */ } else { wpa_printf(MSG_DEBUG, "NFC: Unsupported connection handover " "reported: role=%s type=%s", role, type); @@ -1559,6 +1515,9 @@ static int wpa_supplicant_ctrl_iface_status(struct wpa_supplicant *wpa_s, { char *pos, *end, tmp[30]; int res, verbose, wps, ret; +#ifdef CONFIG_HS20 + const u8 *hs20; +#endif /* CONFIG_HS20 */ if (os_strcmp(params, "-DRIVER") == 0) return wpa_drv_status(wpa_s, buf, buflen); @@ -1697,10 +1656,16 @@ static int wpa_supplicant_ctrl_iface_status(struct wpa_supplicant *wpa_s, #ifdef CONFIG_HS20 if (wpa_s->current_bss && - wpa_bss_get_vendor_ie(wpa_s->current_bss, HS20_IE_VENDOR_TYPE) && + (hs20 = wpa_bss_get_vendor_ie(wpa_s->current_bss, + HS20_IE_VENDOR_TYPE)) && wpa_s->wpa_proto == WPA_PROTO_RSN && wpa_key_mgmt_wpa_ieee8021x(wpa_s->key_mgmt)) { - ret = os_snprintf(pos, end - pos, "hs20=1\n"); + int release = 1; + if (hs20[1] >= 5) { + u8 rel_num = (hs20[6] & 0xf0) >> 4; + release = rel_num + 1; + } + ret = os_snprintf(pos, end - pos, "hs20=%d\n", release); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; @@ -1716,15 +1681,38 @@ static int wpa_supplicant_ctrl_iface_status(struct wpa_supplicant *wpa_s, if (wpa_s->current_ssid->parent_cred != cred) continue; - for (i = 0; cred->domain && i < cred->num_domain; i++) { + if (cred->provisioning_sp) { ret = os_snprintf(pos, end - pos, - "home_sp=%s\n", - cred->domain[i]); + "provisioning_sp=%s\n", + cred->provisioning_sp); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; } + if (!cred->domain) + goto no_domain; + + i = 0; + if (wpa_s->current_bss && wpa_s->current_bss->anqp) { + struct wpabuf *names = + wpa_s->current_bss->anqp->domain_name; + for (i = 0; names && i < cred->num_domain; i++) + { + if (domain_name_list_contains( + names, cred->domain[i], 1)) + break; + } + if (i == cred->num_domain) + i = 0; /* show first entry by default */ + } + ret = os_snprintf(pos, end - pos, "home_sp=%s\n", + cred->domain[i]); + if (ret < 0 || ret >= end - pos) + return pos - buf; + pos += ret; + + no_domain: if (wpa_s->current_bss == NULL || wpa_s->current_bss->anqp == NULL) res = -1; @@ -1873,10 +1861,10 @@ static int wpa_supplicant_ctrl_iface_blacklist(struct wpa_supplicant *wpa_s, * skipped when processing scan results. */ ret = wpa_blacklist_add(wpa_s, bssid); - if (ret != 0) + if (ret < 0) return -1; ret = wpa_blacklist_add(wpa_s, bssid); - if (ret != 0) + if (ret < 0) return -1; os_memcpy(buf, "OK\n", 3); return 3; @@ -2047,7 +2035,8 @@ static char * wpa_supplicant_ie_txt(char *pos, char *end, const char *proto, const u8 *ie, size_t ie_len) { struct wpa_ie_data data; - int first, ret; + char *start; + int ret; ret = os_snprintf(pos, end - pos, "[%s-", proto); if (ret < 0 || ret >= end - pos) @@ -2062,62 +2051,58 @@ static char * wpa_supplicant_ie_txt(char *pos, char *end, const char *proto, return pos; } - first = 1; + start = pos; if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X) { - ret = os_snprintf(pos, end - pos, "%sEAP", first ? "" : "+"); + ret = os_snprintf(pos, end - pos, "%sEAP", + pos == start ? "" : "+"); if (ret < 0 || ret >= end - pos) return pos; pos += ret; - first = 0; } if (data.key_mgmt & WPA_KEY_MGMT_PSK) { - ret = os_snprintf(pos, end - pos, "%sPSK", first ? "" : "+"); + ret = os_snprintf(pos, end - pos, "%sPSK", + pos == start ? "" : "+"); if (ret < 0 || ret >= end - pos) return pos; pos += ret; - first = 0; } if (data.key_mgmt & WPA_KEY_MGMT_WPA_NONE) { - ret = os_snprintf(pos, end - pos, "%sNone", first ? "" : "+"); + ret = os_snprintf(pos, end - pos, "%sNone", + pos == start ? "" : "+"); if (ret < 0 || ret >= end - pos) return pos; pos += ret; - first = 0; } #ifdef CONFIG_IEEE80211R if (data.key_mgmt & WPA_KEY_MGMT_FT_IEEE8021X) { ret = os_snprintf(pos, end - pos, "%sFT/EAP", - first ? "" : "+"); + pos == start ? "" : "+"); if (ret < 0 || ret >= end - pos) return pos; pos += ret; - first = 0; } if (data.key_mgmt & WPA_KEY_MGMT_FT_PSK) { ret = os_snprintf(pos, end - pos, "%sFT/PSK", - first ? "" : "+"); + pos == start ? "" : "+"); if (ret < 0 || ret >= end - pos) return pos; pos += ret; - first = 0; } #endif /* CONFIG_IEEE80211R */ #ifdef CONFIG_IEEE80211W if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256) { ret = os_snprintf(pos, end - pos, "%sEAP-SHA256", - first ? "" : "+"); + pos == start ? "" : "+"); if (ret < 0 || ret >= end - pos) return pos; pos += ret; - first = 0; } if (data.key_mgmt & WPA_KEY_MGMT_PSK_SHA256) { ret = os_snprintf(pos, end - pos, "%sPSK-SHA256", - first ? "" : "+"); + pos == start ? "" : "+"); if (ret < 0 || ret >= end - pos) return pos; pos += ret; - first = 0; } #endif /* CONFIG_IEEE80211W */ @@ -2151,10 +2136,8 @@ static char * wpa_supplicant_wps_ie_txt_buf(struct wpa_supplicant *wpa_s, return pos; if (wps_is_selected_pbc_registrar(wps_ie)) txt = "[WPS-PBC]"; -#ifdef CONFIG_WPS2 else if (wps_is_addr_authorized(wps_ie, wpa_s->own_addr, 0)) txt = "[WPS-AUTH]"; -#endif /* CONFIG_WPS2 */ else if (wps_is_selected_pin_registrar(wps_ie)) txt = "[WPS-PIN]"; else @@ -2221,17 +2204,43 @@ static int wpa_supplicant_ctrl_iface_scan_result( return -1; pos += ret; } - if (bss->caps & IEEE80211_CAP_IBSS) { - ret = os_snprintf(pos, end - pos, "[IBSS]"); + if (bss_is_dmg(bss)) { + const char *s; + ret = os_snprintf(pos, end - pos, "[DMG]"); if (ret < 0 || ret >= end - pos) return -1; pos += ret; - } - if (bss->caps & IEEE80211_CAP_ESS) { - ret = os_snprintf(pos, end - pos, "[ESS]"); + switch (bss->caps & IEEE80211_CAP_DMG_MASK) { + case IEEE80211_CAP_DMG_IBSS: + s = "[IBSS]"; + break; + case IEEE80211_CAP_DMG_AP: + s = "[ESS]"; + break; + case IEEE80211_CAP_DMG_PBSS: + s = "[PBSS]"; + break; + default: + s = ""; + break; + } + ret = os_snprintf(pos, end - pos, "%s", s); if (ret < 0 || ret >= end - pos) return -1; pos += ret; + } else { + if (bss->caps & IEEE80211_CAP_IBSS) { + ret = os_snprintf(pos, end - pos, "[IBSS]"); + if (ret < 0 || ret >= end - pos) + return -1; + pos += ret; + } + if (bss->caps & IEEE80211_CAP_ESS) { + ret = os_snprintf(pos, end - pos, "[ESS]"); + if (ret < 0 || ret >= end - pos) + return -1; + pos += ret; + } } if (p2p) { ret = os_snprintf(pos, end - pos, "[P2P]"); @@ -2295,9 +2304,10 @@ static int wpa_supplicant_ctrl_iface_select_network( { int id; struct wpa_ssid *ssid; + char *pos; /* cmd: "<network id>" or "any" */ - if (os_strcmp(cmd, "any") == 0) { + if (os_strncmp(cmd, "any", 3) == 0) { wpa_printf(MSG_DEBUG, "CTRL_IFACE: SELECT_NETWORK any"); ssid = NULL; } else { @@ -2317,6 +2327,16 @@ static int wpa_supplicant_ctrl_iface_select_network( } } + pos = os_strstr(cmd, " freq="); + if (pos) { + int *freqs = freq_range_to_channel_list(wpa_s, pos + 6); + if (freqs) { + wpa_s->scan_req = MANUAL_SCAN_REQ; + os_free(wpa_s->manual_scan_freqs); + wpa_s->manual_scan_freqs = freqs; + } + } + wpa_supplicant_select_network(wpa_s, ssid); return 0; @@ -2501,6 +2521,39 @@ static int wpa_supplicant_ctrl_iface_remove_network( } +static int wpa_supplicant_ctrl_iface_update_network( + struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, + char *name, char *value) +{ + if (wpa_config_set(ssid, name, value, 0) < 0) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE: Failed to set network " + "variable '%s'", name); + return -1; + } + + if (os_strcmp(name, "bssid") != 0 && + os_strcmp(name, "priority") != 0) + wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid); + + if (wpa_s->current_ssid == ssid || wpa_s->current_ssid == NULL) { + /* + * Invalidate the EAP session cache if anything in the current + * or previously used configuration changes. + */ + eapol_sm_invalidate_cached_session(wpa_s->eapol); + } + + if ((os_strcmp(name, "psk") == 0 && + value[0] == '"' && ssid->ssid_len) || + (os_strcmp(name, "ssid") == 0 && ssid->passphrase)) + wpa_config_update_psk(ssid); + else if (os_strcmp(name, "priority") == 0) + wpa_config_update_prio_list(wpa_s->conf); + + return 0; +} + + static int wpa_supplicant_ctrl_iface_set_network( struct wpa_supplicant *wpa_s, char *cmd) { @@ -2532,32 +2585,8 @@ static int wpa_supplicant_ctrl_iface_set_network( return -1; } - if (wpa_config_set(ssid, name, value, 0) < 0) { - wpa_printf(MSG_DEBUG, "CTRL_IFACE: Failed to set network " - "variable '%s'", name); - return -1; - } - - if (os_strcmp(name, "bssid") != 0 && - os_strcmp(name, "priority") != 0) - wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid); - - if (wpa_s->current_ssid == ssid || wpa_s->current_ssid == NULL) { - /* - * Invalidate the EAP session cache if anything in the current - * or previously used configuration changes. - */ - eapol_sm_invalidate_cached_session(wpa_s->eapol); - } - - if ((os_strcmp(name, "psk") == 0 && - value[0] == '"' && ssid->ssid_len) || - (os_strcmp(name, "ssid") == 0 && ssid->passphrase)) - wpa_config_update_psk(ssid); - else if (os_strcmp(name, "priority") == 0) - wpa_config_update_prio_list(wpa_s->conf); - - return 0; + return wpa_supplicant_ctrl_iface_update_network(wpa_s, ssid, name, + value); } @@ -2605,6 +2634,59 @@ static int wpa_supplicant_ctrl_iface_get_network( } +static int wpa_supplicant_ctrl_iface_dup_network( + struct wpa_supplicant *wpa_s, char *cmd) +{ + struct wpa_ssid *ssid_s, *ssid_d; + char *name, *id, *value; + int id_s, id_d, ret; + + /* cmd: "<src network id> <dst network id> <variable name>" */ + id = os_strchr(cmd, ' '); + if (id == NULL) + return -1; + *id++ = '\0'; + + name = os_strchr(id, ' '); + if (name == NULL) + return -1; + *name++ = '\0'; + + id_s = atoi(cmd); + id_d = atoi(id); + wpa_printf(MSG_DEBUG, "CTRL_IFACE: DUP_NETWORK id=%d -> %d name='%s'", + id_s, id_d, name); + + ssid_s = wpa_config_get_network(wpa_s->conf, id_s); + if (ssid_s == NULL) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE: Could not find " + "network id=%d", id_s); + return -1; + } + + ssid_d = wpa_config_get_network(wpa_s->conf, id_d); + if (ssid_d == NULL) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE: Could not find " + "network id=%d", id_s); + return -1; + } + + value = wpa_config_get(ssid_s, name); + if (value == NULL) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE: Failed to get network " + "variable '%s'", name); + return -1; + } + + ret = wpa_supplicant_ctrl_iface_update_network(wpa_s, ssid_d, name, + value); + + os_free(value); + + return ret; +} + + static int wpa_supplicant_ctrl_iface_list_creds(struct wpa_supplicant *wpa_s, char *buf, size_t buflen) { @@ -2650,6 +2732,8 @@ static int wpa_supplicant_ctrl_iface_add_cred(struct wpa_supplicant *wpa_s, if (cred == NULL) return -1; + wpa_msg(wpa_s, MSG_INFO, CRED_ADDED "%d", cred->id); + ret = os_snprintf(buf, buflen, "%d\n", cred->id); if (ret < 0 || (size_t) ret >= buflen) return -1; @@ -2662,12 +2746,21 @@ static int wpas_ctrl_remove_cred(struct wpa_supplicant *wpa_s, { struct wpa_ssid *ssid; char str[20]; + int id; + + if (cred == NULL) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE: Could not find cred"); + return -1; + } - if (cred == NULL || wpa_config_remove_cred(wpa_s->conf, cred->id) < 0) { + id = cred->id; + if (wpa_config_remove_cred(wpa_s->conf, id) < 0) { wpa_printf(MSG_DEBUG, "CTRL_IFACE: Could not find cred"); return -1; } + wpa_msg(wpa_s, MSG_INFO, CRED_REMOVED "%d", id); + /* Remove any network entry created based on the removed credential */ ssid = wpa_s->conf->ssid; while (ssid) { @@ -2691,7 +2784,8 @@ static int wpa_supplicant_ctrl_iface_remove_cred(struct wpa_supplicant *wpa_s, int id; struct wpa_cred *cred, *prev; - /* cmd: "<cred id>", "all", or "sp_fqdn=<FQDN>" */ + /* cmd: "<cred id>", "all", "sp_fqdn=<FQDN>", or + * "provisioning_sp=<FQDN> */ if (os_strcmp(cmd, "all") == 0) { wpa_printf(MSG_DEBUG, "CTRL_IFACE: REMOVE_CRED all"); cred = wpa_s->conf->cred; @@ -2724,6 +2818,20 @@ static int wpa_supplicant_ctrl_iface_remove_cred(struct wpa_supplicant *wpa_s, return 0; } + if (os_strncmp(cmd, "provisioning_sp=", 16) == 0) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE: REMOVE_CRED provisioning SP FQDN '%s'", + cmd + 16); + cred = wpa_s->conf->cred; + while (cred) { + prev = cred; + cred = cred->next; + if (prev->provisioning_sp && + os_strcmp(prev->provisioning_sp, cmd + 16) == 0) + wpas_ctrl_remove_cred(wpa_s, prev); + } + return 0; + } + id = atoi(cmd); wpa_printf(MSG_DEBUG, "CTRL_IFACE: REMOVE_CRED id=%d", id); @@ -2769,10 +2877,57 @@ static int wpa_supplicant_ctrl_iface_set_cred(struct wpa_supplicant *wpa_s, return -1; } + wpa_msg(wpa_s, MSG_INFO, CRED_MODIFIED "%d %s", cred->id, name); + return 0; } +static int wpa_supplicant_ctrl_iface_get_cred(struct wpa_supplicant *wpa_s, + char *cmd, char *buf, + size_t buflen) +{ + int id; + size_t res; + struct wpa_cred *cred; + char *name, *value; + + /* cmd: "<cred id> <variable name>" */ + name = os_strchr(cmd, ' '); + if (name == NULL) + return -1; + *name++ = '\0'; + + id = atoi(cmd); + wpa_printf(MSG_DEBUG, "CTRL_IFACE: GET_CRED id=%d name='%s'", + id, name); + + cred = wpa_config_get_cred(wpa_s->conf, id); + if (cred == NULL) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE: Could not find cred id=%d", + id); + return -1; + } + + value = wpa_config_get_cred_no_key(cred, name); + if (value == NULL) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE: Failed to get cred variable '%s'", + name); + return -1; + } + + res = os_strlcpy(buf, value, buflen); + if (res >= buflen) { + os_free(value); + return -1; + } + + os_free(value); + + return res; +} + + #ifndef CONFIG_NO_CONFIG_WRITE static int wpa_supplicant_ctrl_iface_save_config(struct wpa_supplicant *wpa_s) { @@ -2820,7 +2975,7 @@ static int ctrl_iface_get_capability_pairwise(int res, char *strict, struct wpa_driver_capa *capa, char *buf, size_t buflen) { - int ret, first = 1; + int ret; char *pos, *end; size_t len; unsigned int i; @@ -2840,11 +2995,11 @@ static int ctrl_iface_get_capability_pairwise(int res, char *strict, for (i = 0; i < ARRAY_SIZE(ciphers); i++) { if (!ciphers[i].group_only && capa->enc & ciphers[i].capa) { ret = os_snprintf(pos, end - pos, "%s%s", - first ? "" : " ", ciphers[i].name); + pos == buf ? "" : " ", + ciphers[i].name); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; - first = 0; } } @@ -2856,7 +3011,7 @@ static int ctrl_iface_get_capability_group(int res, char *strict, struct wpa_driver_capa *capa, char *buf, size_t buflen) { - int ret, first = 1; + int ret; char *pos, *end; size_t len; unsigned int i; @@ -2876,11 +3031,11 @@ static int ctrl_iface_get_capability_group(int res, char *strict, for (i = 0; i < ARRAY_SIZE(ciphers); i++) { if (capa->enc & ciphers[i].capa) { ret = os_snprintf(pos, end - pos, "%s%s", - first ? "" : " ", ciphers[i].name); + pos == buf ? "" : " ", + ciphers[i].name); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; - first = 0; } } @@ -2945,7 +3100,7 @@ static int ctrl_iface_get_capability_proto(int res, char *strict, struct wpa_driver_capa *capa, char *buf, size_t buflen) { - int ret, first = 1; + int ret; char *pos, *end; size_t len; @@ -2963,20 +3118,20 @@ static int ctrl_iface_get_capability_proto(int res, char *strict, if (capa->key_mgmt & (WPA_DRIVER_CAPA_KEY_MGMT_WPA2 | WPA_DRIVER_CAPA_KEY_MGMT_WPA2_PSK)) { - ret = os_snprintf(pos, end - pos, "%sRSN", first ? "" : " "); + ret = os_snprintf(pos, end - pos, "%sRSN", + pos == buf ? "" : " "); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; - first = 0; } if (capa->key_mgmt & (WPA_DRIVER_CAPA_KEY_MGMT_WPA | WPA_DRIVER_CAPA_KEY_MGMT_WPA_PSK)) { - ret = os_snprintf(pos, end - pos, "%sWPA", first ? "" : " "); + ret = os_snprintf(pos, end - pos, "%sWPA", + pos == buf ? "" : " "); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; - first = 0; } return pos - buf; @@ -2987,7 +3142,7 @@ static int ctrl_iface_get_capability_auth_alg(int res, char *strict, struct wpa_driver_capa *capa, char *buf, size_t buflen) { - int ret, first = 1; + int ret; char *pos, *end; size_t len; @@ -3004,28 +3159,27 @@ static int ctrl_iface_get_capability_auth_alg(int res, char *strict, } if (capa->auth & (WPA_DRIVER_AUTH_OPEN)) { - ret = os_snprintf(pos, end - pos, "%sOPEN", first ? "" : " "); + ret = os_snprintf(pos, end - pos, "%sOPEN", + pos == buf ? "" : " "); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; - first = 0; } if (capa->auth & (WPA_DRIVER_AUTH_SHARED)) { ret = os_snprintf(pos, end - pos, "%sSHARED", - first ? "" : " "); + pos == buf ? "" : " "); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; - first = 0; } if (capa->auth & (WPA_DRIVER_AUTH_LEAP)) { - ret = os_snprintf(pos, end - pos, "%sLEAP", first ? "" : " "); + ret = os_snprintf(pos, end - pos, "%sLEAP", + pos == buf ? "" : " "); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; - first = 0; } return pos - buf; @@ -3036,7 +3190,7 @@ static int ctrl_iface_get_capability_modes(int res, char *strict, struct wpa_driver_capa *capa, char *buf, size_t buflen) { - int ret, first = 1; + int ret; char *pos, *end; size_t len; @@ -3053,19 +3207,19 @@ static int ctrl_iface_get_capability_modes(int res, char *strict, } if (capa->flags & WPA_DRIVER_FLAGS_IBSS) { - ret = os_snprintf(pos, end - pos, "%sIBSS", first ? "" : " "); + ret = os_snprintf(pos, end - pos, "%sIBSS", + pos == buf ? "" : " "); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; - first = 0; } if (capa->flags & WPA_DRIVER_FLAGS_AP) { - ret = os_snprintf(pos, end - pos, "%sAP", first ? "" : " "); + ret = os_snprintf(pos, end - pos, "%sAP", + pos == buf ? "" : " "); if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; - first = 0; } return pos - buf; @@ -3158,10 +3312,13 @@ static int ctrl_iface_get_capability_freq(struct wpa_supplicant *wpa_s, for (i = 0; i < wpa_s->hw.modes[j].num_channels; i++) { if (chnl[i].flag & HOSTAPD_CHAN_DISABLED) continue; - ret = os_snprintf(pos, end - pos, " %d = %d MHz%s\n", + ret = os_snprintf(pos, end - pos, " %d = %d MHz%s%s\n", chnl[i].chan, chnl[i].freq, chnl[i].flag & HOSTAPD_CHAN_NO_IBSS ? - " (NO_IBSS)" : ""); + " (NO_IBSS)" : "", + chnl[i].flag & HOSTAPD_CHAN_RADAR ? + " (DFS)" : ""); + if (ret < 0 || ret >= end - pos) return pos - buf; pos += ret; @@ -3236,6 +3393,11 @@ static int wpa_supplicant_ctrl_iface_get_capability( if (os_strcmp(field, "freq") == 0) return ctrl_iface_get_capability_freq(wpa_s, buf, buflen); +#ifdef CONFIG_TDLS + if (os_strcmp(field, "tdls") == 0) + return ctrl_iface_get_capability_tdls(wpa_s, buf, buflen); +#endif /* CONFIG_TDLS */ + wpa_printf(MSG_DEBUG, "CTRL_IFACE: Unknown GET_CAPABILITY field '%s'", field); @@ -3408,17 +3570,43 @@ static int print_bss_info(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, return 0; pos += ret; } - if (bss->caps & IEEE80211_CAP_IBSS) { - ret = os_snprintf(pos, end - pos, "[IBSS]"); + if (bss_is_dmg(bss)) { + const char *s; + ret = os_snprintf(pos, end - pos, "[DMG]"); if (ret < 0 || ret >= end - pos) return 0; pos += ret; - } - if (bss->caps & IEEE80211_CAP_ESS) { - ret = os_snprintf(pos, end - pos, "[ESS]"); + switch (bss->caps & IEEE80211_CAP_DMG_MASK) { + case IEEE80211_CAP_DMG_IBSS: + s = "[IBSS]"; + break; + case IEEE80211_CAP_DMG_AP: + s = "[ESS]"; + break; + case IEEE80211_CAP_DMG_PBSS: + s = "[PBSS]"; + break; + default: + s = ""; + break; + } + ret = os_snprintf(pos, end - pos, "%s", s); if (ret < 0 || ret >= end - pos) return 0; pos += ret; + } else { + if (bss->caps & IEEE80211_CAP_IBSS) { + ret = os_snprintf(pos, end - pos, "[IBSS]"); + if (ret < 0 || ret >= end - pos) + return 0; + pos += ret; + } + if (bss->caps & IEEE80211_CAP_ESS) { + ret = os_snprintf(pos, end - pos, "[ESS]"); + if (ret < 0 || ret >= end - pos) + return 0; + pos += ret; + } } if (wpa_bss_get_vendor_ie(bss, P2P_IE_VENDOR_TYPE) || wpa_bss_get_vendor_ie_beacon(bss, P2P_IE_VENDOR_TYPE)) { @@ -3478,8 +3666,10 @@ static int print_bss_info(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, WFD_IE_VENDOR_TYPE); if (wfd) { ret = os_snprintf(pos, end - pos, "wfd_subelems="); - if (ret < 0 || ret >= end - pos) + if (ret < 0 || ret >= end - pos) { + wpabuf_free(wfd); return 0; + } pos += ret; pos += wpa_snprintf_hex(pos, end - pos, @@ -3518,6 +3708,10 @@ static int print_bss_info(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, anqp->hs20_wan_metrics); pos = anqp_add_hex(pos, end, "hs20_connection_capability", anqp->hs20_connection_capability); + pos = anqp_add_hex(pos, end, "hs20_operating_class", + anqp->hs20_operating_class); + pos = anqp_add_hex(pos, end, "hs20_osu_providers_list", + anqp->hs20_osu_providers_list); #endif /* CONFIG_HS20 */ } #endif /* CONFIG_INTERWORKING */ @@ -3712,6 +3906,7 @@ static int wpa_supplicant_ctrl_iface_bss_flush( } +#ifdef CONFIG_TESTING_OPTIONS static void wpa_supplicant_ctrl_iface_drop_sa(struct wpa_supplicant *wpa_s) { wpa_printf(MSG_DEBUG, "Dropping SA without deauthentication"); @@ -3733,6 +3928,7 @@ static void wpa_supplicant_ctrl_iface_drop_sa(struct wpa_supplicant *wpa_s) MLME_SETPROTECTION_KEY_TYPE_PAIRWISE); wpa_sm_drop_sa(wpa_s->wpa); } +#endif /* CONFIG_TESTING_OPTIONS */ static int wpa_supplicant_ctrl_iface_roam(struct wpa_supplicant *wpa_s, @@ -3789,6 +3985,11 @@ static int p2p_ctrl_find(struct wpa_supplicant *wpa_s, char *cmd) char *pos; unsigned int search_delay; + if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { + wpa_dbg(wpa_s, MSG_INFO, + "Reject P2P_FIND since interface is disabled"); + return -1; + } if (os_strstr(cmd, "type=social")) type = P2P_FIND_ONLY_SOCIAL; else if (os_strstr(cmd, "type=progressive")) @@ -3940,6 +4141,11 @@ static int p2p_ctrl_connect(struct wpa_supplicant *wpa_s, char *cmd, static int p2p_ctrl_listen(struct wpa_supplicant *wpa_s, char *cmd) { unsigned int timeout = atoi(cmd); + if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { + wpa_dbg(wpa_s, MSG_INFO, + "Reject P2P_LISTEN since interface is disabled"); + return -1; + } return wpas_p2p_listen(wpa_s, timeout); } @@ -4379,7 +4585,7 @@ static int p2p_ctrl_group_add_persistent(struct wpa_supplicant *wpa_s, return -1; } - return wpas_p2p_group_add_persistent(wpa_s, ssid, 0, freq, ht40, vht, + return wpas_p2p_group_add_persistent(wpa_s, ssid, 0, freq, 0, ht40, vht, NULL, 0); } @@ -4907,15 +5113,27 @@ static int get_anqp(struct wpa_supplicant *wpa_s, char *dst) #define MAX_ANQP_INFO_ID 100 u16 id[MAX_ANQP_INFO_ID]; size_t num_id = 0; + u32 subtypes = 0; used = hwaddr_aton2(dst, dst_addr); if (used < 0) return -1; pos = dst + used; while (num_id < MAX_ANQP_INFO_ID) { - id[num_id] = atoi(pos); - if (id[num_id]) - num_id++; + if (os_strncmp(pos, "hs20:", 5) == 0) { +#ifdef CONFIG_HS20 + int num = atoi(pos + 5); + if (num <= 0 || num > 31) + return -1; + subtypes |= BIT(num); +#else /* CONFIG_HS20 */ + return -1; +#endif /* CONFIG_HS20 */ + } else { + id[num_id] = atoi(pos); + if (id[num_id]) + num_id++; + } pos = os_strchr(pos + 1, ','); if (pos == NULL) break; @@ -4925,7 +5143,7 @@ static int get_anqp(struct wpa_supplicant *wpa_s, char *dst) if (num_id == 0) return -1; - return anqp_send_req(wpa_s, dst_addr, id, num_id); + return anqp_send_req(wpa_s, dst_addr, id, num_id, subtypes); } @@ -5169,6 +5387,26 @@ static int hs20_get_nai_home_realm_list(struct wpa_supplicant *wpa_s, return ret; } + +static int hs20_icon_request(struct wpa_supplicant *wpa_s, char *cmd) +{ + u8 dst_addr[ETH_ALEN]; + int used; + char *icon; + + used = hwaddr_aton2(cmd, dst_addr); + if (used < 0) + return -1; + + while (cmd[used] == ' ') + used++; + icon = &cmd[used]; + + wpa_s->fetch_osu_icon_in_progress = 0; + return hs20_anqp_send_req(wpa_s, dst_addr, BIT(HS20_STYPE_ICON_REQUEST), + (u8 *) icon, os_strlen(icon)); +} + #endif /* CONFIG_HS20 */ @@ -5393,11 +5631,69 @@ static int wpa_supplicant_driver_cmd(struct wpa_supplicant *wpa_s, char *cmd, #endif /* ANDROID */ +static int wpa_supplicant_vendor_cmd(struct wpa_supplicant *wpa_s, char *cmd, + char *buf, size_t buflen) +{ + int ret; + char *pos; + u8 *data = NULL; + unsigned int vendor_id, subcmd; + struct wpabuf *reply; + size_t data_len = 0; + + /* cmd: <vendor id> <subcommand id> [<hex formatted data>] */ + vendor_id = strtoul(cmd, &pos, 16); + if (!isblank(*pos)) + return -EINVAL; + + subcmd = strtoul(pos, &pos, 10); + + if (*pos != '\0') { + if (!isblank(*pos++)) + return -EINVAL; + data_len = os_strlen(pos); + } + + if (data_len) { + data_len /= 2; + data = os_malloc(data_len); + if (!data) + return -1; + + if (hexstr2bin(pos, data, data_len)) { + wpa_printf(MSG_DEBUG, + "Vendor command: wrong parameter format"); + os_free(data); + return -EINVAL; + } + } + + reply = wpabuf_alloc((buflen - 1) / 2); + if (!reply) { + os_free(data); + return -1; + } + + ret = wpa_drv_vendor_cmd(wpa_s, vendor_id, subcmd, data, data_len, + reply); + + if (ret == 0) + ret = wpa_snprintf_hex(buf, buflen, wpabuf_head_u8(reply), + wpabuf_len(reply)); + + wpabuf_free(reply); + os_free(data); + + return ret; +} + + static void wpa_supplicant_ctrl_iface_flush(struct wpa_supplicant *wpa_s) { wpa_dbg(wpa_s, MSG_DEBUG, "Flush all wpa_supplicant state"); #ifdef CONFIG_P2P + wpas_p2p_cancel(wpa_s); wpas_p2p_stop_find(wpa_s); p2p_ctrl_flush(wpa_s); wpas_p2p_group_remove(wpa_s, "*"); @@ -5406,6 +5702,8 @@ static void wpa_supplicant_ctrl_iface_flush(struct wpa_supplicant *wpa_s) wpa_s->global->p2p_per_sta_psk = 0; wpa_s->conf->num_sec_device_types = 0; wpa_s->p2p_disable_ip_addr_req = 0; + os_free(wpa_s->global->p2p_go_avoid_freq.range); + wpa_s->global->p2p_go_avoid_freq.range = NULL; #endif /* CONFIG_P2P */ #ifdef CONFIG_WPS_TESTING @@ -5454,14 +5752,21 @@ static void wpa_supplicant_ctrl_iface_flush(struct wpa_supplicant *wpa_s) wpa_config_flush_blobs(wpa_s->conf); wpa_s->conf->auto_interworking = 0; wpa_s->conf->okc = 0; - wpa_s->conf->pmf = 0; wpa_sm_set_param(wpa_s->wpa, RSNA_PMK_LIFETIME, 43200); wpa_sm_set_param(wpa_s->wpa, RSNA_PMK_REAUTH_THRESHOLD, 70); wpa_sm_set_param(wpa_s->wpa, RSNA_SA_TIMEOUT, 60); eapol_sm_notify_logoff(wpa_s->eapol, FALSE); - radio_remove_unstarted_work(wpa_s, NULL); + radio_remove_works(wpa_s, NULL, 1); + + wpa_s->next_ssid = NULL; + +#ifdef CONFIG_INTERWORKING + hs20_cancel_fetch_osu(wpa_s); +#endif /* CONFIG_INTERWORKING */ + + wpa_s->ext_mgmt_frame_handling = 0; } @@ -5503,8 +5808,8 @@ static void wpas_ctrl_radio_work_timeout(void *eloop_ctx, void *timeout_ctx) "Timing out external radio work %u (%s)", ework->id, work->type); wpa_msg(work->wpa_s, MSG_INFO, EXT_RADIO_WORK_TIMEOUT "%u", ework->id); - os_free(ework); radio_work_done(work); + os_free(ework); } @@ -5513,6 +5818,10 @@ static void wpas_ctrl_radio_work_cb(struct wpa_radio_work *work, int deinit) struct wpa_external_work *ework = work->ctx; if (deinit) { + if (work->started) + eloop_cancel_timeout(wpas_ctrl_radio_work_timeout, + work, NULL); + os_free(ework); return; } @@ -5599,8 +5908,8 @@ static int wpas_ctrl_radio_work_done(struct wpa_supplicant *wpa_s, char *cmd) "Completed external radio work %u (%s)", ework->id, ework->type); eloop_cancel_timeout(wpas_ctrl_radio_work_timeout, work, NULL); - os_free(ework); radio_work_done(work); + os_free(ework); return 3; /* "OK\n" */ } @@ -5636,14 +5945,14 @@ void wpas_ctrl_radio_work_flush(struct wpa_supplicant *wpa_s) continue; ework = work->ctx; wpa_dbg(wpa_s, MSG_DEBUG, - "Flushing %sexternal radio work %u (%s)", + "Flushing%s external radio work %u (%s)", work->started ? " started" : "", ework->id, ework->type); if (work->started) eloop_cancel_timeout(wpas_ctrl_radio_work_timeout, work, NULL); - os_free(ework); radio_work_done(work); + os_free(ework); } } @@ -5746,6 +6055,145 @@ static void wpas_ctrl_scan(struct wpa_supplicant *wpa_s, char *params, } +#ifdef CONFIG_TESTING_OPTIONS + +static void wpas_ctrl_iface_mgmt_tx_cb(struct wpa_supplicant *wpa_s, + unsigned int freq, const u8 *dst, + const u8 *src, const u8 *bssid, + const u8 *data, size_t data_len, + enum offchannel_send_action_result + result) +{ + wpa_msg(wpa_s, MSG_INFO, "MGMT-TX-STATUS freq=%u dst=" MACSTR + " src=" MACSTR " bssid=" MACSTR " result=%s", + freq, MAC2STR(dst), MAC2STR(src), MAC2STR(bssid), + result == OFFCHANNEL_SEND_ACTION_SUCCESS ? + "SUCCESS" : (result == OFFCHANNEL_SEND_ACTION_NO_ACK ? + "NO_ACK" : "FAILED")); +} + + +static int wpas_ctrl_iface_mgmt_tx(struct wpa_supplicant *wpa_s, char *cmd) +{ + char *pos, *param; + size_t len; + u8 *buf, da[ETH_ALEN], bssid[ETH_ALEN]; + int res, used; + int freq = 0, no_cck = 0, wait_time = 0; + + /* <DA> <BSSID> [freq=<MHz>] [wait_time=<ms>] [no_cck=1] + * <action=Action frame payload> */ + + wpa_printf(MSG_DEBUG, "External MGMT TX: %s", cmd); + + pos = cmd; + used = hwaddr_aton2(pos, da); + if (used < 0) + return -1; + pos += used; + while (*pos == ' ') + pos++; + used = hwaddr_aton2(pos, bssid); + if (used < 0) + return -1; + pos += used; + + param = os_strstr(pos, " freq="); + if (param) { + param += 6; + freq = atoi(param); + } + + param = os_strstr(pos, " no_cck="); + if (param) { + param += 8; + no_cck = atoi(param); + } + + param = os_strstr(pos, " wait_time="); + if (param) { + param += 11; + wait_time = atoi(param); + } + + param = os_strstr(pos, " action="); + if (param == NULL) + return -1; + param += 8; + + len = os_strlen(param); + if (len & 1) + return -1; + len /= 2; + + buf = os_malloc(len); + if (buf == NULL) + return -1; + + if (hexstr2bin(param, buf, len) < 0) { + os_free(buf); + return -1; + } + + res = offchannel_send_action(wpa_s, freq, da, wpa_s->own_addr, bssid, + buf, len, wait_time, + wpas_ctrl_iface_mgmt_tx_cb, no_cck); + os_free(buf); + return res; +} + + +static void wpas_ctrl_iface_mgmt_tx_done(struct wpa_supplicant *wpa_s) +{ + wpa_printf(MSG_DEBUG, "External MGMT TX - done waiting"); + offchannel_send_action_done(wpa_s); +} + + +static int wpas_ctrl_iface_driver_event(struct wpa_supplicant *wpa_s, char *cmd) +{ + char *pos, *param; + union wpa_event_data event; + enum wpa_event_type ev; + + /* <event name> [parameters..] */ + + wpa_dbg(wpa_s, MSG_DEBUG, "Testing - external driver event: %s", cmd); + + pos = cmd; + param = os_strchr(pos, ' '); + if (param) + *param++ = '\0'; + + os_memset(&event, 0, sizeof(event)); + + if (os_strcmp(cmd, "INTERFACE_ENABLED") == 0) { + ev = EVENT_INTERFACE_ENABLED; + } else if (os_strcmp(cmd, "INTERFACE_DISABLED") == 0) { + ev = EVENT_INTERFACE_DISABLED; + } else if (os_strcmp(cmd, "AVOID_FREQUENCIES") == 0) { + ev = EVENT_AVOID_FREQUENCIES; + if (param == NULL) + param = ""; + if (freq_range_list_parse(&event.freq_range, param) < 0) + return -1; + wpa_supplicant_event(wpa_s, ev, &event); + os_free(event.freq_range.range); + return 0; + } else { + wpa_dbg(wpa_s, MSG_DEBUG, "Testing - unknown driver event: %s", + cmd); + return -1; + } + + wpa_supplicant_event(wpa_s, ev, &event); + + return 0; +} + +#endif /* CONFIG_TESTING_OPTIONS */ + + char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, char *buf, size_t *resp_len) { @@ -5765,8 +6213,7 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, os_strlen(WPA_CTRL_RSP)) == 0 ? WPA_CTRL_RSP : "SET_NETWORK"); } else if (os_strncmp(buf, "WPS_NFC_TAG_READ", 16) == 0 || - os_strncmp(buf, "NFC_REPORT_HANDOVER", 19) == 0 || - os_strncmp(buf, "NFC_RX_HANDOVER_SEL", 19) == 0) { + os_strncmp(buf, "NFC_REPORT_HANDOVER", 19) == 0) { wpa_hexdump_ascii_key(MSG_DEBUG, "RX ctrl_iface", (const u8 *) buf, os_strlen(buf)); } else { @@ -5828,6 +6275,14 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, reply_len = -1; else wpas_request_connection(wpa_s); + } else if (os_strcmp(buf, "REATTACH") == 0) { + if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED || + !wpa_s->current_ssid) + reply_len = -1; + else { + wpa_s->reattach = 1; + wpas_request_connection(wpa_s); + } } else if (os_strcmp(buf, "RECONNECT") == 0) { if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) reply_len = -1; @@ -5896,12 +6351,6 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, } else if (os_strncmp(buf, "NFC_GET_HANDOVER_SEL ", 21) == 0) { reply_len = wpas_ctrl_nfc_get_handover_sel( wpa_s, buf + 21, reply, reply_size); - } else if (os_strncmp(buf, "NFC_RX_HANDOVER_REQ ", 20) == 0) { - reply_len = wpas_ctrl_nfc_rx_handover_req( - wpa_s, buf + 20, reply, reply_size); - } else if (os_strncmp(buf, "NFC_RX_HANDOVER_SEL ", 20) == 0) { - if (wpas_ctrl_nfc_rx_handover_sel(wpa_s, buf + 20)) - reply_len = -1; } else if (os_strncmp(buf, "NFC_REPORT_HANDOVER ", 20) == 0) { if (wpas_ctrl_nfc_report_handover(wpa_s, buf + 20)) reply_len = -1; @@ -6092,6 +6541,14 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, } else if (os_strncmp(buf, "HS20_GET_NAI_HOME_REALM_LIST ", 29) == 0) { if (hs20_get_nai_home_realm_list(wpa_s, buf + 29) < 0) reply_len = -1; + } else if (os_strncmp(buf, "HS20_ICON_REQUEST ", 18) == 0) { + if (hs20_icon_request(wpa_s, buf + 18) < 0) + reply_len = -1; + } else if (os_strcmp(buf, "FETCH_OSU") == 0) { + if (hs20_fetch_osu(wpa_s) < 0) + reply_len = -1; + } else if (os_strcmp(buf, "CANCEL_FETCH_OSU") == 0) { + hs20_cancel_fetch_osu(wpa_s); #endif /* CONFIG_HS20 */ } else if (os_strncmp(buf, WPA_CTRL_RSP, os_strlen(WPA_CTRL_RSP)) == 0) { @@ -6161,6 +6618,9 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, } else if (os_strncmp(buf, "GET_NETWORK ", 12) == 0) { reply_len = wpa_supplicant_ctrl_iface_get_network( wpa_s, buf + 12, reply, reply_size); + } else if (os_strncmp(buf, "DUP_NETWORK ", 12) == 0) { + if (wpa_supplicant_ctrl_iface_dup_network(wpa_s, buf + 12)) + reply_len = -1; } else if (os_strcmp(buf, "LIST_CREDS") == 0) { reply_len = wpa_supplicant_ctrl_iface_list_creds( wpa_s, reply, reply_size); @@ -6173,6 +6633,10 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, } else if (os_strncmp(buf, "SET_CRED ", 9) == 0) { if (wpa_supplicant_ctrl_iface_set_cred(wpa_s, buf + 9)) reply_len = -1; + } else if (os_strncmp(buf, "GET_CRED ", 9) == 0) { + reply_len = wpa_supplicant_ctrl_iface_get_cred(wpa_s, buf + 9, + reply, + reply_size); #ifndef CONFIG_NO_CONFIG_WRITE } else if (os_strcmp(buf, "SAVE_CONFIG") == 0) { if (wpa_supplicant_ctrl_iface_save_config(wpa_s)) @@ -6219,8 +6683,10 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, wpas_notify_suspend(wpa_s->global); } else if (os_strcmp(buf, "RESUME") == 0) { wpas_notify_resume(wpa_s->global); +#ifdef CONFIG_TESTING_OPTIONS } else if (os_strcmp(buf, "DROP_SA") == 0) { wpa_supplicant_ctrl_iface_drop_sa(wpa_s); +#endif /* CONFIG_TESTING_OPTIONS */ } else if (os_strncmp(buf, "ROAM ", 5) == 0) { if (wpa_supplicant_ctrl_iface_roam(wpa_s, buf + 5)) reply_len = -1; @@ -6264,6 +6730,9 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, reply_len = wpa_supplicant_driver_cmd(wpa_s, buf + 7, reply, reply_size); #endif /* ANDROID */ + } else if (os_strncmp(buf, "VENDOR ", 7) == 0) { + reply_len = wpa_supplicant_vendor_cmd(wpa_s, buf + 7, reply, + reply_size); } else if (os_strcmp(buf, "REAUTHENTICATE") == 0) { pmksa_cache_clear_current(wpa_s->wpa); eapol_sm_request_reauth(wpa_s->eapol); @@ -6280,6 +6749,16 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, } else if (os_strncmp(buf, "RADIO_WORK ", 11) == 0) { reply_len = wpas_ctrl_radio_work(wpa_s, buf + 11, reply, reply_size); +#ifdef CONFIG_TESTING_OPTIONS + } else if (os_strncmp(buf, "MGMT_TX ", 8) == 0) { + if (wpas_ctrl_iface_mgmt_tx(wpa_s, buf + 8) < 0) + reply_len = -1; + } else if (os_strcmp(buf, "MGMT_TX_DONE") == 0) { + wpas_ctrl_iface_mgmt_tx_done(wpa_s); + } else if (os_strncmp(buf, "DRIVER_EVENT ", 13) == 0) { + if (wpas_ctrl_iface_driver_event(wpa_s, buf + 13) < 0) + reply_len = -1; +#endif /* CONFIG_TESTING_OPTIONS */ } else { os_memcpy(reply, "UNKNOWN COMMAND\n", 16); reply_len = 16; @@ -6497,7 +6976,6 @@ static char * wpas_global_ctrl_iface_redir_p2p(struct wpa_global *global, #ifdef CONFIG_P2P static const char * cmd[] = { "LIST_NETWORKS", - "SAVE_CONFIG", "P2P_FIND", "P2P_STOP_FIND", "P2P_LISTEN", @@ -6517,7 +6995,6 @@ static char * wpas_global_ctrl_iface_redir_p2p(struct wpa_global *global, #endif /* ANDROID */ "GET_NETWORK ", "REMOVE_NETWORK ", - "SET ", "P2P_FIND ", "P2P_CONNECT ", "P2P_LISTEN ", @@ -6538,6 +7015,9 @@ static char * wpas_global_ctrl_iface_redir_p2p(struct wpa_global *global, "P2P_PRESENCE_REQ ", "P2P_EXT_LISTEN ", "P2P_REMOVE_CLIENT ", + "NFC_GET_HANDOVER_SEL ", + "NFC_GET_HANDOVER_REQ ", + "NFC_REPORT_HANDOVER ", NULL }; int found = 0; @@ -6614,6 +7094,9 @@ static int wpas_global_ctrl_iface_set(struct wpa_global *global, char *cmd) } #endif /* CONFIG_WIFI_DISPLAY */ + /* Restore cmd to its original value to allow redirection */ + value[-1] = ' '; + return -1; } @@ -6621,7 +7104,7 @@ static int wpas_global_ctrl_iface_set(struct wpa_global *global, char *cmd) #ifndef CONFIG_NO_CONFIG_WRITE static int wpas_global_ctrl_iface_save_config(struct wpa_global *global) { - int ret = 0; + int ret = 0, saved = 0; struct wpa_supplicant *wpa_s; for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next) { @@ -6635,9 +7118,16 @@ static int wpas_global_ctrl_iface_save_config(struct wpa_global *global) ret = 1; } else { wpa_dbg(wpa_s, MSG_DEBUG, "CTRL_IFACE: SAVE_CONFIG - Configuration updated"); + saved++; } } + if (!saved && !ret) { + wpa_dbg(wpa_s, MSG_DEBUG, + "CTRL_IFACE: SAVE_CONFIG - No configuration files could be updated"); + ret = 1; + } + return ret; } #endif /* CONFIG_NO_CONFIG_WRITE */ @@ -6750,8 +7240,19 @@ char * wpa_supplicant_global_ctrl_iface_process(struct wpa_global *global, } else if (os_strcmp(buf, "RESUME") == 0) { wpas_notify_resume(global); } else if (os_strncmp(buf, "SET ", 4) == 0) { - if (wpas_global_ctrl_iface_set(global, buf + 4)) + if (wpas_global_ctrl_iface_set(global, buf + 4)) { +#ifdef CONFIG_P2P + if (global->p2p_init_wpa_s) { + os_free(reply); + /* Check if P2P redirection would work for this + * command. */ + return wpa_supplicant_ctrl_iface_process( + global->p2p_init_wpa_s, + buf, resp_len); + } +#endif /* CONFIG_P2P */ reply_len = -1; + } #ifndef CONFIG_NO_CONFIG_WRITE } else if (os_strcmp(buf, "SAVE_CONFIG") == 0) { if (wpas_global_ctrl_iface_save_config(global)) @@ -6760,6 +7261,12 @@ char * wpa_supplicant_global_ctrl_iface_process(struct wpa_global *global, } else if (os_strcmp(buf, "STATUS") == 0) { reply_len = wpas_global_ctrl_iface_status(global, reply, reply_size); +#ifdef CONFIG_MODULE_TESTS + } else if (os_strcmp(buf, "MODULE_TESTS") == 0) { + int wpas_module_tests(void); + if (wpas_module_tests() < 0) + reply_len = -1; +#endif /* CONFIG_MODULE_TESTS */ } else { os_memcpy(reply, "UNKNOWN COMMAND\n", 16); reply_len = 16; diff --git a/wpa_supplicant/ctrl_iface.h b/wpa_supplicant/ctrl_iface.h index b0dec53..d54cc07 100644 --- a/wpa_supplicant/ctrl_iface.h +++ b/wpa_supplicant/ctrl_iface.h @@ -32,7 +32,7 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s, char *buf, size_t *resp_len); /** - * wpa_supplicant_ctrl_iface_process - Process global ctrl_iface command + * wpa_supplicant_global_ctrl_iface_process - Process global ctrl_iface command * @global: Pointer to global data from wpa_supplicant_init() * @buf: Received command buffer (nul terminated string) * @resp_len: Variable to be set to the response length diff --git a/wpa_supplicant/ctrl_iface_udp.c b/wpa_supplicant/ctrl_iface_udp.c index 8c09ba1..9d0674d 100644 --- a/wpa_supplicant/ctrl_iface_udp.c +++ b/wpa_supplicant/ctrl_iface_udp.c @@ -30,7 +30,11 @@ */ struct wpa_ctrl_dst { struct wpa_ctrl_dst *next; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 addr; +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in addr; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ socklen_t addrlen; int debug_level; int errors; @@ -51,38 +55,68 @@ static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv, static int wpa_supplicant_ctrl_iface_attach(struct ctrl_iface_priv *priv, +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 *from, +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in *from, +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ socklen_t fromlen) { struct wpa_ctrl_dst *dst; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char addr[INET6_ADDRSTRLEN]; +#endif /* CONFIG_UDP_IPV6 */ dst = os_zalloc(sizeof(*dst)); if (dst == NULL) return -1; - os_memcpy(&dst->addr, from, sizeof(struct sockaddr_in)); + os_memcpy(&dst->addr, from, sizeof(*from)); dst->addrlen = fromlen; dst->debug_level = MSG_INFO; dst->next = priv->ctrl_dst; priv->ctrl_dst = dst; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor attached %s:%d", + inet_ntop(AF_INET6, &from->sin6_addr, addr, sizeof(*from)), + ntohs(from->sin6_port)); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor attached %s:%d", inet_ntoa(from->sin_addr), ntohs(from->sin_port)); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ return 0; } static int wpa_supplicant_ctrl_iface_detach(struct ctrl_iface_priv *priv, +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 *from, +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in *from, +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ socklen_t fromlen) { struct wpa_ctrl_dst *dst, *prev = NULL; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char addr[INET6_ADDRSTRLEN]; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ dst = priv->ctrl_dst; while (dst) { +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + if (from->sin6_port == dst->addr.sin6_port && + !os_memcmp(&from->sin6_addr, &dst->addr.sin6_addr, + sizeof(from->sin6_addr))) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor detached %s:%d", + inet_ntop(AF_INET6, &from->sin6_addr, addr, + sizeof(*from)), + ntohs(from->sin6_port)); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (from->sin_addr.s_addr == dst->addr.sin_addr.s_addr && from->sin_port == dst->addr.sin_port) { wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor detached " "%s:%d", inet_ntoa(from->sin_addr), ntohs(from->sin_port)); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (prev == NULL) priv->ctrl_dst = dst->next; else @@ -98,21 +132,38 @@ static int wpa_supplicant_ctrl_iface_detach(struct ctrl_iface_priv *priv, static int wpa_supplicant_ctrl_iface_level(struct ctrl_iface_priv *priv, +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 *from, +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in *from, +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ socklen_t fromlen, char *level) { struct wpa_ctrl_dst *dst; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char addr[INET6_ADDRSTRLEN]; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ wpa_printf(MSG_DEBUG, "CTRL_IFACE LEVEL %s", level); dst = priv->ctrl_dst; while (dst) { +#if CONFIG_CTRL_IFACE_UDP_IPV6 + if (from->sin6_port == dst->addr.sin6_port && + !os_memcmp(&from->sin6_addr, &dst->addr.sin6_addr, + sizeof(from->sin6_addr))) { + wpa_printf(MSG_DEBUG, "CTRL_IFACE changed monitor level %s:%d", + inet_ntop(AF_INET6, &from->sin6_addr, addr, + sizeof(*from)), + ntohs(from->sin6_port)); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (from->sin_addr.s_addr == dst->addr.sin_addr.s_addr && from->sin_port == dst->addr.sin_port) { wpa_printf(MSG_DEBUG, "CTRL_IFACE changed monitor " "level %s:%d", inet_ntoa(from->sin_addr), ntohs(from->sin_port)); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ dst->debug_level = atoi(level); return 0; } @@ -150,7 +201,14 @@ static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx, struct ctrl_iface_priv *priv = sock_ctx; char buf[256], *pos; int res; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 from; +#ifndef CONFIG_CTRL_IFACE_UDP_REMOTE + char addr[INET6_ADDRSTRLEN]; +#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ struct sockaddr_in from; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ socklen_t fromlen = sizeof(from); char *reply = NULL; size_t reply_len = 0; @@ -165,6 +223,13 @@ static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx, } #ifndef CONFIG_CTRL_IFACE_UDP_REMOTE +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + inet_ntop(AF_INET6, &from.sin6_addr, addr, sizeof(from)); + if (os_strcmp(addr, "::1")) { + wpa_printf(MSG_DEBUG, "CTRL: Drop packet from unexpected source %s", + addr); + } +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (from.sin_addr.s_addr != htonl((127 << 24) | 1)) { /* * The OS networking stack is expected to drop this kind of @@ -176,6 +241,7 @@ static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx, "source %s", inet_ntoa(from.sin_addr)); return; } +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ #endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ buf[res] = '\0'; @@ -269,8 +335,14 @@ struct ctrl_iface_priv * wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s) { struct ctrl_iface_priv *priv; - struct sockaddr_in addr; int port = WPA_CTRL_IFACE_PORT; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + struct sockaddr_in6 addr; + int domain = PF_INET6; +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ + struct sockaddr_in addr; + int domain = PF_INET; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ priv = os_zalloc(sizeof(*priv)); if (priv == NULL) @@ -282,21 +354,34 @@ wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s) if (wpa_s->conf->ctrl_interface == NULL) return priv; - priv->sock = socket(PF_INET, SOCK_DGRAM, 0); + priv->sock = socket(domain, SOCK_DGRAM, 0); if (priv->sock < 0) { perror("socket(PF_INET)"); goto fail; } os_memset(&addr, 0, sizeof(addr)); +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + addr.sin6_family = AF_INET6; +#ifdef CONFIG_CTRL_IFACE_UDP_REMOTE + addr.sin6_addr = in6addr_any; +#else /* CONFIG_CTRL_IFACE_UDP_REMOTE */ + inet_pton(AF_INET6, "::1", &addr.sin6_addr); +#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ addr.sin_family = AF_INET; #ifdef CONFIG_CTRL_IFACE_UDP_REMOTE addr.sin_addr.s_addr = INADDR_ANY; #else /* CONFIG_CTRL_IFACE_UDP_REMOTE */ addr.sin_addr.s_addr = htonl((127 << 24) | 1); #endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */ +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ try_again: +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + addr.sin6_port = htons(port); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ addr.sin_port = htons(port); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (bind(priv->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { port--; if ((WPA_CTRL_IFACE_PORT - port) < WPA_CTRL_IFACE_PORT_LIMIT) @@ -362,6 +447,9 @@ static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv, int idx; char *sbuf; int llen; +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + char addr[INET6_ADDRSTRLEN]; +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ dst = priv->ctrl_dst; if (priv->sock < 0 || dst == NULL) @@ -381,9 +469,16 @@ static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv, while (dst) { next = dst->next; if (level >= dst->debug_level) { +#ifdef CONFIG_CTRL_IFACE_UDP_IPV6 + wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor send %s:%d", + inet_ntop(AF_INET6, &dst->addr.sin6_addr, + addr, sizeof(dst->addr)), + ntohs(dst->addr.sin6_port)); +#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */ wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor send %s:%d", inet_ntoa(dst->addr.sin_addr), ntohs(dst->addr.sin_port)); +#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */ if (sendto(priv->sock, sbuf, llen + len, 0, (struct sockaddr *) &dst->addr, sizeof(dst->addr)) < 0) { diff --git a/wpa_supplicant/ctrl_iface_unix.c b/wpa_supplicant/ctrl_iface_unix.c index d44313c..d4e45de 100644 --- a/wpa_supplicant/ctrl_iface_unix.c +++ b/wpa_supplicant/ctrl_iface_unix.c @@ -244,7 +244,7 @@ static char * wpa_supplicant_ctrl_iface_path(struct wpa_supplicant *wpa_s) { char *buf; size_t len; - char *pbuf, *dir = NULL, *gid_str = NULL; + char *pbuf, *dir = NULL; int res; if (wpa_s->conf->ctrl_interface == NULL) @@ -254,12 +254,11 @@ static char * wpa_supplicant_ctrl_iface_path(struct wpa_supplicant *wpa_s) if (pbuf == NULL) return NULL; if (os_strncmp(pbuf, "DIR=", 4) == 0) { + char *gid_str; dir = pbuf + 4; gid_str = os_strstr(dir, " GROUP="); - if (gid_str) { + if (gid_str) *gid_str = '\0'; - gid_str += 7; - } } else dir = pbuf; @@ -573,7 +572,7 @@ void wpa_supplicant_ctrl_iface_deinit(struct ctrl_iface_priv *priv) if (priv->sock > -1) { char *fname; - char *buf, *dir = NULL, *gid_str = NULL; + char *buf, *dir = NULL; eloop_unregister_read_sock(priv->sock); if (!dl_list_empty(&priv->ctrl_dst)) { /* @@ -599,12 +598,11 @@ void wpa_supplicant_ctrl_iface_deinit(struct ctrl_iface_priv *priv) if (buf == NULL) goto free_dst; if (os_strncmp(buf, "DIR=", 4) == 0) { + char *gid_str; dir = buf + 4; gid_str = os_strstr(dir, " GROUP="); - if (gid_str) { + if (gid_str) *gid_str = '\0'; - gid_str += 7; - } } else dir = buf; diff --git a/wpa_supplicant/dbus/dbus_common.c b/wpa_supplicant/dbus/dbus_common.c index 6caf740..5cc1505 100644 --- a/wpa_supplicant/dbus/dbus_common.c +++ b/wpa_supplicant/dbus/dbus_common.c @@ -320,6 +320,8 @@ static void wpas_dbus_deinit_common(struct wpas_dbus_priv *priv) if (priv->con) { eloop_cancel_timeout(dispatch_initial_dbus_messages, priv->con, NULL); + eloop_cancel_timeout(process_timeout, priv, ELOOP_ALL_CTX); + dbus_connection_set_watch_functions(priv->con, NULL, NULL, NULL, NULL, NULL); dbus_connection_set_timeout_functions(priv->con, NULL, NULL, diff --git a/wpa_supplicant/dbus/dbus_new.c b/wpa_supplicant/dbus/dbus_new.c index f40d421..6bd2a40 100644 --- a/wpa_supplicant/dbus/dbus_new.c +++ b/wpa_supplicant/dbus/dbus_new.c @@ -709,9 +709,9 @@ void wpas_dbus_signal_wps_cred(struct wpa_supplicant *wpa_s, DBusMessage *msg; DBusMessageIter iter, dict_iter; struct wpas_dbus_priv *iface; - char *auth_type[6]; /* we have six possible authorization types */ + char *auth_type[5]; /* we have five possible authentication types */ int at_num = 0; - char *encr_type[4]; /* we have four possible encryption types */ + char *encr_type[3]; /* we have three possible encryption types */ int et_num = 0; iface = wpa_s->global->dbus; @@ -734,20 +734,15 @@ void wpas_dbus_signal_wps_cred(struct wpa_supplicant *wpa_s, auth_type[at_num++] = "open"; if (cred->auth_type & WPS_AUTH_WPAPSK) auth_type[at_num++] = "wpa-psk"; - if (cred->auth_type & WPS_AUTH_SHARED) - auth_type[at_num++] = "shared"; if (cred->auth_type & WPS_AUTH_WPA) auth_type[at_num++] = "wpa-eap"; if (cred->auth_type & WPS_AUTH_WPA2) auth_type[at_num++] = "wpa2-eap"; if (cred->auth_type & WPS_AUTH_WPA2PSK) - auth_type[at_num++] = - "wpa2-psk"; + auth_type[at_num++] = "wpa2-psk"; if (cred->encr_type & WPS_ENCR_NONE) encr_type[et_num++] = "none"; - if (cred->encr_type & WPS_ENCR_WEP) - encr_type[et_num++] = "wep"; if (cred->encr_type & WPS_ENCR_TKIP) encr_type[et_num++] = "tkip"; if (cred->encr_type & WPS_ENCR_AES) @@ -1174,7 +1169,6 @@ void wpas_dbus_signal_p2p_group_started(struct wpa_supplicant *wpa_s, DBusMessage *msg; DBusMessageIter iter, dict_iter; struct wpas_dbus_priv *iface; - char group_obj_path[WPAS_DBUS_OBJECT_PATH_MAX]; iface = wpa_s->parent->global->dbus; @@ -1182,14 +1176,13 @@ void wpas_dbus_signal_p2p_group_started(struct wpa_supplicant *wpa_s, if (iface == NULL) return; - if (wpas_dbus_get_group_obj_path(wpa_s, ssid, group_obj_path) < 0) + if (wpa_s->dbus_groupobj_path == NULL) return; /* New interface has been created for this group */ msg = dbus_message_new_signal(wpa_s->parent->dbus_new_path, WPAS_DBUS_NEW_IFACE_P2PDEVICE, "GroupStarted"); - if (msg == NULL) return; @@ -1212,7 +1205,7 @@ void wpas_dbus_signal_p2p_group_started(struct wpa_supplicant *wpa_s, goto nomem; if (!wpa_dbus_dict_append_object_path(&dict_iter, "group_object", - group_obj_path) || + wpa_s->dbus_groupobj_path) || !wpa_dbus_dict_close_write(&iter, &dict_iter)) goto nomem; @@ -1225,7 +1218,7 @@ nomem: /** * - * Method to emit GONeogtiation Success or Failure signals based + * Method to emit GONegotiation Success or Failure signals based * on status. * @status: Status of the GO neg request. 0 for success, other for errors. */ @@ -2462,6 +2455,12 @@ static const struct wpa_dbus_method_desc wpas_dbus_interface_methods[] = { END_ARGS } }, + { "Reattach", WPAS_DBUS_NEW_IFACE_INTERFACE, + (WPADBusMethodHandler) &wpas_dbus_handler_reattach, + { + END_ARGS + } + }, { "RemoveNetwork", WPAS_DBUS_NEW_IFACE_INTERFACE, (WPADBusMethodHandler) &wpas_dbus_handler_remove_network, { @@ -2643,6 +2642,7 @@ static const struct wpa_dbus_method_desc wpas_dbus_interface_methods[] = { (WPADBusMethodHandler)wpas_dbus_handler_p2p_service_sd_req, { { "args", "a{sv}", ARG_IN }, + { "ref", "t", ARG_OUT }, END_ARGS } }, @@ -2673,13 +2673,6 @@ static const struct wpa_dbus_method_desc wpas_dbus_interface_methods[] = { END_ARGS } }, - { "ServiceDiscoveryExternal", WPAS_DBUS_NEW_IFACE_P2PDEVICE, - (WPADBusMethodHandler)wpas_dbus_handler_p2p_serv_disc_external, - { - { "arg", "i", ARG_IN }, - END_ARGS - } - }, { "AddPersistentGroup", WPAS_DBUS_NEW_IFACE_P2PDEVICE, (WPADBusMethodHandler) wpas_dbus_handler_add_persistent_group, { @@ -2989,7 +2982,6 @@ static const struct wpa_dbus_signal_desc wpas_dbus_interface_signals[] = { { "DeviceFound", WPAS_DBUS_NEW_IFACE_P2PDEVICE, { { "path", "o", ARG_OUT }, - { "properties", "a{sv}", ARG_OUT }, END_ARGS } }, @@ -3052,12 +3044,13 @@ static const struct wpa_dbus_signal_desc wpas_dbus_interface_signals[] = { }, { "GONegotiationSuccess", WPAS_DBUS_NEW_IFACE_P2PDEVICE, { + { "properties", "a{sv}", ARG_OUT }, END_ARGS } }, { "GONegotiationFailure", WPAS_DBUS_NEW_IFACE_P2PDEVICE, { - { "status", "i", ARG_OUT }, + { "properties", "a{sv}", ARG_OUT }, END_ARGS } }, @@ -3274,6 +3267,10 @@ static const struct wpa_dbus_property_desc wpas_dbus_p2p_peer_properties[] = { wpas_dbus_getter_p2p_peer_ies, NULL }, + { "DeviceAddress", WPAS_DBUS_NEW_IFACE_P2P_PEER, "ay", + wpas_dbus_getter_p2p_peer_device_address, + NULL + }, { NULL, NULL, NULL, NULL, NULL } }; diff --git a/wpa_supplicant/dbus/dbus_new.h b/wpa_supplicant/dbus/dbus_new.h index 61c480a..1aec9be 100644 --- a/wpa_supplicant/dbus/dbus_new.h +++ b/wpa_supplicant/dbus/dbus_new.h @@ -83,7 +83,7 @@ enum wpas_dbus_bss_prop { #define WPAS_DBUS_NEW_IFACE_P2P_GROUPMEMBER \ WPAS_DBUS_NEW_INTERFACE ".GroupMember" -/* Errors */ +/* Top-level Errors */ #define WPAS_DBUS_ERROR_UNKNOWN_ERROR \ WPAS_DBUS_NEW_INTERFACE ".UnknownError" #define WPAS_DBUS_ERROR_INVALID_ARGS \ @@ -91,6 +91,8 @@ enum wpas_dbus_bss_prop { #define WPAS_DBUS_ERROR_IFACE_EXISTS \ WPAS_DBUS_NEW_INTERFACE ".InterfaceExists" +#define WPAS_DBUS_ERROR_IFACE_DISABLED \ + WPAS_DBUS_NEW_INTERFACE ".InterfaceDisabled" #define WPAS_DBUS_ERROR_IFACE_UNKNOWN \ WPAS_DBUS_NEW_INTERFACE ".InterfaceUnknown" @@ -118,6 +120,9 @@ enum wpas_dbus_bss_prop { #define WPAS_DBUS_ERROR_SUBSCRIPTION_EPERM \ WPAS_DBUS_NEW_INTERFACE ".SubscriptionNotYou" +/* Interface-level errors */ +#define WPAS_DBUS_ERROR_IFACE_SCAN_ERROR \ + WPAS_DBUS_NEW_IFACE_INTERFACE ".ScanError" void wpas_dbus_subscribe_noc(struct wpas_dbus_priv *priv); void wpas_dbus_unsubscribe_noc(struct wpas_dbus_priv *priv); diff --git a/wpa_supplicant/dbus/dbus_new_handlers.c b/wpa_supplicant/dbus/dbus_new_handlers.c index 5380b43..6e1eedb 100644 --- a/wpa_supplicant/dbus/dbus_new_handlers.c +++ b/wpa_supplicant/dbus/dbus_new_handlers.c @@ -116,6 +116,27 @@ DBusMessage * wpas_dbus_error_invalid_args(DBusMessage *message, } +/** + * wpas_dbus_error_scan_error - Return a new ScanError error message + * @message: Pointer to incoming dbus message this error refers to + * @error: Optional string to be used as the error message + * Returns: a dbus error message + * + * Convenience function to create and return a scan error + */ +DBusMessage * wpas_dbus_error_scan_error(DBusMessage *message, + const char *error) +{ + DBusMessage *reply; + + reply = dbus_message_new_error(message, + WPAS_DBUS_ERROR_IFACE_SCAN_ERROR, + error); + + return reply; +} + + static const char *dont_quote[] = { "key_mgmt", "proto", "pairwise", "auth_alg", "group", "eap", "opensc_engine_path", "pkcs11_engine_path", "pkcs11_module_path", @@ -1330,7 +1351,10 @@ DBusMessage * wpas_dbus_handler_scan(DBusMessage *message, "passive scan"); goto out; } else if (params.freqs && params.freqs[0]) { - wpa_supplicant_trigger_scan(wpa_s, ¶ms); + if (wpa_supplicant_trigger_scan(wpa_s, ¶ms)) { + reply = wpas_dbus_error_scan_error( + message, "Scan request rejected"); + } } else { wpa_s->scan_req = MANUAL_SCAN_REQ; wpa_supplicant_req_scan(wpa_s, 0, 0); @@ -1343,7 +1367,10 @@ DBusMessage * wpas_dbus_handler_scan(DBusMessage *message, #ifdef CONFIG_AUTOSCAN autoscan_deinit(wpa_s); #endif /* CONFIG_AUTOSCAN */ - wpa_supplicant_trigger_scan(wpa_s, ¶ms); + if (wpa_supplicant_trigger_scan(wpa_s, ¶ms)) { + reply = wpas_dbus_error_scan_error( + message, "Scan request rejected"); + } } else { wpa_printf(MSG_DEBUG, "wpas_dbus_handler_scan[dbus]: " "Unknown scan type: %s", type); @@ -1465,10 +1492,10 @@ err: /** - * wpas_dbus_handler_reassociate - Reassociate to current AP + * wpas_dbus_handler_reassociate - Reassociate * @message: Pointer to incoming dbus message * @wpa_s: wpa_supplicant structure for a network interface - * Returns: NotConnected DBus error message if not connected + * Returns: InterfaceDisabled DBus error message if disabled * or NULL otherwise. * * Handler function for "Reassociate" method call of network interface. @@ -1476,7 +1503,30 @@ err: DBusMessage * wpas_dbus_handler_reassociate(DBusMessage *message, struct wpa_supplicant *wpa_s) { + if (wpa_s->wpa_state != WPA_INTERFACE_DISABLED) { + wpas_request_connection(wpa_s); + return NULL; + } + + return dbus_message_new_error(message, WPAS_DBUS_ERROR_IFACE_DISABLED, + "This interface is disabled"); +} + + +/** + * wpas_dbus_handler_reattach - Reattach to current AP + * @message: Pointer to incoming dbus message + * @wpa_s: wpa_supplicant structure for a network interface + * Returns: NotConnected DBus error message if not connected + * or NULL otherwise. + * + * Handler function for "Reattach" method call of network interface. + */ +DBusMessage * wpas_dbus_handler_reattach(DBusMessage *message, + struct wpa_supplicant *wpa_s) +{ if (wpa_s->current_ssid != NULL) { + wpa_s->reattach = 1; wpas_request_connection(wpa_s); return NULL; } @@ -1533,16 +1583,6 @@ DBusMessage * wpas_dbus_handler_remove_network(DBusMessage *message, wpas_notify_network_removed(wpa_s, ssid); - if (wpa_config_remove_network(wpa_s->conf, id) < 0) { - wpa_printf(MSG_ERROR, - "wpas_dbus_handler_remove_network[dbus]: " - "error occurred when removing network %d", id); - reply = wpas_dbus_error_unknown_error( - message, "error removing the specified network on " - "this interface."); - goto out; - } - if (ssid == wpa_s->current_ssid) wpa_supplicant_deauthenticate(wpa_s, WLAN_REASON_DEAUTH_LEAVING); @@ -1553,6 +1593,15 @@ DBusMessage * wpas_dbus_handler_remove_network(DBusMessage *message, wpa_supplicant_req_scan(wpa_s, 0, 0); } + if (wpa_config_remove_network(wpa_s->conf, id) < 0) { + wpa_printf(MSG_ERROR, + "wpas_dbus_handler_remove_network[dbus]: " + "error occurred when removing network %d", id); + reply = wpas_dbus_error_unknown_error( + message, "error removing the specified network on " + "this interface."); + goto out; + } out: os_free(iface); @@ -1995,25 +2044,29 @@ DBusMessage * wpas_dbus_handler_eap_logon(DBusMessage *message, #ifdef CONFIG_TDLS -static DBusMessage * get_peer_hwaddr_helper(DBusMessage *message, - const char *func_name, - u8 *peer_address) +static int get_peer_hwaddr_helper(DBusMessage *message, const char *func_name, + u8 *peer_address, DBusMessage **error) { const char *peer_string; + *error = NULL; + if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &peer_string, - DBUS_TYPE_INVALID)) - return wpas_dbus_error_invalid_args(message, NULL); + DBUS_TYPE_INVALID)) { + *error = wpas_dbus_error_invalid_args(message, NULL); + return -1; + } if (hwaddr_aton(peer_string, peer_address)) { wpa_printf(MSG_DEBUG, "%s: invalid address '%s'", func_name, peer_string); - return wpas_dbus_error_invalid_args( + *error = wpas_dbus_error_invalid_args( message, "Invalid hardware address format"); + return -1; } - return NULL; + return 0; } @@ -2032,8 +2085,7 @@ DBusMessage * wpas_dbus_handler_tdls_discover(DBusMessage *message, DBusMessage *error_reply; int ret; - error_reply = get_peer_hwaddr_helper(message, __func__, peer); - if (error_reply) + if (get_peer_hwaddr_helper(message, __func__, peer, &error_reply) < 0) return error_reply; wpa_printf(MSG_DEBUG, "DBUS TDLS_DISCOVER " MACSTR, MAC2STR(peer)); @@ -2067,8 +2119,7 @@ DBusMessage * wpas_dbus_handler_tdls_setup(DBusMessage *message, DBusMessage *error_reply; int ret; - error_reply = get_peer_hwaddr_helper(message, __func__, peer); - if (error_reply) + if (get_peer_hwaddr_helper(message, __func__, peer, &error_reply) < 0) return error_reply; wpa_printf(MSG_DEBUG, "DBUS TDLS_SETUP " MACSTR, MAC2STR(peer)); @@ -2103,8 +2154,7 @@ DBusMessage * wpas_dbus_handler_tdls_status(DBusMessage *message, DBusMessage *reply; const char *tdls_status; - reply = get_peer_hwaddr_helper(message, __func__, peer); - if (reply) + if (get_peer_hwaddr_helper(message, __func__, peer, &reply) < 0) return reply; wpa_printf(MSG_DEBUG, "DBUS TDLS_STATUS " MACSTR, MAC2STR(peer)); @@ -2133,8 +2183,7 @@ DBusMessage * wpas_dbus_handler_tdls_teardown(DBusMessage *message, DBusMessage *error_reply; int ret; - error_reply = get_peer_hwaddr_helper(message, __func__, peer); - if (error_reply) + if (get_peer_hwaddr_helper(message, __func__, peer, &error_reply) < 0) return error_reply; wpa_printf(MSG_DEBUG, "DBUS TDLS_TEARDOWN " MACSTR, MAC2STR(peer)); @@ -3497,11 +3546,22 @@ dbus_bool_t wpas_dbus_getter_bss_mode(DBusMessageIter *iter, DBusError *error, res = get_bss_helper(args, error, __func__); if (!res) return FALSE; - - if (res->caps & IEEE80211_CAP_IBSS) - mode = "ad-hoc"; - else - mode = "infrastructure"; + if (bss_is_dmg(res)) { + switch (res->caps & IEEE80211_CAP_DMG_MASK) { + case IEEE80211_CAP_DMG_PBSS: + case IEEE80211_CAP_DMG_IBSS: + mode = "ad-hoc"; + break; + case IEEE80211_CAP_DMG_AP: + mode = "infrastructure"; + break; + } + } else { + if (res->caps & IEEE80211_CAP_IBSS) + mode = "ad-hoc"; + else + mode = "infrastructure"; + } return wpas_dbus_simple_property_getter(iter, DBUS_TYPE_STRING, &mode, error); diff --git a/wpa_supplicant/dbus/dbus_new_handlers.h b/wpa_supplicant/dbus/dbus_new_handlers.h index c066944..461970d 100644 --- a/wpa_supplicant/dbus/dbus_new_handlers.h +++ b/wpa_supplicant/dbus/dbus_new_handlers.h @@ -101,6 +101,9 @@ DBusMessage * wpas_dbus_handler_add_network(DBusMessage *message, DBusMessage * wpas_dbus_handler_reassociate(DBusMessage *message, struct wpa_supplicant *wpa_s); +DBusMessage * wpas_dbus_handler_reattach(DBusMessage *message, + struct wpa_supplicant *wpa_s); + DBusMessage * wpas_dbus_handler_remove_network(DBusMessage *message, struct wpa_supplicant *wpa_s); diff --git a/wpa_supplicant/dbus/dbus_new_handlers_p2p.c b/wpa_supplicant/dbus/dbus_new_handlers_p2p.c index 5150a76..20cbeed 100644 --- a/wpa_supplicant/dbus/dbus_new_handlers_p2p.c +++ b/wpa_supplicant/dbus/dbus_new_handlers_p2p.c @@ -346,7 +346,7 @@ DBusMessage * wpas_dbus_handler_p2p_group_add(DBusMessage *message, if (ssid == NULL || ssid->disabled != 2) goto inv_args; - if (wpas_p2p_group_add_persistent(wpa_s, ssid, 0, freq, 0, 0, + if (wpas_p2p_group_add_persistent(wpa_s, ssid, 0, freq, 0, 0, 0, NULL, 0)) { reply = wpas_dbus_error_unknown_error( message, @@ -825,6 +825,11 @@ dbus_bool_t wpas_dbus_getter_p2p_device_config(DBusMessageIter *iter, wpa_s->conf->disassoc_low_ack)) goto err_no_mem; + /* No Group Iface */ + if (!wpa_dbus_dict_append_bool(&dict_iter, "NoGroupIface", + wpa_s->conf->p2p_no_group_iface)) + goto err_no_mem; + if (!wpa_dbus_dict_close_write(&variant_iter, &dict_iter) || !dbus_message_iter_close_container(iter, &variant_iter)) goto err_no_mem; @@ -974,6 +979,9 @@ dbus_bool_t wpas_dbus_setter_p2p_device_config(DBusMessageIter *iter, else if (os_strcmp(entry.key, "disassoc_low_ack") == 0 && entry.type == DBUS_TYPE_UINT32) wpa_s->conf->disassoc_low_ack = entry.uint32_value; + else if (os_strcmp(entry.key, "NoGroupIface") == 0 && + entry.type == DBUS_TYPE_BOOLEAN) + wpa_s->conf->p2p_no_group_iface = entry.bool_value; else goto error; @@ -1470,12 +1478,46 @@ dbus_bool_t wpas_dbus_getter_p2p_peer_vendor_extension(DBusMessageIter *iter, dbus_bool_t wpas_dbus_getter_p2p_peer_ies(DBusMessageIter *iter, DBusError *error, void *user_data) { - dbus_bool_t success; - /* struct peer_handler_args *peer_args = user_data; */ + struct peer_handler_args *peer_args = user_data; + const struct p2p_peer_info *info; - success = wpas_dbus_simple_array_property_getter(iter, DBUS_TYPE_BYTE, - NULL, 0, error); - return success; + info = p2p_get_peer_found(peer_args->wpa_s->global->p2p, + peer_args->p2p_device_addr, 0); + if (info == NULL) { + dbus_set_error(error, DBUS_ERROR_FAILED, + "failed to find peer"); + return FALSE; + } + + if (info->wfd_subelems == NULL) + return wpas_dbus_simple_array_property_getter(iter, + DBUS_TYPE_BYTE, + NULL, 0, error); + + return wpas_dbus_simple_array_property_getter( + iter, DBUS_TYPE_BYTE, (char *) info->wfd_subelems->buf, + info->wfd_subelems->used, error); +} + + +dbus_bool_t wpas_dbus_getter_p2p_peer_device_address(DBusMessageIter *iter, + DBusError *error, + void *user_data) +{ + struct peer_handler_args *peer_args = user_data; + const struct p2p_peer_info *info; + + info = p2p_get_peer_found(peer_args->wpa_s->global->p2p, + peer_args->p2p_device_addr, 0); + if (info == NULL) { + dbus_set_error(error, DBUS_ERROR_FAILED, + "failed to find peer"); + return FALSE; + } + + return wpas_dbus_simple_array_property_getter( + iter, DBUS_TYPE_BYTE, (char *) info->p2p_device_addr, + ETH_ALEN, error); } @@ -2407,7 +2449,7 @@ DBusMessage * wpas_dbus_handler_p2p_service_sd_cancel_req( if (req == 0) goto error; - if (!wpas_p2p_sd_cancel_request(wpa_s, req)) + if (wpas_p2p_sd_cancel_request(wpa_s, req) < 0) goto error; return NULL; diff --git a/wpa_supplicant/dbus/dbus_new_handlers_p2p.h b/wpa_supplicant/dbus/dbus_new_handlers_p2p.h index a11b3c8..67e0e9d 100644 --- a/wpa_supplicant/dbus/dbus_new_handlers_p2p.h +++ b/wpa_supplicant/dbus/dbus_new_handlers_p2p.h @@ -147,6 +147,10 @@ dbus_bool_t wpas_dbus_getter_p2p_peer_ies(DBusMessageIter *iter, DBusError *error, void *user_data); +dbus_bool_t wpas_dbus_getter_p2p_peer_device_address(DBusMessageIter *iter, + DBusError *error, + void *user_data); + /* * P2P Group properties */ diff --git a/wpa_supplicant/dbus/dbus_new_helpers.c b/wpa_supplicant/dbus/dbus_new_helpers.c index e26086d..712bffc 100644 --- a/wpa_supplicant/dbus/dbus_new_helpers.c +++ b/wpa_supplicant/dbus/dbus_new_helpers.c @@ -840,7 +840,6 @@ void wpa_dbus_flush_object_changed_properties(DBusConnection *con, return; eloop_cancel_timeout(flush_object_timeout_handler, con, obj_desc); - dsc = obj_desc->properties; for (dsc = obj_desc->properties, i = 0; dsc && dsc->dbus_property; dsc++, i++) { if (obj_desc->prop_changed_flags == NULL || diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig index 6684782..94c94b1 100644 --- a/wpa_supplicant/defconfig +++ b/wpa_supplicant/defconfig @@ -152,8 +152,6 @@ CONFIG_EAP_LEAP=y # Wi-Fi Protected Setup (WPS) #CONFIG_WPS=y -# Enable WSC 2.0 support -#CONFIG_WPS2=y # Enable WPS external registrar functionality #CONFIG_WPS_ER=y # Disable credentials for an open network by default when acting as a WPS @@ -192,8 +190,10 @@ CONFIG_SMARTCARD=y # Select control interface backend for external programs, e.g, wpa_cli: # unix = UNIX domain sockets (default for Linux/*BSD) # udp = UDP sockets using localhost (127.0.0.1) +# udp6 = UDP IPv6 sockets using localhost (::1) # named_pipe = Windows Named Pipe (default for Windows) # udp-remote = UDP sockets with remote access (only for tests systems/purpose) +# udp6-remote = UDP IPv6 sockets with remote access (only for tests purpose) # y = use default (backwards compatibility) # If this option is commented out, control interface is not included in the # build. @@ -253,7 +253,7 @@ CONFIG_BACKEND=file # main_none = Very basic example (development use only) #CONFIG_MAIN=main -# Select wrapper for operatins system and C library specific functions +# Select wrapper for operating system and C library specific functions # unix = UNIX/POSIX like systems (default) # win32 = Windows systems # none = Empty template @@ -267,6 +267,9 @@ CONFIG_BACKEND=file # Should we use poll instead of select? Select is used by default. #CONFIG_ELOOP_POLL=y +# Should we use epoll instead of select? Select is used by default. +#CONFIG_ELOOP_EPOLL=y + # Select layer 2 packet implementation # linux = Linux packet socket (default) # pcap = libpcap/libdnet/WinPcap diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index 0691b6c..00703d9 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -206,6 +206,14 @@ static inline const char * wpa_drv_get_ifname(struct wpa_supplicant *wpa_s) return NULL; } +static inline const char * +wpa_driver_get_radio_name(struct wpa_supplicant *wpa_s) +{ + if (wpa_s->driver->get_radio_name) + return wpa_s->driver->get_radio_name(wpa_s->drv_priv); + return NULL; +} + static inline const u8 * wpa_drv_get_mac_addr(struct wpa_supplicant *wpa_s) { if (wpa_s->driver->get_mac_addr) { @@ -524,12 +532,14 @@ static inline int wpa_drv_ampdu(struct wpa_supplicant *wpa_s, int ampdu) static inline int wpa_drv_send_tdls_mgmt(struct wpa_supplicant *wpa_s, const u8 *dst, u8 action_code, u8 dialog_token, u16 status_code, - const u8 *buf, size_t len) + u32 peer_capab, const u8 *buf, + size_t len) { if (wpa_s->driver->send_tdls_mgmt) { return wpa_s->driver->send_tdls_mgmt(wpa_s->drv_priv, dst, action_code, dialog_token, - status_code, buf, len); + status_code, peer_capab, + buf, len); } return -1; } @@ -604,4 +614,217 @@ static inline int wpa_drv_set_qos_map(struct wpa_supplicant *wpa_s, qos_map_set_len); } +static inline int wpa_drv_wowlan(struct wpa_supplicant *wpa_s, + const struct wowlan_triggers *triggers) +{ + if (!wpa_s->driver->set_wowlan) + return -1; + return wpa_s->driver->set_wowlan(wpa_s->drv_priv, triggers); +} + +static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s, + int vendor_id, int subcmd, const u8 *data, + size_t data_len, struct wpabuf *buf) +{ + if (!wpa_s->driver->vendor_cmd) + return -1; + return wpa_s->driver->vendor_cmd(wpa_s->drv_priv, vendor_id, subcmd, + data, data_len, buf); +} + + +#ifdef CONFIG_MACSEC + +static inline int wpa_drv_macsec_init(struct wpa_supplicant *wpa_s, + struct macsec_init_params *params) +{ + if (!wpa_s->driver->macsec_init) + return -1; + return wpa_s->driver->macsec_init(wpa_s->drv_priv, params); +} + +static inline int wpa_drv_macsec_deinit(struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->driver->macsec_deinit) + return -1; + return wpa_s->driver->macsec_deinit(wpa_s->drv_priv); +} + +static inline int wpa_drv_enable_protect_frames(struct wpa_supplicant *wpa_s, + Boolean enabled) +{ + if (!wpa_s->driver->enable_protect_frames) + return -1; + return wpa_s->driver->enable_protect_frames(wpa_s->drv_priv, enabled); +} + +static inline int wpa_drv_set_replay_protect(struct wpa_supplicant *wpa_s, + Boolean enabled, u32 window) +{ + if (!wpa_s->driver->set_replay_protect) + return -1; + return wpa_s->driver->set_replay_protect(wpa_s->drv_priv, enabled, + window); +} + +static inline int wpa_drv_set_current_cipher_suite(struct wpa_supplicant *wpa_s, + const u8 *cs, size_t cs_len) +{ + if (!wpa_s->driver->set_current_cipher_suite) + return -1; + return wpa_s->driver->set_current_cipher_suite(wpa_s->drv_priv, cs, + cs_len); +} + +static inline int wpa_drv_enable_controlled_port(struct wpa_supplicant *wpa_s, + Boolean enabled) +{ + if (!wpa_s->driver->enable_controlled_port) + return -1; + return wpa_s->driver->enable_controlled_port(wpa_s->drv_priv, enabled); +} + +static inline int wpa_drv_get_receive_lowest_pn(struct wpa_supplicant *wpa_s, + u32 channel, u8 an, + u32 *lowest_pn) +{ + if (!wpa_s->driver->get_receive_lowest_pn) + return -1; + return wpa_s->driver->get_receive_lowest_pn(wpa_s->drv_priv, channel, + an, lowest_pn); +} + +static inline int wpa_drv_get_transmit_next_pn(struct wpa_supplicant *wpa_s, + u32 channel, u8 an, + u32 *next_pn) +{ + if (!wpa_s->driver->get_transmit_next_pn) + return -1; + return wpa_s->driver->get_transmit_next_pn(wpa_s->drv_priv, channel, + an, next_pn); +} + +static inline int wpa_drv_set_transmit_next_pn(struct wpa_supplicant *wpa_s, + u32 channel, u8 an, + u32 next_pn) +{ + if (!wpa_s->driver->set_transmit_next_pn) + return -1; + return wpa_s->driver->set_transmit_next_pn(wpa_s->drv_priv, channel, + an, next_pn); +} + +static inline int wpa_drv_get_available_receive_sc(struct wpa_supplicant *wpa_s, + u32 *channel) +{ + if (!wpa_s->driver->get_available_receive_sc) + return -1; + return wpa_s->driver->get_available_receive_sc(wpa_s->drv_priv, + channel); +} + +static inline int +wpa_drv_create_receive_sc(struct wpa_supplicant *wpa_s, u32 channel, + const u8 *sci_addr, u16 sci_port, + unsigned int conf_offset, int validation) +{ + if (!wpa_s->driver->create_receive_sc) + return -1; + return wpa_s->driver->create_receive_sc(wpa_s->drv_priv, channel, + sci_addr, sci_port, conf_offset, + validation); +} + +static inline int wpa_drv_delete_receive_sc(struct wpa_supplicant *wpa_s, + u32 channel) +{ + if (!wpa_s->driver->delete_receive_sc) + return -1; + return wpa_s->driver->delete_receive_sc(wpa_s->drv_priv, channel); +} + +static inline int wpa_drv_create_receive_sa(struct wpa_supplicant *wpa_s, + u32 channel, u8 an, + u32 lowest_pn, const u8 *sak) +{ + if (!wpa_s->driver->create_receive_sa) + return -1; + return wpa_s->driver->create_receive_sa(wpa_s->drv_priv, channel, an, + lowest_pn, sak); +} + +static inline int wpa_drv_enable_receive_sa(struct wpa_supplicant *wpa_s, + u32 channel, u8 an) +{ + if (!wpa_s->driver->enable_receive_sa) + return -1; + return wpa_s->driver->enable_receive_sa(wpa_s->drv_priv, channel, an); +} + +static inline int wpa_drv_disable_receive_sa(struct wpa_supplicant *wpa_s, + u32 channel, u8 an) +{ + if (!wpa_s->driver->disable_receive_sa) + return -1; + return wpa_s->driver->disable_receive_sa(wpa_s->drv_priv, channel, an); +} + +static inline int +wpa_drv_get_available_transmit_sc(struct wpa_supplicant *wpa_s, u32 *channel) +{ + if (!wpa_s->driver->get_available_transmit_sc) + return -1; + return wpa_s->driver->get_available_transmit_sc(wpa_s->drv_priv, + channel); +} + +static inline int +wpa_drv_create_transmit_sc(struct wpa_supplicant *wpa_s, u32 channel, + const u8 *sci_addr, u16 sci_port, + unsigned int conf_offset) +{ + if (!wpa_s->driver->create_transmit_sc) + return -1; + return wpa_s->driver->create_transmit_sc(wpa_s->drv_priv, channel, + sci_addr, sci_port, + conf_offset); +} + +static inline int wpa_drv_delete_transmit_sc(struct wpa_supplicant *wpa_s, + u32 channel) +{ + if (!wpa_s->driver->delete_transmit_sc) + return -1; + return wpa_s->driver->delete_transmit_sc(wpa_s->drv_priv, channel); +} + +static inline int wpa_drv_create_transmit_sa(struct wpa_supplicant *wpa_s, + u32 channel, u8 an, + u32 next_pn, + Boolean confidentiality, + const u8 *sak) +{ + if (!wpa_s->driver->create_transmit_sa) + return -1; + return wpa_s->driver->create_transmit_sa(wpa_s->drv_priv, channel, an, + next_pn, confidentiality, sak); +} + +static inline int wpa_drv_enable_transmit_sa(struct wpa_supplicant *wpa_s, + u32 channel, u8 an) +{ + if (!wpa_s->driver->enable_transmit_sa) + return -1; + return wpa_s->driver->enable_transmit_sa(wpa_s->drv_priv, channel, an); +} + +static inline int wpa_drv_disable_transmit_sa(struct wpa_supplicant *wpa_s, + u32 channel, u8 an) +{ + if (!wpa_s->driver->disable_transmit_sa) + return -1; + return wpa_s->driver->disable_transmit_sa(wpa_s->drv_priv, channel, an); +} +#endif /* CONFIG_MACSEC */ + #endif /* DRIVER_I_H */ diff --git a/wpa_supplicant/eap_proxy_dummy.mak b/wpa_supplicant/eap_proxy_dummy.mak new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/wpa_supplicant/eap_proxy_dummy.mak diff --git a/wpa_supplicant/eap_register.c b/wpa_supplicant/eap_register.c index 6cd2fc5..ece5716 100644 --- a/wpa_supplicant/eap_register.c +++ b/wpa_supplicant/eap_register.c @@ -40,6 +40,13 @@ int eap_register_methods(void) ret = eap_peer_unauth_tls_register(); #endif /* EAP_UNAUTH_TLS */ +#ifdef EAP_TLS +#ifdef CONFIG_HS20 + if (ret == 0) + ret = eap_peer_wfa_unauth_tls_register(); +#endif /* CONFIG_HS20 */ +#endif /* EAP_TLS */ + #ifdef EAP_MSCHAPv2 if (ret == 0) ret = eap_peer_mschapv2_register(); diff --git a/wpa_supplicant/eapol_test.c b/wpa_supplicant/eapol_test.c index ac0ab0b..88d4241 100644 --- a/wpa_supplicant/eapol_test.c +++ b/wpa_supplicant/eapol_test.c @@ -46,6 +46,7 @@ struct eapol_test_data { int eapol_test_num_reauths; int no_mppe_keys; int num_mppe_ok, num_mppe_mismatch; + int req_eap_key_name; u8 radius_identifier; struct radius_msg *last_recv_radius; @@ -58,6 +59,8 @@ struct eapol_test_data { u8 authenticator_pmk[PMK_LEN]; size_t authenticator_pmk_len; + u8 authenticator_eap_key_name[256]; + size_t authenticator_eap_key_name_len; int radius_access_accept_received; int radius_access_reject_received; int auth_timed_out; @@ -208,6 +211,13 @@ static void ieee802_1x_encapsulate_radius(struct eapol_test_data *e, goto fail; } + if (e->req_eap_key_name && + !radius_msg_add_attr(msg, RADIUS_ATTR_EAP_KEY_NAME, (u8 *) "\0", + 1)) { + printf("Could not add EAP-Key-Name\n"); + goto fail; + } + if (!find_extra_attr(e->extra_attrs, RADIUS_ATTR_NAS_IP_ADDRESS) && !radius_msg_add_attr(msg, RADIUS_ATTR_NAS_IP_ADDRESS, (u8 *) &e->own_ip_addr, 4)) { @@ -333,6 +343,8 @@ static int eapol_test_compare_pmk(struct eapol_test_data *e) { u8 pmk[PMK_LEN]; int ret = 1; + const u8 *sess_id; + size_t sess_id_len; if (eapol_sm_get_key(e->wpa_s->eapol, pmk, PMK_LEN) == 0) { wpa_hexdump(MSG_DEBUG, "PMK from EAPOL", pmk, PMK_LEN); @@ -361,6 +373,28 @@ static int eapol_test_compare_pmk(struct eapol_test_data *e) else if (!e->no_mppe_keys) e->num_mppe_ok++; + sess_id = eapol_sm_get_session_id(e->wpa_s->eapol, &sess_id_len); + if (!sess_id) + return ret; + if (e->authenticator_eap_key_name_len == 0) { + wpa_printf(MSG_INFO, "No EAP-Key-Name received from server"); + return ret; + } + + if (e->authenticator_eap_key_name_len != sess_id_len || + os_memcmp(e->authenticator_eap_key_name, sess_id, sess_id_len) != 0) + { + wpa_printf(MSG_INFO, + "Locally derived EAP Session-Id does not match EAP-Key-Name from server"); + wpa_hexdump(MSG_DEBUG, "EAP Session-Id", sess_id, sess_id_len); + wpa_hexdump(MSG_DEBUG, "EAP-Key-Name from server", + e->authenticator_eap_key_name, + e->authenticator_eap_key_name_len); + } else { + wpa_printf(MSG_INFO, + "Locally derived EAP Session-Id matches EAP-Key-Name from server"); + } + return ret; } @@ -749,6 +783,8 @@ static void ieee802_1x_get_keys(struct eapol_test_data *e, size_t shared_secret_len) { struct radius_ms_mppe_keys *keys; + u8 *buf; + size_t len; keys = radius_msg_get_ms_keys(msg, req, shared_secret, shared_secret_len); @@ -787,6 +823,14 @@ static void ieee802_1x_get_keys(struct eapol_test_data *e, os_free(keys->recv); os_free(keys); } + + if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_EAP_KEY_NAME, &buf, &len, + NULL) == 0) { + os_memcpy(e->authenticator_eap_key_name, buf, len); + e->authenticator_eap_key_name_len = len; + } else { + e->authenticator_eap_key_name_len = 0; + } } @@ -1095,7 +1139,7 @@ static void eapol_test_terminate(int sig, void *signal_ctx) static void usage(void) { printf("usage:\n" - "eapol_test [-nWS] -c<conf> [-a<AS IP>] [-p<AS port>] " + "eapol_test [-enWS] -c<conf> [-a<AS IP>] [-p<AS port>] " "[-s<AS secret>]\\\n" " [-r<count>] [-t<timeout>] [-C<Connect-Info>] \\\n" " [-M<client MAC address>] [-o<server cert file] \\\n" @@ -1115,6 +1159,7 @@ static void usage(void) " -A<client IP> = IP address of the client, default: select " "automatically\n" " -r<count> = number of re-authentications\n" + " -e = Request EAP-Key-Name\n" " -W = wait for a control interface monitor before starting\n" " -S = save configuration after authentication\n" " -n = no MPPE keys expected\n" @@ -1168,7 +1213,7 @@ int main(int argc, char *argv[]) wpa_debug_show_keys = 1; for (;;) { - c = getopt(argc, argv, "a:A:c:C:M:nN:o:p:r:s:St:W"); + c = getopt(argc, argv, "a:A:c:C:eM:nN:o:p:r:s:St:W"); if (c < 0) break; switch (c) { @@ -1184,6 +1229,9 @@ int main(int argc, char *argv[]) case 'C': eapol_test.connect_info = optarg; break; + case 'e': + eapol_test.req_eap_key_name = 1; + break; case 'M': if (hwaddr_aton(optarg, eapol_test.own_addr)) { usage(); diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index a72f2fa..1ecd6d6 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -303,11 +303,11 @@ int wpa_supplicant_scard_init(struct wpa_supplicant *wpa_s, #ifdef PCSC_FUNCS int aka = 0, sim = 0; - if (ssid->eap.pcsc == NULL || wpa_s->scard != NULL || - wpa_s->conf->external_sim) + if ((ssid != NULL && ssid->eap.pcsc == NULL) || + wpa_s->scard != NULL || wpa_s->conf->external_sim) return 0; - if (ssid->eap.eap_methods == NULL) { + if (ssid == NULL || ssid->eap.eap_methods == NULL) { sim = 1; aka = 1; } else { @@ -398,6 +398,9 @@ static int wpa_supplicant_match_privacy(struct wpa_bss *bss, if (wpa_key_mgmt_wpa(ssid->key_mgmt)) privacy = 1; + if (ssid->key_mgmt & WPA_KEY_MGMT_OSEN) + privacy = 1; + if (bss->caps & IEEE80211_CAP_PRIVACY) return privacy; return !privacy; @@ -539,6 +542,12 @@ static int wpa_supplicant_ssid_bss_match(struct wpa_supplicant *wpa_s, return 0; } + if ((ssid->key_mgmt & WPA_KEY_MGMT_OSEN) && + wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE)) { + wpa_dbg(wpa_s, MSG_DEBUG, " allow in OSEN"); + return 1; + } + if (!wpa_key_mgmt_wpa(ssid->key_mgmt)) { wpa_dbg(wpa_s, MSG_DEBUG, " allow in non-WPA/WPA2"); return 1; @@ -704,12 +713,6 @@ static int rate_match(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) } -static int bss_is_dmg(struct wpa_bss *bss) -{ - return bss->freq > 45000; -} - - /* * Test whether BSS is in an ESS. * This is done differently in DMG (60 GHz) and non-DMG bands @@ -728,13 +731,15 @@ static int bss_is_ess(struct wpa_bss *bss) static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, int i, struct wpa_bss *bss, - struct wpa_ssid *group) + struct wpa_ssid *group, + int only_first_ssid) { u8 wpa_ie_len, rsn_ie_len; int wpa; struct wpa_blacklist *e; const u8 *ie; struct wpa_ssid *ssid; + int osen; ie = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE); wpa_ie_len = ie ? ie[1] : 0; @@ -742,14 +747,18 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, ie = wpa_bss_get_ie(bss, WLAN_EID_RSN); rsn_ie_len = ie ? ie[1] : 0; + ie = wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE); + osen = ie != NULL; + wpa_dbg(wpa_s, MSG_DEBUG, "%d: " MACSTR " ssid='%s' " - "wpa_ie_len=%u rsn_ie_len=%u caps=0x%x level=%d%s%s", + "wpa_ie_len=%u rsn_ie_len=%u caps=0x%x level=%d%s%s%s", i, MAC2STR(bss->bssid), wpa_ssid_txt(bss->ssid, bss->ssid_len), wpa_ie_len, rsn_ie_len, bss->caps, bss->level, wpa_bss_get_vendor_ie(bss, WPS_IE_VENDOR_TYPE) ? " wps" : "", (wpa_bss_get_vendor_ie(bss, P2P_IE_VENDOR_TYPE) || wpa_bss_get_vendor_ie_beacon(bss, P2P_IE_VENDOR_TYPE)) ? - " p2p" : ""); + " p2p" : "", + osen ? " osen=1" : ""); e = wpa_blacklist_get(wpa_s, bss->bssid); if (e) { @@ -789,7 +798,7 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, wpa = wpa_ie_len > 0 || rsn_ie_len > 0; - for (ssid = group; ssid; ssid = ssid->pnext) { + for (ssid = group; ssid; ssid = only_first_ssid ? NULL : ssid->pnext) { int check_ssid = wpa ? 1 : (ssid->ssid_len != 0); int res; @@ -847,7 +856,7 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, if (!wpa_supplicant_ssid_bss_match(wpa_s, ssid, bss)) continue; - if (!wpa && + if (!osen && !wpa && !(ssid->key_mgmt & WPA_KEY_MGMT_NONE) && !(ssid->key_mgmt & WPA_KEY_MGMT_WPS) && !(ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA)) { @@ -862,6 +871,12 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, continue; } + if ((ssid->key_mgmt & WPA_KEY_MGMT_OSEN) && !osen) { + wpa_dbg(wpa_s, MSG_DEBUG, " skip - non-OSEN network " + "not allowed"); + continue; + } + if (!wpa_supplicant_match_privacy(bss, ssid)) { wpa_dbg(wpa_s, MSG_DEBUG, " skip - privacy " "mismatch"); @@ -938,16 +953,22 @@ static struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, static struct wpa_bss * wpa_supplicant_select_bss(struct wpa_supplicant *wpa_s, struct wpa_ssid *group, - struct wpa_ssid **selected_ssid) + struct wpa_ssid **selected_ssid, + int only_first_ssid) { unsigned int i; - wpa_dbg(wpa_s, MSG_DEBUG, "Selecting BSS from priority group %d", - group->priority); + if (only_first_ssid) + wpa_dbg(wpa_s, MSG_DEBUG, "Try to find BSS matching pre-selected network id=%d", + group->id); + else + wpa_dbg(wpa_s, MSG_DEBUG, "Selecting BSS from priority group %d", + group->priority); for (i = 0; i < wpa_s->last_scan_res_used; i++) { struct wpa_bss *bss = wpa_s->last_scan_res[i]; - *selected_ssid = wpa_scan_res_match(wpa_s, i, bss, group); + *selected_ssid = wpa_scan_res_match(wpa_s, i, bss, group, + only_first_ssid); if (!*selected_ssid) continue; wpa_dbg(wpa_s, MSG_DEBUG, " selected BSS " MACSTR @@ -966,16 +987,36 @@ struct wpa_bss * wpa_supplicant_pick_network(struct wpa_supplicant *wpa_s, { struct wpa_bss *selected = NULL; int prio; + struct wpa_ssid *next_ssid = NULL; if (wpa_s->last_scan_res == NULL || wpa_s->last_scan_res_used == 0) return NULL; /* no scan results from last update */ + if (wpa_s->next_ssid) { + struct wpa_ssid *ssid; + + /* check that next_ssid is still valid */ + for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) { + if (ssid == wpa_s->next_ssid) + break; + } + next_ssid = ssid; + wpa_s->next_ssid = NULL; + } + while (selected == NULL) { for (prio = 0; prio < wpa_s->conf->num_prio; prio++) { + if (next_ssid && next_ssid->priority == + wpa_s->conf->pssid[prio]->priority) { + selected = wpa_supplicant_select_bss( + wpa_s, next_ssid, selected_ssid, 1); + if (selected) + break; + } selected = wpa_supplicant_select_bss( wpa_s, wpa_s->conf->pssid[prio], - selected_ssid); + selected_ssid, 0); if (selected) break; } @@ -1022,8 +1063,12 @@ int wpa_supplicant_connect(struct wpa_supplicant *wpa_s, wpa_msg(wpa_s, MSG_INFO, WPS_EVENT_OVERLAP "PBC session overlap"); #ifdef CONFIG_P2P - if (wpas_p2p_notif_pbc_overlap(wpa_s) == 1) + if (wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_CLIENT || + wpa_s->p2p_in_provisioning) { + eloop_register_timeout(0, 0, wpas_p2p_pbc_overlap_cb, + wpa_s, NULL); return -1; + } #endif /* CONFIG_P2P */ #ifdef CONFIG_WPS @@ -1383,7 +1428,8 @@ static int wpas_select_network_from_last_scan(struct wpa_supplicant *wpa_s, return 0; if (wpa_s->p2p_in_provisioning || - wpa_s->show_group_started) { + wpa_s->show_group_started || + wpa_s->p2p_in_invitation) { /* * Use shorter wait during P2P Provisioning * state and during P2P join-a-group operation @@ -2075,9 +2121,7 @@ static void wpa_supplicant_event_disassoc_finish(struct wpa_supplicant *wpa_s, int authenticating; u8 prev_pending_bssid[ETH_ALEN]; struct wpa_bss *fast_reconnect = NULL; -#ifndef CONFIG_NO_SCAN_PROCESSING struct wpa_ssid *fast_reconnect_ssid = NULL; -#endif /* CONFIG_NO_SCAN_PROCESSING */ struct wpa_ssid *last_ssid; authenticating = wpa_s->wpa_state == WPA_AUTHENTICATING; @@ -2099,7 +2143,7 @@ static void wpa_supplicant_event_disassoc_finish(struct wpa_supplicant *wpa_s, "pre-shared key may be incorrect"); if (wpas_p2p_4way_hs_failed(wpa_s) > 0) return; /* P2P group removed */ - wpas_auth_failed(wpa_s); + wpas_auth_failed(wpa_s, "WRONG_KEY"); } if (!wpa_s->disconnected && (!wpa_s->auto_reconnect_disabled || @@ -2120,9 +2164,7 @@ static void wpa_supplicant_event_disassoc_finish(struct wpa_supplicant *wpa_s, * time for some common cases. */ fast_reconnect = wpa_s->current_bss; -#ifndef CONFIG_NO_SCAN_PROCESSING fast_reconnect_ssid = wpa_s->current_ssid; -#endif /* CONFIG_NO_SCAN_PROCESSING */ } else if (wpa_s->wpa_state >= WPA_ASSOCIATING) wpa_supplicant_req_scan(wpa_s, 0, 100000); else @@ -2158,7 +2200,12 @@ static void wpa_supplicant_event_disassoc_finish(struct wpa_supplicant *wpa_s, wpa_s->current_ssid = last_ssid; } - if (fast_reconnect) { + if (fast_reconnect && + !wpas_network_disabled(wpa_s, fast_reconnect_ssid) && + !disallowed_bssid(wpa_s, fast_reconnect->bssid) && + !disallowed_ssid(wpa_s, fast_reconnect->ssid, + fast_reconnect->ssid_len) && + !wpas_temp_disabled(wpa_s, fast_reconnect_ssid)) { #ifndef CONFIG_NO_SCAN_PROCESSING wpa_dbg(wpa_s, MSG_DEBUG, "Try to reconnect to the same BSS"); if (wpa_supplicant_connect(wpa_s, fast_reconnect, @@ -2167,6 +2214,14 @@ static void wpa_supplicant_event_disassoc_finish(struct wpa_supplicant *wpa_s, wpa_supplicant_req_scan(wpa_s, 0, 100000); } #endif /* CONFIG_NO_SCAN_PROCESSING */ + } else if (fast_reconnect) { + /* + * Could not reconnect to the same BSS due to network being + * disabled. Use a new scan to match the alternative behavior + * above, i.e., to continue automatic reconnection attempt in a + * way that enforces disabled network rules. + */ + wpa_supplicant_req_scan(wpa_s, 0, 100000); } } @@ -2318,10 +2373,6 @@ wpa_supplicant_event_interface_status(struct wpa_supplicant *wpa_s, wpa_supplicant_set_state(wpa_s, WPA_INTERFACE_DISABLED); l2_packet_deinit(wpa_s->l2); wpa_s->l2 = NULL; -#ifdef CONFIG_IBSS_RSN - ibss_rsn_deinit(wpa_s->ibss_rsn); - wpa_s->ibss_rsn = NULL; -#endif /* CONFIG_IBSS_RSN */ #ifdef CONFIG_TERMINATE_ONLASTIF /* check if last interface */ if (!any_interfaces(wpa_s->global->ifaces)) @@ -2550,7 +2601,7 @@ static void wpas_event_disconnect(struct wpa_supplicant *wpa_s, const u8 *addr, (wpa_s->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA)) && eapol_sm_failed(wpa_s->eapol))) && !wpa_s->eap_expected_failure)) - wpas_auth_failed(wpa_s); + wpas_auth_failed(wpa_s, "AUTH_FAILED"); #ifdef CONFIG_P2P if (deauth && reason_code > 0) { @@ -2659,10 +2710,52 @@ static void wpas_event_deauth(struct wpa_supplicant *wpa_s, } -static void wpa_supplicant_update_channel_list(struct wpa_supplicant *wpa_s) +static const char * reg_init_str(enum reg_change_initiator init) +{ + switch (init) { + case REGDOM_SET_BY_CORE: + return "CORE"; + case REGDOM_SET_BY_USER: + return "USER"; + case REGDOM_SET_BY_DRIVER: + return "DRIVER"; + case REGDOM_SET_BY_COUNTRY_IE: + return "COUNTRY_IE"; + case REGDOM_BEACON_HINT: + return "BEACON_HINT"; + } + return "?"; +} + + +static const char * reg_type_str(enum reg_type type) +{ + switch (type) { + case REGDOM_TYPE_UNKNOWN: + return "UNKNOWN"; + case REGDOM_TYPE_COUNTRY: + return "COUNTRY"; + case REGDOM_TYPE_WORLD: + return "WORLD"; + case REGDOM_TYPE_CUSTOM_WORLD: + return "CUSTOM_WORLD"; + case REGDOM_TYPE_INTERSECTION: + return "INTERSECTION"; + } + return "?"; +} + + +static void wpa_supplicant_update_channel_list( + struct wpa_supplicant *wpa_s, struct channel_list_changed *info) { struct wpa_supplicant *ifs; + wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_REGDOM_CHANGE "init=%s type=%s%s%s", + reg_init_str(info->type), reg_type_str(info->type), + info->alpha2[0] ? " alpha2=" : "", + info->alpha2[0] ? info->alpha2 : ""); + if (wpa_s->drv_priv == NULL) return; /* Ignore event during drv initialization */ @@ -3077,6 +3170,23 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, u16 fc, stype; const struct ieee80211_mgmt *mgmt; +#ifdef CONFIG_TESTING_OPTIONS + if (wpa_s->ext_mgmt_frame_handling) { + struct rx_mgmt *rx = &data->rx_mgmt; + size_t hex_len = 2 * rx->frame_len + 1; + char *hex = os_malloc(hex_len); + if (hex) { + wpa_snprintf_hex(hex, hex_len, + rx->frame, rx->frame_len); + wpa_msg(wpa_s, MSG_INFO, "MGMT-RX freq=%d datarate=%u ssi_signal=%d %s", + rx->freq, rx->datarate, rx->ssi_signal, + hex); + os_free(hex); + } + break; + } +#endif /* CONFIG_TESTING_OPTIONS */ + mgmt = (const struct ieee80211_mgmt *) data->rx_mgmt.frame; fc = le_to_host16(mgmt->frame_control); @@ -3190,6 +3300,12 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, data->eapol_rx.data_len); break; case EVENT_SIGNAL_CHANGE: + wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_SIGNAL_CHANGE + "above=%d signal=%d noise=%d txrate=%d", + data->signal_change.above_threshold, + data->signal_change.current_signal, + data->signal_change.current_noise, + data->signal_change.current_txrate); bgscan_notify_signal_change( wpa_s, data->signal_change.above_threshold, data->signal_change.current_signal, @@ -3200,6 +3316,12 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, wpa_dbg(wpa_s, MSG_DEBUG, "Interface was enabled"); if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { wpa_supplicant_update_mac_addr(wpa_s); + if (wpa_s->p2p_mgmt) { + wpa_supplicant_set_state(wpa_s, + WPA_DISCONNECTED); + break; + } + #ifdef CONFIG_AP if (!wpa_s->ap_iface) { wpa_supplicant_set_state(wpa_s, @@ -3228,13 +3350,31 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, wpas_p2p_disconnect(wpa_s); break; } + if (wpa_s->p2p_scan_work && wpa_s->global->p2p && + p2p_in_progress(wpa_s->global->p2p) > 1) { + /* This radio work will be cancelled, so clear P2P + * state as well. + */ + p2p_stop_find(wpa_s->global->p2p); + } #endif /* CONFIG_P2P */ + if (wpa_s->wpa_state >= WPA_AUTHENTICATING) { + /* + * Indicate disconnection to keep ctrl_iface events + * consistent. + */ + wpa_supplicant_event_disassoc( + wpa_s, WLAN_REASON_DEAUTH_LEAVING, 1); + } wpa_supplicant_mark_disassoc(wpa_s); + radio_remove_works(wpa_s, NULL, 0); + wpa_supplicant_set_state(wpa_s, WPA_INTERFACE_DISABLED); break; case EVENT_CHANNEL_LIST_CHANGED: - wpa_supplicant_update_channel_list(wpa_s); + wpa_supplicant_update_channel_list( + wpa_s, &data->channel_list_changed); break; case EVENT_INTERFACE_UNAVAILABLE: #ifdef CONFIG_P2P @@ -3300,15 +3440,11 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, * Start a new sched scan to continue searching for more SSIDs * either if timed out or PNO schedule scan is pending. */ - if (wpa_s->sched_scan_timed_out || wpa_s->pno_sched_pending) { - - if (wpa_supplicant_req_sched_scan(wpa_s) < 0 && - wpa_s->pno_sched_pending) { - wpa_msg(wpa_s, MSG_ERROR, "Failed to schedule PNO"); - } else if (wpa_s->pno_sched_pending) { - wpa_s->pno_sched_pending = 0; - wpa_s->pno = 1; - } + if (wpa_s->sched_scan_timed_out) { + wpa_supplicant_req_sched_scan(wpa_s); + } else if (wpa_s->pno_sched_pending) { + wpa_s->pno_sched_pending = 0; + wpas_start_pno(wpa_s); } break; diff --git a/wpa_supplicant/examples/dbus-listen-preq.py b/wpa_supplicant/examples/dbus-listen-preq.py index 5ac9859..5ac9859 100644..100755 --- a/wpa_supplicant/examples/dbus-listen-preq.py +++ b/wpa_supplicant/examples/dbus-listen-preq.py diff --git a/wpa_supplicant/examples/p2p-nfc.py b/wpa_supplicant/examples/p2p-nfc.py index 848f79f..91eba28 100644..100755 --- a/wpa_supplicant/examples/p2p-nfc.py +++ b/wpa_supplicant/examples/p2p-nfc.py @@ -33,6 +33,20 @@ no_input = False srv = None continue_loop = True terminate_now = False +summary_file = None +success_file = None + +def summary(txt): + print txt + if summary_file: + with open(summary_file, 'a') as f: + f.write(txt + "\n") + +def success_report(txt): + summary(txt) + if success_file: + with open(success_file, 'a') as f: + f.write(txt + "\n") def wpas_connect(): ifaces = [] @@ -63,7 +77,7 @@ def wpas_connect(): def wpas_tag_read(message): wpas = wpas_connect() if (wpas == None): - return + return False cmd = "WPS_NFC_TAG_READ " + str(message).encode("hex") global force_freq if force_freq: @@ -77,16 +91,19 @@ def wpas_get_handover_req(): wpas = wpas_connect() if (wpas == None): return None - res = wpas.request("NFC_GET_HANDOVER_REQ NDEF P2P-CR").rstrip().decode("hex") + res = wpas.request("NFC_GET_HANDOVER_REQ NDEF P2P-CR").rstrip() if "FAIL" in res: return None - return res + return res.decode("hex") def wpas_get_handover_req_wps(): wpas = wpas_connect() if (wpas == None): return None - return wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR").rstrip().decode("hex") + res = wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR").rstrip() + if "FAIL" in res: + return None + return res.decode("hex") def wpas_get_handover_sel(tag=False): @@ -94,8 +111,12 @@ def wpas_get_handover_sel(tag=False): if (wpas == None): return None if tag: - return wpas.request("NFC_GET_HANDOVER_SEL NDEF P2P-CR-TAG").rstrip().decode("hex") - return wpas.request("NFC_GET_HANDOVER_SEL NDEF P2P-CR").rstrip().decode("hex") + res = wpas.request("NFC_GET_HANDOVER_SEL NDEF P2P-CR-TAG").rstrip() + else: + res = wpas.request("NFC_GET_HANDOVER_SEL NDEF P2P-CR").rstrip() + if "FAIL" in res: + return None + return res.decode("hex") def wpas_get_handover_sel_wps(): @@ -137,7 +158,7 @@ def p2p_handover_client(llc): if include_p2p_req: data = wpas_get_handover_req() if (data == None): - print "Could not get handover request carrier record from wpa_supplicant" + summary("Could not get handover request carrier record from wpa_supplicant") return print "Handover request carrier record from wpa_supplicant: " + data.encode("hex") datamsg = nfc.ndef.Message(data) @@ -166,31 +187,33 @@ def p2p_handover_client(llc): client = nfc.handover.HandoverClient(llc) try: - print "Trying handover"; + summary("Trying to initiate NFC connection handover") client.connect() - print "Connected for handover" + summary("Connected for handover") except nfc.llcp.ConnectRefused: - print "Handover connection refused" + summary("Handover connection refused") client.close() return except Exception, e: - print "Other exception: " + str(e) + summary("Other exception: " + str(e)) client.close() return - print "Sending handover request" + summary("Sending handover request") if not client.send(message): - print "Failed to send handover request" + summary("Failed to send handover request") + client.close() + return - print "Receiving handover response" + summary("Receiving handover response") message = client._recv() if message is None: - print "No response received" + summary("No response received") client.close() return if message.type != "urn:nfc:wkt:Hs": - print "Response was not Hs - received: " + message.type + summary("Response was not Hs - received: " + message.type) client.close() return @@ -201,7 +224,7 @@ def p2p_handover_client(llc): print e print str(message).encode("hex") message = nfc.ndef.HandoverSelectMessage(message) - print "Handover select received" + summary("Handover select received") try: print message.pretty() except Exception, e: @@ -211,7 +234,10 @@ def p2p_handover_client(llc): print "Remote carrier type: " + carrier.type if carrier.type == "application/vnd.wfa.p2p": print "P2P carrier type match - send to wpa_supplicant" - wpas_report_handover(data, carrier.record, "INIT") + if "OK" in wpas_report_handover(data, carrier.record, "INIT"): + success_report("P2P handover reported successfully (initiator)") + else: + summary("P2P handover report rejected") break print "Remove peer" @@ -237,6 +263,23 @@ class HandoverServer(nfc.handover.HandoverServer): self.ho_server_processing = False self.success = False + # override to avoid parser error in request/response.pretty() in nfcpy + # due to new WSC handover format + def _process_request(self, request): + summary("received handover request {}".format(request.type)) + response = nfc.ndef.Message("\xd1\x02\x01Hs\x12") + if not request.type == 'urn:nfc:wkt:Hr': + summary("not a handover request") + else: + try: + request = nfc.ndef.HandoverRequestMessage(request) + except nfc.ndef.DecodeError as e: + summary("error decoding 'Hr' message: {}".format(e)) + else: + response = self.process_request(request) + summary("send handover response {}".format(response.type)) + return response + def process_request(self, request): self.ho_server_processing = True clear_raw_mode() @@ -268,8 +311,11 @@ class HandoverServer(nfc.handover.HandoverServer): print "Handover select carrier record from wpa_supplicant:" print data.encode("hex") self.sent_carrier = data - wpas_report_handover(self.received_carrier, self.sent_carrier, - "RESP") + if "OK" in wpas_report_handover(self.received_carrier, self.sent_carrier, "RESP"): + success_report("P2P handover reported successfully (responder)") + else: + summary("P2P handover report rejected") + break message = nfc.ndef.Message(data); sel.add_carrier(message[0], "active", message[1:]) @@ -295,8 +341,11 @@ class HandoverServer(nfc.handover.HandoverServer): print "Handover select carrier record from wpa_supplicant:" print data.encode("hex") self.sent_carrier = data - wpas_report_handover_wsc(self.received_carrier, - self.sent_carrier, "RESP") + if "OK" in wpas_report_handover_wsc(self.received_carrier, self.sent_carrier, "RESP"): + success_report("WSC handover reported successfully") + else: + summary("WSC handover report rejected") + break message = nfc.ndef.Message(data); sel.add_carrier(message[0], "active", message[1:]) @@ -310,7 +359,7 @@ class HandoverServer(nfc.handover.HandoverServer): print e print str(sel).encode("hex") - print "Sending handover select" + summary("Sending handover select") self.success = True return sel @@ -349,23 +398,27 @@ def p2p_tag_read(tag): for record in tag.ndef.message: print "record type " + record.type if record.type == "application/vnd.wfa.wsc": - print "WPS tag - send to wpa_supplicant" + summary("WPS tag - send to wpa_supplicant") success = wpas_tag_read(tag.ndef.message) break if record.type == "application/vnd.wfa.p2p": - print "P2P tag - send to wpa_supplicant" + summary("P2P tag - send to wpa_supplicant") success = wpas_tag_read(tag.ndef.message) break else: - print "Empty tag" + summary("Empty tag") + + if success: + success_report("Tag read succeeded") return success def rdwr_connected_p2p_write(tag): - print "Tag found - writing" + summary("Tag found - writing - " + str(tag)) global p2p_sel_data tag.ndef.message = str(p2p_sel_data) + success_report("Tag write succeeded") print "Done - remove tag" global only_one if only_one: @@ -378,7 +431,7 @@ def wps_write_p2p_handover_sel(clf, wait_remove=True): print "Write P2P handover select" data = wpas_get_handover_sel(tag=True) if (data == None): - print "Could not get P2P handover select from wpa_supplicant" + summary("Could not get P2P handover select from wpa_supplicant") return global p2p_sel_wait_remove @@ -400,7 +453,7 @@ def wps_write_p2p_handover_sel(clf, wait_remove=True): def rdwr_connected(tag): global only_one, no_wait - print "Tag connected: " + str(tag) + summary("Tag connected: " + str(tag)) if tag.ndef: print "NDEF tag: " + tag.type @@ -413,7 +466,8 @@ def rdwr_connected(tag): global continue_loop continue_loop = False else: - print "Not an NDEF tag - remove tag" + summary("Not an NDEF tag - remove tag") + return True return not no_wait @@ -504,8 +558,14 @@ def main(): help='do not use stdout input to initiate handover') parser.add_argument('--tag-read-only', '-t', action='store_true', help='tag read only (do not allow connection handover)') + parser.add_argument('--handover-only', action='store_true', + help='connection handover only (do not allow tag read)') parser.add_argument('--freq', '-f', help='forced frequency of operating channel in MHz') + parser.add_argument('--summary', + help='summary file for writing status updates') + parser.add_argument('--success', + help='success file for writing success update') parser.add_argument('command', choices=['write-p2p-sel'], nargs='?') args = parser.parse_args() @@ -533,6 +593,14 @@ def main(): global include_wps_req include_wps_req = False + if args.summary: + global summary_file + summary_file = args.summary + + if args.success: + global success_file + success_file = args.success + if args.no_input: global no_input no_input = True @@ -557,6 +625,11 @@ def main(): if args.tag_read_only: if not clf.connect(rdwr={'on-connect': rdwr_connected}): break + elif args.handover_only: + if not clf.connect(llcp={'on-startup': llcp_startup, + 'on-connect': llcp_connected}, + terminate=terminate_loop): + break else: if not clf.connect(rdwr={'on-connect': rdwr_connected}, llcp={'on-startup': llcp_startup, diff --git a/wpa_supplicant/examples/wps-ap-cli b/wpa_supplicant/examples/wps-ap-cli index 7c6b0aa..7c6b0aa 100644..100755 --- a/wpa_supplicant/examples/wps-ap-cli +++ b/wpa_supplicant/examples/wps-ap-cli diff --git a/wpa_supplicant/examples/wps-nfc.py b/wpa_supplicant/examples/wps-nfc.py index 35d1270..7459eb9 100644..100755 --- a/wpa_supplicant/examples/wps-nfc.py +++ b/wpa_supplicant/examples/wps-nfc.py @@ -26,6 +26,20 @@ wpas_ctrl = '/var/run/wpa_supplicant' srv = None continue_loop = True terminate_now = False +summary_file = None +success_file = None + +def summary(txt): + print txt + if summary_file: + with open(summary_file, 'a') as f: + f.write(txt + "\n") + +def success_report(txt): + summary(txt) + if success_file: + with open(success_file, 'a') as f: + f.write(txt + "\n") def wpas_connect(): ifaces = [] @@ -84,14 +98,19 @@ def wpas_get_password_token(): wpas = wpas_connect() if (wpas == None): return None - return wpas.request("WPS_NFC_TOKEN NDEF").rstrip().decode("hex") - + ret = wpas.request("WPS_NFC_TOKEN NDEF") + if "FAIL" in ret: + return None + return ret.rstrip().decode("hex") def wpas_get_handover_req(): wpas = wpas_connect() if (wpas == None): return None - return wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR").rstrip().decode("hex") + ret = wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR") + if "FAIL" in ret: + return None + return ret.rstrip().decode("hex") def wpas_get_handover_sel(uuid): @@ -123,9 +142,26 @@ class HandoverServer(nfc.handover.HandoverServer): self.ho_server_processing = False self.success = False + # override to avoid parser error in request/response.pretty() in nfcpy + # due to new WSC handover format + def _process_request(self, request): + summary("received handover request {}".format(request.type)) + response = nfc.ndef.Message("\xd1\x02\x01Hs\x12") + if not request.type == 'urn:nfc:wkt:Hr': + summary("not a handover request") + else: + try: + request = nfc.ndef.HandoverRequestMessage(request) + except nfc.ndef.DecodeError as e: + summary("error decoding 'Hr' message: {}".format(e)) + else: + response = self.process_request(request) + summary("send handover response {}".format(response.type)) + return response + def process_request(self, request): self.ho_server_processing = True - print "HandoverServer - request received" + summary("HandoverServer - request received") try: print "Parsed handover request: " + request.pretty() except Exception, e: @@ -136,15 +172,18 @@ class HandoverServer(nfc.handover.HandoverServer): for carrier in request.carriers: print "Remote carrier type: " + carrier.type if carrier.type == "application/vnd.wfa.wsc": - print "WPS carrier type match - add WPS carrier record" + summary("WPS carrier type match - add WPS carrier record") data = wpas_get_handover_sel(self.uuid) if data is None: - print "Could not get handover select carrier record from wpa_supplicant" + summary("Could not get handover select carrier record from wpa_supplicant") continue print "Handover select carrier record from wpa_supplicant:" print data.encode("hex") self.sent_carrier = data - wpas_report_handover(carrier.record, self.sent_carrier, "RESP") + if "OK" in wpas_report_handover(carrier.record, self.sent_carrier, "RESP"): + success_report("Handover reported successfully (responder)") + else: + summary("Handover report rejected (responder)") message = nfc.ndef.Message(data); sel.add_carrier(message[0], "active", message[1:]) @@ -156,17 +195,17 @@ class HandoverServer(nfc.handover.HandoverServer): print e print str(sel).encode("hex") - print "Sending handover select" + summary("Sending handover select") self.success = True return sel def wps_handover_init(llc): - print "Trying to initiate WPS handover" + summary("Trying to initiate WPS handover") data = wpas_get_handover_req() if (data == None): - print "Could not get handover request carrier record from wpa_supplicant" + summary("Could not get handover request carrier record from wpa_supplicant") return print "Handover request carrier record from wpa_supplicant: " + data.encode("hex") @@ -184,27 +223,33 @@ def wps_handover_init(llc): client = nfc.handover.HandoverClient(llc) try: - print "Trying handover"; + summary("Trying to initiate NFC connection handover") client.connect() - print "Connected for handover" + summary("Connected for handover") except nfc.llcp.ConnectRefused: - print "Handover connection refused" + summary("Handover connection refused") + client.close() + return + except Exception, e: + summary("Other exception: " + str(e)) client.close() return - print "Sending handover request" + summary("Sending handover request") if not client.send(message): - print "Failed to send handover request" + summary("Failed to send handover request") + client.close() + return - print "Receiving handover response" + summary("Receiving handover response") message = client._recv() if message is None: - print "No response received" + summary("No response received") client.close() return if message.type != "urn:nfc:wkt:Hs": - print "Response was not Hs - received: " + message.type + summary("Response was not Hs - received: " + message.type) client.close() return @@ -215,7 +260,7 @@ def wps_handover_init(llc): print e print str(message).encode("hex") message = nfc.ndef.HandoverSelectMessage(message) - print "Handover select received" + summary("Handover select received") try: print message.pretty() except Exception, e: @@ -225,7 +270,10 @@ def wps_handover_init(llc): print "Remote carrier type: " + carrier.type if carrier.type == "application/vnd.wfa.wsc": print "WPS carrier type match - send to wpa_supplicant" - wpas_report_handover(data, carrier.record, "INIT") + if "OK" in wpas_report_handover(data, carrier.record, "INIT"): + success_report("Handover reported successfully (initiator)") + else: + summary("Handover report rejected (initiator)") # nfcpy does not support the new format.. #wifi = nfc.ndef.WifiConfigRecord(carrier.record) #print wifi.pretty() @@ -250,11 +298,14 @@ def wps_tag_read(tag, wait_remove=True): for record in tag.ndef.message: print "record type " + record.type if record.type == "application/vnd.wfa.wsc": - print "WPS tag - send to wpa_supplicant" + summary("WPS tag - send to wpa_supplicant") success = wpas_tag_read(tag.ndef.message) break else: - print "Empty tag" + summary("Empty tag") + + if success: + success_report("Tag read succeeded") if wait_remove: print "Remove tag" @@ -265,9 +316,10 @@ def wps_tag_read(tag, wait_remove=True): def rdwr_connected_write(tag): - print "Tag found - writing" + summary("Tag found - writing - " + str(tag)) global write_data tag.ndef.message = str(write_data) + success_report("Tag write succeeded") print "Done - remove tag" global only_one if only_one: @@ -318,7 +370,7 @@ def wps_write_password_tag(clf, wait_remove=True): def rdwr_connected(tag): global only_one, no_wait - print "Tag connected: " + str(tag) + summary("Tag connected: " + str(tag)) if tag.ndef: print "NDEF tag: " + tag.type @@ -331,7 +383,8 @@ def rdwr_connected(tag): global continue_loop continue_loop = False else: - print "Not an NDEF tag - remove tag" + summary("Not an NDEF tag - remove tag") + return True return not no_wait @@ -398,6 +451,10 @@ def main(): help='UUID of an AP (used for WPS ER operations)') parser.add_argument('--id', help='network id (used for WPS ER operations)') + parser.add_argument('--summary', + help='summary file for writing status updates') + parser.add_argument('--success', + help='success file for writing success update') parser.add_argument('command', choices=['write-config', 'write-er-config', 'write-password'], @@ -413,6 +470,14 @@ def main(): global no_wait no_wait = args.no_wait + if args.summary: + global summary_file + summary_file = args.summary + + if args.success: + global success_file + success_file = args.success + logging.basicConfig(level=args.loglevel) try: diff --git a/wpa_supplicant/gas_query.c b/wpa_supplicant/gas_query.c index abcb391..aff1950 100644 --- a/wpa_supplicant/gas_query.c +++ b/wpa_supplicant/gas_query.c @@ -42,6 +42,7 @@ struct gas_query_pending { 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_result result, const struct wpabuf *adv_proto, @@ -64,6 +65,16 @@ 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 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_init - Initialize GAS query component * @wpa_s: Pointer to wpa_supplicant data @@ -199,6 +210,7 @@ static void gas_query_tx_status(struct wpa_supplicant *wpa_s, { struct gas_query_pending *query; struct gas_query *gas = wpa_s->gas; + int dur; if (gas->current == NULL) { wpa_printf(MSG_DEBUG, "GAS: Unexpected TX status: freq=%u dst=" @@ -209,13 +221,15 @@ static void gas_query_tx_status(struct wpa_supplicant *wpa_s, query = gas->current; + dur = ms_from_time(&query->last_oper); wpa_printf(MSG_DEBUG, "GAS: TX status: freq=%u dst=" MACSTR - " result=%d query=%p dialog_token=%u", - freq, MAC2STR(dst), result, query, query->dialog_token); + " result=%d query=%p dialog_token=%u dur=%d ms", + freq, MAC2STR(dst), result, query, query->dialog_token, dur); if (os_memcmp(dst, query->addr, ETH_ALEN) != 0) { wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination"); return; } + os_get_reltime(&query->last_oper); if (result == OFFCHANNEL_SEND_ACTION_SUCCESS) { eloop_cancel_timeout(gas_query_timeout, gas, query); @@ -251,6 +265,7 @@ static int gas_query_tx(struct gas_query *gas, struct gas_query_pending *query, u8 *categ = wpabuf_mhead_u8(req); *categ = WLAN_ACTION_PROTECTED_DUAL; } + os_get_reltime(&query->last_oper); res = offchannel_send_action(gas->wpa_s, query->freq, query->addr, gas->wpa_s->own_addr, query->addr, wpabuf_head(req), wpabuf_len(req), 1000, @@ -452,6 +467,9 @@ int gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa, 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 " @@ -469,7 +487,10 @@ int gas_query_rx(struct gas_query *gas, const u8 *da, const u8 *sa, query->status_code = WPA_GET_LE16(pos); pos += 2; - if (query->status_code != WLAN_STATUS_SUCCESS) { + 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); @@ -573,6 +594,12 @@ static void gas_query_start_cb(struct wpa_radio_work *work, int deinit) struct gas_query *gas = query->gas; if (deinit) { + if (work->started) { + gas->work = NULL; + gas_query_done(gas, query, GAS_QUERY_DELETED_AT_DEINIT); + return; + } + gas_query_free(query, 1); return; } diff --git a/wpa_supplicant/hs20_supplicant.c b/wpa_supplicant/hs20_supplicant.c index 5f30313..ab8b66b 100644 --- a/wpa_supplicant/hs20_supplicant.c +++ b/wpa_supplicant/hs20_supplicant.c @@ -1,6 +1,6 @@ /* * Copyright (c) 2009, Atheros Communications, Inc. - * Copyright (c) 2011-2012, Qualcomm Atheros, 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. @@ -14,22 +14,65 @@ #include "common/ieee802_11_defs.h" #include "common/gas.h" #include "common/wpa_ctrl.h" +#include "rsn_supp/wpa.h" #include "wpa_supplicant_i.h" #include "driver_i.h" #include "config.h" +#include "scan.h" #include "bss.h" +#include "blacklist.h" #include "gas_query.h" #include "interworking.h" #include "hs20_supplicant.h" -void wpas_hs20_add_indication(struct wpabuf *buf) +#define OSU_MAX_ITEMS 10 + +struct osu_lang_string { + char lang[4]; + char text[253]; +}; + +struct osu_icon { + u16 width; + u16 height; + char lang[4]; + char icon_type[256]; + char filename[256]; + unsigned int id; + unsigned int failed:1; +}; + +struct osu_provider { + u8 bssid[ETH_ALEN]; + u8 osu_ssid[32]; + u8 osu_ssid_len; + char server_uri[256]; + u32 osu_methods; /* bit 0 = OMA-DM, bit 1 = SOAP-XML SPP */ + char osu_nai[256]; + struct osu_lang_string friendly_name[OSU_MAX_ITEMS]; + size_t friendly_name_count; + struct osu_lang_string serv_desc[OSU_MAX_ITEMS]; + size_t serv_desc_count; + struct osu_icon icon[OSU_MAX_ITEMS]; + size_t icon_count; +}; + + +void wpas_hs20_add_indication(struct wpabuf *buf, int pps_mo_id) { + u8 conf; + wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); - wpabuf_put_u8(buf, 5); + wpabuf_put_u8(buf, pps_mo_id >= 0 ? 7 : 5); wpabuf_put_be24(buf, OUI_WFA); wpabuf_put_u8(buf, HS20_INDICATION_OUI_TYPE); - wpabuf_put_u8(buf, 0x00); /* Hotspot Configuration */ + conf = HS20_VERSION; + if (pps_mo_id >= 0) + conf |= HS20_PPS_MO_ID_PRESENT; + wpabuf_put_u8(buf, conf); + if (pps_mo_id >= 0) + wpabuf_put_le16(buf, pps_mo_id); } @@ -62,15 +105,29 @@ int is_hs20_network(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, } -struct wpabuf * hs20_build_anqp_req(u32 stypes, const u8 *payload, - size_t payload_len) +int hs20_get_pps_mo_id(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) +{ + struct wpa_cred *cred; + + if (ssid == NULL || ssid->parent_cred == NULL) + return 0; + + for (cred = wpa_s->conf->cred; cred; cred = cred->next) { + if (ssid->parent_cred == cred) + return cred->update_identifier; + } + + return 0; +} + + +void hs20_put_anqp_req(u32 stypes, const u8 *payload, size_t payload_len, + struct wpabuf *buf) { - struct wpabuf *buf; u8 *len_pos; - buf = gas_anqp_build_initial_req(0, 100 + payload_len); if (buf == NULL) - return NULL; + return; len_pos = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); wpabuf_put_be24(buf, OUI_WFA); @@ -80,6 +137,11 @@ struct wpabuf * hs20_build_anqp_req(u32 stypes, const u8 *payload, wpabuf_put_u8(buf, 0); /* Reserved */ if (payload) wpabuf_put_data(buf, payload, payload_len); + } else if (stypes == BIT(HS20_STYPE_ICON_REQUEST)) { + wpabuf_put_u8(buf, HS20_STYPE_ICON_REQUEST); + wpabuf_put_u8(buf, 0); /* Reserved */ + if (payload) + wpabuf_put_data(buf, payload, payload_len); } else { u8 i; wpabuf_put_u8(buf, HS20_STYPE_QUERY_LIST); @@ -92,6 +154,19 @@ struct wpabuf * hs20_build_anqp_req(u32 stypes, const u8 *payload, gas_anqp_set_element_len(buf, len_pos); gas_anqp_set_len(buf); +} + + +struct wpabuf * hs20_build_anqp_req(u32 stypes, const u8 *payload, + size_t payload_len) +{ + struct wpabuf *buf; + + buf = gas_anqp_build_initial_req(0, 100 + payload_len); + if (buf == NULL) + return NULL; + + hs20_put_anqp_req(stypes, payload, payload_len, buf); return buf; } @@ -135,6 +210,116 @@ int hs20_anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst, u32 stypes, } +static int hs20_process_icon_binary_file(struct wpa_supplicant *wpa_s, + const u8 *sa, const u8 *pos, + size_t slen) +{ + char fname[256]; + int png; + FILE *f; + u16 data_len; + + wpa_msg(wpa_s, MSG_INFO, "RX-HS20-ANQP " MACSTR " Icon Binary File", + MAC2STR(sa)); + + if (slen < 4) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File " + "value from " MACSTR, MAC2STR(sa)); + return -1; + } + + wpa_printf(MSG_DEBUG, "HS 2.0: Download Status Code %u", *pos); + if (*pos != 0) + return -1; + pos++; + slen--; + + if ((size_t) 1 + pos[0] > slen) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File " + "value from " MACSTR, MAC2STR(sa)); + return -1; + } + wpa_hexdump_ascii(MSG_DEBUG, "Icon Type", pos + 1, pos[0]); + png = os_strncasecmp((char *) pos + 1, "image/png", 9) == 0; + slen -= 1 + pos[0]; + pos += 1 + pos[0]; + + if (slen < 2) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File " + "value from " MACSTR, MAC2STR(sa)); + return -1; + } + data_len = WPA_GET_LE16(pos); + pos += 2; + slen -= 2; + + if (data_len > slen) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File " + "value from " MACSTR, MAC2STR(sa)); + return -1; + } + + wpa_printf(MSG_DEBUG, "Icon Binary Data: %u bytes", data_len); + if (wpa_s->conf->osu_dir == NULL) + return -1; + + wpa_s->osu_icon_id++; + if (wpa_s->osu_icon_id == 0) + wpa_s->osu_icon_id++; + snprintf(fname, sizeof(fname), "%s/osu-icon-%u.%s", + wpa_s->conf->osu_dir, wpa_s->osu_icon_id, + png ? "png" : "icon"); + f = fopen(fname, "wb"); + if (f == NULL) + return -1; + if (fwrite(pos, slen, 1, f) != 1) { + fclose(f); + unlink(fname); + return -1; + } + fclose(f); + + wpa_msg(wpa_s, MSG_INFO, "RX-HS20-ANQP-ICON %s", fname); + return 0; +} + + +static void hs20_continue_icon_fetch(void *eloop_ctx, void *sock_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + if (wpa_s->fetch_osu_icon_in_progress) + hs20_next_osu_icon(wpa_s); +} + + +static void hs20_osu_icon_fetch_result(struct wpa_supplicant *wpa_s, int res) +{ + size_t i, j; + struct os_reltime now, tmp; + int dur; + + os_get_reltime(&now); + os_reltime_sub(&now, &wpa_s->osu_icon_fetch_start, &tmp); + dur = tmp.sec * 1000 + tmp.usec / 1000; + wpa_printf(MSG_DEBUG, "HS 2.0: Icon fetch dur=%d ms res=%d", + dur, res); + + for (i = 0; i < wpa_s->osu_prov_count; i++) { + struct osu_provider *osu = &wpa_s->osu_prov[i]; + for (j = 0; j < osu->icon_count; j++) { + struct osu_icon *icon = &osu->icon[j]; + if (icon->id || icon->failed) + continue; + if (res < 0) + icon->failed = 1; + else + icon->id = wpa_s->osu_icon_id; + return; + } + } +} + + void hs20_parse_rx_hs20_anqp_resp(struct wpa_supplicant *wpa_s, const u8 *sa, const u8 *data, size_t slen) { @@ -142,6 +327,7 @@ void hs20_parse_rx_hs20_anqp_resp(struct wpa_supplicant *wpa_s, u8 subtype; struct wpa_bss *bss = wpa_bss_get_bssid(wpa_s, sa); struct wpa_bss_anqp *anqp = NULL; + int ret; if (slen < 2) return; @@ -207,8 +393,537 @@ void hs20_parse_rx_hs20_anqp_resp(struct wpa_supplicant *wpa_s, wpabuf_alloc_copy(pos, slen); } break; + case HS20_STYPE_OSU_PROVIDERS_LIST: + wpa_msg(wpa_s, MSG_INFO, "RX-HS20-ANQP " MACSTR + " OSU Providers list", MAC2STR(sa)); + wpa_s->num_prov_found++; + if (anqp) { + wpabuf_free(anqp->hs20_osu_providers_list); + anqp->hs20_osu_providers_list = + wpabuf_alloc_copy(pos, slen); + } + break; + case HS20_STYPE_ICON_BINARY_FILE: + ret = hs20_process_icon_binary_file(wpa_s, sa, pos, slen); + if (wpa_s->fetch_osu_icon_in_progress) { + hs20_osu_icon_fetch_result(wpa_s, ret); + eloop_cancel_timeout(hs20_continue_icon_fetch, + wpa_s, NULL); + eloop_register_timeout(0, 0, hs20_continue_icon_fetch, + wpa_s, NULL); + } + break; default: wpa_printf(MSG_DEBUG, "HS20: Unsupported subtype %u", subtype); break; } } + + +void hs20_notify_parse_done(struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->fetch_osu_icon_in_progress) + return; + if (eloop_is_timeout_registered(hs20_continue_icon_fetch, wpa_s, NULL)) + return; + /* + * We are going through icon fetch, but no icon response was received. + * Assume this means the current AP could not provide an answer to avoid + * getting stuck in fetch iteration. + */ + hs20_icon_fetch_failed(wpa_s); +} + + +static void hs20_free_osu_prov_entry(struct osu_provider *prov) +{ +} + + +void hs20_free_osu_prov(struct wpa_supplicant *wpa_s) +{ + size_t i; + for (i = 0; i < wpa_s->osu_prov_count; i++) + hs20_free_osu_prov_entry(&wpa_s->osu_prov[i]); + os_free(wpa_s->osu_prov); + wpa_s->osu_prov = NULL; + wpa_s->osu_prov_count = 0; +} + + +static void hs20_osu_fetch_done(struct wpa_supplicant *wpa_s) +{ + char fname[256]; + FILE *f; + size_t i, j; + + wpa_s->fetch_osu_info = 0; + wpa_s->fetch_osu_icon_in_progress = 0; + + if (wpa_s->conf->osu_dir == NULL) { + hs20_free_osu_prov(wpa_s); + wpa_s->fetch_anqp_in_progress = 0; + return; + } + + snprintf(fname, sizeof(fname), "%s/osu-providers.txt", + wpa_s->conf->osu_dir); + f = fopen(fname, "w"); + if (f == NULL) { + hs20_free_osu_prov(wpa_s); + return; + } + for (i = 0; i < wpa_s->osu_prov_count; i++) { + struct osu_provider *osu = &wpa_s->osu_prov[i]; + if (i > 0) + fprintf(f, "\n"); + fprintf(f, "OSU-PROVIDER " MACSTR "\n" + "uri=%s\n" + "methods=%08x\n", + MAC2STR(osu->bssid), osu->server_uri, osu->osu_methods); + if (osu->osu_ssid_len) { + fprintf(f, "osu_ssid=%s\n", + wpa_ssid_txt(osu->osu_ssid, + osu->osu_ssid_len)); + } + if (osu->osu_nai[0]) + fprintf(f, "osu_nai=%s\n", osu->osu_nai); + for (j = 0; j < osu->friendly_name_count; j++) { + fprintf(f, "friendly_name=%s:%s\n", + osu->friendly_name[j].lang, + osu->friendly_name[j].text); + } + for (j = 0; j < osu->serv_desc_count; j++) { + fprintf(f, "desc=%s:%s\n", + osu->serv_desc[j].lang, + osu->serv_desc[j].text); + } + for (j = 0; j < osu->icon_count; j++) { + struct osu_icon *icon = &osu->icon[j]; + if (icon->failed) + continue; /* could not fetch icon */ + fprintf(f, "icon=%u:%u:%u:%s:%s:%s\n", + icon->id, icon->width, icon->height, icon->lang, + icon->icon_type, icon->filename); + } + } + fclose(f); + hs20_free_osu_prov(wpa_s); + + wpa_msg(wpa_s, MSG_INFO, "OSU provider fetch completed"); + wpa_s->fetch_anqp_in_progress = 0; +} + + +void hs20_next_osu_icon(struct wpa_supplicant *wpa_s) +{ + size_t i, j; + + wpa_printf(MSG_DEBUG, "HS 2.0: Ready to fetch next icon"); + + for (i = 0; i < wpa_s->osu_prov_count; i++) { + struct osu_provider *osu = &wpa_s->osu_prov[i]; + for (j = 0; j < osu->icon_count; j++) { + struct osu_icon *icon = &osu->icon[j]; + if (icon->id || icon->failed) + continue; + + wpa_printf(MSG_DEBUG, "HS 2.0: Try to fetch icon '%s' " + "from " MACSTR, icon->filename, + MAC2STR(osu->bssid)); + os_get_reltime(&wpa_s->osu_icon_fetch_start); + if (hs20_anqp_send_req(wpa_s, osu->bssid, + BIT(HS20_STYPE_ICON_REQUEST), + (u8 *) icon->filename, + os_strlen(icon->filename)) < 0) { + icon->failed = 1; + continue; + } + return; + } + } + + wpa_printf(MSG_DEBUG, "HS 2.0: No more icons to fetch"); + hs20_osu_fetch_done(wpa_s); +} + + +static void hs20_osu_add_prov(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, + const u8 *osu_ssid, u8 osu_ssid_len, + const u8 *pos, size_t len) +{ + struct osu_provider *prov; + const u8 *end = pos + len; + u16 len2; + const u8 *pos2; + + wpa_hexdump(MSG_DEBUG, "HS 2.0: Parsing OSU Provider", pos, len); + prov = os_realloc_array(wpa_s->osu_prov, + wpa_s->osu_prov_count + 1, + sizeof(*prov)); + if (prov == NULL) + return; + wpa_s->osu_prov = prov; + prov = &prov[wpa_s->osu_prov_count]; + os_memset(prov, 0, sizeof(*prov)); + + os_memcpy(prov->bssid, bss->bssid, ETH_ALEN); + os_memcpy(prov->osu_ssid, osu_ssid, osu_ssid_len); + prov->osu_ssid_len = osu_ssid_len; + + /* OSU Friendly Name Length */ + if (pos + 2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU " + "Friendly Name Length"); + return; + } + len2 = WPA_GET_LE16(pos); + pos += 2; + if (pos + len2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU " + "Friendly Name Duples"); + return; + } + pos2 = pos; + pos += len2; + + /* OSU Friendly Name Duples */ + while (pos2 + 4 <= pos && prov->friendly_name_count < OSU_MAX_ITEMS) { + struct osu_lang_string *f; + if (pos2 + 1 + pos2[0] > pos || pos2[0] < 3) { + wpa_printf(MSG_DEBUG, "Invalid OSU Friendly Name"); + break; + } + f = &prov->friendly_name[prov->friendly_name_count++]; + os_memcpy(f->lang, pos2 + 1, 3); + os_memcpy(f->text, pos2 + 1 + 3, pos2[0] - 3); + pos2 += 1 + pos2[0]; + } + + /* OSU Server URI */ + if (pos + 1 > end || pos + 1 + pos[0] > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU Server " + "URI"); + return; + } + os_memcpy(prov->server_uri, pos + 1, pos[0]); + pos += 1 + pos[0]; + + /* OSU Method list */ + if (pos + 1 > end || pos + 1 + pos[0] > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU Method " + "list"); + return; + } + pos2 = pos + 1; + pos += 1 + pos[0]; + while (pos2 < pos) { + if (*pos2 < 32) + prov->osu_methods |= BIT(*pos2); + pos2++; + } + + /* Icons Available Length */ + if (pos + 2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for Icons " + "Available Length"); + return; + } + len2 = WPA_GET_LE16(pos); + pos += 2; + if (pos + len2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for Icons " + "Available"); + return; + } + pos2 = pos; + pos += len2; + + /* Icons Available */ + while (pos2 < pos) { + struct osu_icon *icon = &prov->icon[prov->icon_count]; + if (pos2 + 2 + 2 + 3 + 1 + 1 > pos) { + wpa_printf(MSG_DEBUG, "HS 2.0: Invalid Icon Metadata"); + break; + } + + icon->width = WPA_GET_LE16(pos2); + pos2 += 2; + icon->height = WPA_GET_LE16(pos2); + pos2 += 2; + os_memcpy(icon->lang, pos2, 3); + pos2 += 3; + + if (pos2 + 1 + pos2[0] > pos) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not room for Icon Type"); + break; + } + os_memcpy(icon->icon_type, pos2 + 1, pos2[0]); + pos2 += 1 + pos2[0]; + + if (pos2 + 1 + pos2[0] > pos) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not room for Icon " + "Filename"); + break; + } + os_memcpy(icon->filename, pos2 + 1, pos2[0]); + pos2 += 1 + pos2[0]; + + prov->icon_count++; + } + + /* OSU_NAI */ + if (pos + 1 > end || pos + 1 + pos[0] > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU_NAI"); + return; + } + os_memcpy(prov->osu_nai, pos + 1, pos[0]); + pos += 1 + pos[0]; + + /* OSU Service Description Length */ + if (pos + 2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU " + "Service Description Length"); + return; + } + len2 = WPA_GET_LE16(pos); + pos += 2; + if (pos + len2 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU " + "Service Description Duples"); + return; + } + pos2 = pos; + pos += len2; + + /* OSU Service Description Duples */ + while (pos2 + 4 <= pos && prov->serv_desc_count < OSU_MAX_ITEMS) { + struct osu_lang_string *f; + if (pos2 + 1 + pos2[0] > pos || pos2[0] < 3) { + wpa_printf(MSG_DEBUG, "Invalid OSU Service " + "Description"); + break; + } + f = &prov->serv_desc[prov->serv_desc_count++]; + os_memcpy(f->lang, pos2 + 1, 3); + os_memcpy(f->text, pos2 + 1 + 3, pos2[0] - 3); + pos2 += 1 + pos2[0]; + } + + wpa_printf(MSG_DEBUG, "HS 2.0: Added OSU Provider through " MACSTR, + MAC2STR(bss->bssid)); + wpa_s->osu_prov_count++; +} + + +void hs20_osu_icon_fetch(struct wpa_supplicant *wpa_s) +{ + struct wpa_bss *bss; + struct wpabuf *prov_anqp; + const u8 *pos, *end; + u16 len; + const u8 *osu_ssid; + u8 osu_ssid_len; + u8 num_providers; + + hs20_free_osu_prov(wpa_s); + + dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { + if (bss->anqp == NULL) + continue; + prov_anqp = bss->anqp->hs20_osu_providers_list; + if (prov_anqp == NULL) + continue; + wpa_printf(MSG_DEBUG, "HS 2.0: Parsing OSU Providers list from " + MACSTR, MAC2STR(bss->bssid)); + wpa_hexdump_buf(MSG_DEBUG, "HS 2.0: OSU Providers list", + prov_anqp); + pos = wpabuf_head(prov_anqp); + end = pos + wpabuf_len(prov_anqp); + + /* OSU SSID */ + if (pos + 1 > end) + continue; + if (pos + 1 + pos[0] > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for " + "OSU SSID"); + continue; + } + osu_ssid_len = *pos++; + if (osu_ssid_len > 32) { + wpa_printf(MSG_DEBUG, "HS 2.0: Invalid OSU SSID " + "Length %u", osu_ssid_len); + continue; + } + osu_ssid = pos; + pos += osu_ssid_len; + + if (pos + 1 > end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for " + "Number of OSU Providers"); + continue; + } + num_providers = *pos++; + wpa_printf(MSG_DEBUG, "HS 2.0: Number of OSU Providers: %u", + num_providers); + + /* OSU Providers */ + while (pos + 2 < end && num_providers > 0) { + num_providers--; + len = WPA_GET_LE16(pos); + pos += 2; + if (pos + len > end) + break; + hs20_osu_add_prov(wpa_s, bss, osu_ssid, + osu_ssid_len, pos, len); + pos += len; + } + + if (pos != end) { + wpa_printf(MSG_DEBUG, "HS 2.0: Ignored %d bytes of " + "extra data after OSU Providers", + (int) (end - pos)); + } + } + + wpa_s->fetch_osu_icon_in_progress = 1; + hs20_next_osu_icon(wpa_s); +} + + +static void hs20_osu_scan_res_handler(struct wpa_supplicant *wpa_s, + struct wpa_scan_results *scan_res) +{ + wpa_printf(MSG_DEBUG, "OSU provisioning fetch scan completed"); + wpa_s->network_select = 0; + wpa_s->fetch_all_anqp = 1; + wpa_s->fetch_osu_info = 1; + wpa_s->fetch_osu_icon_in_progress = 0; + + interworking_start_fetch_anqp(wpa_s); +} + + +int hs20_fetch_osu(struct wpa_supplicant *wpa_s) +{ + if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) { + wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - " + "interface disabled"); + return -1; + } + + if (wpa_s->scanning) { + wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - " + "scanning"); + return -1; + } + + if (wpa_s->conf->osu_dir == NULL) { + wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - " + "osu_dir not configured"); + return -1; + } + + if (wpa_s->fetch_anqp_in_progress || wpa_s->network_select) { + wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - " + "fetch in progress (%d, %d)", + wpa_s->fetch_anqp_in_progress, + wpa_s->network_select); + return -1; + } + + wpa_msg(wpa_s, MSG_INFO, "Starting OSU provisioning information fetch"); + wpa_s->num_osu_scans = 0; + wpa_s->num_prov_found = 0; + hs20_start_osu_scan(wpa_s); + + return 0; +} + + +void hs20_start_osu_scan(struct wpa_supplicant *wpa_s) +{ + wpa_s->num_osu_scans++; + wpa_s->scan_req = MANUAL_SCAN_REQ; + wpa_s->scan_res_handler = hs20_osu_scan_res_handler; + wpa_supplicant_req_scan(wpa_s, 0, 0); +} + + +void hs20_cancel_fetch_osu(struct wpa_supplicant *wpa_s) +{ + wpa_printf(MSG_DEBUG, "Cancel OSU fetch"); + interworking_stop_fetch_anqp(wpa_s); + wpa_s->network_select = 0; + wpa_s->fetch_osu_info = 0; + wpa_s->fetch_osu_icon_in_progress = 0; +} + + +void hs20_icon_fetch_failed(struct wpa_supplicant *wpa_s) +{ + hs20_osu_icon_fetch_result(wpa_s, -1); + eloop_cancel_timeout(hs20_continue_icon_fetch, wpa_s, NULL); + eloop_register_timeout(0, 0, hs20_continue_icon_fetch, wpa_s, NULL); +} + + +void hs20_rx_subscription_remediation(struct wpa_supplicant *wpa_s, + const char *url, u8 osu_method) +{ + if (url) + wpa_msg(wpa_s, MSG_INFO, HS20_SUBSCRIPTION_REMEDIATION "%u %s", + osu_method, url); + else + wpa_msg(wpa_s, MSG_INFO, HS20_SUBSCRIPTION_REMEDIATION); +} + + +void hs20_rx_deauth_imminent_notice(struct wpa_supplicant *wpa_s, u8 code, + u16 reauth_delay, const char *url) +{ + if (!wpa_sm_pmf_enabled(wpa_s->wpa)) { + wpa_printf(MSG_DEBUG, "HS 2.0: Ignore deauthentication imminent notice since PMF was not enabled"); + return; + } + + wpa_msg(wpa_s, MSG_INFO, HS20_DEAUTH_IMMINENT_NOTICE "%u %u %s", + code, reauth_delay, url); + + if (code == HS20_DEAUTH_REASON_CODE_BSS) { + wpa_printf(MSG_DEBUG, "HS 2.0: Add BSS to blacklist"); + wpa_blacklist_add(wpa_s, wpa_s->bssid); + /* TODO: For now, disable full ESS since some drivers may not + * support disabling per BSS. */ + if (wpa_s->current_ssid) { + struct os_reltime now; + os_get_reltime(&now); + if (now.sec + reauth_delay <= + wpa_s->current_ssid->disabled_until.sec) + return; + wpa_printf(MSG_DEBUG, "HS 2.0: Disable network for %u seconds (BSS)", + reauth_delay); + wpa_s->current_ssid->disabled_until.sec = + now.sec + reauth_delay; + } + } + + if (code == HS20_DEAUTH_REASON_CODE_ESS && wpa_s->current_ssid) { + struct os_reltime now; + os_get_reltime(&now); + if (now.sec + reauth_delay <= + wpa_s->current_ssid->disabled_until.sec) + return; + wpa_printf(MSG_DEBUG, "HS 2.0: Disable network for %u seconds", + reauth_delay); + wpa_s->current_ssid->disabled_until.sec = + now.sec + reauth_delay; + } +} + + +void hs20_deinit(struct wpa_supplicant *wpa_s) +{ + eloop_cancel_timeout(hs20_continue_icon_fetch, wpa_s, NULL); + hs20_free_osu_prov(wpa_s); +} diff --git a/wpa_supplicant/hs20_supplicant.h b/wpa_supplicant/hs20_supplicant.h index 1c8481b..06739f5 100644 --- a/wpa_supplicant/hs20_supplicant.h +++ b/wpa_supplicant/hs20_supplicant.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2012, Qualcomm Atheros, 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. @@ -8,15 +8,33 @@ #ifndef HS20_SUPPLICANT_H #define HS20_SUPPLICANT_H -void wpas_hs20_add_indication(struct wpabuf *buf); +void wpas_hs20_add_indication(struct wpabuf *buf, int pps_mo_id); int hs20_anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst, u32 stypes, const u8 *payload, size_t payload_len); struct wpabuf * hs20_build_anqp_req(u32 stypes, const u8 *payload, size_t payload_len); +void hs20_put_anqp_req(u32 stypes, const u8 *payload, size_t payload_len, + struct wpabuf *buf); void hs20_parse_rx_hs20_anqp_resp(struct wpa_supplicant *wpa_s, const u8 *sa, const u8 *data, size_t slen); int is_hs20_network(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, struct wpa_bss *bss); +int hs20_get_pps_mo_id(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid); +void hs20_notify_parse_done(struct wpa_supplicant *wpa_s); + +void hs20_rx_subscription_remediation(struct wpa_supplicant *wpa_s, + const char *url, u8 osu_method); +void hs20_rx_deauth_imminent_notice(struct wpa_supplicant *wpa_s, u8 code, + u16 reauth_delay, const char *url); + +void hs20_free_osu_prov(struct wpa_supplicant *wpa_s); +void hs20_next_osu_icon(struct wpa_supplicant *wpa_s); +void hs20_osu_icon_fetch(struct wpa_supplicant *wpa_s); +int hs20_fetch_osu(struct wpa_supplicant *wpa_s); +void hs20_cancel_fetch_osu(struct wpa_supplicant *wpa_s); +void hs20_icon_fetch_failed(struct wpa_supplicant *wpa_s); +void hs20_start_osu_scan(struct wpa_supplicant *wpa_s); +void hs20_deinit(struct wpa_supplicant *wpa_s); #endif /* HS20_SUPPLICANT_H */ diff --git a/wpa_supplicant/interworking.c b/wpa_supplicant/interworking.c index da8971d..6d1539c 100644 --- a/wpa_supplicant/interworking.c +++ b/wpa_supplicant/interworking.c @@ -27,6 +27,7 @@ #include "bss.h" #include "scan.h" #include "notify.h" +#include "driver_i.h" #include "gas_query.h" #include "hs20_supplicant.h" #include "interworking.h" @@ -46,9 +47,28 @@ static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s); static struct wpa_cred * interworking_credentials_available_realm( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss); + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded); static struct wpa_cred * interworking_credentials_available_3gpp( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss); + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded); + + +static int cred_prio_cmp(const struct wpa_cred *a, const struct wpa_cred *b) +{ + if (a->priority > b->priority) + return 1; + if (a->priority < b->priority) + return -1; + if (a->provisioning_sp == NULL || b->provisioning_sp == NULL || + os_strcmp(a->provisioning_sp, b->provisioning_sp) != 0) + return 0; + if (a->sp_priority < b->sp_priority) + return 1; + if (a->sp_priority > b->sp_priority) + return -1; + return 0; +} static void interworking_reconnect(struct wpa_supplicant *wpa_s) @@ -102,6 +122,9 @@ static void interworking_anqp_resp_cb(void *ctx, const u8 *dst, { struct wpa_supplicant *wpa_s = ctx; + wpa_printf(MSG_DEBUG, "ANQP: Response callback dst=" MACSTR + " dialog_token=%u result=%d status_code=%u", + MAC2STR(dst), dialog_token, result, status_code); anqp_resp_cb(wpa_s, dst, dialog_token, result, adv_proto, resp, status_code); interworking_next_anqp_fetch(wpa_s); @@ -155,12 +178,44 @@ static int cred_with_domain(struct wpa_supplicant *wpa_s) struct wpa_cred *cred; for (cred = wpa_s->conf->cred; cred; cred = cred->next) { - if (cred->domain || cred->pcsc || cred->imsi) + if (cred->domain || cred->pcsc || cred->imsi || + cred->roaming_partner) + return 1; + } + return 0; +} + + +#ifdef CONFIG_HS20 + +static int cred_with_min_backhaul(struct wpa_supplicant *wpa_s) +{ + struct wpa_cred *cred; + + for (cred = wpa_s->conf->cred; cred; cred = cred->next) { + if (cred->min_dl_bandwidth_home || + cred->min_ul_bandwidth_home || + cred->min_dl_bandwidth_roaming || + cred->min_ul_bandwidth_roaming) + return 1; + } + return 0; +} + + +static int cred_with_conn_capab(struct wpa_supplicant *wpa_s) +{ + struct wpa_cred *cred; + + for (cred = wpa_s->conf->cred; cred; cred = cred->next) { + if (cred->num_req_conn_capab) return 1; } return 0; } +#endif /* CONFIG_HS20 */ + static int additional_roaming_consortiums(struct wpa_bss *bss) { @@ -206,8 +261,10 @@ static int interworking_anqp_send_req(struct wpa_supplicant *wpa_s, info_ids[num_info_ids++] = ANQP_IP_ADDR_TYPE_AVAILABILITY; if (all || cred_with_nai_realm(wpa_s)) info_ids[num_info_ids++] = ANQP_NAI_REALM; - if (all || cred_with_3gpp(wpa_s)) + if (all || cred_with_3gpp(wpa_s)) { info_ids[num_info_ids++] = ANQP_3GPP_CELLULAR_NETWORK; + wpa_supplicant_scard_init(wpa_s, NULL); + } if (all || cred_with_domain(wpa_s)) info_ids[num_info_ids++] = ANQP_DOMAIN_NAME; wpa_hexdump(MSG_DEBUG, "Interworking: ANQP Query info", @@ -227,13 +284,17 @@ static int interworking_anqp_send_req(struct wpa_supplicant *wpa_s, wpabuf_put_u8(extra, HS20_STYPE_QUERY_LIST); wpabuf_put_u8(extra, 0); /* Reserved */ wpabuf_put_u8(extra, HS20_STYPE_CAPABILITY_LIST); - if (all) { + if (all) wpabuf_put_u8(extra, HS20_STYPE_OPERATOR_FRIENDLY_NAME); + if (all || cred_with_min_backhaul(wpa_s)) wpabuf_put_u8(extra, HS20_STYPE_WAN_METRICS); + if (all || cred_with_conn_capab(wpa_s)) wpabuf_put_u8(extra, HS20_STYPE_CONNECTION_CAPABILITY); + if (all) wpabuf_put_u8(extra, HS20_STYPE_OPERATING_CLASS); - } + if (all) + wpabuf_put_u8(extra, HS20_STYPE_OSU_PROVIDERS_LIST); gas_anqp_set_element_len(extra, len_pos); } #endif /* CONFIG_HS20 */ @@ -756,7 +817,8 @@ static int set_root_nai(struct wpa_ssid *ssid, const char *imsi, char prefix) static int already_connected(struct wpa_supplicant *wpa_s, struct wpa_cred *cred, struct wpa_bss *bss) { - struct wpa_ssid *ssid; + struct wpa_ssid *ssid, *sel_ssid; + struct wpa_bss *selected; if (wpa_s->wpa_state < WPA_ASSOCIATED || wpa_s->current_ssid == NULL) return 0; @@ -769,6 +831,11 @@ static int already_connected(struct wpa_supplicant *wpa_s, os_memcmp(ssid->ssid, bss->ssid, bss->ssid_len) != 0) return 0; + sel_ssid = NULL; + selected = wpa_supplicant_pick_network(wpa_s, &sel_ssid); + if (selected && sel_ssid && sel_ssid->priority > ssid->priority) + return 0; /* higher priority network in scan results */ + return 1; } @@ -809,9 +876,23 @@ static void remove_duplicate_network(struct wpa_supplicant *wpa_s, static int interworking_set_hs20_params(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) { - if (wpa_config_set(ssid, "key_mgmt", - wpa_s->conf->pmf != NO_MGMT_FRAME_PROTECTION ? - "WPA-EAP WPA-EAP-SHA256" : "WPA-EAP", 0) < 0) + const char *key_mgmt = NULL; +#ifdef CONFIG_IEEE80211R + int res; + struct wpa_driver_capa capa; + + res = wpa_drv_get_capa(wpa_s, &capa); + if (res == 0 && capa.key_mgmt & WPA_DRIVER_CAPA_KEY_MGMT_FT) { + key_mgmt = wpa_s->conf->pmf != NO_MGMT_FRAME_PROTECTION ? + "WPA-EAP WPA-EAP-SHA256 FT-EAP" : + "WPA-EAP FT-EAP"; + } +#endif /* CONFIG_IEEE80211R */ + + if (!key_mgmt) + key_mgmt = wpa_s->conf->pmf != NO_MGMT_FRAME_PROTECTION ? + "WPA-EAP WPA-EAP-SHA256" : "WPA-EAP"; + if (wpa_config_set(ssid, "key_mgmt", key_mgmt, 0) < 0) return -1; if (wpa_config_set(ssid, "proto", "RSN", 0) < 0) return -1; @@ -859,6 +940,7 @@ static int interworking_connect_3gpp(struct wpa_supplicant *wpa_s, goto fail; os_memcpy(ssid->ssid, bss->ssid, bss->ssid_len); ssid->ssid_len = bss->ssid_len; + ssid->eap.sim_num = cred->sim_num; if (interworking_set_hs20_params(wpa_s, ssid) < 0) goto fail; @@ -914,10 +996,7 @@ static int interworking_connect_3gpp(struct wpa_supplicant *wpa_s, goto fail; } - if (cred->password && cred->password[0] && - wpa_config_set_quoted(ssid, "password", cred->password) < 0) - goto fail; - + wpa_s->next_ssid = ssid; wpa_config_update_prio_list(wpa_s->conf); interworking_reconnect(wpa_s); @@ -1046,11 +1125,164 @@ static int cred_excluded_ssid(struct wpa_cred *cred, struct wpa_bss *bss) } +static int cred_below_min_backhaul(struct wpa_supplicant *wpa_s, + struct wpa_cred *cred, struct wpa_bss *bss) +{ + int res; + unsigned int dl_bandwidth, ul_bandwidth; + const u8 *wan; + u8 wan_info, dl_load, ul_load; + u16 lmd; + u32 ul_speed, dl_speed; + + if (!cred->min_dl_bandwidth_home && + !cred->min_ul_bandwidth_home && + !cred->min_dl_bandwidth_roaming && + !cred->min_ul_bandwidth_roaming) + return 0; /* No bandwidth constraint specified */ + + if (bss->anqp == NULL || bss->anqp->hs20_wan_metrics == NULL) + return 0; /* No WAN Metrics known - ignore constraint */ + + wan = wpabuf_head(bss->anqp->hs20_wan_metrics); + wan_info = wan[0]; + if (wan_info & BIT(3)) + return 1; /* WAN link at capacity */ + lmd = WPA_GET_LE16(wan + 11); + if (lmd == 0) + return 0; /* Downlink/Uplink Load was not measured */ + dl_speed = WPA_GET_LE32(wan + 1); + ul_speed = WPA_GET_LE32(wan + 5); + dl_load = wan[9]; + ul_load = wan[10]; + + if (dl_speed >= 0xffffff) + dl_bandwidth = dl_speed / 255 * (255 - dl_load); + else + dl_bandwidth = dl_speed * (255 - dl_load) / 255; + + if (ul_speed >= 0xffffff) + ul_bandwidth = ul_speed / 255 * (255 - ul_load); + else + ul_bandwidth = ul_speed * (255 - ul_load) / 255; + + res = interworking_home_sp_cred(wpa_s, cred, bss->anqp ? + bss->anqp->domain_name : NULL); + if (res > 0) { + if (cred->min_dl_bandwidth_home > dl_bandwidth) + return 1; + if (cred->min_ul_bandwidth_home > ul_bandwidth) + return 1; + } else { + if (cred->min_dl_bandwidth_roaming > dl_bandwidth) + return 1; + if (cred->min_ul_bandwidth_roaming > ul_bandwidth) + return 1; + } + + return 0; +} + + +static int cred_over_max_bss_load(struct wpa_supplicant *wpa_s, + struct wpa_cred *cred, struct wpa_bss *bss) +{ + const u8 *ie; + int res; + + if (!cred->max_bss_load) + return 0; /* No BSS Load constraint specified */ + + ie = wpa_bss_get_ie(bss, WLAN_EID_BSS_LOAD); + if (ie == NULL || ie[1] < 3) + return 0; /* No BSS Load advertised */ + + res = interworking_home_sp_cred(wpa_s, cred, bss->anqp ? + bss->anqp->domain_name : NULL); + if (res <= 0) + return 0; /* Not a home network */ + + return ie[4] > cred->max_bss_load; +} + + +static int has_proto_match(const u8 *pos, const u8 *end, u8 proto) +{ + while (pos + 4 <= end) { + if (pos[0] == proto && pos[3] == 1 /* Open */) + return 1; + pos += 4; + } + + return 0; +} + + +static int has_proto_port_match(const u8 *pos, const u8 *end, u8 proto, + u16 port) +{ + while (pos + 4 <= end) { + if (pos[0] == proto && WPA_GET_LE16(&pos[1]) == port && + pos[3] == 1 /* Open */) + return 1; + pos += 4; + } + + return 0; +} + + +static int cred_conn_capab_missing(struct wpa_supplicant *wpa_s, + struct wpa_cred *cred, struct wpa_bss *bss) +{ + int res; + const u8 *capab, *end; + unsigned int i, j; + int *ports; + + if (!cred->num_req_conn_capab) + return 0; /* No connection capability constraint specified */ + + if (bss->anqp == NULL || bss->anqp->hs20_connection_capability == NULL) + return 0; /* No Connection Capability known - ignore constraint + */ + + res = interworking_home_sp_cred(wpa_s, cred, bss->anqp ? + bss->anqp->domain_name : NULL); + if (res > 0) + return 0; /* No constraint in home network */ + + capab = wpabuf_head(bss->anqp->hs20_connection_capability); + end = capab + wpabuf_len(bss->anqp->hs20_connection_capability); + + for (i = 0; i < cred->num_req_conn_capab; i++) { + ports = cred->req_conn_capab_port[i]; + if (!ports) { + if (!has_proto_match(capab, end, + cred->req_conn_capab_proto[i])) + return 1; + } else { + for (j = 0; ports[j] > -1; j++) { + if (!has_proto_port_match( + capab, end, + cred->req_conn_capab_proto[i], + ports[j])) + return 1; + } + } + } + + return 0; +} + + static struct wpa_cred * interworking_credentials_available_roaming_consortium( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded) { struct wpa_cred *cred, *selected = NULL; const u8 *ie; + int is_excluded = 0; ie = wpa_bss_get_ie(bss, WLAN_EID_ROAMING_CONSORTIUM); @@ -1073,16 +1305,33 @@ static struct wpa_cred * interworking_credentials_available_roaming_consortium( cred->roaming_consortium_len)) continue; - if (cred_excluded_ssid(cred, bss)) - continue; if (cred_no_required_oi_match(cred, bss)) continue; - - if (selected == NULL || - selected->priority < cred->priority) - selected = cred; + if (!ignore_bw && cred_below_min_backhaul(wpa_s, cred, bss)) + continue; + if (!ignore_bw && cred_over_max_bss_load(wpa_s, cred, bss)) + continue; + if (!ignore_bw && cred_conn_capab_missing(wpa_s, cred, bss)) + continue; + if (cred_excluded_ssid(cred, bss)) { + if (excluded == NULL) + continue; + if (selected == NULL) { + selected = cred; + is_excluded = 1; + } + } else { + if (selected == NULL || is_excluded || + cred_prio_cmp(selected, cred) < 0) { + selected = cred; + is_excluded = 0; + } + } } + if (excluded) + *excluded = is_excluded; + return selected; } @@ -1191,6 +1440,8 @@ static int interworking_set_eap_params(struct wpa_ssid *ssid, cred->domain_suffix_match) < 0) return -1; + ssid->eap.ocsp = cred->ocsp; + return 0; } @@ -1241,6 +1492,7 @@ static int interworking_connect_roaming_consortium( cred->eap_method->method == EAP_TYPE_TTLS) < 0) goto fail; + wpa_s->next_ssid = ssid; wpa_config_update_prio_list(wpa_s->conf); interworking_reconnect(wpa_s); @@ -1253,7 +1505,8 @@ fail: } -int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) +static int interworking_connect_helper(struct wpa_supplicant *wpa_s, + struct wpa_bss *bss, int allow_excluded) { struct wpa_cred *cred, *cred_rc, *cred_3gpp; struct wpa_ssid *ssid; @@ -1261,6 +1514,7 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) struct nai_realm_eap *eap = NULL; u16 count, i; char buf[100]; + int excluded = 0, *excl = allow_excluded ? &excluded : NULL; if (wpa_s->conf->cred == NULL || bss == NULL) return -1; @@ -1271,6 +1525,10 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) return -1; } + wpa_printf(MSG_DEBUG, "Interworking: Considering BSS " MACSTR + " for connection (allow_excluded=%d)", + MAC2STR(bss->bssid), allow_excluded); + if (!wpa_bss_get_ie(bss, WLAN_EID_RSN)) { /* * We currently support only HS 2.0 networks and those are @@ -1281,35 +1539,80 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) return -1; } - cred_rc = interworking_credentials_available_roaming_consortium(wpa_s, - bss); + cred_rc = interworking_credentials_available_roaming_consortium( + wpa_s, bss, 0, excl); if (cred_rc) { wpa_printf(MSG_DEBUG, "Interworking: Highest roaming " - "consortium matching credential priority %d", - cred_rc->priority); + "consortium matching credential priority %d " + "sp_priority %d", + cred_rc->priority, cred_rc->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; } - cred = interworking_credentials_available_realm(wpa_s, bss); + cred = interworking_credentials_available_realm(wpa_s, bss, 0, excl); if (cred) { wpa_printf(MSG_DEBUG, "Interworking: Highest NAI Realm list " - "matching credential priority %d", - cred->priority); + "matching credential priority %d sp_priority %d", + cred->priority, cred->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; } - cred_3gpp = interworking_credentials_available_3gpp(wpa_s, bss); + cred_3gpp = interworking_credentials_available_3gpp(wpa_s, bss, 0, + excl); if (cred_3gpp) { wpa_printf(MSG_DEBUG, "Interworking: Highest 3GPP matching " - "credential priority %d", cred_3gpp->priority); + "credential priority %d sp_priority %d", + cred_3gpp->priority, cred_3gpp->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; + } + + if (!cred_rc && !cred && !cred_3gpp) { + wpa_printf(MSG_DEBUG, "Interworking: No full credential matches - consider options without BW(etc.) limits"); + cred_rc = interworking_credentials_available_roaming_consortium( + wpa_s, bss, 1, excl); + if (cred_rc) { + wpa_printf(MSG_DEBUG, "Interworking: Highest roaming " + "consortium matching credential priority %d " + "sp_priority %d (ignore BW)", + cred_rc->priority, cred_rc->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; + } + + cred = interworking_credentials_available_realm(wpa_s, bss, 1, + excl); + if (cred) { + wpa_printf(MSG_DEBUG, "Interworking: Highest NAI Realm " + "list matching credential priority %d " + "sp_priority %d (ignore BW)", + cred->priority, cred->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; + } + + cred_3gpp = interworking_credentials_available_3gpp(wpa_s, bss, + 1, excl); + if (cred_3gpp) { + wpa_printf(MSG_DEBUG, "Interworking: Highest 3GPP " + "matching credential priority %d " + "sp_priority %d (ignore BW)", + cred_3gpp->priority, cred_3gpp->sp_priority); + if (allow_excluded && excl && !(*excl)) + excl = NULL; + } } if (cred_rc && - (cred == NULL || cred_rc->priority >= cred->priority) && - (cred_3gpp == NULL || cred_rc->priority >= cred_3gpp->priority)) + (cred == NULL || cred_prio_cmp(cred_rc, cred) >= 0) && + (cred_3gpp == NULL || cred_prio_cmp(cred_rc, cred_3gpp) >= 0)) return interworking_connect_roaming_consortium(wpa_s, cred_rc, bss); if (cred_3gpp && - (cred == NULL || cred_3gpp->priority >= cred->priority)) { + (cred == NULL || cred_prio_cmp(cred_3gpp, cred) >= 0)) { return interworking_connect_3gpp(wpa_s, cred_3gpp, bss); } @@ -1443,6 +1746,7 @@ int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) nai_realm_free(realm, count); + wpa_s->next_ssid = ssid; wpa_config_update_prio_list(wpa_s->conf); interworking_reconnect(wpa_s); @@ -1456,13 +1760,46 @@ fail: } +int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) +{ + return interworking_connect_helper(wpa_s, bss, 1); +} + + +#ifdef PCSC_FUNCS +static int interworking_pcsc_read_imsi(struct wpa_supplicant *wpa_s) +{ + size_t len; + + if (wpa_s->imsi[0] && wpa_s->mnc_len) + return 0; + + len = sizeof(wpa_s->imsi) - 1; + if (scard_get_imsi(wpa_s->scard, wpa_s->imsi, &len)) { + scard_deinit(wpa_s->scard); + wpa_s->scard = NULL; + wpa_msg(wpa_s, MSG_ERROR, "Could not read IMSI"); + return -1; + } + wpa_s->imsi[len] = '\0'; + wpa_s->mnc_len = scard_get_mnc_len(wpa_s->scard); + wpa_printf(MSG_DEBUG, "SCARD: IMSI %s (MNC length %d)", + wpa_s->imsi, wpa_s->mnc_len); + + return 0; +} +#endif /* PCSC_FUNCS */ + + static struct wpa_cred * interworking_credentials_available_3gpp( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded) { struct wpa_cred *selected = NULL; #ifdef INTERWORKING_3GPP struct wpa_cred *cred; int ret; + int is_excluded = 0; if (bss->anqp == NULL || bss->anqp->anqp_3gpp == NULL) return NULL; @@ -1492,8 +1829,9 @@ static struct wpa_cred * interworking_credentials_available_3gpp( size_t msin_len; #ifdef PCSC_FUNCS - if (cred->pcsc && wpa_s->conf->pcsc_reader && wpa_s->scard && - wpa_s->imsi[0]) { + if (cred->pcsc && wpa_s->scard) { + if (interworking_pcsc_read_imsi(wpa_s) < 0) + continue; imsi = wpa_s->imsi; mnc_len = wpa_s->mnc_len; goto compare; @@ -1534,26 +1872,49 @@ static struct wpa_cred * interworking_credentials_available_3gpp( ret = plmn_id_match(bss->anqp->anqp_3gpp, imsi, mnc_len); wpa_printf(MSG_DEBUG, "PLMN match %sfound", ret ? "" : "not "); if (ret) { - if (cred_excluded_ssid(cred, bss)) - continue; if (cred_no_required_oi_match(cred, bss)) continue; - if (selected == NULL || - selected->priority < cred->priority) - selected = cred; + if (!ignore_bw && + cred_below_min_backhaul(wpa_s, cred, bss)) + continue; + if (!ignore_bw && + cred_over_max_bss_load(wpa_s, cred, bss)) + continue; + if (!ignore_bw && + cred_conn_capab_missing(wpa_s, cred, bss)) + continue; + if (cred_excluded_ssid(cred, bss)) { + if (excluded == NULL) + continue; + if (selected == NULL) { + selected = cred; + is_excluded = 1; + } + } else { + if (selected == NULL || is_excluded || + cred_prio_cmp(selected, cred) < 0) { + selected = cred; + is_excluded = 0; + } + } } } + + if (excluded) + *excluded = is_excluded; #endif /* INTERWORKING_3GPP */ return selected; } static struct wpa_cred * interworking_credentials_available_realm( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded) { struct wpa_cred *cred, *selected = NULL; struct nai_realm *realm; u16 count, i; + int is_excluded = 0; if (bss->anqp == NULL || bss->anqp->nai_realm == NULL) return NULL; @@ -1578,13 +1939,32 @@ static struct wpa_cred * interworking_credentials_available_realm( if (!nai_realm_match(&realm[i], cred->realm)) continue; if (nai_realm_find_eap(cred, &realm[i])) { - if (cred_excluded_ssid(cred, bss)) - continue; if (cred_no_required_oi_match(cred, bss)) continue; - if (selected == NULL || - selected->priority < cred->priority) - selected = cred; + if (!ignore_bw && + cred_below_min_backhaul(wpa_s, cred, bss)) + continue; + if (!ignore_bw && + cred_over_max_bss_load(wpa_s, cred, bss)) + continue; + if (!ignore_bw && + cred_conn_capab_missing(wpa_s, cred, bss)) + continue; + if (cred_excluded_ssid(cred, bss)) { + if (excluded == NULL) + continue; + if (selected == NULL) { + selected = cred; + is_excluded = 1; + } + } else { + if (selected == NULL || is_excluded || + cred_prio_cmp(selected, cred) < 0) + { + selected = cred; + is_excluded = 0; + } + } break; } } @@ -1592,14 +1972,19 @@ static struct wpa_cred * interworking_credentials_available_realm( nai_realm_free(realm, count); + if (excluded) + *excluded = is_excluded; + return selected; } -static struct wpa_cred * interworking_credentials_available( - struct wpa_supplicant *wpa_s, struct wpa_bss *bss) +static struct wpa_cred * interworking_credentials_available_helper( + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int ignore_bw, + int *excluded) { struct wpa_cred *cred, *cred2; + int excluded1, excluded2; if (disallowed_bssid(wpa_s, bss->bssid) || disallowed_ssid(wpa_s, bss->ssid, bss->ssid_len)) { @@ -1608,26 +1993,56 @@ static struct wpa_cred * interworking_credentials_available( return NULL; } - cred = interworking_credentials_available_realm(wpa_s, bss); - cred2 = interworking_credentials_available_3gpp(wpa_s, bss); - if (cred && cred2 && cred2->priority >= cred->priority) + cred = interworking_credentials_available_realm(wpa_s, bss, ignore_bw, + &excluded1); + cred2 = interworking_credentials_available_3gpp(wpa_s, bss, ignore_bw, + &excluded2); + if (cred && cred2 && + (cred_prio_cmp(cred2, cred) >= 0 || (!excluded2 && excluded1))) { cred = cred2; - if (!cred) + excluded1 = excluded2; + } + if (!cred) { cred = cred2; + excluded1 = excluded2; + } - cred2 = interworking_credentials_available_roaming_consortium(wpa_s, - bss); - if (cred && cred2 && cred2->priority >= cred->priority) + cred2 = interworking_credentials_available_roaming_consortium( + wpa_s, bss, ignore_bw, &excluded2); + if (cred && cred2 && + (cred_prio_cmp(cred2, cred) >= 0 || (!excluded2 && excluded1))) { cred = cred2; - if (!cred) + excluded1 = excluded2; + } + if (!cred) { cred = cred2; + excluded1 = excluded2; + } + if (excluded) + *excluded = excluded1; return cred; } -static int domain_name_list_contains(struct wpabuf *domain_names, - const char *domain) +static struct wpa_cred * interworking_credentials_available( + struct wpa_supplicant *wpa_s, struct wpa_bss *bss, int *excluded) +{ + struct wpa_cred *cred; + + if (excluded) + *excluded = 0; + cred = interworking_credentials_available_helper(wpa_s, bss, 0, + excluded); + if (cred) + return cred; + return interworking_credentials_available_helper(wpa_s, bss, 1, + excluded); +} + + +int domain_name_list_contains(struct wpabuf *domain_names, + const char *domain, int exact_match) { const u8 *pos, *end; size_t len; @@ -1645,6 +2060,12 @@ static int domain_name_list_contains(struct wpabuf *domain_names, if (pos[0] == len && os_strncasecmp(domain, (const char *) (pos + 1), len) == 0) return 1; + if (!exact_match && pos[0] > len && pos[pos[0] - len] == '.') { + const char *ap = (const char *) (pos + 1); + int offset = pos[0] - len; + if (os_strncasecmp(domain, ap + offset, len) == 0) + return 1; + } pos += 1 + pos[0]; } @@ -1666,13 +2087,14 @@ int interworking_home_sp_cred(struct wpa_supplicant *wpa_s, int mnc_len = 0; if (cred->imsi) imsi = cred->imsi; -#ifdef CONFIG_PCSC - else if (cred->pcsc && wpa_s->conf->pcsc_reader && - wpa_s->scard && wpa_s->imsi[0]) { +#ifdef PCSC_FUNCS + else if (cred->pcsc && wpa_s->scard) { + if (interworking_pcsc_read_imsi(wpa_s) < 0) + return -1; imsi = wpa_s->imsi; mnc_len = wpa_s->mnc_len; } -#endif /* CONFIG_PCSC */ +#endif /* PCSC_FUNCS */ #ifdef CONFIG_EAP_PROXY else if (cred->pcsc && wpa_s->mnc_len > 0 && wpa_s->imsi[0]) { imsi = wpa_s->imsi; @@ -1687,7 +2109,7 @@ int interworking_home_sp_cred(struct wpa_supplicant *wpa_s, wpa_printf(MSG_DEBUG, "Interworking: Search for match " "with SIM/USIM domain %s", realm); if (realm && - domain_name_list_contains(domain_names, realm)) + domain_name_list_contains(domain_names, realm, 1)) return 1; if (realm) ret = 0; @@ -1700,7 +2122,7 @@ int interworking_home_sp_cred(struct wpa_supplicant *wpa_s, for (i = 0; i < cred->num_domain; i++) { wpa_printf(MSG_DEBUG, "Interworking: Search for match with " "home SP FQDN %s", cred->domain[i]); - if (domain_name_list_contains(domain_names, cred->domain[i])) + if (domain_name_list_contains(domain_names, cred->domain[i], 1)) return 1; } @@ -1752,19 +2174,127 @@ static int interworking_find_network_match(struct wpa_supplicant *wpa_s) } +static int roaming_partner_match(struct wpa_supplicant *wpa_s, + struct roaming_partner *partner, + struct wpabuf *domain_names) +{ + wpa_printf(MSG_DEBUG, "Interworking: Comparing roaming_partner info fqdn='%s' exact_match=%d priority=%u country='%s'", + partner->fqdn, partner->exact_match, partner->priority, + partner->country); + wpa_hexdump_ascii(MSG_DEBUG, "Interworking: Domain names", + wpabuf_head(domain_names), + wpabuf_len(domain_names)); + if (!domain_name_list_contains(domain_names, partner->fqdn, + partner->exact_match)) + return 0; + /* TODO: match Country */ + return 1; +} + + +static u8 roaming_prio(struct wpa_supplicant *wpa_s, struct wpa_cred *cred, + struct wpa_bss *bss) +{ + size_t i; + + if (bss->anqp == NULL || bss->anqp->domain_name == NULL) { + wpa_printf(MSG_DEBUG, "Interworking: No ANQP domain name info -> use default roaming partner priority 128"); + return 128; /* cannot check preference with domain name */ + } + + if (interworking_home_sp_cred(wpa_s, cred, bss->anqp->domain_name) > 0) + { + wpa_printf(MSG_DEBUG, "Interworking: Determined to be home SP -> use maximum preference 0 as roaming partner priority"); + return 0; /* max preference for home SP network */ + } + + for (i = 0; i < cred->num_roaming_partner; i++) { + if (roaming_partner_match(wpa_s, &cred->roaming_partner[i], + bss->anqp->domain_name)) { + wpa_printf(MSG_DEBUG, "Interworking: Roaming partner preference match - priority %u", + cred->roaming_partner[i].priority); + return cred->roaming_partner[i].priority; + } + } + + wpa_printf(MSG_DEBUG, "Interworking: No roaming partner preference match - use default roaming partner priority 128"); + return 128; +} + + +static struct wpa_bss * pick_best_roaming_partner(struct wpa_supplicant *wpa_s, + struct wpa_bss *selected, + struct wpa_cred *cred) +{ + struct wpa_bss *bss; + u8 best_prio, prio; + struct wpa_cred *cred2; + + /* + * Check if any other BSS is operated by a more preferred roaming + * partner. + */ + + best_prio = roaming_prio(wpa_s, cred, selected); + wpa_printf(MSG_DEBUG, "Interworking: roaming_prio=%u for selected BSS " + MACSTR " (cred=%d)", best_prio, MAC2STR(selected->bssid), + cred->id); + + dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { + if (bss == selected) + continue; + cred2 = interworking_credentials_available(wpa_s, bss, NULL); + if (!cred2) + continue; + if (!wpa_bss_get_ie(bss, WLAN_EID_RSN)) + continue; + prio = roaming_prio(wpa_s, cred2, bss); + wpa_printf(MSG_DEBUG, "Interworking: roaming_prio=%u for BSS " + MACSTR " (cred=%d)", prio, MAC2STR(bss->bssid), + cred2->id); + if (prio < best_prio) { + int bh1, bh2, load1, load2, conn1, conn2; + bh1 = cred_below_min_backhaul(wpa_s, cred, selected); + load1 = cred_over_max_bss_load(wpa_s, cred, selected); + conn1 = cred_conn_capab_missing(wpa_s, cred, selected); + bh2 = cred_below_min_backhaul(wpa_s, cred2, bss); + load2 = cred_over_max_bss_load(wpa_s, cred2, bss); + conn2 = cred_conn_capab_missing(wpa_s, cred2, bss); + wpa_printf(MSG_DEBUG, "Interworking: old: %d %d %d new: %d %d %d", + bh1, load1, conn1, bh2, load2, conn2); + if (bh1 || load1 || conn1 || !(bh2 || load2 || conn2)) { + wpa_printf(MSG_DEBUG, "Interworking: Better roaming partner " MACSTR " selected", MAC2STR(bss->bssid)); + best_prio = prio; + selected = bss; + } + } + } + + return selected; +} + + static void interworking_select_network(struct wpa_supplicant *wpa_s) { struct wpa_bss *bss, *selected = NULL, *selected_home = NULL; - int selected_prio = -999999, selected_home_prio = -999999; + struct wpa_bss *selected2 = NULL, *selected2_home = NULL; unsigned int count = 0; const char *type; int res; - struct wpa_cred *cred; + struct wpa_cred *cred, *selected_cred = NULL; + struct wpa_cred *selected_home_cred = NULL; + struct wpa_cred *selected2_cred = NULL; + struct wpa_cred *selected2_home_cred = NULL; wpa_s->network_select = 0; + wpa_printf(MSG_DEBUG, "Interworking: Select network (auto_select=%d)", + wpa_s->auto_select); dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { - cred = interworking_credentials_available(wpa_s, bss); + int excluded = 0; + int bh, bss_load, conn_capab; + cred = interworking_credentials_available(wpa_s, bss, + &excluded); if (!cred) continue; if (!wpa_bss_get_ie(bss, WLAN_EID_RSN)) { @@ -1777,7 +2307,8 @@ static void interworking_select_network(struct wpa_supplicant *wpa_s) "RSN", MAC2STR(bss->bssid)); continue; } - count++; + if (!excluded) + count++; res = interworking_home_sp(wpa_s, bss->anqp ? bss->anqp->domain_name : NULL); if (res > 0) @@ -1786,29 +2317,75 @@ static void interworking_select_network(struct wpa_supplicant *wpa_s) type = "roaming"; else type = "unknown"; - wpa_msg(wpa_s, MSG_INFO, INTERWORKING_AP MACSTR " type=%s", - MAC2STR(bss->bssid), type); + bh = cred_below_min_backhaul(wpa_s, cred, bss); + bss_load = cred_over_max_bss_load(wpa_s, cred, bss); + conn_capab = cred_conn_capab_missing(wpa_s, cred, bss); + wpa_msg(wpa_s, MSG_INFO, "%s" MACSTR " type=%s%s%s%s id=%d priority=%d sp_priority=%d", + excluded ? INTERWORKING_BLACKLISTED : INTERWORKING_AP, + MAC2STR(bss->bssid), type, + bh ? " below_min_backhaul=1" : "", + bss_load ? " over_max_bss_load=1" : "", + conn_capab ? " conn_capab_missing=1" : "", + cred->id, cred->priority, cred->sp_priority); + if (excluded) + continue; if (wpa_s->auto_select || (wpa_s->conf->auto_interworking && wpa_s->auto_network_select)) { - if (selected == NULL || - cred->priority > selected_prio) { - selected = bss; - selected_prio = cred->priority; - } - if (res > 0 && - (selected_home == NULL || - cred->priority > selected_home_prio)) { - selected_home = bss; - selected_home_prio = cred->priority; + if (bh || bss_load || conn_capab) { + if (selected2_cred == NULL || + cred_prio_cmp(cred, selected2_cred) > 0) { + wpa_printf(MSG_DEBUG, "Interworking: Mark as selected2"); + selected2 = bss; + selected2_cred = cred; + } + if (res > 0 && + (selected2_home_cred == NULL || + cred_prio_cmp(cred, selected2_home_cred) > + 0)) { + wpa_printf(MSG_DEBUG, "Interworking: Mark as selected2_home"); + selected2_home = bss; + selected2_home_cred = cred; + } + } else { + if (selected_cred == NULL || + cred_prio_cmp(cred, selected_cred) > 0) { + wpa_printf(MSG_DEBUG, "Interworking: Mark as selected"); + selected = bss; + selected_cred = cred; + } + if (res > 0 && + (selected_home_cred == NULL || + cred_prio_cmp(cred, selected_home_cred) > + 0)) { + wpa_printf(MSG_DEBUG, "Interworking: Mark as selected_home"); + selected_home = bss; + selected_home_cred = cred; + } } } } if (selected_home && selected_home != selected && - selected_home_prio >= selected_prio) { + selected_home_cred && + (selected_cred == NULL || + cred_prio_cmp(selected_home_cred, selected_cred) >= 0)) { /* Prefer network operated by the Home SP */ + wpa_printf(MSG_DEBUG, "Interworking: Overrided selected with selected_home"); selected = selected_home; + selected_cred = selected_home_cred; + } + + if (!selected) { + if (selected2_home) { + wpa_printf(MSG_DEBUG, "Interworking: Use home BSS with BW limit mismatch since no other network could be selected"); + selected = selected2_home; + selected_cred = selected2_home_cred; + } else if (selected2) { + wpa_printf(MSG_DEBUG, "Interworking: Use visited BSS with BW limit mismatch since no other network could be selected"); + selected = selected2; + selected_cred = selected2_cred; + } } if (count == 0) { @@ -1820,9 +2397,10 @@ static void interworking_select_network(struct wpa_supplicant *wpa_s) if (interworking_find_network_match(wpa_s)) { wpa_printf(MSG_DEBUG, "Interworking: Possible BSS " "match for enabled network configurations"); - if (wpa_s->auto_select) + if (wpa_s->auto_select) { interworking_reconnect(wpa_s); - return; + return; + } } if (wpa_s->auto_network_select) { @@ -1837,8 +2415,18 @@ static void interworking_select_network(struct wpa_supplicant *wpa_s) "with matching credentials found"); } - if (selected) + if (selected) { + wpa_printf(MSG_DEBUG, "Interworking: Selected " MACSTR, + MAC2STR(selected->bssid)); + selected = pick_best_roaming_partner(wpa_s, selected, + selected_cred); + wpa_printf(MSG_DEBUG, "Interworking: Selected " MACSTR + " (after best roaming partner selection)", + MAC2STR(selected->bssid)); + wpa_msg(wpa_s, MSG_INFO, INTERWORKING_SELECTED MACSTR, + MAC2STR(selected->bssid)); interworking_connect(wpa_s, selected); + } } @@ -1885,8 +2473,21 @@ static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s) int found = 0; const u8 *ie; - if (eloop_terminated() || !wpa_s->fetch_anqp_in_progress) + wpa_printf(MSG_DEBUG, "Interworking: next_anqp_fetch - " + "fetch_anqp_in_progress=%d fetch_osu_icon_in_progress=%d", + wpa_s->fetch_anqp_in_progress, + wpa_s->fetch_osu_icon_in_progress); + + if (eloop_terminated() || !wpa_s->fetch_anqp_in_progress) { + wpa_printf(MSG_DEBUG, "Interworking: Stop next-ANQP-fetch"); return; + } + + if (wpa_s->fetch_osu_icon_in_progress) { + wpa_printf(MSG_DEBUG, "Interworking: Next icon (in progress)"); + hs20_next_osu_icon(wpa_s); + return; + } dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { if (!(bss->caps & IEEE80211_CAP_ESS)) @@ -1920,6 +2521,17 @@ static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s) } if (found == 0) { + if (wpa_s->fetch_osu_info) { + if (wpa_s->num_prov_found == 0 && + wpa_s->num_osu_scans < 3) { + wpa_printf(MSG_DEBUG, "HS 2.0: No OSU providers seen - try to scan again"); + hs20_start_osu_scan(wpa_s); + return; + } + wpa_printf(MSG_DEBUG, "Interworking: Next icon"); + hs20_osu_icon_fetch(wpa_s); + return; + } wpa_msg(wpa_s, MSG_INFO, "ANQP fetch completed"); wpa_s->fetch_anqp_in_progress = 0; if (wpa_s->network_select) @@ -1947,6 +2559,7 @@ int interworking_fetch_anqp(struct wpa_supplicant *wpa_s) wpa_s->network_select = 0; wpa_s->fetch_all_anqp = 1; + wpa_s->fetch_osu_info = 0; interworking_start_fetch_anqp(wpa_s); @@ -1964,9 +2577,10 @@ void interworking_stop_fetch_anqp(struct wpa_supplicant *wpa_s) int anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst, - u16 info_ids[], size_t num_ids) + u16 info_ids[], size_t num_ids, u32 subtypes) { struct wpabuf *buf; + struct wpabuf *hs20_buf = NULL; int ret = 0; int freq; struct wpa_bss *bss; @@ -1984,7 +2598,17 @@ int anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst, wpa_printf(MSG_DEBUG, "ANQP: Query Request to " MACSTR " for %u id(s)", MAC2STR(dst), (unsigned int) num_ids); - buf = anqp_build_req(info_ids, num_ids, NULL); +#ifdef CONFIG_HS20 + if (subtypes != 0) { + hs20_buf = wpabuf_alloc(100); + if (hs20_buf == NULL) + return -1; + hs20_put_anqp_req(subtypes, NULL, 0, hs20_buf); + } +#endif /* CONFIG_HS20 */ + + buf = anqp_build_req(info_ids, num_ids, hs20_buf); + wpabuf_free(hs20_buf); if (buf == NULL) return -1; @@ -2144,14 +2768,22 @@ void anqp_resp_cb(void *ctx, const u8 *dst, u8 dialog_token, u16 slen; struct wpa_bss *bss = NULL, *tmp; - if (result != GAS_QUERY_SUCCESS) + wpa_printf(MSG_DEBUG, "Interworking: anqp_resp_cb dst=" MACSTR + " dialog_token=%u result=%d status_code=%u", + MAC2STR(dst), dialog_token, result, status_code); + if (result != GAS_QUERY_SUCCESS) { + if (wpa_s->fetch_osu_icon_in_progress) + hs20_icon_fetch_failed(wpa_s); return; + } pos = wpabuf_head(adv_proto); if (wpabuf_len(adv_proto) < 4 || pos[0] != WLAN_EID_ADV_PROTO || pos[1] < 2 || pos[3] != ACCESS_NETWORK_QUERY_PROTOCOL) { wpa_printf(MSG_DEBUG, "ANQP: Unexpected Advertisement " "Protocol in response"); + if (wpa_s->fetch_osu_icon_in_progress) + hs20_icon_fetch_failed(wpa_s); return; } @@ -2191,6 +2823,8 @@ void anqp_resp_cb(void *ctx, const u8 *dst, u8 dialog_token, slen); pos += slen; } + + hs20_notify_parse_done(wpa_s); } @@ -2211,6 +2845,7 @@ int interworking_select(struct wpa_supplicant *wpa_s, int auto_select, wpa_s->auto_network_select = 0; wpa_s->auto_select = !!auto_select; wpa_s->fetch_all_anqp = 0; + wpa_s->fetch_osu_info = 0; wpa_printf(MSG_DEBUG, "Interworking: Start scan for network " "selection"); wpa_s->scan_res_handler = interworking_scan_res_handler; diff --git a/wpa_supplicant/interworking.h b/wpa_supplicant/interworking.h index c8e7093..38ef745 100644 --- a/wpa_supplicant/interworking.h +++ b/wpa_supplicant/interworking.h @@ -12,7 +12,7 @@ enum gas_query_result; int anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst, - u16 info_ids[], size_t num_ids); + u16 info_ids[], size_t num_ids, u32 subtypes); void anqp_resp_cb(void *ctx, const u8 *dst, u8 dialog_token, enum gas_query_result result, const struct wpabuf *adv_proto, @@ -29,5 +29,7 @@ void interworking_start_fetch_anqp(struct wpa_supplicant *wpa_s); int interworking_home_sp_cred(struct wpa_supplicant *wpa_s, struct wpa_cred *cred, struct wpabuf *domain_names); +int domain_name_list_contains(struct wpabuf *domain_names, + const char *domain, int exact_match); #endif /* INTERWORKING_H */ diff --git a/wpa_supplicant/main.c b/wpa_supplicant/main.c index d56935d..d2e839d 100644 --- a/wpa_supplicant/main.c +++ b/wpa_supplicant/main.c @@ -43,6 +43,9 @@ static void usage(void) " [-o<override driver>] [-O<override ctrl>] \\\n" " [-N -i<ifname> -c<conf> [-C<ctrl>] " "[-D<driver>] \\\n" +#ifdef CONFIG_P2P + " [-m<P2P Device config file>] \\\n" +#endif /* CONFIG_P2P */ " [-p<driver_param>] [-b<br_ifname>] [-I<config file>] " "...]\n" "\n" @@ -92,6 +95,9 @@ static void usage(void) #endif /* CONFIG_DBUS */ printf(" -v = show version\n" " -W = wait for a control interface monitor before starting\n" +#ifdef CONFIG_P2P + " -m = Configuration file for the P2P Device interface\n" +#endif /* CONFIG_P2P */ " -N = start describing new interface\n"); printf("example:\n" @@ -169,7 +175,7 @@ int main(int argc, char *argv[]) for (;;) { c = getopt(argc, argv, - "b:Bc:C:D:de:f:g:G:hi:I:KLNo:O:p:P:qsTtuvW"); + "b:Bc:C:D:de:f:g:G:hi:I:KLm:No:O:p:P:qsTtuvW"); if (c < 0) break; switch (c) { @@ -229,6 +235,11 @@ int main(int argc, char *argv[]) license(); exitcode = 0; goto out; +#ifdef CONFIG_P2P + case 'm': + iface->conf_p2p_dev = optarg; + break; +#endif /* CONFIG_P2P */ case 'o': params.override_driver = optarg; break; diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c index a82fbf3..2db1d54 100644 --- a/wpa_supplicant/notify.c +++ b/wpa_supplicant/notify.c @@ -252,6 +252,8 @@ void wpas_notify_persistent_group_removed(struct wpa_supplicant *wpa_s, void wpas_notify_network_removed(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) { + if (wpa_s->next_ssid == ssid) + wpa_s->next_ssid = NULL; if (wpa_s->wpa) wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid); if (!ssid->p2p_group && wpa_s->global->p2p_group_formation != wpa_s) diff --git a/wpa_supplicant/offchannel.c b/wpa_supplicant/offchannel.c index 40cbea1..77683b6 100644 --- a/wpa_supplicant/offchannel.c +++ b/wpa_supplicant/offchannel.c @@ -55,11 +55,12 @@ static void wpas_send_action_cb(void *eloop_ctx, void *timeout_ctx) without_roc = wpa_s->pending_action_without_roc; wpa_s->pending_action_without_roc = 0; - wpa_printf(MSG_DEBUG, "Off-channel: Send Action callback " - "(without_roc=%d pending_action_tx=%p)", - without_roc, wpa_s->pending_action_tx); + wpa_printf(MSG_DEBUG, + "Off-channel: Send Action callback (without_roc=%d pending_action_tx=%p pending_action_tx_done=%d)", + without_roc, wpa_s->pending_action_tx, + !!wpa_s->pending_action_tx_done); - if (wpa_s->pending_action_tx == NULL) + if (wpa_s->pending_action_tx == NULL || wpa_s->pending_action_tx_done) return; /* @@ -235,6 +236,7 @@ int offchannel_send_action(struct wpa_supplicant *wpa_s, unsigned int freq, MAC2STR(wpa_s->pending_action_dst)); wpabuf_free(wpa_s->pending_action_tx); } + wpa_s->pending_action_tx_done = 0; wpa_s->pending_action_tx = wpabuf_alloc(len); if (wpa_s->pending_action_tx == NULL) { wpa_printf(MSG_DEBUG, "Off-channel: Failed to allocate Action " @@ -251,18 +253,22 @@ int offchannel_send_action(struct wpa_supplicant *wpa_s, unsigned int freq, if (freq != 0 && wpa_s->drv_flags & WPA_DRIVER_FLAGS_OFFCHANNEL_TX) { struct wpa_supplicant *iface; + int ret; iface = wpas_get_tx_interface(wpa_s, wpa_s->pending_action_src); wpa_s->action_tx_wait_time = wait_time; - return wpa_drv_send_action( + ret = wpa_drv_send_action( iface, wpa_s->pending_action_freq, wait_time, wpa_s->pending_action_dst, wpa_s->pending_action_src, wpa_s->pending_action_bssid, wpabuf_head(wpa_s->pending_action_tx), wpabuf_len(wpa_s->pending_action_tx), wpa_s->pending_action_no_cck); + if (ret == 0) + wpa_s->pending_action_tx_done = 1; + return ret; } if (freq) { diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c index 2928b6f..393f13b 100644 --- a/wpa_supplicant/p2p_supplicant.c +++ b/wpa_supplicant/p2p_supplicant.c @@ -57,7 +57,8 @@ #ifndef P2P_MAX_INITIAL_CONN_WAIT /* * How many seconds to wait for initial 4-way handshake to get completed after - * WPS provisioning step. + * WPS provisioning step or after the re-invocation of a persistent group on a + * P2P Client. */ #define P2P_MAX_INITIAL_CONN_WAIT 10 #endif /* P2P_MAX_INITIAL_CONN_WAIT */ @@ -123,6 +124,8 @@ static void wpas_p2p_group_freq_conflict(void *eloop_ctx, void *timeout_ctx); static void wpas_p2p_fallback_to_go_neg(struct wpa_supplicant *wpa_s, int group_added); static int wpas_p2p_stop_find_oper(struct wpa_supplicant *wpa_s); +static void wpas_stop_listen(void *ctx); +static void wpas_p2p_psk_failure_removal(void *eloop_ctx, void *timeout_ctx); /* @@ -252,7 +255,12 @@ static void wpas_p2p_trigger_scan_cb(struct wpa_radio_work *work, int deinit) int ret; if (deinit) { - wpa_scan_free_params(params); + if (!work->started) { + wpa_scan_free_params(params); + return; + } + + wpa_s->p2p_scan_work = NULL; return; } @@ -355,7 +363,7 @@ static int wpas_p2p_scan(void *ctx, enum p2p_scan_type type, int freq, break; } - radio_remove_unstarted_work(wpa_s, "p2p-scan"); + radio_remove_works(wpa_s, "p2p-scan", 0); if (radio_add_work(wpa_s, 0, "p2p-scan", 0, wpas_p2p_trigger_scan_cb, params) < 0) goto fail; @@ -495,6 +503,8 @@ static int wpas_p2p_group_delete(struct wpa_supplicant *wpa_s, wpa_s->p2p_in_provisioning = 0; } + wpa_s->p2p_in_invitation = 0; + /* * Make sure wait for the first client does not remain active after the * group has been removed. @@ -716,12 +726,10 @@ static int wpas_p2p_store_persistent_group(struct wpa_supplicant *wpa_s, changed = 1; } -#ifndef CONFIG_NO_CONFIG_WRITE if (changed && wpa_s->conf->update_config && wpa_config_write(wpa_s->confname, wpa_s->conf)) { wpa_printf(MSG_DEBUG, "P2P: Failed to update configuration"); } -#endif /* CONFIG_NO_CONFIG_WRITE */ return s->id; } @@ -789,11 +797,9 @@ static void wpas_p2p_add_persistent_group_client(struct wpa_supplicant *wpa_s, addr, ETH_ALEN); } -#ifndef CONFIG_NO_CONFIG_WRITE if (wpa_s->parent->conf->update_config && wpa_config_write(wpa_s->parent->confname, wpa_s->parent->conf)) wpa_printf(MSG_DEBUG, "P2P: Failed to update configuration"); -#endif /* CONFIG_NO_CONFIG_WRITE */ } @@ -818,6 +824,7 @@ static void wpas_group_formation_completed(struct wpa_supplicant *wpa_s, wpa_s->global->p2p_group_formation = NULL; wpa_s->p2p_in_provisioning = 0; } + wpa_s->p2p_in_invitation = 0; if (!success) { wpa_msg_global(wpa_s->parent, MSG_INFO, @@ -1001,6 +1008,12 @@ static void wpas_send_action_cb(struct wpa_radio_work *work, int deinit) struct send_action_work *awork = work->ctx; if (deinit) { + if (work->started) { + eloop_cancel_timeout(wpas_p2p_send_action_work_timeout, + wpa_s, NULL); + wpa_s->p2p_send_action_work = NULL; + offchannel_send_action_done(wpa_s); + } os_free(awork); return; } @@ -1338,6 +1351,7 @@ static void wpas_start_wps_go(struct wpa_supplicant *wpa_s, wpa_s->ap_configured_cb = p2p_go_configured; wpa_s->ap_configured_cb_ctx = wpa_s; wpa_s->ap_configured_cb_data = wpa_s->go_params; + wpa_s->scan_req = NORMAL_SCAN_REQ; wpa_s->connect_without_scan = ssid; wpa_s->reassociate = 1; wpa_s->disconnected = 0; @@ -1614,6 +1628,9 @@ static void wpas_go_neg_completed(void *ctx, struct p2p_go_neg_results *res) wpas_p2p_init_group_interface(wpa_s, res->role_go); if (group_wpa_s == NULL) { wpas_p2p_remove_pending_group_interface(wpa_s); + eloop_cancel_timeout(wpas_p2p_long_listen_timeout, + wpa_s, NULL); + wpas_p2p_group_formation_failed(wpa_s); return; } if (group_wpa_s != wpa_s) { @@ -1746,6 +1763,10 @@ static void wpas_start_listen_cb(struct wpa_radio_work *work, int deinit) struct wpas_p2p_listen_work *lwork = work->ctx; if (deinit) { + if (work->started) { + wpa_s->p2p_listen_work = NULL; + wpas_stop_listen(wpa_s); + } wpas_p2p_listen_work_free(lwork); return; } @@ -2956,6 +2977,7 @@ static u8 wpas_invitation_process(void *ctx, const u8 *sa, const u8 *bssid, os_memcmp(sa, wpa_s->p2p_auth_invite, ETH_ALEN) == 0) { wpa_printf(MSG_DEBUG, "P2P: Accept previously initiated " "invitation to re-invoke a persistent group"); + os_memset(wpa_s->p2p_auth_invite, 0, ETH_ALEN); } else if (!wpa_s->conf->persistent_reconnect) return P2P_SC_FAIL_INFO_CURRENTLY_UNAVAILABLE; @@ -3055,7 +3077,7 @@ static void wpas_invitation_received(void *ctx, const u8 *sa, const u8 *bssid, if (s) { int go = s->mode == WPAS_MODE_P2P_GO; wpas_p2p_group_add_persistent( - wpa_s, s, go, go ? op_freq : 0, 0, 0, NULL, + wpa_s, s, go, 0, op_freq, 0, 0, NULL, go ? P2P_MAX_INITIAL_CONN_WAIT_GO_REINVOKE : 0); } else if (bssid) { wpa_s->user_initiated_pd = 0; @@ -3136,11 +3158,9 @@ static void wpas_remove_persistent_peer(struct wpa_supplicant *wpa_s, ssid->p2p_client_list + (i + 1) * ETH_ALEN, (ssid->num_p2p_clients - i - 1) * ETH_ALEN); ssid->num_p2p_clients--; -#ifndef CONFIG_NO_CONFIG_WRITE if (wpa_s->parent->conf->update_config && wpa_config_write(wpa_s->parent->confname, wpa_s->parent->conf)) wpa_printf(MSG_DEBUG, "P2P: Failed to update configuration"); -#endif /* CONFIG_NO_CONFIG_WRITE */ } @@ -3164,7 +3184,8 @@ static void wpas_remove_persistent_client(struct wpa_supplicant *wpa_s, static void wpas_invitation_result(void *ctx, int status, const u8 *bssid, const struct p2p_channels *channels, - const u8 *peer, int neg_freq) + const u8 *peer, int neg_freq, + int peer_oper_freq) { struct wpa_supplicant *wpa_s = ctx; struct wpa_ssid *ssid; @@ -3224,16 +3245,20 @@ static void wpas_invitation_result(void *ctx, int status, const u8 *bssid, "starting persistent group"); os_sleep(0, 50000); - freq = wpa_s->p2p_persistent_go_freq; if (neg_freq > 0 && ssid->mode == WPAS_MODE_P2P_GO && - freq_included(channels, neg_freq)) { - wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Use frequence %d MHz from invitation for GO mode", - neg_freq); + freq_included(channels, neg_freq)) freq = neg_freq; - } + else if (peer_oper_freq > 0 && ssid->mode != WPAS_MODE_P2P_GO && + freq_included(channels, peer_oper_freq)) + freq = peer_oper_freq; + else + freq = 0; + wpa_printf(MSG_DEBUG, "P2P: Persistent group invitation success - op_freq=%d MHz SSID=%s", + freq, wpa_ssid_txt(ssid->ssid, ssid->ssid_len)); wpas_p2p_group_add_persistent(wpa_s, ssid, ssid->mode == WPAS_MODE_P2P_GO, + wpa_s->p2p_persistent_go_freq, freq, wpa_s->p2p_go_ht40, wpa_s->p2p_go_vht, channels, @@ -3469,7 +3494,7 @@ static enum chan_allowed wpas_p2p_verify_channel(struct wpa_supplicant *wpa_s, struct hostapd_hw_modes *mode, u8 channel, u8 bw) { - int flag; + int flag = 0; enum chan_allowed res, res2; res2 = res = has_channel(wpa_s->global, mode, channel, &flag); @@ -3678,7 +3703,20 @@ int wpas_p2p_add_p2pdev_interface(struct wpa_supplicant *wpa_s) iface.ifname = wpa_s->pending_interface_name; iface.driver = wpa_s->driver->name; iface.driver_param = wpa_s->conf->driver_param; - iface.confname = wpa_s->confname; + + /* + * If a P2P Device configuration file was given, use it as the interface + * configuration file (instead of using parent's configuration file. + */ + if (wpa_s->conf_p2p_dev) { + iface.confname = wpa_s->conf_p2p_dev; + iface.ctrl_interface = NULL; + } else { + iface.confname = wpa_s->confname; + iface.ctrl_interface = wpa_s->conf->ctrl_interface; + } + iface.conf_p2p_dev = NULL; + p2pdev_wpa_s = wpa_supplicant_add_iface(wpa_s->global, &iface); if (!p2pdev_wpa_s) { wpa_printf(MSG_DEBUG, "P2P: Failed to add P2P Device interface"); @@ -3713,6 +3751,13 @@ static void wpas_presence_resp(void *ctx, const u8 *src, u8 status, } +static int _wpas_p2p_in_progress(void *ctx) +{ + struct wpa_supplicant *wpa_s = ctx; + return wpas_p2p_in_progress(wpa_s); +} + + /** * wpas_p2p_init - Initialize P2P module for %wpa_supplicant * @global: Pointer to global data from wpa_supplicant_init() @@ -3760,6 +3805,7 @@ int wpas_p2p_init(struct wpa_global *global, struct wpa_supplicant *wpa_s) p2p.go_connected = wpas_go_connected; p2p.presence_resp = wpas_presence_resp; p2p.is_concurrent_session_active = wpas_is_concurrent_session_active; + p2p.is_p2p_in_progress = _wpas_p2p_in_progress; os_memcpy(wpa_s->global->p2p_dev_addr, wpa_s->own_addr, ETH_ALEN); os_memcpy(p2p.dev_addr, wpa_s->global->p2p_dev_addr, ETH_ALEN); @@ -3889,6 +3935,7 @@ void wpas_p2p_deinit(struct wpa_supplicant *wpa_s) os_free(wpa_s->go_params); wpa_s->go_params = NULL; + eloop_cancel_timeout(wpas_p2p_psk_failure_removal, wpa_s, NULL); eloop_cancel_timeout(wpas_p2p_group_formation_timeout, wpa_s, NULL); eloop_cancel_timeout(wpas_p2p_join_scan, wpa_s, NULL); wpa_s->p2p_long_listen = 0; @@ -4763,6 +4810,10 @@ void wpas_p2p_remain_on_channel_cb(struct wpa_supplicant *wpa_s, p2p_listen_cb(wpa_s->global->p2p, wpa_s->pending_listen_freq, wpa_s->pending_listen_duration); wpa_s->pending_listen_freq = 0; + } else { + wpa_printf(MSG_DEBUG, "P2P: Ignore remain-on-channel callback (off_channel_freq=%u pending_listen_freq=%d freq=%u duration=%u)", + wpa_s->off_channel_freq, wpa_s->pending_listen_freq, + freq, duration); } } @@ -5128,7 +5179,8 @@ int wpas_p2p_group_add(struct wpa_supplicant *wpa_s, int persistent_group, static int wpas_start_p2p_client(struct wpa_supplicant *wpa_s, - struct wpa_ssid *params, int addr_allocated) + struct wpa_ssid *params, int addr_allocated, + int freq) { struct wpa_ssid *ssid; @@ -5165,7 +5217,14 @@ static int wpas_start_p2p_client(struct wpa_supplicant *wpa_s, ssid->passphrase = os_strdup(params->passphrase); wpa_s->show_group_started = 1; + wpa_s->p2p_in_invitation = 1; + wpa_s->p2p_invite_go_freq = freq; + eloop_cancel_timeout(wpas_p2p_group_formation_timeout, wpa_s->parent, + NULL); + eloop_register_timeout(P2P_MAX_INITIAL_CONN_WAIT, 0, + wpas_p2p_group_formation_timeout, + wpa_s->parent, NULL); wpa_supplicant_select_network(wpa_s, ssid); return 0; @@ -5174,12 +5233,12 @@ static int wpas_start_p2p_client(struct wpa_supplicant *wpa_s, int wpas_p2p_group_add_persistent(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, int addr_allocated, - int freq, int ht40, int vht, - const struct p2p_channels *channels, + int force_freq, int neg_freq, int ht40, + int vht, const struct p2p_channels *channels, int connection_timeout) { struct p2p_go_neg_results params; - int go = 0; + int go = 0, freq; if (ssid->disabled != 2 || ssid->ssid == NULL) return -1; @@ -5199,16 +5258,22 @@ int wpas_p2p_group_add_persistent(struct wpa_supplicant *wpa_s, wpa_s->p2p_fallback_to_go_neg = 0; + if (force_freq > 0) { + freq = wpas_p2p_select_go_freq(wpa_s, force_freq); + if (freq < 0) + return -1; + } else { + freq = wpas_p2p_select_go_freq(wpa_s, neg_freq); + if (freq < 0 || (freq > 0 && !freq_included(channels, freq))) + freq = 0; + } + if (ssid->mode == WPAS_MODE_INFRA) - return wpas_start_p2p_client(wpa_s, ssid, addr_allocated); + return wpas_start_p2p_client(wpa_s, ssid, addr_allocated, freq); if (ssid->mode != WPAS_MODE_P2P_GO) return -1; - freq = wpas_p2p_select_go_freq(wpa_s, freq); - if (freq < 0) - return -1; - if (wpas_p2p_init_go_params(wpa_s, ¶ms, freq, ht40, vht, channels)) return -1; @@ -6340,9 +6405,17 @@ int wpas_p2p_notif_pbc_overlap(struct wpa_supplicant *wpa_s) } +void wpas_p2p_pbc_overlap_cb(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + wpas_p2p_notif_pbc_overlap(wpa_s); +} + + void wpas_p2p_update_channel_list(struct wpa_supplicant *wpa_s) { struct p2p_channels chan, cli_chan; + struct wpa_supplicant *ifs; if (wpa_s->global == NULL || wpa_s->global->p2p == NULL) return; @@ -6356,6 +6429,28 @@ void wpas_p2p_update_channel_list(struct wpa_supplicant *wpa_s) } p2p_update_channel_list(wpa_s->global->p2p, &chan, &cli_chan); + + for (ifs = wpa_s->global->ifaces; ifs; ifs = ifs->next) { + int freq; + if (!ifs->current_ssid || + !ifs->current_ssid->p2p_group || + (ifs->current_ssid->mode != WPAS_MODE_P2P_GO && + ifs->current_ssid->mode != WPAS_MODE_P2P_GROUP_FORMATION)) + continue; + freq = ifs->current_ssid->frequency; + if (freq_included(&chan, freq)) { + wpa_dbg(ifs, MSG_DEBUG, + "P2P GO operating frequency %d MHz in valid range", + freq); + continue; + } + + wpa_dbg(ifs, MSG_DEBUG, + "P2P GO operating in invalid frequency %d MHz", freq); + /* TODO: Consider using CSA or removing the group within + * wpa_supplicant */ + wpa_msg(ifs, MSG_INFO, P2P_EVENT_REMOVE_AND_REFORM_GROUP); + } } @@ -6421,6 +6516,11 @@ int wpas_p2p_cancel(struct wpa_supplicant *wpa_s) wpas_p2p_group_delete(wpa_s, P2P_GROUP_REMOVAL_REQUESTED); break; + } else if (wpa_s->p2p_in_invitation) { + wpa_printf(MSG_DEBUG, "P2P: Interface %s in invitation found - cancelling", + wpa_s->ifname); + found = 1; + wpas_p2p_group_formation_failed(wpa_s); } } @@ -6610,6 +6710,7 @@ void wpas_p2p_notify_ap_sta_authorized(struct wpa_supplicant *wpa_s, wpa_s->p2p_go_group_formation_completed = 1; wpa_s->global->p2p_group_formation = NULL; wpa_s->p2p_in_provisioning = 0; + wpa_s->p2p_in_invitation = 0; } wpa_s->global->p2p_go_wait_client.sec = 0; if (addr == NULL) @@ -6715,7 +6816,7 @@ void wpas_p2p_new_psk_cb(struct wpa_supplicant *wpa_s, const u8 *mac_addr, { struct wpa_ssid *ssid = wpa_s->current_ssid; struct wpa_ssid *persistent; - struct psk_list_entry *p; + struct psk_list_entry *p, *last; if (psk_len != sizeof(p->psk)) return; @@ -6775,10 +6876,9 @@ void wpas_p2p_new_psk_cb(struct wpa_supplicant *wpa_s, const u8 *mac_addr, } os_memcpy(p->psk, psk, psk_len); - if (dl_list_len(&persistent->psk_list) > P2P_MAX_STORED_CLIENTS) { - struct psk_list_entry *last; - last = dl_list_last(&persistent->psk_list, - struct psk_list_entry, list); + if (dl_list_len(&persistent->psk_list) > P2P_MAX_STORED_CLIENTS && + (last = dl_list_last(&persistent->psk_list, + struct psk_list_entry, list))) { wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Remove oldest PSK entry for " MACSTR " (p2p=%u) to make room for a new one", MAC2STR(last->addr), last->p2p); @@ -6798,11 +6898,9 @@ void wpas_p2p_new_psk_cb(struct wpa_supplicant *wpa_s, const u8 *mac_addr, } dl_list_add(&persistent->psk_list, &p->list); -#ifndef CONFIG_NO_CONFIG_WRITE if (wpa_s->parent->conf->update_config && wpa_config_write(wpa_s->parent->confname, wpa_s->parent->conf)) wpa_printf(MSG_DEBUG, "P2P: Failed to update configuration"); -#endif /* CONFIG_NO_CONFIG_WRITE */ } @@ -6813,14 +6911,10 @@ static void wpas_p2p_remove_psk(struct wpa_supplicant *wpa_s, int res; res = wpas_p2p_remove_psk_entry(wpa_s, s, addr, iface_addr); - if (res > 0) { -#ifndef CONFIG_NO_CONFIG_WRITE - if (wpa_s->conf->update_config && - wpa_config_write(wpa_s->confname, wpa_s->conf)) - wpa_dbg(wpa_s, MSG_DEBUG, - "P2P: Failed to update configuration"); -#endif /* CONFIG_NO_CONFIG_WRITE */ - } + if (res > 0 && wpa_s->conf->update_config && + wpa_config_write(wpa_s->confname, wpa_s->conf)) + wpa_dbg(wpa_s, MSG_DEBUG, + "P2P: Failed to update configuration"); } @@ -7526,7 +7620,16 @@ int wpas_p2p_nfc_tag_enabled(struct wpa_supplicant *wpa_s, int enabled) return -1; wpa_s->p2p_peer_oob_pk_hash_known = 0; - wpa_s->create_p2p_iface = wpas_p2p_create_iface(wpa_s); + if (wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_GO || + wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_CLIENT) { + /* + * P2P Group Interface present and the command came on group + * interface, so enable the token for the current interface. + */ + wpa_s->create_p2p_iface = 0; + } else { + wpa_s->create_p2p_iface = wpas_p2p_create_iface(wpa_s); + } if (wpa_s->create_p2p_iface) { enum wpa_driver_if_type iftype; diff --git a/wpa_supplicant/p2p_supplicant.h b/wpa_supplicant/p2p_supplicant.h index 685313c..0bf3ca9 100644 --- a/wpa_supplicant/p2p_supplicant.h +++ b/wpa_supplicant/p2p_supplicant.h @@ -36,8 +36,8 @@ int wpas_p2p_group_add(struct wpa_supplicant *wpa_s, int persistent_group, int freq, int ht40, int vht); int wpas_p2p_group_add_persistent(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, int addr_allocated, - int freq, int ht40, int vht, - const struct p2p_channels *channels, + int force_freq, int neg_freq, int ht40, + int vht, const struct p2p_channels *channels, int connection_timeout); struct p2p_group * wpas_p2p_group_init(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid); @@ -158,6 +158,7 @@ int wpas_p2p_nfc_report_handover(struct wpa_supplicant *wpa_s, int init, const struct wpabuf *req, const struct wpabuf *sel, int forced_freq); int wpas_p2p_nfc_tag_enabled(struct wpa_supplicant *wpa_s, int enabled); +void wpas_p2p_pbc_overlap_cb(void *eloop_ctx, void *timeout_ctx); #ifdef CONFIG_P2P int wpas_p2p_4way_hs_failed(struct wpa_supplicant *wpa_s); diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c index 18d243e..4d96e82 100644 --- a/wpa_supplicant/scan.c +++ b/wpa_supplicant/scan.c @@ -148,7 +148,13 @@ static void wpas_trigger_scan_cb(struct wpa_radio_work *work, int deinit) int ret; if (deinit) { - wpa_scan_free_params(params); + if (!work->started) { + wpa_scan_free_params(params); + return; + } + wpa_supplicant_notify_scanning(wpa_s, 0); + wpas_notify_scan_done(wpa_s, 0); + wpa_s->scan_work = NULL; return; } @@ -320,6 +326,32 @@ static void wpa_supplicant_optimize_freqs( } wpa_s->p2p_in_provisioning++; } + + if (params->freqs == NULL && wpa_s->p2p_in_invitation) { + /* + * Optimize scan based on GO information during persistent + * group reinvocation + */ + if (wpa_s->p2p_in_invitation < 5 && + wpa_s->p2p_invite_go_freq > 0) { + wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Scan only GO preferred frequency %d MHz during invitation", + wpa_s->p2p_invite_go_freq); + params->freqs = os_zalloc(2 * sizeof(int)); + if (params->freqs) + params->freqs[0] = wpa_s->p2p_invite_go_freq; + } + wpa_s->p2p_in_invitation++; + if (wpa_s->p2p_in_invitation > 20) { + /* + * This should not really happen since the variable is + * cleared on group removal, but if it does happen, make + * sure we do not get stuck in special invitation scan + * mode. + */ + wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Clear p2p_in_invitation"); + wpa_s->p2p_in_invitation = 0; + } + } #endif /* CONFIG_P2P */ #ifdef CONFIG_WPS @@ -359,11 +391,17 @@ static void wpas_add_interworking_elements(struct wpa_supplicant *wpa_s, return; wpabuf_put_u8(buf, WLAN_EID_EXT_CAPAB); - wpabuf_put_u8(buf, 4); + wpabuf_put_u8(buf, 6); wpabuf_put_u8(buf, 0x00); wpabuf_put_u8(buf, 0x00); wpabuf_put_u8(buf, 0x00); wpabuf_put_u8(buf, 0x80); /* Bit 31 - Interworking */ + wpabuf_put_u8(buf, 0x00); +#ifdef CONFIG_HS20 + wpabuf_put_u8(buf, 0x40); /* Bit 46 - WNM-Notification */ +#else /* CONFIG_HS20 */ + wpabuf_put_u8(buf, 0x00); +#endif /* CONFIG_HS20 */ wpabuf_put_u8(buf, WLAN_EID_INTERWORKING); wpabuf_put_u8(buf, is_zero_ether_addr(wpa_s->conf->hessid) ? 1 : @@ -419,7 +457,7 @@ static struct wpabuf * wpa_supplicant_extra_ies(struct wpa_supplicant *wpa_s) #ifdef CONFIG_HS20 if (wpa_s->conf->hs20 && wpabuf_resize(&extra_ie, 7) == 0) - wpas_hs20_add_indication(extra_ie); + wpas_hs20_add_indication(extra_ie, -1); #endif /* CONFIG_HS20 */ return extra_ie; @@ -627,6 +665,19 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx) params.num_ssids = 1; goto ssid_list_set; } + + if (wpa_s->p2p_in_invitation) { + if (wpa_s->current_ssid) { + wpa_printf(MSG_DEBUG, "P2P: Use specific SSID for scan during invitation"); + params.ssids[0].ssid = wpa_s->current_ssid->ssid; + params.ssids[0].ssid_len = + wpa_s->current_ssid->ssid_len; + params.num_ssids = 1; + } else { + wpa_printf(MSG_DEBUG, "P2P: No specific SSID known for scan during invitation"); + } + goto ssid_list_set; + } #endif /* CONFIG_P2P */ /* Find the starting point from which to continue scanning */ @@ -653,6 +704,36 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx) * wildcard SSID. */ ssid = NULL; + } else if (wpa_s->reattach && wpa_s->current_ssid != NULL) { + /* + * Perform single-channel single-SSID scan for + * reassociate-to-same-BSS operation. + */ + /* Setup SSID */ + ssid = wpa_s->current_ssid; + wpa_hexdump_ascii(MSG_DEBUG, "Scan SSID", + ssid->ssid, ssid->ssid_len); + params.ssids[0].ssid = ssid->ssid; + params.ssids[0].ssid_len = ssid->ssid_len; + params.num_ssids = 1; + + /* + * Allocate memory for frequency array, allocate one extra + * slot for the zero-terminator. + */ + params.freqs = os_malloc(sizeof(int) * 2); + if (params.freqs == NULL) { + wpa_dbg(wpa_s, MSG_ERROR, "Memory allocation failed"); + return; + } + params.freqs[0] = wpa_s->assoc_freq; + params.freqs[1] = 0; + + /* + * Reset the reattach flag so that we fall back to full scan if + * this scan fails. + */ + wpa_s->reattach = 0; } else { struct wpa_ssid *start = ssid, *tssid; int freqs_set = 0; @@ -789,7 +870,7 @@ ssid_list_set: } #ifdef CONFIG_P2P - if (wpa_s->p2p_in_provisioning || + if (wpa_s->p2p_in_provisioning || wpa_s->p2p_in_invitation || (wpa_s->show_group_started && wpa_s->go_params)) { /* * The interface may not yet be in P2P mode, so we have to @@ -1801,3 +1882,115 @@ void wpa_scan_free_params(struct wpa_driver_scan_params *params) os_free(params->filter_ssids); os_free(params); } + + +int wpas_start_pno(struct wpa_supplicant *wpa_s) +{ + int ret, interval; + size_t i, num_ssid; + struct wpa_ssid *ssid; + struct wpa_driver_scan_params params; + + if (!wpa_s->sched_scan_supported) + return -1; + + if (wpa_s->pno || wpa_s->pno_sched_pending) + return 0; + + if ((wpa_s->wpa_state > WPA_SCANNING) && + (wpa_s->wpa_state <= WPA_COMPLETED)) { + wpa_printf(MSG_ERROR, "PNO: In assoc process"); + return -EAGAIN; + } + + if (wpa_s->wpa_state == WPA_SCANNING) { + wpa_supplicant_cancel_scan(wpa_s); + if (wpa_s->sched_scanning) { + wpa_printf(MSG_DEBUG, "Schedule PNO on completion of " + "ongoing sched scan"); + wpa_supplicant_cancel_sched_scan(wpa_s); + wpa_s->pno_sched_pending = 1; + return 0; + } + } + + os_memset(¶ms, 0, sizeof(params)); + + num_ssid = 0; + ssid = wpa_s->conf->ssid; + while (ssid) { + if (!wpas_network_disabled(wpa_s, ssid)) + num_ssid++; + ssid = ssid->next; + } + if (num_ssid > WPAS_MAX_SCAN_SSIDS) { + wpa_printf(MSG_DEBUG, "PNO: Use only the first %u SSIDs from " + "%u", WPAS_MAX_SCAN_SSIDS, (unsigned int) num_ssid); + num_ssid = WPAS_MAX_SCAN_SSIDS; + } + + if (num_ssid == 0) { + wpa_printf(MSG_DEBUG, "PNO: No configured SSIDs"); + return -1; + } + + params.filter_ssids = os_malloc(sizeof(struct wpa_driver_scan_filter) * + num_ssid); + if (params.filter_ssids == NULL) + return -1; + i = 0; + ssid = wpa_s->conf->ssid; + while (ssid) { + if (!wpas_network_disabled(wpa_s, ssid)) { + params.ssids[i].ssid = ssid->ssid; + params.ssids[i].ssid_len = ssid->ssid_len; + params.num_ssids++; + os_memcpy(params.filter_ssids[i].ssid, ssid->ssid, + ssid->ssid_len); + params.filter_ssids[i].ssid_len = ssid->ssid_len; + params.num_filter_ssids++; + i++; + if (i == num_ssid) + break; + } + ssid = ssid->next; + } + + if (wpa_s->conf->filter_rssi) + params.filter_rssi = wpa_s->conf->filter_rssi; + + interval = wpa_s->conf->sched_scan_interval ? + wpa_s->conf->sched_scan_interval : 10; + + if (params.freqs == NULL && wpa_s->manual_sched_scan_freqs) { + wpa_dbg(wpa_s, MSG_DEBUG, "Limit sched scan to specified channels"); + params.freqs = wpa_s->manual_sched_scan_freqs; + } + + ret = wpa_supplicant_start_sched_scan(wpa_s, ¶ms, interval); + os_free(params.filter_ssids); + if (ret == 0) + wpa_s->pno = 1; + else + wpa_msg(wpa_s, MSG_ERROR, "Failed to schedule PNO"); + return ret; +} + + +int wpas_stop_pno(struct wpa_supplicant *wpa_s) +{ + int ret = 0; + + if (!wpa_s->pno) + return 0; + + ret = wpa_supplicant_stop_sched_scan(wpa_s); + + wpa_s->pno = 0; + wpa_s->pno_sched_pending = 0; + + if (wpa_s->wpa_state == WPA_SCANNING) + wpa_supplicant_req_scan(wpa_s, 0, 0); + + return ret; +} diff --git a/wpa_supplicant/scan.h b/wpa_supplicant/scan.h index e4c8989..946d2b3 100644 --- a/wpa_supplicant/scan.h +++ b/wpa_supplicant/scan.h @@ -46,5 +46,7 @@ int wpa_supplicant_stop_sched_scan(struct wpa_supplicant *wpa_s); struct wpa_driver_scan_params * wpa_scan_clone_params(const struct wpa_driver_scan_params *src); void wpa_scan_free_params(struct wpa_driver_scan_params *params); +int wpas_start_pno(struct wpa_supplicant *wpa_s); +int wpas_stop_pno(struct wpa_supplicant *wpa_s); #endif /* SCAN_H */ diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c index 451f5ae..9b6667a 100644 --- a/wpa_supplicant/sme.c +++ b/wpa_supplicant/sme.c @@ -360,7 +360,8 @@ static void sme_send_authentication(struct wpa_supplicant *wpa_s, struct wpabuf *hs20; hs20 = wpabuf_alloc(20); if (hs20) { - wpas_hs20_add_indication(hs20); + int pps_mo_id = hs20_get_pps_mo_id(wpa_s, ssid); + wpas_hs20_add_indication(hs20, pps_mo_id); os_memcpy(wpa_s->sme.assoc_req_ie + wpa_s->sme.assoc_req_ie_len, wpabuf_head(hs20), wpabuf_len(hs20)); @@ -415,6 +416,32 @@ static void sme_send_authentication(struct wpa_supplicant *wpa_s, if (old_ssid != wpa_s->current_ssid) wpas_notify_network_changed(wpa_s); +#ifdef CONFIG_P2P + /* + * If multi-channel concurrency is not supported, check for any + * frequency conflict. In case of any frequency conflict, remove the + * least prioritized connection. + */ + if (wpa_s->num_multichan_concurrent < 2) { + int freq, num; + num = get_shared_radio_freqs(wpa_s, &freq, 1); + if (num > 0 && freq > 0 && freq != params.freq) { + wpa_printf(MSG_DEBUG, + "Conflicting frequency found (%d != %d)", + freq, params.freq); + if (wpas_p2p_handle_frequency_conflicts(wpa_s, + params.freq, + ssid) < 0) { + wpas_connection_failed(wpa_s, bss->bssid); + wpa_supplicant_mark_disassoc(wpa_s); + wpabuf_free(resp); + wpas_connect_work_done(wpa_s); + return; + } + } + } +#endif /* CONFIG_P2P */ + wpa_s->sme.auth_alg = params.auth_alg; if (wpa_drv_authenticate(wpa_s, ¶ms) < 0) { wpa_msg(wpa_s, MSG_INFO, "SME: Authentication request to the " @@ -444,6 +471,9 @@ static void sme_auth_start_cb(struct wpa_radio_work *work, int deinit) struct wpa_supplicant *wpa_s = work->wpa_s; if (deinit) { + if (work->started) + wpa_s->connect_work = NULL; + wpas_connect_work_free(cwork); return; } @@ -472,6 +502,17 @@ void sme_authenticate(struct wpa_supplicant *wpa_s, return; } + if (radio_work_pending(wpa_s, "sme-connect")) { + /* + * The previous sme-connect work might no longer be valid due to + * the fact that the BSS list was updated. In addition, it makes + * sense to adhere to the 'newer' decision. + */ + wpa_dbg(wpa_s, MSG_DEBUG, + "SME: Remove previous pending sme-connect"); + radio_remove_works(wpa_s, "sme-connect", 0); + } + cwork = os_zalloc(sizeof(*cwork)); if (cwork == NULL) return; @@ -702,6 +743,8 @@ void sme_associate(struct wpa_supplicant *wpa_s, enum wpas_mode mode, params.wpa_ie_len = wpa_s->sme.assoc_req_ie_len; params.pairwise_suite = wpa_s->pairwise_cipher; params.group_suite = wpa_s->group_cipher; + params.key_mgmt_suite = wpa_s->key_mgmt; + params.wpa_proto = wpa_s->wpa_proto; #ifdef CONFIG_HT_OVERRIDES os_memset(&htcaps, 0, sizeof(htcaps)); os_memset(&htcaps_mask, 0, sizeof(htcaps_mask)); @@ -748,6 +791,10 @@ void sme_associate(struct wpa_supplicant *wpa_s, enum wpas_mode mode, params.wpa_proto = WPA_PROTO_WPA; wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, elems.wpa_ie - 2, elems.wpa_ie_len + 2); + } else if (elems.osen) { + params.wpa_proto = WPA_PROTO_OSEN; + wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, elems.osen - 2, + elems.osen_len + 2); } else wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, NULL, 0); if (wpa_s->current_ssid && wpa_s->current_ssid->p2p_group) @@ -955,8 +1002,11 @@ static void sme_send_2040_bss_coex(struct wpa_supplicant *wpa_s, struct ieee80211_2040_intol_chan_report *ic_report; struct wpabuf *buf; - wpa_printf(MSG_DEBUG, "SME: Send 20/40 BSS Coexistence to " MACSTR, - MAC2STR(wpa_s->bssid)); + wpa_printf(MSG_DEBUG, "SME: Send 20/40 BSS Coexistence to " MACSTR + " (num_channels=%u num_intol=%u)", + MAC2STR(wpa_s->bssid), num_channels, num_intol); + wpa_hexdump(MSG_DEBUG, "SME: 20/40 BSS Intolerant Channels", + chan_list, num_channels); buf = wpabuf_alloc(2 + /* action.category + action_code */ sizeof(struct ieee80211_2040_bss_coex_ie) + @@ -1038,8 +1088,14 @@ int sme_proc_obss_scan(struct wpa_supplicant *wpa_s) ie = wpa_bss_get_ie(bss, WLAN_EID_HT_CAP); ht_cap = (ie && (ie[1] == 26)) ? WPA_GET_LE16(ie + 2) : 0; + wpa_printf(MSG_DEBUG, "SME OBSS scan BSS " MACSTR + " freq=%u chan=%u ht_cap=0x%x", + MAC2STR(bss->bssid), bss->freq, channel, ht_cap); if (!ht_cap || (ht_cap & HT_CAP_INFO_40MHZ_INTOLERANT)) { + if (ht_cap & HT_CAP_INFO_40MHZ_INTOLERANT) + num_intol++; + /* Check whether the channel is already considered */ for (i = 0; i < num_channels; i++) { if (channel == chan_list[i]) @@ -1048,9 +1104,6 @@ int sme_proc_obss_scan(struct wpa_supplicant *wpa_s) if (i != num_channels) continue; - if (ht_cap & HT_CAP_INFO_40MHZ_INTOLERANT) - num_intol++; - chan_list[num_channels++] = channel; } } diff --git a/wpa_supplicant/wifi_display.c b/wpa_supplicant/wifi_display.c index 578199e..8435b63 100644 --- a/wpa_supplicant/wifi_display.c +++ b/wpa_supplicant/wifi_display.c @@ -41,6 +41,9 @@ static int wifi_display_update_wfd_ie(struct wpa_global *global) struct wpabuf *ie, *buf; size_t len, plen; + if (global->p2p == NULL) + return 0; + wpa_printf(MSG_DEBUG, "WFD: Update WFD IE"); if (!global->wifi_display) { diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c index 65b2783..e395ef1 100644 --- a/wpa_supplicant/wnm_sta.c +++ b/wpa_supplicant/wnm_sta.c @@ -18,6 +18,7 @@ #include "ctrl_iface.h" #include "bss.h" #include "wnm_sta.h" +#include "hs20_supplicant.h" #define MAX_TFS_IE_LEN 1024 #define WNM_MAX_NEIGHBOR_REPORT 10 @@ -234,16 +235,20 @@ static void ieee802_11_rx_wnmsleep_resp(struct wpa_supplicant *wpa_s, const u8 *frm, int len) { /* - * Action [1] | Diaglog Token [1] | Key Data Len [2] | Key Data | + * Action [1] | Dialog Token [1] | Key Data Len [2] | Key Data | * WNM-Sleep Mode IE | TFS Response IE */ u8 *pos = (u8 *) frm; /* point to payload after the action field */ - u16 key_len_total = le_to_host16(*((u16 *)(frm+2))); + u16 key_len_total; struct wnm_sleep_element *wnmsleep_ie = NULL; /* multiple TFS Resp IE (assuming consecutive) */ u8 *tfsresp_ie_start = NULL; u8 *tfsresp_ie_end = NULL; + if (len < 3) + return; + key_len_total = WPA_GET_LE16(frm + 1); + wpa_printf(MSG_DEBUG, "WNM-Sleep Mode Response token=%u key_len_total=%d", frm[0], key_len_total); pos += 3 + key_len_total; @@ -314,6 +319,7 @@ void wnm_deallocate_memory(struct wpa_supplicant *wpa_s) os_free(wpa_s->wnm_neighbor_report_elements[i].mul_bssid); } + wpa_s->wnm_num_neighbor_report = 0; os_free(wpa_s->wnm_neighbor_report_elements); wpa_s->wnm_neighbor_report_elements = NULL; } @@ -328,10 +334,10 @@ static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep, wpa_printf(MSG_DEBUG, "WNM: Too short TSF"); break; } + os_free(rep->tsf_info); rep->tsf_info = os_zalloc(sizeof(struct tsf_info)); if (rep->tsf_info == NULL) break; - rep->tsf_info->present = 1; os_memcpy(rep->tsf_info->tsf_offset, pos, 2); os_memcpy(rep->tsf_info->beacon_interval, pos + 2, 2); break; @@ -341,11 +347,11 @@ static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep, "country string"); break; } + os_free(rep->con_coun_str); rep->con_coun_str = os_zalloc(sizeof(struct condensed_country_string)); if (rep->con_coun_str == NULL) break; - rep->con_coun_str->present = 1; os_memcpy(rep->con_coun_str->country_string, pos, 2); break; case WNM_NEIGHBOR_BSS_TRANSITION_CANDIDATE: @@ -354,25 +360,25 @@ static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep, "candidate"); break; } + os_free(rep->bss_tran_can); rep->bss_tran_can = os_zalloc(sizeof(struct bss_transition_candidate)); if (rep->bss_tran_can == NULL) break; - rep->bss_tran_can->present = 1; rep->bss_tran_can->preference = pos[0]; break; case WNM_NEIGHBOR_BSS_TERMINATION_DURATION: - if (elen < 12) { + if (elen < 10) { wpa_printf(MSG_DEBUG, "WNM: Too short BSS termination " "duration"); break; } + os_free(rep->bss_term_dur); rep->bss_term_dur = os_zalloc(sizeof(struct bss_termination_duration)); if (rep->bss_term_dur == NULL) break; - rep->bss_term_dur->present = 1; - os_memcpy(rep->bss_term_dur->duration, pos, 12); + os_memcpy(rep->bss_term_dur->duration, pos, 10); break; case WNM_NEIGHBOR_BEARING: if (elen < 8) { @@ -380,51 +386,51 @@ static void wnm_parse_neighbor_report_elem(struct neighbor_report *rep, "bearing"); break; } + os_free(rep->bearing); rep->bearing = os_zalloc(sizeof(struct bearing)); if (rep->bearing == NULL) break; - rep->bearing->present = 1; os_memcpy(rep->bearing->bearing, pos, 8); break; case WNM_NEIGHBOR_MEASUREMENT_PILOT: - if (elen < 2) { + if (elen < 1) { wpa_printf(MSG_DEBUG, "WNM: Too short measurement " "pilot"); break; } + os_free(rep->meas_pilot); rep->meas_pilot = os_zalloc(sizeof(struct measurement_pilot)); if (rep->meas_pilot == NULL) break; - rep->meas_pilot->present = 1; rep->meas_pilot->measurement_pilot = pos[0]; - rep->meas_pilot->num_vendor_specific = pos[1]; - os_memcpy(rep->meas_pilot->vendor_specific, pos + 2, elen - 2); + rep->meas_pilot->subelem_len = elen - 1; + os_memcpy(rep->meas_pilot->subelems, pos + 1, elen - 1); break; case WNM_NEIGHBOR_RRM_ENABLED_CAPABILITIES: - if (elen < 4) { + if (elen < 5) { wpa_printf(MSG_DEBUG, "WNM: Too short RRM enabled " "capabilities"); break; } + os_free(rep->rrm_cap); rep->rrm_cap = os_zalloc(sizeof(struct rrm_enabled_capabilities)); if (rep->rrm_cap == NULL) break; - rep->rrm_cap->present = 1; - os_memcpy(rep->rrm_cap->capabilities, pos, 4); + os_memcpy(rep->rrm_cap->capabilities, pos, 5); break; case WNM_NEIGHBOR_MULTIPLE_BSSID: - if (elen < 2) { + if (elen < 1) { wpa_printf(MSG_DEBUG, "WNM: Too short multiple BSSID"); break; } + os_free(rep->mul_bssid); rep->mul_bssid = os_zalloc(sizeof(struct multiple_bssid)); if (rep->mul_bssid == NULL) break; - rep->mul_bssid->present = 1; rep->mul_bssid->max_bssid_indicator = pos[0]; - rep->mul_bssid->num_vendor_specific = pos[1]; - os_memcpy(rep->mul_bssid->vendor_specific, pos + 2, elen - 2); + rep->mul_bssid->subelem_len = elen - 1; + os_memcpy(rep->mul_bssid->subelems, pos + 1, elen - 1); break; } } @@ -455,8 +461,15 @@ static void wnm_parse_neighbor_report(struct wpa_supplicant *wpa_s, id = *pos++; elen = *pos++; + wpa_printf(MSG_DEBUG, "WNM: Subelement id=%u len=%u", id, elen); + left -= 2; + if (elen > left) { + wpa_printf(MSG_DEBUG, + "WNM: Truncated neighbor report subelement"); + break; + } wnm_parse_neighbor_report_elem(rep, id, elen, pos); - left -= 2 + elen; + left -= elen; pos += elen; } } @@ -470,12 +483,11 @@ static int compare_scan_neighbor_results(struct wpa_supplicant *wpa_s, u8 i, j; - if (scan_res == NULL || num_neigh_rep == 0) + if (scan_res == NULL || num_neigh_rep == 0 || !wpa_s->current_bss) return 0; wpa_printf(MSG_DEBUG, "WNM: Current BSS " MACSTR " RSSI %d", - MAC2STR(wpa_s->bssid), - wpa_s->current_bss ? wpa_s->current_bss->level : 0); + MAC2STR(wpa_s->bssid), wpa_s->current_bss->level); for (i = 0; i < num_neigh_rep; i++) { for (j = 0; j < scan_res->num; j++) { @@ -690,10 +702,12 @@ static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, wpa_printf(MSG_DEBUG, "WNM: Truncated request"); return; } - wnm_parse_neighbor_report( - wpa_s, pos, len, - &wpa_s->wnm_neighbor_report_elements[ - wpa_s->wnm_num_neighbor_report]); + if (tag == WLAN_EID_NEIGHBOR_REPORT) { + struct neighbor_report *rep; + rep = &wpa_s->wnm_neighbor_report_elements[ + wpa_s->wnm_num_neighbor_report]; + wnm_parse_neighbor_report(wpa_s, pos, len, rep); + } pos += len; wpa_s->wnm_num_neighbor_report++; @@ -751,6 +765,153 @@ int wnm_send_bss_transition_mgmt_query(struct wpa_supplicant *wpa_s, } +static void ieee802_11_rx_wnm_notif_req_wfa(struct wpa_supplicant *wpa_s, + const u8 *sa, const u8 *data, + int len) +{ + const u8 *pos, *end, *next; + u8 ie, ie_len; + + pos = data; + end = data + len; + + while (pos + 1 < end) { + ie = *pos++; + ie_len = *pos++; + wpa_printf(MSG_DEBUG, "WNM: WFA subelement %u len %u", + ie, ie_len); + if (ie_len > end - pos) { + wpa_printf(MSG_DEBUG, "WNM: Not enough room for " + "subelement"); + break; + } + next = pos + ie_len; + if (ie_len < 4) { + pos = next; + continue; + } + wpa_printf(MSG_DEBUG, "WNM: Subelement OUI %06x type %u", + WPA_GET_BE24(pos), pos[3]); + +#ifdef CONFIG_HS20 + if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 5 && + WPA_GET_BE24(pos) == OUI_WFA && + pos[3] == HS20_WNM_SUB_REM_NEEDED) { + /* Subscription Remediation subelement */ + const u8 *ie_end; + u8 url_len; + char *url; + u8 osu_method; + + wpa_printf(MSG_DEBUG, "WNM: Subscription Remediation " + "subelement"); + ie_end = pos + ie_len; + pos += 4; + url_len = *pos++; + if (url_len == 0) { + wpa_printf(MSG_DEBUG, "WNM: No Server URL included"); + url = NULL; + osu_method = 1; + } else { + if (pos + url_len + 1 > ie_end) { + wpa_printf(MSG_DEBUG, "WNM: Not enough room for Server URL (len=%u) and Server Method (left %d)", + url_len, + (int) (ie_end - pos)); + break; + } + url = os_malloc(url_len + 1); + if (url == NULL) + break; + os_memcpy(url, pos, url_len); + url[url_len] = '\0'; + osu_method = pos[url_len]; + } + hs20_rx_subscription_remediation(wpa_s, url, + osu_method); + os_free(url); + pos = next; + continue; + } + + if (ie == WLAN_EID_VENDOR_SPECIFIC && ie_len >= 8 && + WPA_GET_BE24(pos) == OUI_WFA && + pos[3] == HS20_WNM_DEAUTH_IMMINENT_NOTICE) { + const u8 *ie_end; + u8 url_len; + char *url; + u8 code; + u16 reauth_delay; + + ie_end = pos + ie_len; + pos += 4; + code = *pos++; + reauth_delay = WPA_GET_LE16(pos); + pos += 2; + url_len = *pos++; + wpa_printf(MSG_DEBUG, "WNM: HS 2.0 Deauthentication " + "Imminent - Reason Code %u " + "Re-Auth Delay %u URL Length %u", + code, reauth_delay, url_len); + if (pos + url_len > ie_end) + break; + url = os_malloc(url_len + 1); + if (url == NULL) + break; + os_memcpy(url, pos, url_len); + url[url_len] = '\0'; + hs20_rx_deauth_imminent_notice(wpa_s, code, + reauth_delay, url); + os_free(url); + pos = next; + continue; + } +#endif /* CONFIG_HS20 */ + + pos = next; + } +} + + +static void ieee802_11_rx_wnm_notif_req(struct wpa_supplicant *wpa_s, + const u8 *sa, const u8 *frm, int len) +{ + const u8 *pos, *end; + u8 dialog_token, type; + + /* Dialog Token [1] | Type [1] | Subelements */ + + if (len < 2 || sa == NULL) + return; + end = frm + len; + pos = frm; + dialog_token = *pos++; + type = *pos++; + + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Received WNM-Notification Request " + "(dialog_token %u type %u sa " MACSTR ")", + dialog_token, type, MAC2STR(sa)); + wpa_hexdump(MSG_DEBUG, "WNM-Notification Request subelements", + pos, end - pos); + + if (wpa_s->wpa_state != WPA_COMPLETED || + os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) { + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: WNM-Notification frame not " + "from our AP - ignore it"); + return; + } + + switch (type) { + case 1: + ieee802_11_rx_wnm_notif_req_wfa(wpa_s, sa, pos, end - pos); + break; + default: + wpa_dbg(wpa_s, MSG_DEBUG, "WNM: Ignore unknown " + "WNM-Notification type %u", type); + break; + } +} + + void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s, const struct ieee80211_mgmt *mgmt, size_t len) { @@ -782,6 +943,9 @@ void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s, case WNM_SLEEP_MODE_RESP: ieee802_11_rx_wnmsleep_resp(wpa_s, pos, end - pos); break; + case WNM_NOTIFICATION_REQ: + ieee802_11_rx_wnm_notif_req(wpa_s, mgmt->sa, pos, end - pos); + break; default: wpa_printf(MSG_ERROR, "WNM: Unknown request"); break; diff --git a/wpa_supplicant/wnm_sta.h b/wpa_supplicant/wnm_sta.h index de87301..d2eb96d 100644 --- a/wpa_supplicant/wnm_sta.h +++ b/wpa_supplicant/wnm_sta.h @@ -10,48 +10,40 @@ #define WNM_STA_H struct tsf_info { - u8 present; u8 tsf_offset[2]; u8 beacon_interval[2]; }; struct condensed_country_string { - u8 present; u8 country_string[2]; }; struct bss_transition_candidate { - u8 present; u8 preference; }; struct bss_termination_duration { - u8 present; - u8 duration[12]; + u8 duration[10]; }; struct bearing { - u8 present; u8 bearing[8]; }; struct measurement_pilot { - u8 present; u8 measurement_pilot; - u8 num_vendor_specific; - u8 vendor_specific[255]; + u8 subelem_len; + u8 subelems[255]; }; struct rrm_enabled_capabilities { - u8 present; - u8 capabilities[4]; + u8 capabilities[5]; }; struct multiple_bssid { - u8 present; u8 max_bssid_indicator; - u8 num_vendor_specific; - u8 vendor_specific[255]; + u8 subelem_len; + u8 subelems[255]; }; struct neighbor_report { diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c index d66e864..b3812ca 100644 --- a/wpa_supplicant/wpa_cli.c +++ b/wpa_supplicant/wpa_cli.c @@ -676,6 +676,12 @@ static int wpa_cli_cmd_reassociate(struct wpa_ctrl *ctrl, int argc, } +static int wpa_cli_cmd_reattach(struct wpa_ctrl *ctrl, int argc, char *argv[]) +{ + return wpa_ctrl_command(ctrl, "REATTACH"); +} + + static int wpa_cli_cmd_preauthenticate(struct wpa_ctrl *ctrl, int argc, char *argv[]) { @@ -836,58 +842,6 @@ static int wpa_cli_cmd_nfc_get_handover_sel(struct wpa_ctrl *ctrl, int argc, } -static int wpa_cli_cmd_nfc_rx_handover_req(struct wpa_ctrl *ctrl, int argc, - char *argv[]) -{ - int ret; - char *buf; - size_t buflen; - - if (argc != 1) { - printf("Invalid 'nfc_rx_handover_req' command - one argument " - "is required.\n"); - return -1; - } - - buflen = 21 + os_strlen(argv[0]); - buf = os_malloc(buflen); - if (buf == NULL) - return -1; - os_snprintf(buf, buflen, "NFC_RX_HANDOVER_REQ %s", argv[0]); - - ret = wpa_ctrl_command(ctrl, buf); - os_free(buf); - - return ret; -} - - -static int wpa_cli_cmd_nfc_rx_handover_sel(struct wpa_ctrl *ctrl, int argc, - char *argv[]) -{ - int ret; - char *buf; - size_t buflen; - - if (argc != 1) { - printf("Invalid 'nfc_rx_handover_sel' command - one argument " - "is required.\n"); - return -1; - } - - buflen = 21 + os_strlen(argv[0]); - buf = os_malloc(buflen); - if (buf == NULL) - return -1; - os_snprintf(buf, buflen, "NFC_RX_HANDOVER_SEL %s", argv[0]); - - ret = wpa_ctrl_command(ctrl, buf); - os_free(buf); - - return ret; -} - - static int wpa_cli_cmd_nfc_report_handover(struct wpa_ctrl *ctrl, int argc, char *argv[]) { @@ -1462,6 +1416,24 @@ static int wpa_cli_cmd_get_network(struct wpa_ctrl *ctrl, int argc, } +static int wpa_cli_cmd_dup_network(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + if (argc == 0) { + wpa_cli_show_network_variables(); + return 0; + } + + if (argc < 3) { + printf("Invalid DUP_NETWORK command: needs three arguments\n" + "(src netid, dest netid, and variable name)\n"); + return -1; + } + + return wpa_cli_cmd(ctrl, "DUP_NETWORK", 3, argc, argv); +} + + static int wpa_cli_cmd_list_creds(struct wpa_ctrl *ctrl, int argc, char *argv[]) { @@ -1494,6 +1466,18 @@ static int wpa_cli_cmd_set_cred(struct wpa_ctrl *ctrl, int argc, char *argv[]) } +static int wpa_cli_cmd_get_cred(struct wpa_ctrl *ctrl, int argc, char *argv[]) +{ + if (argc != 2) { + printf("Invalid GET_CRED command: needs two arguments\n" + "(cred id, variable name)\n"); + return -1; + } + + return wpa_cli_cmd(ctrl, "GET_CRED", 2, argc, argv); +} + + static int wpa_cli_cmd_disconnect(struct wpa_ctrl *ctrl, int argc, char *argv[]) { @@ -1586,7 +1570,7 @@ static int wpa_cli_cmd_interface(struct wpa_ctrl *ctrl, int argc, char *argv[]) os_free(ctrl_ifname); ctrl_ifname = os_strdup(argv[0]); - if (wpa_cli_open_connection(ctrl_ifname, 1)) { + if (wpa_cli_open_connection(ctrl_ifname, 1) == 0) { printf("Connected to interface '%s.\n", ctrl_ifname); } else { printf("Could not connect to interface '%s' - re-trying\n", @@ -1746,10 +1730,12 @@ static int wpa_cli_cmd_resume(struct wpa_ctrl *ctrl, int argc, char *argv[]) } +#ifdef CONFIG_TESTING_OPTIONS static int wpa_cli_cmd_drop_sa(struct wpa_ctrl *ctrl, int argc, char *argv[]) { return wpa_ctrl_command(ctrl, "DROP_SA"); } +#endif /* CONFIG_TESTING_OPTIONS */ static int wpa_cli_cmd_roam(struct wpa_ctrl *ctrl, int argc, char *argv[]) @@ -2324,6 +2310,37 @@ static int wpa_cli_cmd_get_nai_home_realm_list(struct wpa_ctrl *ctrl, int argc, return wpa_ctrl_command(ctrl, cmd); } + +static int wpa_cli_cmd_hs20_icon_request(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + char cmd[512]; + + if (argc < 2) { + printf("Command needs two arguments (dst mac addr and " + "icon name)\n"); + return -1; + } + + if (write_cmd(cmd, sizeof(cmd), "HS20_ICON_REQUEST", argc, argv) < 0) + return -1; + + return wpa_ctrl_command(ctrl, cmd); +} + + +static int wpa_cli_cmd_fetch_osu(struct wpa_ctrl *ctrl, int argc, char *argv[]) +{ + return wpa_ctrl_command(ctrl, "FETCH_OSU"); +} + + +static int wpa_cli_cmd_cancel_fetch_osu(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + return wpa_ctrl_command(ctrl, "CANCEL_FETCH_OSU"); +} + #endif /* CONFIG_HS20 */ @@ -2421,6 +2438,12 @@ static int wpa_cli_cmd_driver(struct wpa_ctrl *ctrl, int argc, char *argv[]) #endif /* ANDROID */ +static int wpa_cli_cmd_vendor(struct wpa_ctrl *ctrl, int argc, char *argv[]) +{ + return wpa_cli_cmd(ctrl, "VENDOR", 1, argc, argv); +} + + static int wpa_cli_cmd_flush(struct wpa_ctrl *ctrl, int argc, char *argv[]) { return wpa_ctrl_command(ctrl, "FLUSH"); @@ -2499,6 +2522,9 @@ static struct wpa_cli_cmd wpa_cli_commands[] = { { "reassociate", wpa_cli_cmd_reassociate, NULL, cli_cmd_flag_none, "= force reassociation" }, + { "reattach", wpa_cli_cmd_reattach, NULL, + cli_cmd_flag_none, + "= force reassociation back to the same BSS" }, { "preauthenticate", wpa_cli_cmd_preauthenticate, wpa_cli_complete_bss, cli_cmd_flag_none, "<BSSID> = force preauthentication" }, @@ -2562,6 +2588,10 @@ static struct wpa_cli_cmd wpa_cli_commands[] = { { "get_network", wpa_cli_cmd_get_network, NULL, cli_cmd_flag_none, "<network id> <variable> = get network variables" }, + { "dup_network", wpa_cli_cmd_dup_network, NULL, + cli_cmd_flag_none, + "<src network id> <dst network id> <variable> = duplicate network variables" + }, { "list_creds", wpa_cli_cmd_list_creds, NULL, cli_cmd_flag_none, "= list configured credentials" }, @@ -2574,6 +2604,9 @@ static struct wpa_cli_cmd wpa_cli_commands[] = { { "set_cred", wpa_cli_cmd_set_cred, NULL, cli_cmd_flag_sensitive, "<cred id> <variable> <value> = set credential variables" }, + { "get_cred", wpa_cli_cmd_get_cred, NULL, + cli_cmd_flag_none, + "<cred id> <variable> = get credential variables" }, { "save_config", wpa_cli_cmd_save_config, NULL, cli_cmd_flag_none, "= save the current configuration" }, @@ -2667,12 +2700,6 @@ static struct wpa_cli_cmd wpa_cli_commands[] = { { "nfc_get_handover_sel", wpa_cli_cmd_nfc_get_handover_sel, NULL, cli_cmd_flag_none, "<NDEF> <WPS> = create NFC handover select" }, - { "nfc_rx_handover_req", wpa_cli_cmd_nfc_rx_handover_req, NULL, - cli_cmd_flag_none, - "<hexdump of payload> = report received NFC handover request" }, - { "nfc_rx_handover_sel", wpa_cli_cmd_nfc_rx_handover_sel, NULL, - cli_cmd_flag_none, - "<hexdump of payload> = report received NFC handover select" }, { "nfc_report_handover", wpa_cli_cmd_nfc_report_handover, NULL, cli_cmd_flag_none, "<role> <type> <hexdump of req> <hexdump of sel> = report completed " @@ -2736,8 +2763,10 @@ static struct wpa_cli_cmd wpa_cli_commands[] = { "= notification of suspend/hibernate" }, { "resume", wpa_cli_cmd_resume, NULL, cli_cmd_flag_none, "= notification of resume/thaw" }, +#ifdef CONFIG_TESTING_OPTIONS { "drop_sa", wpa_cli_cmd_drop_sa, NULL, cli_cmd_flag_none, "= drop SA without deauth/disassoc (test command)" }, +#endif /* CONFIG_TESTING_OPTIONS */ { "roam", wpa_cli_cmd_roam, wpa_cli_complete_bss, cli_cmd_flag_none, "<addr> = roam to the specified BSS" }, @@ -2860,6 +2889,14 @@ static struct wpa_cli_cmd wpa_cli_commands[] = { { "nai_home_realm_list", wpa_cli_cmd_get_nai_home_realm_list, wpa_cli_complete_bss, cli_cmd_flag_none, "<addr> <home realm> = get HS20 nai home realm list" }, + { "hs20_icon_request", wpa_cli_cmd_hs20_icon_request, + wpa_cli_complete_bss, cli_cmd_flag_none, + "<addr> <icon name> = get Hotspot 2.0 OSU icon" }, + { "fetch_osu", wpa_cli_cmd_fetch_osu, NULL, cli_cmd_flag_none, + "= fetch OSU provider information from all APs" }, + { "cancel_fetch_osu", wpa_cli_cmd_cancel_fetch_osu, NULL, + cli_cmd_flag_none, + "= cancel fetch_osu command" }, #endif /* CONFIG_HS20 */ { "sta_autoconnect", wpa_cli_cmd_sta_autoconnect, NULL, cli_cmd_flag_none, @@ -2902,6 +2939,9 @@ static struct wpa_cli_cmd wpa_cli_commands[] = { #endif /* ANDROID */ { "radio_work", wpa_cli_cmd_radio_work, NULL, cli_cmd_flag_none, "= radio_work <show/add/done>" }, + { "vendor", wpa_cli_cmd_vendor, NULL, cli_cmd_flag_none, + "<vendor id> <command id> [<hex formatted command argument>] = Send vendor command" + }, { NULL, NULL, NULL, cli_cmd_flag_none, NULL } }; @@ -3210,6 +3250,10 @@ static void wpa_cli_action_process(const char *msg) wpa_cli_exec(action_file, ctrl_ifname, pos); } else if (str_match(pos, ESS_DISASSOC_IMMINENT)) { wpa_cli_exec(action_file, ctrl_ifname, pos); + } else if (str_match(pos, HS20_SUBSCRIPTION_REMEDIATION)) { + wpa_cli_exec(action_file, ctrl_ifname, pos); + } else if (str_match(pos, HS20_DEAUTH_IMMINENT_NOTICE)) { + wpa_cli_exec(action_file, ctrl_ifname, pos); } else if (str_match(pos, WPA_EVENT_TERMINATING)) { printf("wpa_supplicant is terminating - stop monitoring\n"); wpa_cli_quit = 1; diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 455b158..0b871d0 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -51,6 +51,7 @@ #include "offchannel.h" #include "hs20_supplicant.h" #include "wnm_sta.h" +#include "wpas_kay.h" const char *wpa_supplicant_version = "wpa_supplicant v" VERSION_STR "\n" @@ -298,6 +299,8 @@ void wpa_supplicant_initiate_eapol(struct wpa_supplicant *wpa_s) eapol_conf.external_sim = wpa_s->conf->external_sim; eapol_sm_notify_config(wpa_s->eapol, &ssid->eap, &eapol_conf); #endif /* IEEE8021X_EAPOL */ + + ieee802_1x_alloc_kay_sm(wpa_s, ssid); } @@ -397,6 +400,11 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) os_free(wpa_s->confanother); wpa_s->confanother = NULL; +#ifdef CONFIG_P2P + os_free(wpa_s->conf_p2p_dev); + wpa_s->conf_p2p_dev = NULL; +#endif /* CONFIG_P2P */ + wpa_sm_set_eapol(wpa_s->wpa, NULL); eapol_sm_deinit(wpa_s->eapol); wpa_s->eapol = NULL; @@ -455,11 +463,16 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) os_free(wpa_s->manual_scan_freqs); wpa_s->manual_scan_freqs = NULL; + os_free(wpa_s->manual_sched_scan_freqs); + wpa_s->manual_sched_scan_freqs = NULL; + gas_query_deinit(wpa_s->gas); wpa_s->gas = NULL; free_hw_features(wpa_s); + ieee802_1x_dealloc_kay_sm(wpa_s); + os_free(wpa_s->bssid_filter); wpa_s->bssid_filter = NULL; @@ -483,6 +496,10 @@ static void wpa_supplicant_cleanup(struct wpa_supplicant *wpa_s) os_free(wpa_s->last_scan_res); wpa_s->last_scan_res = NULL; + +#ifdef CONFIG_HS20 + hs20_deinit(wpa_s); +#endif /* CONFIG_HS20 */ } @@ -569,12 +586,16 @@ static void wpa_supplicant_start_bgscan(struct wpa_supplicant *wpa_s) name = wpa_s->current_ssid->bgscan; else name = wpa_s->conf->bgscan; - if (name == NULL) + if (name == NULL || name[0] == '\0') return; if (wpas_driver_bss_selection(wpa_s)) return; if (wpa_s->current_ssid == wpa_s->bgscan_ssid) return; +#ifdef CONFIG_P2P + if (wpa_s->p2p_group_interface != NOT_P2P_GROUP_INTERFACE) + return; +#endif /* CONFIG_P2P */ bgscan_deinit(wpa_s); if (wpa_s->current_ssid) { @@ -657,8 +678,11 @@ void wpa_supplicant_set_state(struct wpa_supplicant *wpa_s, wpa_s->normal_scans = 0; } - if (state == WPA_COMPLETED) + if (state == WPA_COMPLETED) { wpas_connect_work_done(wpa_s); + /* Reinitialize normal_scan counter */ + wpa_s->normal_scans = 0; + } if (state != WPA_SCANNING) wpa_supplicant_notify_scanning(wpa_s, 0); @@ -727,13 +751,13 @@ void wpa_supplicant_terminate_proc(struct wpa_global *global) struct wpa_supplicant *wpa_s = global->ifaces; while (wpa_s) { struct wpa_supplicant *next = wpa_s->next; + if (wpas_wps_terminate_pending(wpa_s) == 1) + pending = 1; #ifdef CONFIG_P2P if (wpa_s->p2p_group_interface != NOT_P2P_GROUP_INTERFACE || (wpa_s->current_ssid && wpa_s->current_ssid->p2p_group)) wpas_p2p_disconnect(wpa_s); #endif /* CONFIG_P2P */ - if (wpas_wps_terminate_pending(wpa_s) == 1) - pending = 1; wpa_s = next; } #endif /* CONFIG_WPS */ @@ -933,13 +957,14 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, { struct wpa_ie_data ie; int sel, proto; - const u8 *bss_wpa, *bss_rsn; + const u8 *bss_wpa, *bss_rsn, *bss_osen; if (bss) { bss_wpa = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE); bss_rsn = wpa_bss_get_ie(bss, WLAN_EID_RSN); + bss_osen = wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE); } else - bss_wpa = bss_rsn = NULL; + bss_wpa = bss_rsn = bss_osen = NULL; if (bss_rsn && (ssid->proto & WPA_PROTO_RSN) && wpa_parse_wpa_ie(bss_rsn, 2 + bss_rsn[1], &ie) == 0 && @@ -955,11 +980,22 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, (ie.key_mgmt & ssid->key_mgmt)) { wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using IEEE 802.11i/D3.0"); proto = WPA_PROTO_WPA; +#ifdef CONFIG_HS20 + } else if (bss_osen && (ssid->proto & WPA_PROTO_OSEN)) { + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: using OSEN"); + /* TODO: parse OSEN element */ + ie.group_cipher = WPA_CIPHER_CCMP; + ie.pairwise_cipher = WPA_CIPHER_CCMP; + ie.key_mgmt = WPA_KEY_MGMT_OSEN; + proto = WPA_PROTO_OSEN; +#endif /* CONFIG_HS20 */ } else if (bss) { wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to select WPA/RSN"); return -1; } else { - if (ssid->proto & WPA_PROTO_RSN) + if (ssid->proto & WPA_PROTO_OSEN) + proto = WPA_PROTO_OSEN; + else if (ssid->proto & WPA_PROTO_RSN) proto = WPA_PROTO_RSN; else proto = WPA_PROTO_WPA; @@ -992,7 +1028,7 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, wpa_s->wpa_proto = proto; wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_PROTO, proto); wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_RSN_ENABLED, - !!(ssid->proto & WPA_PROTO_RSN)); + !!(ssid->proto & (WPA_PROTO_RSN | WPA_PROTO_OSEN))); if (bss || !wpa_s->ap_ies_from_associnfo) { if (wpa_sm_set_ap_wpa_ie(wpa_s->wpa, bss_wpa, @@ -1063,6 +1099,11 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, } else if (sel & WPA_KEY_MGMT_WPA_NONE) { wpa_s->key_mgmt = WPA_KEY_MGMT_WPA_NONE; wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using KEY_MGMT WPA-NONE"); +#ifdef CONFIG_HS20 + } else if (sel & WPA_KEY_MGMT_OSEN) { + wpa_s->key_mgmt = WPA_KEY_MGMT_OSEN; + wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: using KEY_MGMT OSEN"); +#endif /* CONFIG_HS20 */ } else { wpa_msg(wpa_s, MSG_WARNING, "WPA: Failed to select " "authenticated key management type"); @@ -1084,6 +1125,18 @@ int wpa_supplicant_set_suites(struct wpa_supplicant *wpa_s, wpa_s->mgmt_group_cipher = WPA_CIPHER_AES_128_CMAC; wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using MGMT group cipher " "AES-128-CMAC"); + } else if (sel & WPA_CIPHER_BIP_GMAC_128) { + wpa_s->mgmt_group_cipher = WPA_CIPHER_BIP_GMAC_128; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using MGMT group cipher " + "BIP-GMAC-128"); + } else if (sel & WPA_CIPHER_BIP_GMAC_256) { + wpa_s->mgmt_group_cipher = WPA_CIPHER_BIP_GMAC_256; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using MGMT group cipher " + "BIP-GMAC-256"); + } else if (sel & WPA_CIPHER_BIP_CMAC_256) { + wpa_s->mgmt_group_cipher = WPA_CIPHER_BIP_CMAC_256; + wpa_dbg(wpa_s, MSG_DEBUG, "WPA: using MGMT group cipher " + "BIP-CMAC-256"); } else { wpa_s->mgmt_group_cipher = 0; wpa_dbg(wpa_s, MSG_DEBUG, "WPA: not using MGMT group cipher"); @@ -1208,6 +1261,10 @@ static void wpas_ext_capab_byte(struct wpa_supplicant *wpa_s, u8 *pos, int idx) #endif /* CONFIG_INTERWORKING */ break; case 5: /* Bits 40-47 */ +#ifdef CONFIG_HS20 + if (wpa_s->conf->hs20) + *pos |= 0x40; /* Bit 46 - WNM-Notification */ +#endif /* CONFIG_HS20 */ break; case 6: /* Bits 48-55 */ break; @@ -1218,7 +1275,7 @@ static void wpas_ext_capab_byte(struct wpa_supplicant *wpa_s, u8 *pos, int idx) int wpas_build_ext_capab(struct wpa_supplicant *wpa_s, u8 *buf) { u8 *pos = buf; - u8 len = 4, i; + u8 len = 6, i; if (len < wpa_s->extended_capa_len) len = wpa_s->extended_capa_len; @@ -1366,6 +1423,11 @@ void wpa_supplicant_associate(struct wpa_supplicant *wpa_s, return; } + if (radio_work_pending(wpa_s, "connect")) { + wpa_dbg(wpa_s, MSG_DEBUG, "Reject wpa_supplicant_associate() call since pending work exist"); + return; + } + cwork = os_zalloc(sizeof(*cwork)); if (cwork == NULL) return; @@ -1399,8 +1461,19 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) struct ieee80211_ht_capabilities htcaps; struct ieee80211_ht_capabilities htcaps_mask; #endif /* CONFIG_HT_OVERRIDES */ +#ifdef CONFIG_VHT_OVERRIDES + struct ieee80211_vht_capabilities vhtcaps; + struct ieee80211_vht_capabilities vhtcaps_mask; +#endif /* CONFIG_VHT_OVERRIDES */ if (deinit) { + if (work->started) { + wpa_s->connect_work = NULL; + + /* cancel possible auth. timeout */ + eloop_cancel_timeout(wpa_supplicant_timeout, wpa_s, + NULL); + } wpas_connect_work_free(cwork); return; } @@ -1574,7 +1647,8 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) struct wpabuf *hs20; hs20 = wpabuf_alloc(20); if (hs20) { - wpas_hs20_add_indication(hs20); + int pps_mo_id = hs20_get_pps_mo_id(wpa_s, ssid); + wpas_hs20_add_indication(hs20, pps_mo_id); os_memcpy(wpa_ie + wpa_ie_len, wpabuf_head(hs20), wpabuf_len(hs20)); wpa_ie_len += wpabuf_len(hs20); @@ -1656,6 +1730,8 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) params.bssid = bss->bssid; params.freq = bss->freq; } + params.bssid_hint = bss->bssid; + params.freq_hint = bss->freq; } else { params.ssid = ssid->ssid; params.ssid_len = ssid->ssid_len; @@ -1670,6 +1746,14 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) if (ssid->mode == WPAS_MODE_IBSS && ssid->frequency > 0 && params.freq == 0) params.freq = ssid->frequency; /* Initial channel for IBSS */ + + if (ssid->mode == WPAS_MODE_IBSS) { + if (ssid->beacon_int) + params.beacon_int = ssid->beacon_int; + else + params.beacon_int = wpa_s->conf->beacon_int; + } + params.wpa_ie = wpa_ie; params.wpa_ie_len = wpa_ie_len; params.pairwise_suite = cipher_pairwise; @@ -1728,6 +1812,13 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) params.htcaps_mask = (u8 *) &htcaps_mask; wpa_supplicant_apply_ht_overrides(wpa_s, ssid, ¶ms); #endif /* CONFIG_HT_OVERRIDES */ +#ifdef CONFIG_VHT_OVERRIDES + os_memset(&vhtcaps, 0, sizeof(vhtcaps)); + os_memset(&vhtcaps_mask, 0, sizeof(vhtcaps_mask)); + params.vhtcaps = &vhtcaps; + params.vhtcaps_mask = &vhtcaps_mask; + wpa_supplicant_apply_vht_overrides(wpa_s, wpa_s->current_ssid, ¶ms); +#endif /* CONFIG_VHT_OVERRIDES */ #ifdef CONFIG_P2P /* @@ -1736,9 +1827,11 @@ static void wpas_start_assoc_cb(struct wpa_radio_work *work, int deinit) * least prioritized connection. */ if (wpa_s->num_multichan_concurrent < 2) { - int freq = wpa_drv_shared_freq(wpa_s); - if (freq > 0 && freq != params.freq) { - wpa_printf(MSG_DEBUG, "Shared interface with conflicting frequency found (%d != %d)", + int freq, num; + num = get_shared_radio_freqs(wpa_s, &freq, 1); + if (num > 0 && freq > 0 && freq != params.freq) { + wpa_printf(MSG_DEBUG, + "Assoc conflicting freq found (%d != %d)", freq, params.freq); if (wpas_p2p_handle_frequency_conflicts(wpa_s, params.freq, @@ -2629,9 +2722,11 @@ int wpa_supplicant_driver_init(struct wpa_supplicant *wpa_s) wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED); interface_count = 0; } - if (wpa_supplicant_delayed_sched_scan(wpa_s, interface_count, + if (!wpa_s->p2p_mgmt && + wpa_supplicant_delayed_sched_scan(wpa_s, + interface_count % 3, 100000)) - wpa_supplicant_req_scan(wpa_s, interface_count, + wpa_supplicant_req_scan(wpa_s, interface_count % 3, 100000); interface_count++; } else @@ -2837,6 +2932,27 @@ static int wpa_set_disable_sgi(struct wpa_supplicant *wpa_s, } +static int wpa_set_disable_ldpc(struct wpa_supplicant *wpa_s, + struct ieee80211_ht_capabilities *htcaps, + struct ieee80211_ht_capabilities *htcaps_mask, + int disabled) +{ + /* Masking these out disables LDPC */ + u16 msk = host_to_le16(HT_CAP_INFO_LDPC_CODING_CAP); + + wpa_msg(wpa_s, MSG_DEBUG, "set_disable_ldpc: %d", disabled); + + if (disabled) + htcaps->ht_capabilities_info &= ~msk; + else + htcaps->ht_capabilities_info |= msk; + + htcaps_mask->ht_capabilities_info |= msk; + + return 0; +} + + void wpa_supplicant_apply_ht_overrides( struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, struct wpa_driver_associate_params *params) @@ -2860,6 +2976,13 @@ void wpa_supplicant_apply_ht_overrides( wpa_set_ampdu_density(wpa_s, htcaps, htcaps_mask, ssid->ampdu_density); wpa_set_disable_ht40(wpa_s, htcaps, htcaps_mask, ssid->disable_ht40); wpa_set_disable_sgi(wpa_s, htcaps, htcaps_mask, ssid->disable_sgi); + wpa_set_disable_ldpc(wpa_s, htcaps, htcaps_mask, ssid->disable_ldpc); + + if (ssid->ht40_intolerant) { + u16 bit = host_to_le16(HT_CAP_INFO_40MHZ_INTOLERANT); + htcaps->ht_capabilities_info |= bit; + htcaps_mask->ht_capabilities_info |= bit; + } } #endif /* CONFIG_HT_OVERRIDES */ @@ -2872,6 +2995,10 @@ void wpa_supplicant_apply_vht_overrides( { struct ieee80211_vht_capabilities *vhtcaps; struct ieee80211_vht_capabilities *vhtcaps_mask; +#ifdef CONFIG_HT_OVERRIDES + int max_ampdu; + const u32 max_ampdu_mask = VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MAX; +#endif /* CONFIG_HT_OVERRIDES */ if (!ssid) return; @@ -2887,6 +3014,20 @@ void wpa_supplicant_apply_vht_overrides( vhtcaps->vht_capabilities_info = ssid->vht_capa; vhtcaps_mask->vht_capabilities_info = ssid->vht_capa_mask; +#ifdef CONFIG_HT_OVERRIDES + /* if max ampdu is <= 3, we have to make the HT cap the same */ + if (ssid->vht_capa_mask & max_ampdu_mask) { + max_ampdu = (ssid->vht_capa & max_ampdu_mask) >> + find_first_bit(max_ampdu_mask); + + max_ampdu = max_ampdu < 3 ? max_ampdu : 3; + wpa_set_ampdu_factor(wpa_s, + (void *) params->htcaps, + (void *) params->htcaps_mask, + max_ampdu); + } +#endif /* CONFIG_HT_OVERRIDES */ + #define OVERRIDE_MCS(i) \ if (ssid->vht_tx_mcs_nss_ ##i >= 0) { \ vhtcaps_mask->vht_supported_mcs_set.tx_map |= \ @@ -2987,6 +3128,79 @@ int wpas_init_ext_pw(struct wpa_supplicant *wpa_s) } +static int wpas_check_wowlan_trigger(const char *start, const char *trigger, + int capa_trigger, u8 *param_trigger) +{ + if (os_strcmp(start, trigger) != 0) + return 0; + if (!capa_trigger) + return 0; + + *param_trigger = 1; + return 1; +} + + +int wpas_set_wowlan_triggers(struct wpa_supplicant *wpa_s, + struct wpa_driver_capa *capa) +{ + struct wowlan_triggers triggers; + char *start, *end, *buf; + int last, ret; + + if (!wpa_s->conf->wowlan_triggers) + return 0; + + buf = os_strdup(wpa_s->conf->wowlan_triggers); + if (buf == NULL) + return -1; + + os_memset(&triggers, 0, sizeof(triggers)); + +#define CHECK_TRIGGER(trigger) \ + wpas_check_wowlan_trigger(start, #trigger, \ + capa->wowlan_triggers.trigger, \ + &triggers.trigger) + + start = buf; + while (*start != '\0') { + while (isblank(*start)) + start++; + if (*start == '\0') + break; + end = start; + while (!isblank(*end) && *end != '\0') + end++; + last = *end == '\0'; + *end = '\0'; + + if (!CHECK_TRIGGER(any) && + !CHECK_TRIGGER(disconnect) && + !CHECK_TRIGGER(magic_pkt) && + !CHECK_TRIGGER(gtk_rekey_failure) && + !CHECK_TRIGGER(eap_identity_req) && + !CHECK_TRIGGER(four_way_handshake) && + !CHECK_TRIGGER(rfkill_release)) { + wpa_printf(MSG_DEBUG, + "Unknown/unsupported wowlan trigger '%s'", + start); + ret = -1; + goto out; + } + + if (last) + break; + start = end + 1; + } +#undef CHECK_TRIGGER + + ret = wpa_drv_wowlan(wpa_s, &triggers); +out: + os_free(buf); + return ret; +} + + static struct wpa_radio * radio_add_interface(struct wpa_supplicant *wpa_s, const char *rn) { @@ -3075,25 +3289,40 @@ static void radio_start_next_work(void *eloop_ctx, void *timeout_ctx) } -void radio_remove_unstarted_work(struct wpa_supplicant *wpa_s, const char *type) +/* + * This function removes both started and pending radio works running on + * the provided interface's radio. + * Prior to the removal of the radio work, its callback (cb) is called with + * deinit set to be 1. Each work's callback is responsible for clearing its + * internal data and restoring to a correct state. + * @wpa_s: wpa_supplicant data + * @type: type of works to be removed + * @remove_all: 1 to remove all the works on this radio, 0 to remove only + * this interface's works. + */ +void radio_remove_works(struct wpa_supplicant *wpa_s, + const char *type, int remove_all) { struct wpa_radio_work *work, *tmp; struct wpa_radio *radio = wpa_s->radio; dl_list_for_each_safe(work, tmp, &radio->work, struct wpa_radio_work, list) { - if (type && (work->started || os_strcmp(type, work->type) != 0)) + if (type && os_strcmp(type, work->type) != 0) continue; - if (work->started) { - wpa_dbg(wpa_s, MSG_DEBUG, "Leaving started radio work '%s'@%p in the list", - work->type, work); + + /* skip other ifaces' works */ + if (!remove_all && work->wpa_s != wpa_s) continue; - } - wpa_dbg(wpa_s, MSG_DEBUG, "Remove unstarted radio work '%s'@%p", - work->type, work); + + wpa_dbg(wpa_s, MSG_DEBUG, "Remove radio work '%s'@%p%s", + work->type, work, work->started ? " (started)" : ""); work->cb(work, 1); radio_work_free(work); } + + /* in case we removed the started work */ + radio_work_check_next(wpa_s); } @@ -3107,15 +3336,13 @@ static void radio_remove_interface(struct wpa_supplicant *wpa_s) wpa_printf(MSG_DEBUG, "Remove interface %s from radio %s", wpa_s->ifname, radio->name); dl_list_del(&wpa_s->radio_list); - if (!dl_list_empty(&radio->ifaces)) { - wpa_s->radio = NULL; + radio_remove_works(wpa_s, NULL, 0); + wpa_s->radio = NULL; + if (!dl_list_empty(&radio->ifaces)) return; /* Interfaces remain for this radio */ - } wpa_printf(MSG_DEBUG, "Remove radio %s", radio->name); - radio_remove_unstarted_work(wpa_s, NULL); eloop_cancel_timeout(radio_start_next_work, radio, NULL); - wpa_s->radio = NULL; os_free(radio); } @@ -3213,6 +3440,20 @@ void radio_work_done(struct wpa_radio_work *work) } +int radio_work_pending(struct wpa_supplicant *wpa_s, const char *type) +{ + struct wpa_radio_work *work; + struct wpa_radio *radio = wpa_s->radio; + + dl_list_for_each(work, &radio->work, struct wpa_radio_work, list) { + if (work->wpa_s == wpa_s && os_strcmp(work->type, type) == 0) + return 1; + } + + return 0; +} + + static int wpas_init_driver(struct wpa_supplicant *wpa_s, struct wpa_interface *iface) { @@ -3250,10 +3491,7 @@ next_driver: os_strlcpy(wpa_s->ifname, ifname, sizeof(wpa_s->ifname)); } - if (wpa_s->driver->get_radio_name) - rn = wpa_s->driver->get_radio_name(wpa_s->drv_priv); - else - rn = NULL; + rn = wpa_driver_get_radio_name(wpa_s); if (rn && rn[0] == '\0') rn = NULL; @@ -3300,6 +3538,11 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, wpa_s->confanother = os_rel2abs_path(iface->confanother); wpa_config_read(wpa_s->confanother, wpa_s->conf); +#ifdef CONFIG_P2P + wpa_s->conf_p2p_dev = os_rel2abs_path(iface->conf_p2p_dev); + wpa_config_read(wpa_s->conf_p2p_dev, wpa_s->conf); +#endif /* CONFIG_P2P */ + /* * Override ctrl_interface and driver_param if set on command * line. @@ -3485,6 +3728,14 @@ static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s, if (wpa_bss_init(wpa_s) < 0) return -1; + /* + * Set Wake-on-WLAN triggers, if configured. + * Note: We don't restore/remove the triggers on shutdown (it doesn't + * have effect anyway when the interface is down). + */ + if (wpas_set_wowlan_triggers(wpa_s, &capa) < 0) + return -1; + #ifdef CONFIG_EAP_PROXY { size_t len; @@ -3525,6 +3776,8 @@ static void wpa_supplicant_deinit_iface(struct wpa_supplicant *wpa_s, wpa_supplicant_cleanup(wpa_s); #ifdef CONFIG_P2P + if (wpa_s == wpa_s->parent) + wpas_p2p_group_remove(wpa_s, "*"); if (wpa_s == wpa_s->global->p2p_init_wpa_s && wpa_s->global->p2p) { wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Disable P2P since removing " "the management interface is being removed"); @@ -4080,7 +4333,7 @@ void wpas_connection_failed(struct wpa_supplicant *wpa_s, const u8 *bssid) if (count > 3 && wpa_s->current_ssid) { wpa_printf(MSG_DEBUG, "Continuous association failures - " "consider temporary network disabling"); - wpas_auth_failed(wpa_s); + wpas_auth_failed(wpa_s, "CONN_FAILED"); } switch (count) { @@ -4228,7 +4481,7 @@ int wpas_network_disabled(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) } if (wpa_key_mgmt_wpa_psk(ssid->key_mgmt) && !ssid->psk_set && - !ssid->ext_psk) + (!ssid->passphrase || ssid->ssid_len != 0) && !ssid->ext_psk) return 1; return 0; @@ -4245,7 +4498,7 @@ int wpas_is_p2p_prioritized(struct wpa_supplicant *wpa_s) } -void wpas_auth_failed(struct wpa_supplicant *wpa_s) +void wpas_auth_failed(struct wpa_supplicant *wpa_s, char *reason) { struct wpa_ssid *ssid = wpa_s->current_ssid; int dur; @@ -4275,17 +4528,23 @@ void wpas_auth_failed(struct wpa_supplicant *wpa_s) if (ssid->auth_failures > 50) dur = 300; - else if (ssid->auth_failures > 20) - dur = 120; else if (ssid->auth_failures > 10) - dur = 60; + dur = 120; else if (ssid->auth_failures > 5) + dur = 90; + else if (ssid->auth_failures > 3) + dur = 60; + else if (ssid->auth_failures > 2) dur = 30; else if (ssid->auth_failures > 1) dur = 20; else dur = 10; + if (ssid->auth_failures > 1 && + wpa_key_mgmt_wpa_ieee8021x(ssid->key_mgmt)) + dur += os_random() % (ssid->auth_failures * 10); + os_get_reltime(&now); if (now.sec + dur <= ssid->disabled_until.sec) return; @@ -4293,9 +4552,9 @@ void wpas_auth_failed(struct wpa_supplicant *wpa_s) ssid->disabled_until.sec = now.sec + dur; wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_TEMP_DISABLED - "id=%d ssid=\"%s\" auth_failures=%u duration=%d", + "id=%d ssid=\"%s\" auth_failures=%u duration=%d reason=%s", ssid->id, wpa_ssid_txt(ssid->ssid, ssid->ssid_len), - ssid->auth_failures, dur); + ssid->auth_failures, dur, reason); } @@ -4412,7 +4671,7 @@ int get_shared_radio_freqs(struct wpa_supplicant *wpa_s, } /* If get_radio_name is not supported, use only the local freq */ - if (!wpa_s->driver->get_radio_name) { + if (!wpa_driver_get_radio_name(wpa_s)) { freq = wpa_drv_shared_freq(wpa_s); if (freq > 0 && idx < len && (idx == 0 || freq_array[0] != freq)) diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf index 9d3bf6d..243787f 100644 --- a/wpa_supplicant/wpa_supplicant.conf +++ b/wpa_supplicant/wpa_supplicant.conf @@ -81,6 +81,8 @@ ctrl_interface=/var/run/wpa_supplicant # to make wpa_supplicant interoperate with these APs, the version number is set # to 1 by default. This configuration value can be used to set it to the new # version (2). +# Note: When using MACsec, eapol_version shall be set to 3, which is +# defined in IEEE Std 802.1X-2010. eapol_version=1 # AP scanning/selection @@ -97,6 +99,8 @@ eapol_version=1 # non-WPA drivers when using IEEE 802.1X mode; do not try to associate with # APs (i.e., external program needs to control association). This mode must # also be used when using wired Ethernet drivers. +# Note: macsec_qca driver is one type of Ethernet driver which implements +# macsec feature. # 2: like 0, but associate with APs using security policy and SSID (but not # BSSID); this can be used, e.g., with ndiswrapper and NDIS drivers to # enable operation with hidden SSIDs and optimized roaming; in this mode, @@ -432,6 +436,59 @@ fast_reauth=1 # matching with the network. Multiple entries can be used to specify more # than one SSID. # +# roaming_partner: Roaming partner information +# This optional field can be used to configure preferences between roaming +# partners. The field is a string in following format: +# <FQDN>,<0/1 exact match>,<priority>,<* or country code> +# (non-exact match means any subdomain matches the entry; priority is in +# 0..255 range with 0 being the highest priority) +# +# update_identifier: PPS MO ID +# (Hotspot 2.0 PerProviderSubscription/UpdateIdentifier) +# +# provisioning_sp: FQDN of the SP that provisioned the credential +# This optional field can be used to keep track of the SP that provisioned +# the credential to find the PPS MO (./Wi-Fi/<provisioning_sp>). +# +# Minimum backhaul threshold (PPS/<X+>/Policy/MinBackhauldThreshold/*) +# These fields can be used to specify minimum download/upload backhaul +# bandwidth that is preferred for the credential. This constraint is +# ignored if the AP does not advertise WAN Metrics information or if the +# limit would prevent any connection. Values are in kilobits per second. +# min_dl_bandwidth_home +# min_ul_bandwidth_home +# min_dl_bandwidth_roaming +# min_ul_bandwidth_roaming +# +# max_bss_load: Maximum BSS Load Channel Utilization (1..255) +# (PPS/<X+>/Policy/MaximumBSSLoadValue) +# This value is used as the maximum channel utilization for network +# selection purposes for home networks. If the AP does not advertise +# BSS Load or if the limit would prevent any connection, this constraint +# will be ignored. +# +# req_conn_capab: Required connection capability +# (PPS/<X+>/Policy/RequiredProtoPortTuple) +# This value is used to configure set of required protocol/port pairs that +# a roaming network shall support (include explicitly in Connection +# Capability ANQP element). This constraint is ignored if the AP does not +# advertise Connection Capability or if this constraint would prevent any +# network connection. This policy is not used in home networks. +# Format: <protocol>[:<comma-separated list of ports] +# Multiple entries can be used to list multiple requirements. +# For example, number of common TCP protocols: +# req_conn_capab=6,22,80,443 +# For example, IPSec/IKE: +# req_conn_capab=17:500 +# req_conn_capab=50 +# +# ocsp: Whether to use/require OCSP to check server certificate +# 0 = do not use OCSP stapling (TLS certificate status extension) +# 1 = try to use OCSP stapling, but not require response +# 2 = require valid OCSP stapling response +# +# sim_num: Identifier for which SIM to use in multi-SIM devices +# # for example: # #cred={ @@ -552,6 +609,8 @@ fast_reauth=1 # bgscan="learn:<short bgscan interval in seconds>:<signal strength threshold>: # <long interval>[:<database file name>]" # bgscan="learn:30:-45:300:/etc/wpa_supplicant/network1.bgscan" +# Explicitly disable bgscan by setting +# bgscan="" # # This option can also be set outside of all network blocks for the bgscan # parameter to apply for all the networks that have no specific bgscan @@ -620,8 +679,16 @@ fast_reauth=1 # bit0 (1): require dynamically generated unicast WEP key # bit1 (2): require dynamically generated broadcast WEP key # (3 = require both keys; default) -# Note: When using wired authentication, eapol_flags must be set to 0 for the -# authentication to be completed successfully. +# Note: When using wired authentication (including macsec_qca driver), +# eapol_flags must be set to 0 for the authentication to be completed +# successfully. +# +# macsec_policy: IEEE 802.1X/MACsec options +# This determines how sessions are secured with MACsec. It is currently +# applicable only when using the macsec_qca driver interface. +# 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. # # mixed_cell: This option can be used to configure whether so called mixed # cells, i.e., networks that use both plaintext and encryption in the same @@ -800,6 +867,10 @@ fast_reauth=1 # EAP workarounds are disabled with eap_workarounds=0. # For EAP-FAST, this must be set to 0 (or left unconfigured for the # default value to be used automatically). +# tls_disable_tlsv1_1=1 - disable use of TLSv1.1 (a workaround for AAA servers +# that have issues interoperating with updated TLS version) +# tls_disable_tlsv1_2=1 - disable use of TLSv1.2 (a workaround for AAA servers +# that have issues interoperating with updated TLS version) # # Following certificate/private key fields are used in inner Phase2 # authentication when using EAP-TTLS or EAP-PEAP. @@ -890,6 +961,14 @@ fast_reauth=1 # 0 = SGI enabled (if AP supports it) # 1 = SGI disabled # +# disable_ldpc: Whether LDPC should be disabled. +# 0 = LDPC enabled (if AP supports it) +# 1 = LDPC disabled +# +# ht40_intolerant: Whether 40 MHz intolerant should be indicated. +# 0 = 40 MHz tolerant (default) +# 1 = 40 MHz intolerant +# # ht_mcs: Configure allowed MCS rates. # Parsed as an array of bytes, in base-16 (ascii-hex) # ht_mcs="" // Use all available (default) @@ -901,6 +980,9 @@ fast_reauth=1 # 0 = Enable MAX-AMSDU if hardware supports it. # 1 = Disable AMSDU # +# ampdu_factor: Maximum A-MPDU Length Exponent +# Value: 0-3, see 7.3.2.56.3 in IEEE Std 802.11n-2009. +# # ampdu_density: Allow overriding AMPDU density configuration. # Treated as hint by the kernel. # -1 = Do not make any changes. @@ -1271,3 +1353,17 @@ freq_list=5180 network={ key_mgmt=NONE } + + +# Example MACsec configuration +#network={ +# key_mgmt=IEEE8021X +# eap=TTLS +# phase2="auth=PAP" +# anonymous_identity="anonymous@example.com" +# identity="user@example.com" +# password="secretr" +# ca_cert="/etc/cert/ca.pem" +# eapol_flags=0 +# macsec_policy=1 +#} diff --git a/wpa_supplicant/wpa_supplicant_conf.sh b/wpa_supplicant/wpa_supplicant_conf.sh index f36eef1..f36eef1 100644..100755 --- a/wpa_supplicant/wpa_supplicant_conf.sh +++ b/wpa_supplicant/wpa_supplicant_conf.sh diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h index 267c226..a83c8cd 100644 --- a/wpa_supplicant/wpa_supplicant_i.h +++ b/wpa_supplicant/wpa_supplicant_i.h @@ -64,6 +64,17 @@ struct wpa_interface { */ const char *confanother; +#ifdef CONFIG_P2P + /** + * conf_p2p_dev - Additional configuration file used to hold the + * P2P Device configuration parameters. + * + * This can also be %NULL. In such a case, if a P2P Device dedicated + * interfaces is created, the main configuration file will be used. + */ + const char *conf_p2p_dev; +#endif /* CONFIG_P2P */ + /** * ctrl_interface - Control interface parameter * @@ -305,9 +316,10 @@ int radio_add_work(struct wpa_supplicant *wpa_s, unsigned int freq, void (*cb)(struct wpa_radio_work *work, int deinit), void *ctx); void radio_work_done(struct wpa_radio_work *work); -void radio_remove_unstarted_work(struct wpa_supplicant *wpa_s, - const char *type); +void radio_remove_works(struct wpa_supplicant *wpa_s, + const char *type, int remove_all); void radio_work_check_next(struct wpa_supplicant *wpa_s); +int radio_work_pending(struct wpa_supplicant *wpa_s, const char *type); struct wpa_connect_work { unsigned int sme:1; @@ -385,6 +397,11 @@ struct wpa_supplicant { char *confname; char *confanother; + +#ifdef CONFIG_P2P + char *conf_p2p_dev; +#endif /* CONFIG_P2P */ + struct wpa_config *conf; int countermeasures; struct os_reltime last_michael_mic_error; @@ -419,6 +436,9 @@ struct wpa_supplicant { enum { WPA_SETBAND_AUTO, WPA_SETBAND_5G, WPA_SETBAND_2G } setband; + /* Preferred network for the next connection attempt */ + struct wpa_ssid *next_ssid; + /* previous scan was wildcard when interleaving between * wildcard scans and specific SSID scan when max_ssids=1 */ int prev_scan_wildcard; @@ -527,6 +547,7 @@ struct wpa_supplicant { int scan_runs; /* number of scan runs since WPS was started */ int *next_scan_freqs; int *manual_scan_freqs; + int *manual_sched_scan_freqs; unsigned int manual_scan_passive:1; unsigned int manual_scan_use_id:1; unsigned int manual_scan_only_new:1; @@ -574,6 +595,7 @@ struct wpa_supplicant { u8 pending_eapol_rx_src[ETH_ALEN]; unsigned int last_eapol_matches_bssid:1; unsigned int eap_expected_failure:1; + unsigned int reattach:1; /* reassociation to the same BSS requested */ struct ibss_rsn *ibss_rsn; @@ -632,6 +654,7 @@ struct wpa_supplicant { unsigned int pending_action_freq; int pending_action_no_cck; int pending_action_without_roc; + unsigned int pending_action_tx_done:1; void (*pending_action_tx_status_cb)(struct wpa_supplicant *wpa_s, unsigned int freq, const u8 *dst, const u8 *src, const u8 *bssid, @@ -665,6 +688,8 @@ struct wpa_supplicant { u8 p2p_auth_invite[ETH_ALEN]; int p2p_sd_over_ctrl_iface; int p2p_in_provisioning; + int p2p_in_invitation; + int p2p_invite_go_freq; int pending_invite_ssid_id; int show_group_started; u8 go_dev_addr[ETH_ALEN]; @@ -751,7 +776,6 @@ struct wpa_supplicant { int after_wps; int known_wps_freq; unsigned int wps_freq; - u16 wps_ap_channel; int wps_fragment_size; int auto_reconnect_disabled; @@ -768,7 +792,15 @@ struct wpa_supplicant { unsigned int auto_select:1; unsigned int auto_network_select:1; unsigned int fetch_all_anqp:1; + unsigned int fetch_osu_info:1; + unsigned int fetch_osu_icon_in_progress:1; struct wpa_bss *interworking_gas_bss; + unsigned int osu_icon_id; + struct osu_provider *osu_prov; + size_t osu_prov_count; + struct os_reltime osu_icon_fetch_start; + unsigned int num_osu_scans; + unsigned int num_prov_found; #endif /* CONFIG_INTERWORKING */ unsigned int drv_capa_known; @@ -777,6 +809,9 @@ struct wpa_supplicant { u16 num_modes; u16 flags; } hw; +#ifdef CONFIG_MACSEC + struct ieee802_1x_kay *kay; +#endif /* CONFIG_MACSEC */ int pno; int pno_sched_pending; @@ -791,6 +826,7 @@ struct wpa_supplicant { u8 last_gas_dialog_token, prev_gas_dialog_token; unsigned int no_keep_alive:1; + unsigned int ext_mgmt_frame_handling:1; #ifdef CONFIG_WNM u8 wnm_dialog_token; @@ -898,7 +934,7 @@ void wpa_supplicant_clear_status(struct wpa_supplicant *wpa_s); void wpas_connection_failed(struct wpa_supplicant *wpa_s, const u8 *bssid); int wpas_driver_bss_selection(struct wpa_supplicant *wpa_s); int wpas_is_p2p_prioritized(struct wpa_supplicant *wpa_s); -void wpas_auth_failed(struct wpa_supplicant *wpa_s); +void wpas_auth_failed(struct wpa_supplicant *wpa_s, char *reason); void wpas_clear_temp_disabled(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, int clear_failures); int disallowed_bssid(struct wpa_supplicant *wpa_s, const u8 *bssid); diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c index e8a4b35..350b122 100644 --- a/wpa_supplicant/wpas_glue.c +++ b/wpa_supplicant/wpas_glue.c @@ -26,6 +26,7 @@ #include "bss.h" #include "scan.h" #include "notify.h" +#include "wpas_kay.h" #ifndef CONFIG_NO_CONFIG_BLOBS @@ -254,6 +255,8 @@ static void wpa_supplicant_eapol_cb(struct eapol_sm *eapol, * authentication failure. */ wpa_supplicant_req_auth_timeout(wpa_s, 2, 0); + } else { + ieee802_1x_notify_create_actor(wpa_s, wpa_s->last_eapol_src); } if (result != EAPOL_SUPP_RESULT_SUCCESS || @@ -558,12 +561,12 @@ static int wpa_supplicant_tdls_get_capa(void *ctx, int *tdls_supported, static int wpa_supplicant_send_tdls_mgmt(void *ctx, const u8 *dst, u8 action_code, u8 dialog_token, - u16 status_code, const u8 *buf, - size_t len) + u16 status_code, u32 peer_capab, + const u8 *buf, size_t len) { struct wpa_supplicant *wpa_s = ctx; return wpa_drv_send_tdls_mgmt(wpa_s, dst, action_code, dialog_token, - status_code, buf, len); + status_code, peer_capab, buf, len); } diff --git a/wpa_supplicant/wpas_kay.c b/wpa_supplicant/wpas_kay.c new file mode 100644 index 0000000..354decf --- /dev/null +++ b/wpa_supplicant/wpas_kay.c @@ -0,0 +1,378 @@ +/* + * IEEE 802.1X-2010 KaY Interface + * 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 <openssl/ssl.h> +#include "utils/includes.h" + +#include "utils/common.h" +#include "eap_peer/eap.h" +#include "eap_peer/eap_i.h" +#include "eapol_supp/eapol_supp_sm.h" +#include "pae/ieee802_1x_key.h" +#include "pae/ieee802_1x_kay.h" +#include "wpa_supplicant_i.h" +#include "config.h" +#include "config_ssid.h" +#include "driver_i.h" +#include "wpas_kay.h" + + +#define DEFAULT_KEY_LEN 16 +/* secure Connectivity Association Key Name (CKN) */ +#define DEFAULT_CKN_LEN 16 + + +static int wpas_macsec_init(void *priv, struct macsec_init_params *params) +{ + return wpa_drv_macsec_init(priv, params); +} + + +static int wpas_macsec_deinit(void *priv) +{ + return wpa_drv_macsec_deinit(priv); +} + + +static int wpas_enable_protect_frames(void *wpa_s, Boolean enabled) +{ + return wpa_drv_enable_protect_frames(wpa_s, enabled); +} + + +static int wpas_set_replay_protect(void *wpa_s, Boolean enabled, u32 window) +{ + return wpa_drv_set_replay_protect(wpa_s, enabled, window); +} + + +static int wpas_set_current_cipher_suite(void *wpa_s, const u8 *cs, + size_t cs_len) +{ + return wpa_drv_set_current_cipher_suite(wpa_s, cs, cs_len); +} + + +static int wpas_enable_controlled_port(void *wpa_s, Boolean enabled) +{ + return wpa_drv_enable_controlled_port(wpa_s, enabled); +} + + +static int wpas_get_receive_lowest_pn(void *wpa_s, u32 channel, + u8 an, u32 *lowest_pn) +{ + return wpa_drv_get_receive_lowest_pn(wpa_s, channel, an, lowest_pn); +} + + +static int wpas_get_transmit_next_pn(void *wpa_s, u32 channel, + u8 an, u32 *next_pn) +{ + return wpa_drv_get_transmit_next_pn(wpa_s, channel, an, next_pn); +} + + +static int wpas_set_transmit_next_pn(void *wpa_s, u32 channel, + u8 an, u32 next_pn) +{ + return wpa_drv_set_transmit_next_pn(wpa_s, channel, an, next_pn); +} + + +static int wpas_get_available_receive_sc(void *wpa_s, u32 *channel) +{ + return wpa_drv_get_available_receive_sc(wpa_s, channel); +} + + +static unsigned int conf_offset_val(enum confidentiality_offset co) +{ + switch (co) { + case CONFIDENTIALITY_OFFSET_30: + return 30; + break; + case CONFIDENTIALITY_OFFSET_50: + return 50; + default: + return 0; + } +} + + +static int wpas_create_receive_sc(void *wpa_s, u32 channel, + struct ieee802_1x_mka_sci *sci, + enum validate_frames vf, + enum confidentiality_offset co) +{ + return wpa_drv_create_receive_sc(wpa_s, channel, sci->addr, sci->port, + conf_offset_val(co), vf); +} + + +static int wpas_delete_receive_sc(void *wpa_s, u32 channel) +{ + return wpa_drv_delete_receive_sc(wpa_s, channel); +} + + +static int wpas_create_receive_sa(void *wpa_s, u32 channel, u8 an, + u32 lowest_pn, const u8 *sak) +{ + return wpa_drv_create_receive_sa(wpa_s, channel, an, lowest_pn, sak); +} + + +static int wpas_enable_receive_sa(void *wpa_s, u32 channel, u8 an) +{ + return wpa_drv_enable_receive_sa(wpa_s, channel, an); +} + + +static int wpas_disable_receive_sa(void *wpa_s, u32 channel, u8 an) +{ + return wpa_drv_disable_receive_sa(wpa_s, channel, an); +} + + +static int wpas_get_available_transmit_sc(void *wpa_s, u32 *channel) +{ + return wpa_drv_get_available_transmit_sc(wpa_s, channel); +} + + +static int +wpas_create_transmit_sc(void *wpa_s, u32 channel, + const struct ieee802_1x_mka_sci *sci, + enum confidentiality_offset co) +{ + return wpa_drv_create_transmit_sc(wpa_s, channel, sci->addr, sci->port, + conf_offset_val(co)); +} + + +static int wpas_delete_transmit_sc(void *wpa_s, u32 channel) +{ + return wpa_drv_delete_transmit_sc(wpa_s, channel); +} + + +static int wpas_create_transmit_sa(void *wpa_s, u32 channel, u8 an, + u32 next_pn, Boolean confidentiality, + const u8 *sak) +{ + return wpa_drv_create_transmit_sa(wpa_s, channel, an, next_pn, + confidentiality, sak); +} + + +static int wpas_enable_transmit_sa(void *wpa_s, u32 channel, u8 an) +{ + return wpa_drv_enable_transmit_sa(wpa_s, channel, an); +} + + +static int wpas_disable_transmit_sa(void *wpa_s, u32 channel, u8 an) +{ + return wpa_drv_disable_transmit_sa(wpa_s, channel, an); +} + + +int ieee802_1x_alloc_kay_sm(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) +{ + struct ieee802_1x_kay_ctx *kay_ctx; + struct ieee802_1x_kay *res = NULL; + enum macsec_policy policy; + + ieee802_1x_dealloc_kay_sm(wpa_s); + + if (!ssid || ssid->macsec_policy == 0) + return 0; + + policy = ssid->macsec_policy == 1 ? SHOULD_SECURE : DO_NOT_SECURE; + + kay_ctx = os_zalloc(sizeof(*kay_ctx)); + if (!kay_ctx) + return -1; + + kay_ctx->ctx = wpa_s; + + kay_ctx->macsec_init = wpas_macsec_init; + kay_ctx->macsec_deinit = wpas_macsec_deinit; + kay_ctx->enable_protect_frames = wpas_enable_protect_frames; + kay_ctx->set_replay_protect = wpas_set_replay_protect; + kay_ctx->set_current_cipher_suite = wpas_set_current_cipher_suite; + kay_ctx->enable_controlled_port = wpas_enable_controlled_port; + kay_ctx->get_receive_lowest_pn = wpas_get_receive_lowest_pn; + kay_ctx->get_transmit_next_pn = wpas_get_transmit_next_pn; + kay_ctx->set_transmit_next_pn = wpas_set_transmit_next_pn; + kay_ctx->get_available_receive_sc = wpas_get_available_receive_sc; + kay_ctx->create_receive_sc = wpas_create_receive_sc; + kay_ctx->delete_receive_sc = wpas_delete_receive_sc; + kay_ctx->create_receive_sa = wpas_create_receive_sa; + kay_ctx->enable_receive_sa = wpas_enable_receive_sa; + kay_ctx->disable_receive_sa = wpas_disable_receive_sa; + kay_ctx->get_available_transmit_sc = wpas_get_available_transmit_sc; + kay_ctx->create_transmit_sc = wpas_create_transmit_sc; + kay_ctx->delete_transmit_sc = wpas_delete_transmit_sc; + kay_ctx->create_transmit_sa = wpas_create_transmit_sa; + kay_ctx->enable_transmit_sa = wpas_enable_transmit_sa; + kay_ctx->disable_transmit_sa = wpas_disable_transmit_sa; + + res = ieee802_1x_kay_init(kay_ctx, policy, wpa_s->ifname, + wpa_s->own_addr); + if (res == NULL) { + os_free(kay_ctx); + return -1; + } + + wpa_s->kay = res; + + return 0; +} + + +void ieee802_1x_dealloc_kay_sm(struct wpa_supplicant *wpa_s) +{ + if (!wpa_s->kay) + return; + + ieee802_1x_kay_deinit(wpa_s->kay); + wpa_s->kay = NULL; +} + + +static int ieee802_1x_auth_get_session_id(struct wpa_supplicant *wpa_s, + const u8 *addr, u8 *sid, size_t *len) +{ + const u8 *session_id; + size_t id_len, need_len; + + session_id = eapol_sm_get_session_id(wpa_s->eapol, &id_len); + if (session_id == NULL) { + wpa_printf(MSG_DEBUG, + "Failed to get SessionID from EAPOL state machines"); + return -1; + } + + need_len = 1 + 2 * SSL3_RANDOM_SIZE; + if (need_len > id_len) { + wpa_printf(MSG_DEBUG, "EAP Session-Id not long enough"); + return -1; + } + + os_memcpy(sid, session_id, need_len); + *len = need_len; + + return 0; +} + + +static int ieee802_1x_auth_get_msk(struct wpa_supplicant *wpa_s, const u8 *addr, + u8 *msk, size_t *len) +{ + u8 key[EAP_MSK_LEN]; + size_t keylen; + struct eapol_sm *sm; + int res; + + sm = wpa_s->eapol; + if (sm == NULL) + return -1; + + keylen = EAP_MSK_LEN; + res = eapol_sm_get_key(sm, key, keylen); + if (res) { + wpa_printf(MSG_DEBUG, + "Failed to get MSK from EAPOL state machines"); + return -1; + } + + if (keylen > *len) + keylen = *len; + os_memcpy(msk, key, keylen); + *len = keylen; + + return 0; +} + + +void * ieee802_1x_notify_create_actor(struct wpa_supplicant *wpa_s, + const u8 *peer_addr) +{ + u8 *sid; + size_t sid_len = 128; + struct mka_key_name *ckn; + struct mka_key *cak; + struct mka_key *msk; + void *res = NULL; + + if (!wpa_s->kay || wpa_s->kay->policy == DO_NOT_SECURE) + return NULL; + + wpa_printf(MSG_DEBUG, + "IEEE 802.1X: External notification - Create MKA for " + MACSTR, MAC2STR(peer_addr)); + + msk = os_zalloc(sizeof(*msk)); + sid = os_zalloc(sid_len); + ckn = os_zalloc(sizeof(*ckn)); + cak = os_zalloc(sizeof(*cak)); + if (!msk || !sid || !ckn || !cak) + goto fail; + + msk->len = DEFAULT_KEY_LEN; + if (ieee802_1x_auth_get_msk(wpa_s, wpa_s->bssid, msk->key, &msk->len)) { + wpa_printf(MSG_ERROR, "IEEE 802.1X: Could not get MSK"); + goto fail; + } + + if (ieee802_1x_auth_get_session_id(wpa_s, wpa_s->bssid, sid, &sid_len)) + { + wpa_printf(MSG_ERROR, + "IEEE 802.1X: Could not get EAP Session Id"); + goto fail; + } + + /* Derive CAK from MSK */ + cak->len = DEFAULT_KEY_LEN; + if (ieee802_1x_cak_128bits_aes_cmac(msk->key, wpa_s->own_addr, + peer_addr, cak->key)) { + 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_128bits_aes_cmac(msk->key, wpa_s->own_addr, + peer_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(wpa_s->kay, ckn, cak, 0, + EAP_EXCHANGE, FALSE); + +fail: + if (msk) { + os_memset(msk, 0, sizeof(*msk)); + os_free(msk); + } + os_free(sid); + os_free(ckn); + if (cak) { + os_memset(cak, 0, sizeof(*cak)); + os_free(cak); + } + + return res; +} diff --git a/wpa_supplicant/wpas_kay.h b/wpa_supplicant/wpas_kay.h new file mode 100644 index 0000000..b7236d0 --- /dev/null +++ b/wpa_supplicant/wpas_kay.h @@ -0,0 +1,41 @@ +/* + * IEEE 802.1X-2010 KaY Interface + * 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 WPAS_KAY_H +#define WPAS_KAY_H + +#ifdef CONFIG_MACSEC + +int ieee802_1x_alloc_kay_sm(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid); +void * ieee802_1x_notify_create_actor(struct wpa_supplicant *wpa_s, + const u8 *peer_addr); +void ieee802_1x_dealloc_kay_sm(struct wpa_supplicant *wpa_s); + +#else /* CONFIG_MACSEC */ + +static inline int ieee802_1x_alloc_kay_sm(struct wpa_supplicant *wpa_s, + struct wpa_ssid *ssid) +{ + return 0; +} + +static inline void * +ieee802_1x_notify_create_actor(struct wpa_supplicant *wpa_s, + const u8 *peer_addr) +{ + return NULL; +} + +static inline void ieee802_1x_dealloc_kay_sm(struct wpa_supplicant *wpa_s) +{ +} + +#endif /* CONFIG_MACSEC */ + +#endif /* WPAS_KAY_H */ diff --git a/wpa_supplicant/wpas_module_tests.c b/wpa_supplicant/wpas_module_tests.c new file mode 100644 index 0000000..e4c83b5 --- /dev/null +++ b/wpa_supplicant/wpas_module_tests.c @@ -0,0 +1,102 @@ +/* + * wpa_supplicant module tests + * Copyright (c) 2014, Jouni Malinen <j@w1.fi> + * + * 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 "wpa_supplicant_i.h" +#include "blacklist.h" + + +static int wpas_blacklist_module_tests(void) +{ + struct wpa_supplicant wpa_s; + int ret = -1; + + os_memset(&wpa_s, 0, sizeof(wpa_s)); + + wpa_blacklist_clear(&wpa_s); + + if (wpa_blacklist_get(NULL, NULL) != NULL || + wpa_blacklist_get(NULL, (u8 *) "123456") != NULL || + wpa_blacklist_get(&wpa_s, NULL) != NULL || + wpa_blacklist_get(&wpa_s, (u8 *) "123456") != NULL) + goto fail; + + if (wpa_blacklist_add(NULL, NULL) == 0 || + wpa_blacklist_add(NULL, (u8 *) "123456") == 0 || + wpa_blacklist_add(&wpa_s, NULL) == 0) + goto fail; + + if (wpa_blacklist_del(NULL, NULL) == 0 || + wpa_blacklist_del(NULL, (u8 *) "123456") == 0 || + wpa_blacklist_del(&wpa_s, NULL) == 0 || + wpa_blacklist_del(&wpa_s, (u8 *) "123456") == 0) + goto fail; + + if (wpa_blacklist_add(&wpa_s, (u8 *) "111111") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "111111") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "222222") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "333333") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "444444") < 0 || + wpa_blacklist_del(&wpa_s, (u8 *) "333333") < 0 || + wpa_blacklist_del(&wpa_s, (u8 *) "xxxxxx") == 0 || + wpa_blacklist_get(&wpa_s, (u8 *) "xxxxxx") != NULL || + wpa_blacklist_get(&wpa_s, (u8 *) "111111") == NULL || + wpa_blacklist_get(&wpa_s, (u8 *) "222222") == NULL || + wpa_blacklist_get(&wpa_s, (u8 *) "444444") == NULL || + wpa_blacklist_del(&wpa_s, (u8 *) "111111") < 0 || + wpa_blacklist_del(&wpa_s, (u8 *) "222222") < 0 || + wpa_blacklist_del(&wpa_s, (u8 *) "444444") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "111111") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "222222") < 0 || + wpa_blacklist_add(&wpa_s, (u8 *) "333333") < 0) + goto fail; + + ret = 0; +fail: + wpa_blacklist_clear(&wpa_s); + + if (ret) + wpa_printf(MSG_ERROR, "blacklist module test failure"); + + return ret; +} + + +int wpas_module_tests(void) +{ + int ret = 0; + + wpa_printf(MSG_INFO, "wpa_supplicant module tests"); + + if (wpas_blacklist_module_tests() < 0) + ret = -1; + +#ifdef CONFIG_WPS + { + int wps_module_tests(void); + if (wps_module_tests() < 0) + ret = -1; + } +#endif /* CONFIG_WPS */ + + { + int utils_module_tests(void); + if (utils_module_tests() < 0) + ret = -1; + } + + { + int common_module_tests(void); + if (common_module_tests() < 0) + ret = -1; + } + + return ret; +} diff --git a/wpa_supplicant/wps_supplicant.c b/wpa_supplicant/wps_supplicant.c index 537aac3..de015ee 100644 --- a/wpa_supplicant/wps_supplicant.c +++ b/wpa_supplicant/wps_supplicant.c @@ -52,6 +52,25 @@ static void wpas_wps_clear_ap_info(struct wpa_supplicant *wpa_s) } +static void wpas_wps_assoc_with_cred(void *eloop_ctx, void *timeout_ctx) +{ + struct wpa_supplicant *wpa_s = eloop_ctx; + int use_fast_assoc = timeout_ctx != NULL; + + wpa_printf(MSG_DEBUG, "WPS: Continuing association after eapol_cb"); + if (!use_fast_assoc || + wpa_supplicant_fast_associate(wpa_s) != 1) + wpa_supplicant_req_scan(wpa_s, 0, 0); +} + + +static void wpas_wps_assoc_with_cred_cancel(struct wpa_supplicant *wpa_s) +{ + eloop_cancel_timeout(wpas_wps_assoc_with_cred, wpa_s, (void *) 0); + eloop_cancel_timeout(wpas_wps_assoc_with_cred, wpa_s, (void *) 1); +} + + int wpas_wps_eapol_cb(struct wpa_supplicant *wpa_s) { #ifdef CONFIG_P2P @@ -124,9 +143,18 @@ int wpas_wps_eapol_cb(struct wpa_supplicant *wpa_s) wpabuf_free(wps); } - if (!use_fast_assoc || - wpa_supplicant_fast_associate(wpa_s) != 1) - wpa_supplicant_req_scan(wpa_s, 0, 0); + /* + * Complete the next step from an eloop timeout to allow pending + * driver events related to the disconnection to be processed + * first. This makes it less likely for disconnection event to + * cause problems with the following connection. + */ + wpa_printf(MSG_DEBUG, "WPS: Continue association from timeout"); + wpas_wps_assoc_with_cred_cancel(wpa_s); + eloop_register_timeout(0, 10000, + wpas_wps_assoc_with_cred, wpa_s, + use_fast_assoc ? (void *) 1 : + (void *) 0); return 1; } @@ -278,7 +306,6 @@ static int wpa_supplicant_wps_cred(void *ctx, { struct wpa_supplicant *wpa_s = ctx; struct wpa_ssid *ssid = wpa_s->current_ssid; - u8 key_idx = 0; u16 auth_type; #ifdef CONFIG_WPS_REG_DISABLE_OPEN int registrar = 0; @@ -324,7 +351,6 @@ static int wpa_supplicant_wps_cred(void *ctx, } if (auth_type != WPS_AUTH_OPEN && - auth_type != WPS_AUTH_SHARED && auth_type != WPS_AUTH_WPAPSK && auth_type != WPS_AUTH_WPA2PSK) { wpa_printf(MSG_DEBUG, "WPS: Ignored credentials for " @@ -372,6 +398,17 @@ static int wpa_supplicant_wps_cred(void *ctx, ssid = wpa_config_add_network(wpa_s->conf); if (ssid == NULL) return -1; + if (wpa_s->current_ssid) { + /* + * Should the GO issue multiple credentials for some + * reason, each credential should be marked as a + * temporary P2P group similarly to the one that gets + * marked as such based on the pre-configured values + * used for the WPS network block. + */ + ssid->p2p_group = wpa_s->current_ssid->p2p_group; + ssid->temporary = wpa_s->current_ssid->temporary; + } wpas_notify_network_added(wpa_s, ssid); } @@ -387,38 +424,6 @@ static int wpa_supplicant_wps_cred(void *ctx, switch (cred->encr_type) { case WPS_ENCR_NONE: break; - case WPS_ENCR_WEP: - if (cred->key_len <= 0) - break; - if (cred->key_len != 5 && cred->key_len != 13 && - cred->key_len != 10 && cred->key_len != 26) { - wpa_printf(MSG_ERROR, "WPS: Invalid WEP Key length " - "%lu", (unsigned long) cred->key_len); - return -1; - } - if (cred->key_idx > NUM_WEP_KEYS) { - wpa_printf(MSG_ERROR, "WPS: Invalid WEP Key index %d", - cred->key_idx); - return -1; - } - if (cred->key_idx) - key_idx = cred->key_idx - 1; - if (cred->key_len == 10 || cred->key_len == 26) { - if (hexstr2bin((char *) cred->key, - ssid->wep_key[key_idx], - cred->key_len / 2) < 0) { - wpa_printf(MSG_ERROR, "WPS: Invalid WEP Key " - "%d", key_idx); - return -1; - } - ssid->wep_key_len[key_idx] = cred->key_len / 2; - } else { - os_memcpy(ssid->wep_key[key_idx], cred->key, - cred->key_len); - ssid->wep_key_len[key_idx] = cred->key_len; - } - ssid->wep_tx_keyidx = key_idx; - break; case WPS_ENCR_TKIP: ssid->pairwise_cipher = WPA_CIPHER_TKIP; break; @@ -443,11 +448,6 @@ static int wpa_supplicant_wps_cred(void *ctx, } #endif /* CONFIG_WPS_REG_DISABLE_OPEN */ break; - case WPS_AUTH_SHARED: - ssid->auth_alg = WPA_AUTH_ALG_SHARED; - ssid->key_mgmt = WPA_KEY_MGMT_NONE; - ssid->proto = 0; - break; case WPS_AUTH_WPAPSK: ssid->auth_alg = WPA_AUTH_ALG_OPEN; ssid->key_mgmt = WPA_KEY_MGMT_PSK; @@ -489,9 +489,6 @@ static int wpa_supplicant_wps_cred(void *ctx, wpas_wps_security_workaround(wpa_s, ssid, cred); - if (cred->ap_channel) - wpa_s->wps_ap_channel = cred->ap_channel; - wpas_wps_remove_dup_network(wpa_s, ssid); #ifndef CONFIG_NO_CONFIG_WRITE @@ -513,15 +510,6 @@ static int wpa_supplicant_wps_cred(void *ctx, } -#ifdef CONFIG_P2P -static void wpas_wps_pbc_overlap_cb(void *eloop_ctx, void *timeout_ctx) -{ - struct wpa_supplicant *wpa_s = eloop_ctx; - wpas_p2p_notif_pbc_overlap(wpa_s); -} -#endif /* CONFIG_P2P */ - - static void wpa_supplicant_wps_event_m2d(struct wpa_supplicant *wpa_s, struct wps_event_m2d *m2d) { @@ -540,7 +528,7 @@ static void wpa_supplicant_wps_event_m2d(struct wpa_supplicant *wpa_s, * Notify P2P from eloop timeout to avoid issues with the * interface getting removed while processing a message. */ - eloop_register_timeout(0, 0, wpas_wps_pbc_overlap_cb, wpa_s, + eloop_register_timeout(0, 0, wpas_p2p_pbc_overlap_cb, wpa_s, NULL); } #endif /* CONFIG_P2P */ @@ -1304,7 +1292,6 @@ static void wpas_wps_set_sel_reg_cb(void *ctx, int sel_reg, u16 dev_passwd_id, static u16 wps_fix_config_methods(u16 config_methods) { -#ifdef CONFIG_WPS2 if ((config_methods & (WPS_CONFIG_DISPLAY | WPS_CONFIG_VIRT_DISPLAY | WPS_CONFIG_PHY_DISPLAY)) == WPS_CONFIG_DISPLAY) { @@ -1319,7 +1306,6 @@ static u16 wps_fix_config_methods(u16 config_methods) "virtual_push_button for WPS 2.0 compliance"); config_methods |= WPS_CONFIG_VIRT_PUSHBUTTON; } -#endif /* CONFIG_WPS2 */ return config_methods; } @@ -1473,11 +1459,16 @@ static void wpas_wps_nfc_clear(struct wps_context *wps) void wpas_wps_deinit(struct wpa_supplicant *wpa_s) { + wpas_wps_assoc_with_cred_cancel(wpa_s); eloop_cancel_timeout(wpas_wps_timeout, wpa_s, NULL); eloop_cancel_timeout(wpas_wps_clear_timeout, wpa_s, NULL); eloop_cancel_timeout(wpas_wps_reenable_networks_cb, wpa_s, NULL); wpas_wps_clear_ap_info(wpa_s); +#ifdef CONFIG_P2P + eloop_cancel_timeout(wpas_p2p_pbc_overlap_cb, wpa_s, NULL); +#endif /* CONFIG_P2P */ + if (wpa_s->wps == NULL) return; @@ -1934,8 +1925,10 @@ int wpas_wps_er_config(struct wpa_supplicant *wpa_s, const char *uuid, if (os_strcmp(settings->encr, "NONE") == 0) cred.encr_type = WPS_ENCR_NONE; +#ifdef CONFIG_TESTING_OPTIONS else if (os_strcmp(settings->encr, "WEP") == 0) cred.encr_type = WPS_ENCR_WEP; +#endif /* CONFIG_TESTING_OPTIONS */ else if (os_strcmp(settings->encr, "TKIP") == 0) cred.encr_type = WPS_ENCR_TKIP; else if (os_strcmp(settings->encr, "CCMP") == 0) @@ -2197,8 +2190,6 @@ int wpas_wps_start_nfc(struct wpa_supplicant *wpa_s, const u8 *go_dev_addr, static int wpas_wps_use_cred(struct wpa_supplicant *wpa_s, struct wps_parse_attr *attr) { - wpa_s->wps_ap_channel = 0; - /* * Disable existing networks temporarily to allow the newly learned * credential to be preferred. Enable the temporarily disabled networks @@ -2214,18 +2205,8 @@ static int wpas_wps_use_cred(struct wpa_supplicant *wpa_s, if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) return 0; - if (!wpa_s->wps_ap_channel && attr->ap_channel) { - wpa_s->wps_ap_channel = WPA_GET_BE16(attr->ap_channel); - wpa_printf(MSG_DEBUG, "WPS: Credential container indicated AP Channel %d", - wpa_s->wps_ap_channel); - } - - wpa_printf(MSG_DEBUG, "WPS: Request reconnection with new network " - "based on the received credential added"); - wpa_s->normal_scans = 0; - wpa_supplicant_reinit_autoscan(wpa_s); - if (wpa_s->wps_ap_channel) { - u16 chan = wpa_s->wps_ap_channel; + if (attr->ap_channel) { + u16 chan = WPA_GET_BE16(attr->ap_channel); int freq = 0; if (chan >= 1 && chan <= 13) @@ -2236,12 +2217,17 @@ static int wpas_wps_use_cred(struct wpa_supplicant *wpa_s, freq = 5000 + 5 * chan; if (freq) { - wpa_printf(MSG_DEBUG, "WPS: Credential indicated " - "AP channel %u -> %u MHz", chan, freq); + wpa_printf(MSG_DEBUG, "WPS: Credential container indicated AP channel %u -> %u MHz", + chan, freq); wpa_s->after_wps = 5; wpa_s->wps_freq = freq; } } + + wpa_printf(MSG_DEBUG, "WPS: Request reconnection with new network " + "based on the received credential added"); + wpa_s->normal_scans = 0; + wpa_supplicant_reinit_autoscan(wpa_s); wpa_s->disconnected = 0; wpa_s->reassociate = 1; @@ -2420,16 +2406,8 @@ struct wpabuf * wpas_wps_nfc_handover_sel(struct wpa_supplicant *wpa_s, } -int wpas_wps_nfc_rx_handover_req(struct wpa_supplicant *wpa_s, - const struct wpabuf *data) -{ - /* TODO */ - return -1; -} - - -int wpas_wps_nfc_rx_handover_sel(struct wpa_supplicant *wpa_s, - const struct wpabuf *data) +static int wpas_wps_nfc_rx_handover_sel(struct wpa_supplicant *wpa_s, + const struct wpabuf *data) { struct wpabuf *wps; int ret = -1; diff --git a/wpa_supplicant/wps_supplicant.h b/wpa_supplicant/wps_supplicant.h index 86e9d09..2263512 100644 --- a/wpa_supplicant/wps_supplicant.h +++ b/wpa_supplicant/wps_supplicant.h @@ -75,10 +75,6 @@ struct wpabuf * wpas_wps_nfc_handover_req(struct wpa_supplicant *wpa_s, int ndef); struct wpabuf * wpas_wps_nfc_handover_sel(struct wpa_supplicant *wpa_s, int ndef, int cr, const char *uuid); -int wpas_wps_nfc_rx_handover_req(struct wpa_supplicant *wpa_s, - const struct wpabuf *data); -int wpas_wps_nfc_rx_handover_sel(struct wpa_supplicant *wpa_s, - const struct wpabuf *data); int wpas_wps_nfc_report_handover(struct wpa_supplicant *wpa_s, const struct wpabuf *req, const struct wpabuf *sel); |