hostapd/wpa_supplicant/interworking.c
Jouni Malinen 476aed355a Interworking: Move BSS ANQP information into separate struct
This is an initial step in allowing the ANQP responses to be shared
among multiple BSSes if the BSSes are determined to be operating under
identical configuration.

Signed-hostap: Jouni Malinen <jouni@qca.qualcomm.com>
2012-09-04 16:22:35 +03:00

1956 lines
46 KiB
C

/*
* Interworking (IEEE 802.11u)
* 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 "common.h"
#include "common/ieee802_11_defs.h"
#include "common/gas.h"
#include "common/wpa_ctrl.h"
#include "utils/pcsc_funcs.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 "config_ssid.h"
#include "bss.h"
#include "scan.h"
#include "notify.h"
#include "gas_query.h"
#include "hs20_supplicant.h"
#include "interworking.h"
#if defined(EAP_SIM) | defined(EAP_SIM_DYNAMIC)
#define INTERWORKING_3GPP
#else
#if defined(EAP_AKA) | defined(EAP_AKA_DYNAMIC)
#define INTERWORKING_3GPP
#else
#if defined(EAP_AKA_PRIME) | defined(EAP_AKA_PRIME_DYNAMIC)
#define INTERWORKING_3GPP
#endif
#endif
#endif
static void interworking_next_anqp_fetch(struct wpa_supplicant *wpa_s);
static void interworking_reconnect(struct wpa_supplicant *wpa_s)
{
if (wpa_s->wpa_state >= WPA_AUTHENTICATING) {
wpa_supplicant_cancel_sched_scan(wpa_s);
wpa_supplicant_deauthenticate(wpa_s,
WLAN_REASON_DEAUTH_LEAVING);
}
wpa_s->disconnected = 0;
wpa_s->reassociate = 1;
if (wpa_s->last_scan_res_used > 0) {
struct os_time now;
os_get_time(&now);
if (now.sec - wpa_s->last_scan.sec <= 5) {
wpa_printf(MSG_DEBUG, "Interworking: Old scan results "
"are fresh - connect without new scan");
if (wpas_select_network_from_last_scan(wpa_s) == 0)
return;
}
}
wpa_supplicant_req_scan(wpa_s, 0, 0);
}
static struct wpabuf * anqp_build_req(u16 info_ids[], size_t num_ids,
struct wpabuf *extra)
{
struct wpabuf *buf;
size_t i;
u8 *len_pos;
buf = gas_anqp_build_initial_req(0, 4 + num_ids * 2 +
(extra ? wpabuf_len(extra) : 0));
if (buf == NULL)
return NULL;
len_pos = gas_anqp_add_element(buf, ANQP_QUERY_LIST);
for (i = 0; i < num_ids; i++)
wpabuf_put_le16(buf, info_ids[i]);
gas_anqp_set_element_len(buf, len_pos);
if (extra)
wpabuf_put_buf(buf, extra);
gas_anqp_set_len(buf);
return buf;
}
static void interworking_anqp_resp_cb(void *ctx, const u8 *dst,
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;
anqp_resp_cb(wpa_s, dst, dialog_token, result, adv_proto, resp,
status_code);
interworking_next_anqp_fetch(wpa_s);
}
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 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[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));
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;
res = gas_query_req(wpa_s->gas, bss->bssid, bss->freq, buf,
interworking_anqp_resp_cb, wpa_s);
if (res < 0) {
wpa_printf(MSG_DEBUG, "ANQP: Failed to send Query Request");
ret = -1;
} else
wpa_printf(MSG_DEBUG, "ANQP: Query started with dialog token "
"%u", res);
wpabuf_free(buf);
return ret;
}
struct nai_realm_eap {
u8 method;
u8 inner_method;
enum nai_realm_eap_auth_inner_non_eap inner_non_eap;
u8 cred_type;
u8 tunneled_cred_type;
};
struct nai_realm {
u8 encoding;
char *realm;
u8 eap_count;
struct nai_realm_eap *eap;
};
static void nai_realm_free(struct nai_realm *realms, u16 count)
{
u16 i;
if (realms == NULL)
return;
for (i = 0; i < count; i++) {
os_free(realms[i].eap);
os_free(realms[i].realm);
}
os_free(realms);
}
static const u8 * nai_realm_parse_eap(struct nai_realm_eap *e, const u8 *pos,
const u8 *end)
{
u8 elen, auth_count, a;
const u8 *e_end;
if (pos + 3 > end) {
wpa_printf(MSG_DEBUG, "No room for EAP Method fixed fields");
return NULL;
}
elen = *pos++;
if (pos + elen > end || elen < 2) {
wpa_printf(MSG_DEBUG, "No room for EAP Method subfield");
return NULL;
}
e_end = pos + elen;
e->method = *pos++;
auth_count = *pos++;
wpa_printf(MSG_DEBUG, "EAP Method: len=%u method=%u auth_count=%u",
elen, e->method, auth_count);
for (a = 0; a < auth_count; a++) {
u8 id, len;
if (pos + 2 > end || pos + 2 + pos[1] > end) {
wpa_printf(MSG_DEBUG, "No room for Authentication "
"Parameter subfield");
return NULL;
}
id = *pos++;
len = *pos++;
switch (id) {
case NAI_REALM_EAP_AUTH_NON_EAP_INNER_AUTH:
if (len < 1)
break;
e->inner_non_eap = *pos;
if (e->method != EAP_TYPE_TTLS)
break;
switch (*pos) {
case NAI_REALM_INNER_NON_EAP_PAP:
wpa_printf(MSG_DEBUG, "EAP-TTLS/PAP");
break;
case NAI_REALM_INNER_NON_EAP_CHAP:
wpa_printf(MSG_DEBUG, "EAP-TTLS/CHAP");
break;
case NAI_REALM_INNER_NON_EAP_MSCHAP:
wpa_printf(MSG_DEBUG, "EAP-TTLS/MSCHAP");
break;
case NAI_REALM_INNER_NON_EAP_MSCHAPV2:
wpa_printf(MSG_DEBUG, "EAP-TTLS/MSCHAPV2");
break;
}
break;
case NAI_REALM_EAP_AUTH_INNER_AUTH_EAP_METHOD:
if (len < 1)
break;
e->inner_method = *pos;
wpa_printf(MSG_DEBUG, "Inner EAP method: %u",
e->inner_method);
break;
case NAI_REALM_EAP_AUTH_CRED_TYPE:
if (len < 1)
break;
e->cred_type = *pos;
wpa_printf(MSG_DEBUG, "Credential Type: %u",
e->cred_type);
break;
case NAI_REALM_EAP_AUTH_TUNNELED_CRED_TYPE:
if (len < 1)
break;
e->tunneled_cred_type = *pos;
wpa_printf(MSG_DEBUG, "Tunneled EAP Method Credential "
"Type: %u", e->tunneled_cred_type);
break;
default:
wpa_printf(MSG_DEBUG, "Unsupported Authentication "
"Parameter: id=%u len=%u", id, len);
wpa_hexdump(MSG_DEBUG, "Authentication Parameter "
"Value", pos, len);
break;
}
pos += len;
}
return e_end;
}
static const u8 * nai_realm_parse_realm(struct nai_realm *r, const u8 *pos,
const u8 *end)
{
u16 len;
const u8 *f_end;
u8 realm_len, e;
if (end - pos < 4) {
wpa_printf(MSG_DEBUG, "No room for NAI Realm Data "
"fixed fields");
return NULL;
}
len = WPA_GET_LE16(pos); /* NAI Realm Data field Length */
pos += 2;
if (pos + len > end || len < 3) {
wpa_printf(MSG_DEBUG, "No room for NAI Realm Data "
"(len=%u; left=%u)",
len, (unsigned int) (end - pos));
return NULL;
}
f_end = pos + len;
r->encoding = *pos++;
realm_len = *pos++;
if (pos + realm_len > f_end) {
wpa_printf(MSG_DEBUG, "No room for NAI Realm "
"(len=%u; left=%u)",
realm_len, (unsigned int) (f_end - pos));
return NULL;
}
wpa_hexdump_ascii(MSG_DEBUG, "NAI Realm", pos, realm_len);
r->realm = os_malloc(realm_len + 1);
if (r->realm == NULL)
return NULL;
os_memcpy(r->realm, pos, realm_len);
r->realm[realm_len] = '\0';
pos += realm_len;
if (pos + 1 > f_end) {
wpa_printf(MSG_DEBUG, "No room for EAP Method Count");
return NULL;
}
r->eap_count = *pos++;
wpa_printf(MSG_DEBUG, "EAP Count: %u", r->eap_count);
if (pos + r->eap_count * 3 > f_end) {
wpa_printf(MSG_DEBUG, "No room for EAP Methods");
return NULL;
}
r->eap = os_calloc(r->eap_count, sizeof(struct nai_realm_eap));
if (r->eap == NULL)
return NULL;
for (e = 0; e < r->eap_count; e++) {
pos = nai_realm_parse_eap(&r->eap[e], pos, f_end);
if (pos == NULL)
return NULL;
}
return f_end;
}
static struct nai_realm * nai_realm_parse(struct wpabuf *anqp, u16 *count)
{
struct nai_realm *realm;
const u8 *pos, *end;
u16 i, num;
if (anqp == NULL || wpabuf_len(anqp) < 2)
return NULL;
pos = wpabuf_head_u8(anqp);
end = pos + wpabuf_len(anqp);
num = WPA_GET_LE16(pos);
wpa_printf(MSG_DEBUG, "NAI Realm Count: %u", num);
pos += 2;
if (num * 5 > end - pos) {
wpa_printf(MSG_DEBUG, "Invalid NAI Realm Count %u - not "
"enough data (%u octets) for that many realms",
num, (unsigned int) (end - pos));
return NULL;
}
realm = os_calloc(num, sizeof(struct nai_realm));
if (realm == NULL)
return NULL;
for (i = 0; i < num; i++) {
pos = nai_realm_parse_realm(&realm[i], pos, end);
if (pos == NULL) {
nai_realm_free(realm, num);
return NULL;
}
}
*count = num;
return realm;
}
static int nai_realm_match(struct nai_realm *realm, const char *home_realm)
{
char *tmp, *pos, *end;
int match = 0;
if (realm->realm == NULL || home_realm == NULL)
return 0;
if (os_strchr(realm->realm, ';') == NULL)
return os_strcasecmp(realm->realm, home_realm) == 0;
tmp = os_strdup(realm->realm);
if (tmp == NULL)
return 0;
pos = tmp;
while (*pos) {
end = os_strchr(pos, ';');
if (end)
*end = '\0';
if (os_strcasecmp(pos, home_realm) == 0) {
match = 1;
break;
}
if (end == NULL)
break;
pos = end + 1;
}
os_free(tmp);
return match;
}
static int nai_realm_cred_username(struct nai_realm_eap *eap)
{
if (eap_get_name(EAP_VENDOR_IETF, eap->method) == NULL)
return 0; /* method not supported */
if (eap->method != EAP_TYPE_TTLS && eap->method != EAP_TYPE_PEAP) {
/* Only tunneled methods with username/password supported */
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_TTLS) {
if (eap->inner_method == 0 && eap->inner_non_eap == 0)
return 0;
if (eap->inner_method &&
eap_get_name(EAP_VENDOR_IETF, eap->inner_method) == NULL)
return 0;
if (eap->inner_non_eap &&
eap->inner_non_eap != NAI_REALM_INNER_NON_EAP_PAP &&
eap->inner_non_eap != NAI_REALM_INNER_NON_EAP_CHAP &&
eap->inner_non_eap != NAI_REALM_INNER_NON_EAP_MSCHAP &&
eap->inner_non_eap != NAI_REALM_INNER_NON_EAP_MSCHAPV2)
return 0;
}
if (eap->inner_method &&
eap->inner_method != EAP_TYPE_GTC &&
eap->inner_method != EAP_TYPE_MSCHAPV2)
return 0;
return 1;
}
static int nai_realm_cred_cert(struct nai_realm_eap *eap)
{
if (eap_get_name(EAP_VENDOR_IETF, eap->method) == NULL)
return 0; /* method not supported */
if (eap->method != EAP_TYPE_TLS) {
/* Only EAP-TLS supported for credential authentication */
return 0;
}
return 1;
}
static struct nai_realm_eap * nai_realm_find_eap(struct wpa_cred *cred,
struct nai_realm *realm)
{
u8 e;
if (cred == NULL ||
cred->username == NULL ||
cred->username[0] == '\0' ||
((cred->password == NULL ||
cred->password[0] == '\0') &&
(cred->private_key == NULL ||
cred->private_key[0] == '\0')))
return NULL;
for (e = 0; e < realm->eap_count; e++) {
struct nai_realm_eap *eap = &realm->eap[e];
if (cred->password && cred->password[0] &&
nai_realm_cred_username(eap))
return eap;
if (cred->private_key && cred->private_key[0] &&
nai_realm_cred_cert(eap))
return eap;
}
return NULL;
}
#ifdef INTERWORKING_3GPP
static int plmn_id_match(struct wpabuf *anqp, const char *imsi, int mnc_len)
{
u8 plmn[3];
const u8 *pos, *end;
u8 udhl;
/* See Annex A of 3GPP TS 24.234 v8.1.0 for description */
plmn[0] = (imsi[0] - '0') | ((imsi[1] - '0') << 4);
plmn[1] = imsi[2] - '0';
/* default to MNC length 3 if unknown */
if (mnc_len != 2)
plmn[1] |= (imsi[5] - '0') << 4;
else
plmn[1] |= 0xf0;
plmn[2] = (imsi[3] - '0') | ((imsi[4] - '0') << 4);
if (anqp == NULL)
return 0;
pos = wpabuf_head_u8(anqp);
end = pos + wpabuf_len(anqp);
if (pos + 2 > end)
return 0;
if (*pos != 0) {
wpa_printf(MSG_DEBUG, "Unsupported GUD version 0x%x", *pos);
return 0;
}
pos++;
udhl = *pos++;
if (pos + udhl > end) {
wpa_printf(MSG_DEBUG, "Invalid UDHL");
return 0;
}
end = pos + udhl;
while (pos + 2 <= end) {
u8 iei, len;
const u8 *l_end;
iei = *pos++;
len = *pos++ & 0x7f;
if (pos + len > end)
break;
l_end = pos + len;
if (iei == 0 && len > 0) {
/* PLMN List */
u8 num, i;
num = *pos++;
for (i = 0; i < num; i++) {
if (pos + 3 > end)
break;
if (os_memcmp(pos, plmn, 3) == 0)
return 1; /* Found matching PLMN */
pos += 3;
}
}
pos = l_end;
}
return 0;
}
static int build_root_nai(char *nai, size_t nai_len, const char *imsi,
size_t mnc_len, char prefix)
{
const char *sep, *msin;
char *end, *pos;
size_t msin_len, plmn_len;
/*
* TS 23.003, Clause 14 (3GPP to WLAN Interworking)
* Root NAI:
* <aka:0|sim:1><IMSI>@wlan.mnc<MNC>.mcc<MCC>.3gppnetwork.org
* <MNC> is zero-padded to three digits in case two-digit MNC is used
*/
if (imsi == NULL || os_strlen(imsi) > 16) {
wpa_printf(MSG_DEBUG, "No valid IMSI available");
return -1;
}
sep = os_strchr(imsi, '-');
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;
if (plmn_len != 5 && plmn_len != 6)
return -1;
msin_len = os_strlen(msin);
pos = nai;
end = nai + nai_len;
if (prefix)
*pos++ = prefix;
os_memcpy(pos, imsi, plmn_len);
pos += plmn_len;
os_memcpy(pos, msin, msin_len);
pos += msin_len;
pos += os_snprintf(pos, end - pos, "@wlan.mnc");
if (plmn_len == 5) {
*pos++ = '0';
*pos++ = imsi[3];
*pos++ = imsi[4];
} else {
*pos++ = imsi[3];
*pos++ = imsi[4];
*pos++ = imsi[5];
}
pos += os_snprintf(pos, end - pos, ".mcc%c%c%c.3gppnetwork.org",
imsi[0], imsi[1], imsi[2]);
return 0;
}
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, 0, prefix) < 0)
return -1;
return wpa_config_set_quoted(ssid, "identity", nai);
}
#endif /* INTERWORKING_3GPP */
static int interworking_set_hs20_params(struct wpa_ssid *ssid)
{
if (wpa_config_set(ssid, "key_mgmt", "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)
{
#ifdef INTERWORKING_3GPP
struct wpa_cred *cred;
struct wpa_ssid *ssid;
const u8 *ie;
if (bss->anqp == NULL || bss->anqp->anqp_3gpp == NULL)
return -1;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
char *sep;
const char *imsi;
int mnc_len;
#ifdef PCSC_FUNCS
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;
goto compare;
}
#endif /* PCSC_FUNCS */
if (cred->imsi == NULL || !cred->imsi[0] ||
cred->milenage == NULL || !cred->milenage[0])
continue;
sep = os_strchr(cred->imsi, '-');
if (sep == NULL ||
(sep - cred->imsi != 5 && sep - cred->imsi != 6))
continue;
mnc_len = sep - cred->imsi - 3;
imsi = cred->imsi;
#ifdef PCSC_FUNCS
compare:
#endif /* PCSC_FUNCS */
if (plmn_id_match(bss->anqp->anqp_3gpp, imsi, mnc_len))
break;
}
if (cred == NULL)
return -1;
ie = wpa_bss_get_ie(bss, WLAN_EID_SSID);
if (ie == NULL)
return -1;
wpa_printf(MSG_DEBUG, "Interworking: Connect with " MACSTR " (3GPP)",
MAC2STR(bss->bssid));
ssid = wpa_config_add_network(wpa_s->conf);
if (ssid == NULL)
return -1;
wpas_notify_network_added(wpa_s, ssid);
wpa_config_set_network_defaults(ssid);
ssid->priority = cred->priority;
ssid->temporary = 1;
ssid->ssid = os_zalloc(ie[1] + 1);
if (ssid->ssid == NULL)
goto fail;
os_memcpy(ssid->ssid, ie + 2, ie[1]);
ssid->ssid_len = ie[1];
if (interworking_set_hs20_params(ssid) < 0)
goto fail;
/* 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");
goto fail;
}
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) {
wpa_printf(MSG_DEBUG, "Failed to set Root NAI");
goto fail;
}
if (cred->milenage && cred->milenage[0]) {
if (wpa_config_set_quoted(ssid, "password",
cred->milenage) < 0)
goto fail;
} else if (cred->pcsc) {
if (wpa_config_set_quoted(ssid, "pcsc", "") < 0)
goto fail;
if (wpa_s->conf->pcsc_pin &&
wpa_config_set_quoted(ssid, "pin", wpa_s->conf->pcsc_pin)
< 0)
goto fail;
}
if (cred->password && cred->password[0] &&
wpa_config_set_quoted(ssid, "password", cred->password) < 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);
#endif /* INTERWORKING_3GPP */
return -1;
}
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 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 (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;
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;
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(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;
struct wpa_ssid *ssid;
struct nai_realm *realm;
struct nai_realm_eap *eap = NULL;
u16 count, i;
char buf[100];
const u8 *ie;
if (wpa_s->conf->cred == NULL || bss == NULL)
return -1;
ie = wpa_bss_get_ie(bss, WLAN_EID_SSID);
if (ie == NULL || ie[1] == 0) {
wpa_printf(MSG_DEBUG, "Interworking: No SSID known for "
MACSTR, MAC2STR(bss->bssid));
return -1;
}
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));
count = 0;
}
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
for (i = 0; i < count; i++) {
if (!nai_realm_match(&realm[i], cred->realm))
continue;
eap = nai_realm_find_eap(cred, &realm[i]);
if (eap)
break;
}
if (eap)
break;
}
if (!eap) {
if (interworking_connect_3gpp(wpa_s, bss) == 0) {
if (realm)
nai_realm_free(realm, count);
return 0;
}
wpa_printf(MSG_DEBUG, "Interworking: No matching credentials "
"and EAP method found for " MACSTR,
MAC2STR(bss->bssid));
nai_realm_free(realm, count);
return -1;
}
wpa_printf(MSG_DEBUG, "Interworking: Connect with " MACSTR,
MAC2STR(bss->bssid));
ssid = wpa_config_add_network(wpa_s->conf);
if (ssid == NULL) {
nai_realm_free(realm, count);
return -1;
}
wpas_notify_network_added(wpa_s, ssid);
wpa_config_set_network_defaults(ssid);
ssid->priority = cred->priority;
ssid->temporary = 1;
ssid->ssid = os_zalloc(ie[1] + 1);
if (ssid->ssid == NULL)
goto fail;
os_memcpy(ssid->ssid, ie + 2, ie[1]);
ssid->ssid_len = ie[1];
if (interworking_set_hs20_params(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;
}
switch (eap->inner_non_eap) {
case NAI_REALM_INNER_NON_EAP_PAP:
if (wpa_config_set(ssid, "phase2", "\"auth=PAP\"", 0) <
0)
goto fail;
break;
case NAI_REALM_INNER_NON_EAP_CHAP:
if (wpa_config_set(ssid, "phase2", "\"auth=CHAP\"", 0)
< 0)
goto fail;
break;
case NAI_REALM_INNER_NON_EAP_MSCHAP:
if (wpa_config_set(ssid, "phase2", "\"auth=MSCHAP\"",
0) < 0)
goto fail;
break;
case NAI_REALM_INNER_NON_EAP_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));
if (wpa_config_set(ssid, "phase2", buf, 0) < 0)
goto fail;
break;
case EAP_TYPE_TLS:
break;
}
if (interworking_set_eap_params(ssid, cred,
eap->method == EAP_TYPE_TTLS) < 0)
goto fail;
nai_realm_free(realm, count);
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);
nai_realm_free(realm, count);
return -1;
}
static struct wpa_cred * interworking_credentials_available_3gpp(
struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
{
struct wpa_cred *cred, *selected = NULL;
int ret;
#ifdef INTERWORKING_3GPP
if (bss->anqp == NULL || bss->anqp->anqp_3gpp == NULL)
return NULL;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
char *sep;
const char *imsi;
int mnc_len;
#ifdef PCSC_FUNCS
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;
goto compare;
}
#endif /* PCSC_FUNCS */
if (cred->imsi == NULL || !cred->imsi[0] ||
cred->milenage == NULL || !cred->milenage[0])
continue;
sep = os_strchr(cred->imsi, '-');
if (sep == NULL ||
(sep - cred->imsi != 5 && sep - cred->imsi != 6))
continue;
mnc_len = sep - cred->imsi - 3;
imsi = cred->imsi;
#ifdef PCSC_FUNCS
compare:
#endif /* PCSC_FUNCS */
wpa_printf(MSG_DEBUG, "Interworking: Parsing 3GPP info from "
MACSTR, MAC2STR(bss->bssid));
ret = plmn_id_match(bss->anqp->anqp_3gpp, imsi, mnc_len);
wpa_printf(MSG_DEBUG, "PLMN match %sfound", ret ? "" : "not ");
if (ret) {
if (selected == NULL ||
selected->priority < cred->priority)
selected = cred;
}
}
#endif /* INTERWORKING_3GPP */
return selected;
}
static struct wpa_cred * interworking_credentials_available_realm(
struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
{
struct wpa_cred *cred, *selected = NULL;
struct nai_realm *realm;
u16 count, i;
if (bss->anqp == NULL || bss->anqp->nai_realm == NULL)
return NULL;
if (wpa_s->conf->cred == NULL)
return NULL;
wpa_printf(MSG_DEBUG, "Interworking: Parsing NAI Realm list from "
MACSTR, MAC2STR(bss->bssid));
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));
return NULL;
}
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
if (cred->realm == NULL)
continue;
for (i = 0; i < count; i++) {
if (!nai_realm_match(&realm[i], cred->realm))
continue;
if (nai_realm_find_eap(cred, &realm[i])) {
if (selected == NULL ||
selected->priority < cred->priority)
selected = cred;
break;
}
}
}
nai_realm_free(realm, count);
return selected;
}
static struct wpa_cred * interworking_credentials_available(
struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
{
struct wpa_cred *cred, *cred2;
cred = interworking_credentials_available_realm(wpa_s, bss);
cred2 = interworking_credentials_available_3gpp(wpa_s, bss);
if (cred && cred2 && cred2->priority >= cred->priority)
cred = cred2;
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 domain_name_list_contains(struct wpabuf *domain_names,
const char *domain)
{
const u8 *pos, *end;
size_t len;
len = os_strlen(domain);
pos = wpabuf_head(domain_names);
end = pos + wpabuf_len(domain_names);
while (pos + 1 < end) {
if (pos + 1 + pos[0] > end)
break;
wpa_hexdump_ascii(MSG_DEBUG, "Interworking: AP domain name",
pos + 1, pos[0]);
if (pos[0] == len &&
os_strncasecmp(domain, (const char *) (pos + 1), len) == 0)
return 1;
pos += 1 + pos[0];
}
return 0;
}
static int interworking_home_sp(struct wpa_supplicant *wpa_s,
struct wpabuf *domain_names)
{
struct wpa_cred *cred;
#ifdef INTERWORKING_3GPP
char nai[100], *realm;
#endif /* INTERWORKING_3GPP */
if (domain_names == NULL || wpa_s->conf->cred == NULL)
return -1;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
#ifdef INTERWORKING_3GPP
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 (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 (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;
}
return 0;
}
static int interworking_find_network_match(struct wpa_supplicant *wpa_s)
{
struct wpa_bss *bss;
struct wpa_ssid *ssid;
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(wpa_s, ssid) ||
ssid->mode != WPAS_MODE_INFRA)
continue;
if (ssid->ssid_len != bss->ssid_len ||
os_memcmp(ssid->ssid, bss->ssid, ssid->ssid_len) !=
0)
continue;
/*
* TODO: Consider more accurate matching of security
* configuration similarly to what is done in events.c
*/
return 1;
}
}
return 0;
}
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;
unsigned int count = 0;
const char *type;
int res;
struct wpa_cred *cred;
wpa_s->network_select = 0;
dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
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 ?
bss->anqp->domain_name : NULL);
if (res > 0)
type = "home";
else if (res == 0)
type = "roaming";
else
type = "unknown";
wpa_msg(wpa_s, MSG_INFO, INTERWORKING_AP MACSTR " type=%s",
MAC2STR(bss->bssid), type);
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 (selected_home && selected_home != selected &&
selected_home_prio >= selected_prio) {
/* Prefer network operated by the Home SP */
selected = selected_home;
}
if (count == 0) {
/*
* No matching network was found based on configured
* credentials. Check whether any of the enabled network blocks
* have matching APs.
*/
if (interworking_find_network_match(wpa_s)) {
wpa_printf(MSG_DEBUG, "Interworking: Possible BSS "
"match for enabled network configurations");
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;
}
wpa_msg(wpa_s, MSG_INFO, INTERWORKING_NO_MATCH "No network "
"with matching credentials found");
}
if (selected)
interworking_connect(wpa_s, selected);
}
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)
return;
dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
if (!(bss->caps & IEEE80211_CAP_ESS))
continue;
ie = wpa_bss_get_ie(bss, WLAN_EID_EXT_CAPAB);
if (ie == NULL || ie[1] < 4 || !(ie[5] & 0x80))
continue; /* AP does not support Interworking */
if (!(bss->flags & WPA_BSS_ANQP_FETCH_TRIED)) {
if (bss->anqp == NULL) {
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 "
MACSTR, MAC2STR(bss->bssid));
interworking_anqp_send_req(wpa_s, bss);
break;
}
}
if (found == 0) {
wpa_msg(wpa_s, MSG_INFO, "ANQP fetch completed");
wpa_s->fetch_anqp_in_progress = 0;
if (wpa_s->network_select)
interworking_select_network(wpa_s);
}
}
void interworking_start_fetch_anqp(struct wpa_supplicant *wpa_s)
{
struct wpa_bss *bss;
dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list)
bss->flags &= ~WPA_BSS_ANQP_FETCH_TRIED;
wpa_s->fetch_anqp_in_progress = 1;
interworking_next_anqp_fetch(wpa_s);
}
int interworking_fetch_anqp(struct wpa_supplicant *wpa_s)
{
if (wpa_s->fetch_anqp_in_progress || wpa_s->network_select)
return 0;
wpa_s->network_select = 0;
wpa_s->fetch_all_anqp = 1;
interworking_start_fetch_anqp(wpa_s);
return 0;
}
void interworking_stop_fetch_anqp(struct wpa_supplicant *wpa_s)
{
if (!wpa_s->fetch_anqp_in_progress)
return;
wpa_s->fetch_anqp_in_progress = 0;
}
int anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst,
u16 info_ids[], size_t num_ids)
{
struct wpabuf *buf;
int ret = 0;
int freq;
struct wpa_bss *bss;
int res;
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, "ANQP: Query Request to " MACSTR " for %u id(s)",
MAC2STR(dst), (unsigned int) num_ids);
buf = anqp_build_req(info_ids, num_ids, NULL);
if (buf == NULL)
return -1;
res = gas_query_req(wpa_s->gas, dst, freq, buf, anqp_resp_cb, wpa_s);
if (res < 0) {
wpa_printf(MSG_DEBUG, "ANQP: Failed to send Query Request");
ret = -1;
} else
wpa_printf(MSG_DEBUG, "ANQP: Query started with dialog token "
"%u", res);
wpabuf_free(buf);
return ret;
}
static void interworking_parse_rx_anqp_resp(struct wpa_supplicant *wpa_s,
const u8 *sa, u16 info_id,
const u8 *data, size_t slen)
{
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
" ANQP Capability list", MAC2STR(sa));
break;
case ANQP_VENUE_NAME:
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
" Venue Name", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Venue Name", pos, slen);
if (anqp) {
wpabuf_free(anqp->venue_name);
anqp->venue_name = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_NETWORK_AUTH_TYPE:
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
" Network Authentication Type information",
MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Network Authentication "
"Type", pos, slen);
if (anqp) {
wpabuf_free(anqp->network_auth_type);
anqp->network_auth_type = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_ROAMING_CONSORTIUM:
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
" Roaming Consortium list", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: Roaming Consortium",
pos, slen);
if (anqp) {
wpabuf_free(anqp->roaming_consortium);
anqp->roaming_consortium = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_IP_ADDR_TYPE_AVAILABILITY:
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
" IP Address Type Availability information",
MAC2STR(sa));
wpa_hexdump(MSG_MSGDUMP, "ANQP: IP Address Availability",
pos, slen);
if (anqp) {
wpabuf_free(anqp->ip_addr_type_availability);
anqp->ip_addr_type_availability =
wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_NAI_REALM:
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 (anqp) {
wpabuf_free(anqp->nai_realm);
anqp->nai_realm = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_3GPP_CELLULAR_NETWORK:
wpa_msg(wpa_s, MSG_INFO, "RX-ANQP " MACSTR
" 3GPP Cellular Network information", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "ANQP: 3GPP Cellular Network",
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 (anqp) {
wpabuf_free(anqp->domain_name);
anqp->domain_name = wpabuf_alloc_copy(pos, slen);
}
break;
case ANQP_VENDOR_SPECIFIC:
if (slen < 3)
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",
WPA_GET_BE24(pos));
return;
}
break;
default:
wpa_printf(MSG_DEBUG, "Interworking: Unsupported ANQP Info ID "
"%u", info_id);
break;
}
}
void anqp_resp_cb(void *ctx, const u8 *dst, 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;
const u8 *pos;
const u8 *end;
u16 info_id;
u16 slen;
if (result != GAS_QUERY_SUCCESS)
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");
return;
}
pos = wpabuf_head(resp);
end = pos + wpabuf_len(resp);
while (pos < end) {
if (pos + 4 > end) {
wpa_printf(MSG_DEBUG, "ANQP: Invalid element");
break;
}
info_id = WPA_GET_LE16(pos);
pos += 2;
slen = WPA_GET_LE16(pos);
pos += 2;
if (pos + slen > end) {
wpa_printf(MSG_DEBUG, "ANQP: Invalid element length "
"for Info ID %u", info_id);
break;
}
interworking_parse_rx_anqp_resp(wpa_s, dst, info_id, pos,
slen);
pos += slen;
}
}
static void interworking_scan_res_handler(struct wpa_supplicant *wpa_s,
struct wpa_scan_results *scan_res)
{
wpa_printf(MSG_DEBUG, "Interworking: Scan results available - start "
"ANQP fetch");
interworking_start_fetch_anqp(wpa_s);
}
int interworking_select(struct wpa_supplicant *wpa_s, int auto_select)
{
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_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;
}