#include "common/gas.h"
#include "common/wpa_ctrl.h"
#include "utils/pcsc_funcs.h"
+#include "utils/eloop.h"
#include "drivers/driver.h"
#include "eap_common/eap_defs.h"
+#include "eap_peer/eap.h"
#include "eap_peer/eap_methods.h"
#include "wpa_supplicant_i.h"
#include "config.h"
#include "scan.h"
#include "notify.h"
#include "gas_query.h"
+#include "hs20_supplicant.h"
#include "interworking.h"
}
wpa_s->disconnected = 0;
wpa_s->reassociate = 1;
+
+ if (wpa_supplicant_fast_associate(wpa_s) >= 0)
+ return;
+
wpa_supplicant_req_scan(wpa_s, 0, 0);
}
}
+static int cred_with_roaming_consortium(struct wpa_supplicant *wpa_s)
+{
+ struct wpa_cred *cred;
+
+ for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
+ if (cred->roaming_consortium_len)
+ return 1;
+ }
+ return 0;
+}
+
+
+static int cred_with_3gpp(struct wpa_supplicant *wpa_s)
+{
+ struct wpa_cred *cred;
+
+ for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
+ if (cred->pcsc || cred->imsi)
+ return 1;
+ }
+ return 0;
+}
+
+
+static int cred_with_nai_realm(struct wpa_supplicant *wpa_s)
+{
+ struct wpa_cred *cred;
+
+ for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
+ if (cred->pcsc || cred->imsi)
+ continue;
+ if (!cred->eap_method)
+ return 1;
+ if (cred->realm && cred->roaming_consortium_len == 0)
+ return 1;
+ }
+ return 0;
+}
+
+
+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)
+ return 1;
+ }
+ return 0;
+}
+
+
+static int additional_roaming_consortiums(struct wpa_bss *bss)
+{
+ const u8 *ie;
+ ie = wpa_bss_get_ie(bss, WLAN_EID_ROAMING_CONSORTIUM);
+ if (ie == NULL || ie[1] == 0)
+ return 0;
+ return ie[2]; /* Number of ANQP OIs */
+}
+
+
+static void interworking_continue_anqp(void *eloop_ctx, void *sock_ctx)
+{
+ struct wpa_supplicant *wpa_s = eloop_ctx;
+ interworking_next_anqp_fetch(wpa_s);
+}
+
+
static int interworking_anqp_send_req(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss)
{
struct wpabuf *buf;
int ret = 0;
int res;
- u16 info_ids[] = {
- ANQP_CAPABILITY_LIST,
- ANQP_VENUE_NAME,
- ANQP_NETWORK_AUTH_TYPE,
- ANQP_ROAMING_CONSORTIUM,
- ANQP_IP_ADDR_TYPE_AVAILABILITY,
- ANQP_NAI_REALM,
- ANQP_3GPP_CELLULAR_NETWORK,
- ANQP_DOMAIN_NAME
- };
+ u16 info_ids[8];
+ size_t num_info_ids = 0;
struct wpabuf *extra = NULL;
+ int all = wpa_s->fetch_all_anqp;
wpa_printf(MSG_DEBUG, "Interworking: ANQP Query Request to " MACSTR,
MAC2STR(bss->bssid));
- buf = anqp_build_req(info_ids, sizeof(info_ids) / sizeof(info_ids[0]),
- extra);
+ info_ids[num_info_ids++] = ANQP_CAPABILITY_LIST;
+ if (all) {
+ info_ids[num_info_ids++] = ANQP_VENUE_NAME;
+ info_ids[num_info_ids++] = ANQP_NETWORK_AUTH_TYPE;
+ }
+ if (all || (cred_with_roaming_consortium(wpa_s) &&
+ additional_roaming_consortiums(bss)))
+ info_ids[num_info_ids++] = ANQP_ROAMING_CONSORTIUM;
+ if (all)
+ 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))
+ info_ids[num_info_ids++] = ANQP_3GPP_CELLULAR_NETWORK;
+ if (all || cred_with_domain(wpa_s))
+ info_ids[num_info_ids++] = ANQP_DOMAIN_NAME;
+ wpa_hexdump(MSG_DEBUG, "Interworking: ANQP Query info",
+ (u8 *) info_ids, num_info_ids * 2);
+
+#ifdef CONFIG_HS20
+ if (wpa_bss_get_vendor_ie(bss, HS20_IE_VENDOR_TYPE)) {
+ u8 *len_pos;
+
+ extra = wpabuf_alloc(100);
+ if (!extra)
+ return -1;
+
+ len_pos = gas_anqp_add_element(extra, ANQP_VENDOR_SPECIFIC);
+ wpabuf_put_be24(extra, OUI_WFA);
+ wpabuf_put_u8(extra, HS20_ANQP_OUI_TYPE);
+ wpabuf_put_u8(extra, HS20_STYPE_QUERY_LIST);
+ wpabuf_put_u8(extra, 0); /* Reserved */
+ wpabuf_put_u8(extra, HS20_STYPE_CAPABILITY_LIST);
+ if (all) {
+ wpabuf_put_u8(extra,
+ HS20_STYPE_OPERATOR_FRIENDLY_NAME);
+ wpabuf_put_u8(extra, HS20_STYPE_WAN_METRICS);
+ wpabuf_put_u8(extra, HS20_STYPE_CONNECTION_CAPABILITY);
+ wpabuf_put_u8(extra, HS20_STYPE_OPERATING_CLASS);
+ }
+ gas_anqp_set_element_len(extra, len_pos);
+ }
+#endif /* CONFIG_HS20 */
+
+ buf = anqp_build_req(info_ids, num_info_ids, extra);
wpabuf_free(extra);
if (buf == NULL)
return -1;
if (res < 0) {
wpa_printf(MSG_DEBUG, "ANQP: Failed to send Query Request");
ret = -1;
+ eloop_register_timeout(0, 0, interworking_continue_anqp, wpa_s,
+ NULL);
} else
wpa_printf(MSG_DEBUG, "ANQP: Query started with dialog token "
"%u", res);
wpa_printf(MSG_DEBUG, "No room for EAP Methods");
return NULL;
}
- r->eap = os_zalloc(r->eap_count * sizeof(struct nai_realm_eap));
+ r->eap = os_calloc(r->eap_count, sizeof(struct nai_realm_eap));
if (r->eap == NULL)
return NULL;
return NULL;
}
- realm = os_zalloc(num * sizeof(struct nai_realm));
+ realm = os_calloc(num, sizeof(struct nai_realm));
if (realm == NULL)
return NULL;
return 0;
}
- if (eap->method == EAP_TYPE_PEAP &&
- eap_get_name(EAP_VENDOR_IETF, eap->inner_method) == NULL)
- return 0;
+ if (eap->method == EAP_TYPE_PEAP) {
+ if (eap->inner_method &&
+ eap_get_name(EAP_VENDOR_IETF, eap->inner_method) == NULL)
+ return 0;
+ if (!eap->inner_method &&
+ eap_get_name(EAP_VENDOR_IETF, EAP_TYPE_MSCHAPV2) == NULL)
+ return 0;
+ }
if (eap->method == EAP_TYPE_TTLS) {
if (eap->inner_method == 0 && eap->inner_non_eap == 0)
- return 0;
+ return 1; /* Assume TTLS/MSCHAPv2 is used */
if (eap->inner_method &&
eap_get_name(EAP_VENDOR_IETF, eap->inner_method) == NULL)
return 0;
break;
if (os_memcmp(pos, plmn, 3) == 0)
return 1; /* Found matching PLMN */
+ pos += 3;
}
}
static int build_root_nai(char *nai, size_t nai_len, const char *imsi,
- char prefix)
+ size_t mnc_len, char prefix)
{
const char *sep, *msin;
char *end, *pos;
return -1;
}
sep = os_strchr(imsi, '-');
- if (sep == NULL)
+ if (sep) {
+ plmn_len = sep - imsi;
+ msin = sep + 1;
+ } else if (mnc_len && os_strlen(imsi) >= 3 + mnc_len) {
+ plmn_len = 3 + mnc_len;
+ msin = imsi + plmn_len;
+ } else
return -1;
- plmn_len = sep - imsi;
if (plmn_len != 5 && plmn_len != 6)
return -1;
- msin = sep + 1;
msin_len = os_strlen(msin);
pos = nai;
static int set_root_nai(struct wpa_ssid *ssid, const char *imsi, char prefix)
{
char nai[100];
- if (build_root_nai(nai, sizeof(nai), imsi, prefix) < 0)
+ if (build_root_nai(nai, sizeof(nai), imsi, 0, prefix) < 0)
return -1;
return wpa_config_set_quoted(ssid, "identity", nai);
}
#endif /* INTERWORKING_3GPP */
+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)
+ return -1;
+ if (wpa_config_set(ssid, "proto", "RSN", 0) < 0)
+ return -1;
+ if (wpa_config_set(ssid, "pairwise", "CCMP", 0) < 0)
+ return -1;
+ return 0;
+}
+
+
static int interworking_connect_3gpp(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss)
{
struct wpa_cred *cred;
struct wpa_ssid *ssid;
const u8 *ie;
+ int eap_type;
+ int res;
+ char prefix;
- if (bss->anqp_3gpp == NULL)
+ if (bss->anqp == NULL || bss->anqp->anqp_3gpp == NULL)
return -1;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
#ifdef PCSC_FUNCS
compare:
#endif /* PCSC_FUNCS */
- if (plmn_id_match(bss->anqp_3gpp, imsi, mnc_len))
+ if (plmn_id_match(bss->anqp->anqp_3gpp, imsi, mnc_len))
break;
}
if (cred == NULL)
ssid = wpa_config_add_network(wpa_s->conf);
if (ssid == NULL)
return -1;
+ ssid->parent_cred = cred;
wpas_notify_network_added(wpa_s, ssid);
wpa_config_set_network_defaults(ssid);
os_memcpy(ssid->ssid, ie + 2, ie[1]);
ssid->ssid_len = ie[1];
- /* TODO: figure out whether to use EAP-SIM, EAP-AKA, or EAP-AKA' */
- if (wpa_config_set(ssid, "eap", "SIM", 0) < 0) {
- wpa_printf(MSG_DEBUG, "EAP-SIM not supported");
+ if (interworking_set_hs20_params(wpa_s, ssid) < 0)
goto fail;
- }
+
+ eap_type = EAP_TYPE_SIM;
if (cred->pcsc && wpa_s->scard && scard_supports_umts(wpa_s->scard))
- wpa_config_set(ssid, "eap", "AKA", 0);
- if (!cred->pcsc && set_root_nai(ssid, cred->imsi, '1') < 0) {
+ eap_type = EAP_TYPE_AKA;
+ if (cred->eap_method && cred->eap_method[0].vendor == EAP_VENDOR_IETF) {
+ if (cred->eap_method[0].method == EAP_TYPE_SIM ||
+ cred->eap_method[0].method == EAP_TYPE_AKA ||
+ cred->eap_method[0].method == EAP_TYPE_AKA_PRIME)
+ eap_type = cred->eap_method[0].method;
+ }
+
+ switch (eap_type) {
+ case EAP_TYPE_SIM:
+ prefix = '1';
+ res = wpa_config_set(ssid, "eap", "SIM", 0);
+ break;
+ case EAP_TYPE_AKA:
+ prefix = '0';
+ res = wpa_config_set(ssid, "eap", "AKA", 0);
+ break;
+ case EAP_TYPE_AKA_PRIME:
+ prefix = '6';
+ res = wpa_config_set(ssid, "eap", "AKA'", 0);
+ break;
+ default:
+ res = -1;
+ break;
+ }
+ if (res < 0) {
+ wpa_printf(MSG_DEBUG, "Selected EAP method (%d) not supported",
+ eap_type);
+ goto fail;
+ }
+
+ if (!cred->pcsc && set_root_nai(ssid, cred->imsi, prefix) < 0) {
wpa_printf(MSG_DEBUG, "Failed to set Root NAI");
goto fail;
}
}
+static int roaming_consortium_element_match(const u8 *ie, const u8 *rc_id,
+ size_t rc_len)
+{
+ const u8 *pos, *end;
+ u8 lens;
+
+ if (ie == NULL)
+ return 0;
+
+ pos = ie + 2;
+ end = ie + 2 + ie[1];
+
+ /* Roaming Consortium element:
+ * Number of ANQP OIs
+ * OI #1 and #2 lengths
+ * OI #1, [OI #2], [OI #3]
+ */
+
+ if (pos + 2 > end)
+ return 0;
+
+ pos++; /* skip Number of ANQP OIs */
+ lens = *pos++;
+ if (pos + (lens & 0x0f) + (lens >> 4) > end)
+ return 0;
+
+ if ((lens & 0x0f) == rc_len && os_memcmp(pos, rc_id, rc_len) == 0)
+ return 1;
+ pos += lens & 0x0f;
+
+ if ((lens >> 4) == rc_len && os_memcmp(pos, rc_id, rc_len) == 0)
+ return 1;
+ pos += lens >> 4;
+
+ if (pos < end && (size_t) (end - pos) == rc_len &&
+ os_memcmp(pos, rc_id, rc_len) == 0)
+ return 1;
+
+ return 0;
+}
+
+
+static int roaming_consortium_anqp_match(const struct wpabuf *anqp,
+ const u8 *rc_id, size_t rc_len)
+{
+ const u8 *pos, *end;
+ u8 len;
+
+ if (anqp == NULL)
+ return 0;
+
+ pos = wpabuf_head(anqp);
+ end = pos + wpabuf_len(anqp);
+
+ /* Set of <OI Length, OI> duples */
+ while (pos < end) {
+ len = *pos++;
+ if (pos + len > end)
+ break;
+ if (len == rc_len && os_memcmp(pos, rc_id, rc_len) == 0)
+ return 1;
+ pos += len;
+ }
+
+ return 0;
+}
+
+
+static int roaming_consortium_match(const u8 *ie, const struct wpabuf *anqp,
+ const u8 *rc_id, size_t rc_len)
+{
+ return roaming_consortium_element_match(ie, rc_id, rc_len) ||
+ roaming_consortium_anqp_match(anqp, rc_id, rc_len);
+}
+
+
+static int cred_excluded_ssid(struct wpa_cred *cred, struct wpa_bss *bss)
+{
+ size_t i;
+
+ if (!cred->excluded_ssid)
+ return 0;
+
+ for (i = 0; i < cred->num_excluded_ssid; i++) {
+ struct excluded_ssid *e = &cred->excluded_ssid[i];
+ if (bss->ssid_len == e->ssid_len &&
+ os_memcmp(bss->ssid, e->ssid, e->ssid_len) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static struct wpa_cred * interworking_credentials_available_roaming_consortium(
+ struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+{
+ struct wpa_cred *cred, *selected = NULL;
+ const u8 *ie;
+
+ ie = wpa_bss_get_ie(bss, WLAN_EID_ROAMING_CONSORTIUM);
+
+ if (ie == NULL &&
+ (bss->anqp == NULL || bss->anqp->roaming_consortium == NULL))
+ return NULL;
+
+ if (wpa_s->conf->cred == NULL)
+ return NULL;
+
+ for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
+ if (cred->roaming_consortium_len == 0)
+ continue;
+
+ if (!roaming_consortium_match(ie,
+ bss->anqp ?
+ bss->anqp->roaming_consortium :
+ NULL,
+ cred->roaming_consortium,
+ cred->roaming_consortium_len))
+ continue;
+
+ if (cred_excluded_ssid(cred, bss))
+ continue;
+
+ if (selected == NULL ||
+ selected->priority < cred->priority)
+ selected = cred;
+ }
+
+ return selected;
+}
+
+
+static int interworking_set_eap_params(struct wpa_ssid *ssid,
+ struct wpa_cred *cred, int ttls)
+{
+ if (cred->eap_method) {
+ ttls = cred->eap_method->vendor == EAP_VENDOR_IETF &&
+ cred->eap_method->method == EAP_TYPE_TTLS;
+
+ os_free(ssid->eap.eap_methods);
+ ssid->eap.eap_methods =
+ os_malloc(sizeof(struct eap_method_type) * 2);
+ if (ssid->eap.eap_methods == NULL)
+ return -1;
+ os_memcpy(ssid->eap.eap_methods, cred->eap_method,
+ sizeof(*cred->eap_method));
+ ssid->eap.eap_methods[1].vendor = EAP_VENDOR_IETF;
+ ssid->eap.eap_methods[1].method = EAP_TYPE_NONE;
+ }
+
+ if (ttls && cred->username && cred->username[0]) {
+ const char *pos;
+ char *anon;
+ /* Use anonymous NAI in Phase 1 */
+ pos = os_strchr(cred->username, '@');
+ if (pos) {
+ size_t buflen = 9 + os_strlen(pos) + 1;
+ anon = os_malloc(buflen);
+ if (anon == NULL)
+ return -1;
+ os_snprintf(anon, buflen, "anonymous%s", pos);
+ } else if (cred->realm) {
+ size_t buflen = 10 + os_strlen(cred->realm) + 1;
+ anon = os_malloc(buflen);
+ if (anon == NULL)
+ return -1;
+ os_snprintf(anon, buflen, "anonymous@%s", cred->realm);
+ } else {
+ anon = os_strdup("anonymous");
+ if (anon == NULL)
+ return -1;
+ }
+ if (wpa_config_set_quoted(ssid, "anonymous_identity", anon) <
+ 0) {
+ os_free(anon);
+ return -1;
+ }
+ os_free(anon);
+ }
+
+ if (cred->username && cred->username[0] &&
+ wpa_config_set_quoted(ssid, "identity", cred->username) < 0)
+ return -1;
+
+ if (cred->password && cred->password[0]) {
+ if (cred->ext_password &&
+ wpa_config_set(ssid, "password", cred->password, 0) < 0)
+ return -1;
+ if (!cred->ext_password &&
+ wpa_config_set_quoted(ssid, "password", cred->password) <
+ 0)
+ return -1;
+ }
+
+ if (cred->client_cert && cred->client_cert[0] &&
+ wpa_config_set_quoted(ssid, "client_cert", cred->client_cert) < 0)
+ return -1;
+
+#ifdef ANDROID
+ if (cred->private_key &&
+ os_strncmp(cred->private_key, "keystore://", 11) == 0) {
+ /* Use OpenSSL engine configuration for Android keystore */
+ if (wpa_config_set_quoted(ssid, "engine_id", "keystore") < 0 ||
+ wpa_config_set_quoted(ssid, "key_id",
+ cred->private_key + 11) < 0 ||
+ wpa_config_set(ssid, "engine", "1", 0) < 0)
+ return -1;
+ } else
+#endif /* ANDROID */
+ if (cred->private_key && cred->private_key[0] &&
+ wpa_config_set_quoted(ssid, "private_key", cred->private_key) < 0)
+ return -1;
+
+ if (cred->private_key_passwd && cred->private_key_passwd[0] &&
+ wpa_config_set_quoted(ssid, "private_key_passwd",
+ cred->private_key_passwd) < 0)
+ return -1;
+
+ if (cred->phase1) {
+ os_free(ssid->eap.phase1);
+ ssid->eap.phase1 = os_strdup(cred->phase1);
+ }
+ if (cred->phase2) {
+ os_free(ssid->eap.phase2);
+ ssid->eap.phase2 = os_strdup(cred->phase2);
+ }
+
+ if (cred->ca_cert && cred->ca_cert[0] &&
+ wpa_config_set_quoted(ssid, "ca_cert", cred->ca_cert) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+static int interworking_connect_roaming_consortium(
+ struct wpa_supplicant *wpa_s, struct wpa_cred *cred,
+ struct wpa_bss *bss, const u8 *ssid_ie)
+{
+ struct wpa_ssid *ssid;
+
+ wpa_printf(MSG_DEBUG, "Interworking: Connect with " MACSTR " based on "
+ "roaming consortium match", MAC2STR(bss->bssid));
+
+ ssid = wpa_config_add_network(wpa_s->conf);
+ if (ssid == NULL)
+ return -1;
+ ssid->parent_cred = cred;
+ wpas_notify_network_added(wpa_s, ssid);
+ wpa_config_set_network_defaults(ssid);
+ ssid->priority = cred->priority;
+ ssid->temporary = 1;
+ ssid->ssid = os_zalloc(ssid_ie[1] + 1);
+ if (ssid->ssid == NULL)
+ goto fail;
+ os_memcpy(ssid->ssid, ssid_ie + 2, ssid_ie[1]);
+ ssid->ssid_len = ssid_ie[1];
+
+ if (interworking_set_hs20_params(wpa_s, ssid) < 0)
+ goto fail;
+
+ if (cred->eap_method == NULL) {
+ wpa_printf(MSG_DEBUG, "Interworking: No EAP method set for "
+ "credential using roaming consortium");
+ goto fail;
+ }
+
+ if (interworking_set_eap_params(
+ ssid, cred,
+ cred->eap_method->vendor == EAP_VENDOR_IETF &&
+ cred->eap_method->method == EAP_TYPE_TTLS) < 0)
+ goto fail;
+
+ wpa_config_update_prio_list(wpa_s->conf);
+ interworking_reconnect(wpa_s);
+
+ return 0;
+
+fail:
+ wpas_notify_network_removed(wpa_s, ssid);
+ wpa_config_remove_network(wpa_s->conf, ssid->id);
+ return -1;
+}
+
+
int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
{
struct wpa_cred *cred;
return -1;
}
- realm = nai_realm_parse(bss->anqp_nai_realm, &count);
+ if (!wpa_bss_get_ie(bss, WLAN_EID_RSN)) {
+ /*
+ * We currently support only HS 2.0 networks and those are
+ * required to use WPA2-Enterprise.
+ */
+ wpa_printf(MSG_DEBUG, "Interworking: Network does not use "
+ "RSN");
+ return -1;
+ }
+
+ cred = interworking_credentials_available_roaming_consortium(wpa_s,
+ bss);
+ if (cred)
+ return interworking_connect_roaming_consortium(wpa_s, cred,
+ bss, ie);
+
+ realm = nai_realm_parse(bss->anqp ? bss->anqp->nai_realm : NULL,
+ &count);
if (realm == NULL) {
wpa_printf(MSG_DEBUG, "Interworking: Could not parse NAI "
"Realm list from " MACSTR, MAC2STR(bss->bssid));
nai_realm_free(realm, count);
return -1;
}
+ ssid->parent_cred = cred;
wpas_notify_network_added(wpa_s, ssid);
wpa_config_set_network_defaults(ssid);
ssid->priority = cred->priority;
os_memcpy(ssid->ssid, ie + 2, ie[1]);
ssid->ssid_len = ie[1];
- if (wpa_config_set(ssid, "eap", eap_get_name(EAP_VENDOR_IETF,
- eap->method), 0) < 0)
- goto fail;
-
- if (eap->method == EAP_TYPE_TTLS &&
- cred->username && cred->username[0]) {
- const char *pos;
- char *anon;
- /* Use anonymous NAI in Phase 1 */
- pos = os_strchr(cred->username, '@');
- if (pos) {
- size_t buflen = 9 + os_strlen(pos) + 1;
- anon = os_malloc(buflen);
- if (anon == NULL)
- goto fail;
- os_snprintf(anon, buflen, "anonymous%s", pos);
- } else if (cred->realm) {
- size_t buflen = 10 + os_strlen(cred->realm) + 1;
- anon = os_malloc(buflen);
- if (anon == NULL)
- goto fail;
- os_snprintf(anon, buflen, "anonymous@%s", cred->realm);
- } else {
- anon = os_strdup("anonymous");
- if (anon == NULL)
- goto fail;
- }
- if (wpa_config_set_quoted(ssid, "anonymous_identity", anon) <
- 0) {
- os_free(anon);
- goto fail;
- }
- os_free(anon);
- }
-
- if (cred->username && cred->username[0] &&
- wpa_config_set_quoted(ssid, "identity", cred->username) < 0)
- goto fail;
-
- if (cred->password && cred->password[0] &&
- wpa_config_set_quoted(ssid, "password", cred->password) < 0)
- goto fail;
-
- if (cred->client_cert && cred->client_cert[0] &&
- wpa_config_set_quoted(ssid, "client_cert", cred->client_cert) < 0)
- goto fail;
-
- if (cred->private_key && cred->private_key[0] &&
- wpa_config_set_quoted(ssid, "private_key", cred->private_key) < 0)
+ if (interworking_set_hs20_params(wpa_s, ssid) < 0)
goto fail;
- if (cred->private_key_passwd && cred->private_key_passwd[0] &&
- wpa_config_set_quoted(ssid, "private_key_passwd",
- cred->private_key_passwd) < 0)
+ if (wpa_config_set(ssid, "eap", eap_get_name(EAP_VENDOR_IETF,
+ eap->method), 0) < 0)
goto fail;
switch (eap->method) {
0) < 0)
goto fail;
break;
+ default:
+ /* EAP params were not set - assume TTLS/MSCHAPv2 */
+ if (wpa_config_set(ssid, "phase2", "\"auth=MSCHAPV2\"",
+ 0) < 0)
+ goto fail;
+ break;
}
break;
case EAP_TYPE_PEAP:
os_snprintf(buf, sizeof(buf), "\"auth=%s\"",
- eap_get_name(EAP_VENDOR_IETF, eap->inner_method));
+ eap_get_name(EAP_VENDOR_IETF,
+ eap->inner_method ?
+ eap->inner_method :
+ EAP_TYPE_MSCHAPV2));
if (wpa_config_set(ssid, "phase2", buf, 0) < 0)
goto fail;
break;
break;
}
- if (cred->ca_cert && cred->ca_cert[0] &&
- wpa_config_set_quoted(ssid, "ca_cert", cred->ca_cert) < 0)
+ if (interworking_set_eap_params(ssid, cred,
+ eap->method == EAP_TYPE_TTLS) < 0)
goto fail;
nai_realm_free(realm, count);
int ret;
#ifdef INTERWORKING_3GPP
- if (bss->anqp_3gpp == NULL)
+ if (bss->anqp == NULL || bss->anqp->anqp_3gpp == NULL)
return NULL;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
#endif /* PCSC_FUNCS */
wpa_printf(MSG_DEBUG, "Interworking: Parsing 3GPP info from "
MACSTR, MAC2STR(bss->bssid));
- ret = plmn_id_match(bss->anqp_3gpp, imsi, mnc_len);
+ 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 (selected == NULL ||
selected->priority < cred->priority)
selected = cred;
struct nai_realm *realm;
u16 count, i;
- if (bss->anqp_nai_realm == NULL)
+ if (bss->anqp == NULL || bss->anqp->nai_realm == NULL)
return NULL;
if (wpa_s->conf->cred == NULL)
wpa_printf(MSG_DEBUG, "Interworking: Parsing NAI Realm list from "
MACSTR, MAC2STR(bss->bssid));
- realm = nai_realm_parse(bss->anqp_nai_realm, &count);
+ realm = nai_realm_parse(bss->anqp->nai_realm, &count);
if (realm == NULL) {
wpa_printf(MSG_DEBUG, "Interworking: Could not parse NAI "
"Realm list from " MACSTR, MAC2STR(bss->bssid));
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 (selected == NULL ||
selected->priority < cred->priority)
selected = cred;
if (!cred)
cred = cred2;
+ cred2 = interworking_credentials_available_roaming_consortium(wpa_s,
+ bss);
+ if (cred && cred2 && cred2->priority >= cred->priority)
+ cred = cred2;
+ if (!cred)
+ cred = cred2;
+
return cred;
}
}
-static int interworking_home_sp(struct wpa_supplicant *wpa_s,
- struct wpabuf *domain_names)
+int interworking_home_sp_cred(struct wpa_supplicant *wpa_s,
+ struct wpa_cred *cred,
+ struct wpabuf *domain_names)
{
- struct wpa_cred *cred;
#ifdef INTERWORKING_3GPP
char nai[100], *realm;
+
+ char *imsi = NULL;
+ 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]) {
+ imsi = wpa_s->imsi;
+ mnc_len = wpa_s->mnc_len;
+ }
+#endif /* CONFIG_PCSC */
+ if (domain_names &&
+ imsi && build_root_nai(nai, sizeof(nai), imsi, mnc_len, 0) == 0) {
+ realm = os_strchr(nai, '@');
+ if (realm)
+ realm++;
+ wpa_printf(MSG_DEBUG, "Interworking: Search for match "
+ "with SIM/USIM domain %s", realm);
+ if (realm &&
+ domain_name_list_contains(domain_names, realm))
+ return 1;
+ }
#endif /* INTERWORKING_3GPP */
+ if (domain_names == NULL || cred->domain == NULL)
+ return 0;
+
+ wpa_printf(MSG_DEBUG, "Interworking: Search for match with "
+ "home SP FQDN %s", cred->domain);
+ if (domain_name_list_contains(domain_names, cred->domain))
+ return 1;
+
+ return 0;
+}
+
+
+static int interworking_home_sp(struct wpa_supplicant *wpa_s,
+ struct wpabuf *domain_names)
+{
+ struct wpa_cred *cred;
+
if (domain_names == NULL || wpa_s->conf->cred == NULL)
return -1;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
-#ifdef INTERWORKING_3GPP
- if (cred->imsi &&
- build_root_nai(nai, sizeof(nai), cred->imsi, 0) == 0) {
- realm = os_strchr(nai, '@');
- if (realm)
- realm++;
- wpa_printf(MSG_DEBUG, "Interworking: Search for match "
- "with SIM/USIM domain %s", realm);
- if (realm &&
- domain_name_list_contains(domain_names, realm))
- return 1;
- }
-#endif /* INTERWORKING_3GPP */
-
- if (cred->domain == NULL)
- continue;
-
- wpa_printf(MSG_DEBUG, "Interworking: Search for match with "
- "home SP FQDN %s", cred->domain);
- if (domain_name_list_contains(domain_names, cred->domain))
- return 1;
+ int res = interworking_home_sp_cred(wpa_s, cred, domain_names);
+ if (res)
+ return res;
}
return 0;
dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) {
- if (wpas_network_disabled(ssid) ||
+ if (wpas_network_disabled(wpa_s, ssid) ||
ssid->mode != WPAS_MODE_INFRA)
continue;
if (ssid->ssid_len != bss->ssid_len ||
cred = interworking_credentials_available(wpa_s, bss);
if (!cred)
continue;
+ if (!wpa_bss_get_ie(bss, WLAN_EID_RSN)) {
+ /*
+ * We currently support only HS 2.0 networks and those
+ * are required to use WPA2-Enterprise.
+ */
+ wpa_printf(MSG_DEBUG, "Interworking: Credential match "
+ "with " MACSTR " but network does not use "
+ "RSN", MAC2STR(bss->bssid));
+ continue;
+ }
count++;
- res = interworking_home_sp(wpa_s, bss->anqp_domain_name);
+ res = interworking_home_sp(wpa_s, bss->anqp ?
+ bss->anqp->domain_name : NULL);
if (res > 0)
type = "home";
else if (res == 0)
type = "unknown";
wpa_msg(wpa_s, MSG_INFO, INTERWORKING_AP MACSTR " type=%s",
MAC2STR(bss->bssid), type);
- if (wpa_s->auto_select) {
+ if (wpa_s->auto_select ||
+ (wpa_s->conf->auto_interworking &&
+ wpa_s->auto_network_select)) {
if (selected == NULL ||
cred->priority > selected_prio) {
selected = bss;
if (interworking_find_network_match(wpa_s)) {
wpa_printf(MSG_DEBUG, "Interworking: Possible BSS "
"match for enabled network configurations");
- interworking_reconnect(wpa_s);
+ if (wpa_s->auto_select)
+ interworking_reconnect(wpa_s);
+ return;
+ }
+
+ if (wpa_s->auto_network_select) {
+ wpa_printf(MSG_DEBUG, "Interworking: Continue "
+ "scanning after ANQP fetch");
+ wpa_supplicant_req_scan(wpa_s, wpa_s->scan_interval,
+ 0);
return;
}
}
+static struct wpa_bss_anqp *
+interworking_match_anqp_info(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+{
+ struct wpa_bss *other;
+
+ if (is_zero_ether_addr(bss->hessid))
+ return NULL; /* Cannot be in the same homegenous ESS */
+
+ dl_list_for_each(other, &wpa_s->bss, struct wpa_bss, list) {
+ if (other == bss)
+ continue;
+ if (other->anqp == NULL)
+ continue;
+ if (other->anqp->roaming_consortium == NULL &&
+ other->anqp->nai_realm == NULL &&
+ other->anqp->anqp_3gpp == NULL &&
+ other->anqp->domain_name == NULL)
+ continue;
+ if (!(other->flags & WPA_BSS_ANQP_FETCH_TRIED))
+ continue;
+ if (os_memcmp(bss->hessid, other->hessid, ETH_ALEN) != 0)
+ continue;
+ if (bss->ssid_len != other->ssid_len ||
+ os_memcmp(bss->ssid, other->ssid, bss->ssid_len) != 0)
+ continue;
+
+ wpa_printf(MSG_DEBUG, "Interworking: Share ANQP data with "
+ "already fetched BSSID " MACSTR " and " MACSTR,
+ MAC2STR(other->bssid), MAC2STR(bss->bssid));
+ other->anqp->users++;
+ return other->anqp;
+ }
+
+ return NULL;
+}
+
+
static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s)
{
struct wpa_bss *bss;
int found = 0;
const u8 *ie;
- if (!wpa_s->fetch_anqp_in_progress)
+ if (eloop_terminated() || !wpa_s->fetch_anqp_in_progress)
return;
dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
continue; /* AP does not support Interworking */
if (!(bss->flags & WPA_BSS_ANQP_FETCH_TRIED)) {
+ if (bss->anqp == NULL) {
+ bss->anqp = interworking_match_anqp_info(wpa_s,
+ bss);
+ if (bss->anqp) {
+ /* Shared data already fetched */
+ continue;
+ }
+ bss->anqp = wpa_bss_anqp_alloc();
+ if (bss->anqp == NULL)
+ break;
+ }
found++;
bss->flags |= WPA_BSS_ANQP_FETCH_TRIED;
wpa_msg(wpa_s, MSG_INFO, "Starting ANQP fetch for "
}
-static void interworking_start_fetch_anqp(struct wpa_supplicant *wpa_s)
+void interworking_start_fetch_anqp(struct wpa_supplicant *wpa_s)
{
struct wpa_bss *bss;
return 0;
wpa_s->network_select = 0;
+ wpa_s->fetch_all_anqp = 1;
interworking_start_fetch_anqp(wpa_s);
freq = wpa_s->assoc_freq;
bss = wpa_bss_get_bssid(wpa_s, dst);
- if (bss)
+ if (bss) {
+ wpa_bss_anqp_unshare_alloc(bss);
freq = bss->freq;
+ }
if (freq <= 0)
return -1;
{
const u8 *pos = data;
struct wpa_bss *bss = wpa_bss_get_bssid(wpa_s, sa);
+ struct wpa_bss_anqp *anqp = NULL;
+#ifdef CONFIG_HS20
+ u8 type;
+#endif /* CONFIG_HS20 */
+
+ if (bss)
+ anqp = bss->anqp;
switch (info_id) {
case ANQP_CAPABILITY_LIST:
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
" Venue Name", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Venue Name", pos, slen);
- if (bss) {
- wpabuf_free(bss->anqp_venue_name);
- bss->anqp_venue_name = wpabuf_alloc_copy(pos, slen);
+ if (anqp) {
+ wpabuf_free(anqp->venue_name);
+ anqp->venue_name = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_NETWORK_AUTH_TYPE:
MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Network Authentication "
"Type", pos, slen);
- if (bss) {
- wpabuf_free(bss->anqp_network_auth_type);
- bss->anqp_network_auth_type =
- wpabuf_alloc_copy(pos, slen);
+ if (anqp) {
+ wpabuf_free(anqp->network_auth_type);
+ anqp->network_auth_type = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_ROAMING_CONSORTIUM:
" Roaming Consortium list", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Roaming Consortium",
pos, slen);
- if (bss) {
- wpabuf_free(bss->anqp_roaming_consortium);
- bss->anqp_roaming_consortium =
- wpabuf_alloc_copy(pos, slen);
+ if (anqp) {
+ wpabuf_free(anqp->roaming_consortium);
+ anqp->roaming_consortium = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_IP_ADDR_TYPE_AVAILABILITY:
MAC2STR(sa));
wpa_hexdump(MSG_MSGDUMP, "ANQP: IP Address Availability",
pos, slen);
- if (bss) {
- wpabuf_free(bss->anqp_ip_addr_type_availability);
- bss->anqp_ip_addr_type_availability =
+ if (anqp) {
+ wpabuf_free(anqp->ip_addr_type_availability);
+ anqp->ip_addr_type_availability =
wpabuf_alloc_copy(pos, slen);
}
break;
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
" NAI Realm list", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: NAI Realm", pos, slen);
- if (bss) {
- wpabuf_free(bss->anqp_nai_realm);
- bss->anqp_nai_realm = wpabuf_alloc_copy(pos, slen);
+ if (anqp) {
+ wpabuf_free(anqp->nai_realm);
+ anqp->nai_realm = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_3GPP_CELLULAR_NETWORK:
" 3GPP Cellular Network information", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: 3GPP Cellular Network",
pos, slen);
- if (bss) {
- wpabuf_free(bss->anqp_3gpp);
- bss->anqp_3gpp = wpabuf_alloc_copy(pos, slen);
+ if (anqp) {
+ wpabuf_free(anqp->anqp_3gpp);
+ anqp->anqp_3gpp = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_DOMAIN_NAME:
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
" Domain Name list", MAC2STR(sa));
wpa_hexdump_ascii(MSG_MSGDUMP, "ANQP: Domain Name", pos, slen);
- if (bss) {
- wpabuf_free(bss->anqp_domain_name);
- bss->anqp_domain_name = wpabuf_alloc_copy(pos, slen);
+ if (anqp) {
+ wpabuf_free(anqp->domain_name);
+ anqp->domain_name = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_VENDOR_SPECIFIC:
return;
switch (WPA_GET_BE24(pos)) {
+#ifdef CONFIG_HS20
+ case OUI_WFA:
+ pos += 3;
+ slen -= 3;
+
+ if (slen < 1)
+ return;
+ type = *pos++;
+ slen--;
+
+ switch (type) {
+ case HS20_ANQP_OUI_TYPE:
+ hs20_parse_rx_hs20_anqp_resp(wpa_s, sa, pos,
+ slen);
+ break;
+ default:
+ wpa_printf(MSG_DEBUG, "HS20: Unsupported ANQP "
+ "vendor type %u", type);
+ break;
+ }
+ break;
+#endif /* CONFIG_HS20 */
default:
wpa_printf(MSG_DEBUG, "Interworking: Unsupported "
"vendor-specific ANQP OUI %06x",
{
interworking_stop_fetch_anqp(wpa_s);
wpa_s->network_select = 1;
+ wpa_s->auto_network_select = 0;
wpa_s->auto_select = !!auto_select;
+ wpa_s->fetch_all_anqp = 0;
wpa_printf(MSG_DEBUG, "Interworking: Start scan for network "
"selection");
wpa_s->scan_res_handler = interworking_scan_res_handler;
- wpa_s->scan_req = 2;
+ wpa_s->scan_req = MANUAL_SCAN_REQ;
wpa_supplicant_req_scan(wpa_s, 0, 0);
return 0;
}
+
+
+static void gas_resp_cb(void *ctx, const u8 *addr, u8 dialog_token,
+ enum gas_query_result result,
+ const struct wpabuf *adv_proto,
+ const struct wpabuf *resp, u16 status_code)
+{
+ struct wpa_supplicant *wpa_s = ctx;
+
+ wpa_msg(wpa_s, MSG_INFO, GAS_RESPONSE_INFO "addr=" MACSTR
+ " dialog_token=%d status_code=%d resp_len=%d",
+ MAC2STR(addr), dialog_token, status_code,
+ resp ? (int) wpabuf_len(resp) : -1);
+ if (!resp)
+ return;
+
+ wpabuf_free(wpa_s->last_gas_resp);
+ wpa_s->last_gas_resp = wpabuf_dup(resp);
+ if (wpa_s->last_gas_resp == NULL)
+ return;
+ os_memcpy(wpa_s->last_gas_addr, addr, ETH_ALEN);
+ wpa_s->last_gas_dialog_token = dialog_token;
+}
+
+
+int gas_send_request(struct wpa_supplicant *wpa_s, const u8 *dst,
+ const struct wpabuf *adv_proto,
+ const struct wpabuf *query)
+{
+ struct wpabuf *buf;
+ int ret = 0;
+ int freq;
+ struct wpa_bss *bss;
+ int res;
+ size_t len;
+ u8 query_resp_len_limit = 0, pame_bi = 0;
+
+ freq = wpa_s->assoc_freq;
+ bss = wpa_bss_get_bssid(wpa_s, dst);
+ if (bss)
+ freq = bss->freq;
+ if (freq <= 0)
+ return -1;
+
+ wpa_printf(MSG_DEBUG, "GAS request to " MACSTR " (freq %d MHz)",
+ MAC2STR(dst), freq);
+ wpa_hexdump_buf(MSG_DEBUG, "Advertisement Protocol ID", adv_proto);
+ wpa_hexdump_buf(MSG_DEBUG, "GAS Query", query);
+
+ len = 3 + wpabuf_len(adv_proto) + 2;
+ if (query)
+ len += wpabuf_len(query);
+ buf = gas_build_initial_req(0, len);
+ if (buf == NULL)
+ return -1;
+
+ /* Advertisement Protocol IE */
+ wpabuf_put_u8(buf, WLAN_EID_ADV_PROTO);
+ wpabuf_put_u8(buf, 1 + wpabuf_len(adv_proto)); /* Length */
+ wpabuf_put_u8(buf, (query_resp_len_limit & 0x7f) |
+ (pame_bi ? 0x80 : 0));
+ wpabuf_put_buf(buf, adv_proto);
+
+ /* GAS Query */
+ if (query) {
+ wpabuf_put_le16(buf, wpabuf_len(query));
+ wpabuf_put_buf(buf, query);
+ } else
+ wpabuf_put_le16(buf, 0);
+
+ res = gas_query_req(wpa_s->gas, dst, freq, buf, gas_resp_cb, wpa_s);
+ if (res < 0) {
+ wpa_printf(MSG_DEBUG, "GAS: Failed to send Query Request");
+ ret = -1;
+ } else
+ wpa_printf(MSG_DEBUG, "GAS: Query started with dialog token "
+ "%u", res);
+
+ wpabuf_free(buf);
+ return ret;
+}