+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 (len > end - pos)
+ 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_no_required_oi_match(struct wpa_cred *cred, struct wpa_bss *bss)
+{
+ const u8 *ie;
+
+ if (cred->required_roaming_consortium_len == 0)
+ return 0;
+
+ ie = wpa_bss_get_ie(bss, WLAN_EID_ROAMING_CONSORTIUM);
+
+ if (ie == NULL &&
+ (bss->anqp == NULL || bss->anqp->roaming_consortium == NULL))
+ return 1;
+
+ return !roaming_consortium_match(ie,
+ bss->anqp ?
+ bss->anqp->roaming_consortium : NULL,
+ cred->required_roaming_consortium,
+ cred->required_roaming_consortium_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 int cred_below_min_backhaul(struct wpa_supplicant *wpa_s,
+ struct wpa_cred *cred, struct wpa_bss *bss)
+{
+#ifdef CONFIG_HS20
+ 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;
+ }
+#endif /* CONFIG_HS20 */
+
+ 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;
+}
+
+
+#ifdef CONFIG_HS20
+
+static int has_proto_match(const u8 *pos, const u8 *end, u8 proto)
+{
+ while (end - pos >= 4) {
+ 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 (end - pos >= 4) {
+ if (pos[0] == proto && WPA_GET_LE16(&pos[1]) == port &&
+ pos[3] == 1 /* Open */)
+ return 1;
+ pos += 4;
+ }
+
+ return 0;
+}
+
+#endif /* CONFIG_HS20 */
+
+
+static int cred_conn_capab_missing(struct wpa_supplicant *wpa_s,
+ struct wpa_cred *cred, struct wpa_bss *bss)
+{
+#ifdef CONFIG_HS20
+ 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;
+ }
+ }
+ }
+#endif /* CONFIG_HS20 */
+
+ return 0;
+}
+
+
+static struct wpa_cred * interworking_credentials_available_roaming_consortium(
+ 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);
+
+ 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_no_required_oi_match(cred, bss))
+ continue;
+ 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;
+}
+
+
+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 (!ttls && cred->username && cred->username[0] && cred->realm &&
+ !os_strchr(cred->username, '@')) {
+ char *id;
+ size_t buflen;
+ int res;
+
+ buflen = os_strlen(cred->username) + 1 +
+ os_strlen(cred->realm) + 1;
+
+ id = os_malloc(buflen);
+ if (!id)
+ return -1;
+ os_snprintf(id, buflen, "%s@%s", cred->username, cred->realm);
+ res = wpa_config_set_quoted(ssid, "identity", id);
+ os_free(id);
+ if (res < 0)
+ return -1;
+ } else 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;
+
+ if (cred->domain_suffix_match && cred->domain_suffix_match[0] &&
+ wpa_config_set_quoted(ssid, "domain_suffix_match",
+ cred->domain_suffix_match) < 0)
+ return -1;
+
+ ssid->eap.ocsp = cred->ocsp;
+
+ return 0;
+}
+
+
+static int interworking_connect_roaming_consortium(
+ struct wpa_supplicant *wpa_s, struct wpa_cred *cred,
+ struct wpa_bss *bss, int only_add)
+{
+ struct wpa_ssid *ssid;
+
+ wpa_msg(wpa_s, MSG_DEBUG, "Interworking: Connect with " MACSTR
+ " based on roaming consortium match", MAC2STR(bss->bssid));
+
+ if (already_connected(wpa_s, cred, bss)) {
+ wpa_msg(wpa_s, MSG_INFO, INTERWORKING_ALREADY_CONNECTED MACSTR,
+ MAC2STR(bss->bssid));
+ return wpa_s->current_ssid->id;
+ }
+
+ remove_duplicate_network(wpa_s, cred, bss);
+
+ 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(bss->ssid_len + 1);
+ if (ssid->ssid == NULL)
+ goto fail;
+ os_memcpy(ssid->ssid, bss->ssid, bss->ssid_len);
+ ssid->ssid_len = bss->ssid_len;
+
+ if (interworking_set_hs20_params(wpa_s, ssid) < 0)
+ goto fail;
+
+ if (cred->eap_method == NULL) {
+ wpa_msg(wpa_s, 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_s->next_ssid = ssid;
+ wpa_config_update_prio_list(wpa_s->conf);
+ if (!only_add)
+ interworking_reconnect(wpa_s);
+
+ return ssid->id;
+
+fail:
+ wpas_notify_network_removed(wpa_s, ssid);
+ wpa_config_remove_network(wpa_s->conf, ssid->id);
+ return -1;
+}
+
+
+static int interworking_connect_helper(struct wpa_supplicant *wpa_s,
+ struct wpa_bss *bss, int allow_excluded,
+ int only_add)
+{
+ struct wpa_cred *cred, *cred_rc, *cred_3gpp;
+ struct wpa_ssid *ssid;
+ struct nai_realm *realm;
+ struct nai_realm_eap *eap = NULL;
+ u16 count, i;
+ char buf[100];
+ int excluded = 0, *excl = allow_excluded ? &excluded : NULL;
+ const char *name;
+
+ if (wpa_s->conf->cred == NULL || bss == NULL)
+ return -1;
+ if (disallowed_bssid(wpa_s, bss->bssid) ||
+ disallowed_ssid(wpa_s, bss->ssid, bss->ssid_len)) {
+ wpa_msg(wpa_s, MSG_DEBUG,
+ "Interworking: Reject connection to disallowed BSS "
+ MACSTR, MAC2STR(bss->bssid));
+ 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
+ * required to use WPA2-Enterprise.
+ */
+ wpa_msg(wpa_s, MSG_DEBUG,
+ "Interworking: Network does not use RSN");
+ return -1;
+ }
+
+ cred_rc = interworking_credentials_available_roaming_consortium(
+ wpa_s, bss, 0, excl);
+ if (cred_rc) {
+ wpa_msg(wpa_s, MSG_DEBUG,
+ "Interworking: Highest roaming 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, 0, excl);
+ if (cred) {
+ wpa_msg(wpa_s, MSG_DEBUG,
+ "Interworking: Highest NAI Realm list 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, 0,
+ excl);
+ if (cred_3gpp) {
+ wpa_msg(wpa_s, MSG_DEBUG,
+ "Interworking: Highest 3GPP matching 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_msg(wpa_s, 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_msg(wpa_s, 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_msg(wpa_s, 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_msg(wpa_s, 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_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, only_add);
+
+ if (cred_3gpp &&
+ (cred == NULL || cred_prio_cmp(cred_3gpp, cred) >= 0)) {
+ return interworking_connect_3gpp(wpa_s, cred_3gpp, bss,
+ only_add);
+ }
+
+ if (cred == NULL) {
+ wpa_msg(wpa_s, MSG_DEBUG,
+ "Interworking: No matching credentials found for "
+ MACSTR, MAC2STR(bss->bssid));
+ return -1;
+ }
+
+ realm = nai_realm_parse(bss->anqp ? bss->anqp->nai_realm : NULL,
+ &count);
+ if (realm == NULL) {
+ wpa_msg(wpa_s, MSG_DEBUG,
+ "Interworking: Could not parse NAI Realm list from "
+ MACSTR, MAC2STR(bss->bssid));
+ return -1;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (!nai_realm_match(&realm[i], cred->realm))
+ continue;
+ eap = nai_realm_find_eap(wpa_s, cred, &realm[i]);
+ if (eap)
+ break;
+ }
+
+ if (!eap) {
+ wpa_msg(wpa_s, MSG_DEBUG,
+ "Interworking: No matching credentials and EAP method found for "
+ MACSTR, MAC2STR(bss->bssid));
+ nai_realm_free(realm, count);
+ return -1;
+ }
+
+ wpa_msg(wpa_s, MSG_DEBUG, "Interworking: Connect with " MACSTR,
+ MAC2STR(bss->bssid));
+
+ if (already_connected(wpa_s, cred, bss)) {
+ wpa_msg(wpa_s, MSG_INFO, INTERWORKING_ALREADY_CONNECTED MACSTR,
+ MAC2STR(bss->bssid));
+ nai_realm_free(realm, count);
+ return 0;
+ }
+
+ remove_duplicate_network(wpa_s, cred, bss);
+
+ ssid = wpa_config_add_network(wpa_s->conf);
+ if (ssid == NULL) {
+ 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;
+ ssid->temporary = 1;
+ ssid->ssid = os_zalloc(bss->ssid_len + 1);
+ if (ssid->ssid == NULL)
+ goto fail;
+ os_memcpy(ssid->ssid, bss->ssid, bss->ssid_len);
+ ssid->ssid_len = bss->ssid_len;
+
+ if (interworking_set_hs20_params(wpa_s, ssid) < 0)
+ goto fail;
+
+ if (wpa_config_set(ssid, "eap", eap_get_name(EAP_VENDOR_IETF,
+ eap->method), 0) < 0)
+ goto fail;
+
+ switch (eap->method) {
+ case EAP_TYPE_TTLS:
+ if (eap->inner_method) {
+ os_snprintf(buf, sizeof(buf), "\"autheap=%s\"",
+ eap_get_name(EAP_VENDOR_IETF,
+ eap->inner_method));
+ if (wpa_config_set(ssid, "phase2", buf, 0) < 0)
+ goto fail;
+ break;