hostap/wpa_supplicant/hs20_supplicant.c
Jouni Malinen 0c5edededa DPP: Fix config exchange with gas_rand_mac_addr
Do not use a random MAC address for the GAS exchange that is used as
part of the DPP protocol exchange since that would break DPP.
Configurator expects the same MAC address to be used for DPP
Authentication exchange and DPP Configuration exchange (GAS).

Since the DPP Authentication exchange has already used the MAC address
configured on the interface, use of a random address for the GAS
exchange would not provide any additional privacy protection. If a
random MAC address needs to be used for this type of an exchange, that
random address would need to be first configured on the interface before
starting DPP exchange.

This does not change GAS query behavior for any other use case, i.e.,
the gas_rand_mac_addr configuration continues to apply to all the
Interworking/Hotspot 2.0 uses.

Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
2020-04-22 17:09:01 +03:00

1347 lines
33 KiB
C

/*
* Copyright (c) 2009, Atheros Communications, Inc.
* Copyright (c) 2011-2013, Qualcomm Atheros, Inc.
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include <sys/stat.h>
#include "common.h"
#include "eloop.h"
#include "common/ieee802_11_common.h"
#include "common/ieee802_11_defs.h"
#include "common/gas.h"
#include "common/wpa_ctrl.h"
#include "rsn_supp/wpa.h"
#include "wpa_supplicant_i.h"
#include "driver_i.h"
#include "config.h"
#include "scan.h"
#include "bss.h"
#include "blacklist.h"
#include "gas_query.h"
#include "interworking.h"
#include "hs20_supplicant.h"
#include "base64.h"
#define OSU_MAX_ITEMS 10
struct osu_lang_string {
char lang[4];
char text[253];
};
struct osu_icon {
u16 width;
u16 height;
char lang[4];
char icon_type[256];
char filename[256];
unsigned int id;
unsigned int failed:1;
};
struct osu_provider {
u8 bssid[ETH_ALEN];
u8 osu_ssid[SSID_MAX_LEN];
u8 osu_ssid_len;
u8 osu_ssid2[SSID_MAX_LEN];
u8 osu_ssid2_len;
char server_uri[256];
u32 osu_methods; /* bit 0 = OMA-DM, bit 1 = SOAP-XML SPP */
char osu_nai[256];
char osu_nai2[256];
struct osu_lang_string friendly_name[OSU_MAX_ITEMS];
size_t friendly_name_count;
struct osu_lang_string serv_desc[OSU_MAX_ITEMS];
size_t serv_desc_count;
struct osu_icon icon[OSU_MAX_ITEMS];
size_t icon_count;
};
void hs20_configure_frame_filters(struct wpa_supplicant *wpa_s)
{
struct wpa_bss *bss = wpa_s->current_bss;
u8 *bssid = wpa_s->bssid;
const u8 *ie;
const u8 *ext_capa;
u32 filter = 0;
if (!bss || !is_hs20_network(wpa_s, wpa_s->current_ssid, bss)) {
wpa_printf(MSG_DEBUG,
"Not configuring frame filtering - BSS " MACSTR
" is not a Hotspot 2.0 network", MAC2STR(bssid));
return;
}
ie = wpa_bss_get_vendor_ie(bss, HS20_IE_VENDOR_TYPE);
/* Check if DGAF disabled bit is zero (5th byte in the IE) */
if (!ie || ie[1] < 5)
wpa_printf(MSG_DEBUG,
"Not configuring frame filtering - Can't extract DGAF bit");
else if (!(ie[6] & HS20_DGAF_DISABLED))
filter |= WPA_DATA_FRAME_FILTER_FLAG_GTK;
ext_capa = wpa_bss_get_ie(bss, WLAN_EID_EXT_CAPAB);
if (!ext_capa || ext_capa[1] < 2) {
wpa_printf(MSG_DEBUG,
"Not configuring frame filtering - Can't extract Proxy ARP bit");
return;
}
if (wpa_bss_ext_capab(bss, WLAN_EXT_CAPAB_PROXY_ARP))
filter |= WPA_DATA_FRAME_FILTER_FLAG_ARP |
WPA_DATA_FRAME_FILTER_FLAG_NA;
wpa_drv_configure_frame_filters(wpa_s, filter);
}
void wpas_hs20_add_indication(struct wpabuf *buf, int pps_mo_id, int ap_release)
{
int release;
u8 conf;
release = (HS20_VERSION >> 4) + 1;
if (ap_release > 0 && release > ap_release)
release = ap_release;
if (release < 2)
pps_mo_id = -1;
wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC);
wpabuf_put_u8(buf, pps_mo_id >= 0 ? 7 : 5);
wpabuf_put_be24(buf, OUI_WFA);
wpabuf_put_u8(buf, HS20_INDICATION_OUI_TYPE);
conf = (release - 1) << 4;
if (pps_mo_id >= 0)
conf |= HS20_PPS_MO_ID_PRESENT;
wpabuf_put_u8(buf, conf);
if (pps_mo_id >= 0)
wpabuf_put_le16(buf, pps_mo_id);
}
void wpas_hs20_add_roam_cons_sel(struct wpabuf *buf,
const struct wpa_ssid *ssid)
{
if (!ssid->roaming_consortium_selection ||
!ssid->roaming_consortium_selection_len)
return;
wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC);
wpabuf_put_u8(buf, 4 + ssid->roaming_consortium_selection_len);
wpabuf_put_be24(buf, OUI_WFA);
wpabuf_put_u8(buf, HS20_ROAMING_CONS_SEL_OUI_TYPE);
wpabuf_put_data(buf, ssid->roaming_consortium_selection,
ssid->roaming_consortium_selection_len);
}
int get_hs20_version(struct wpa_bss *bss)
{
const u8 *ie;
if (!bss)
return 0;
ie = wpa_bss_get_vendor_ie(bss, HS20_IE_VENDOR_TYPE);
if (!ie || ie[1] < 5)
return 0;
return ((ie[6] >> 4) & 0x0f) + 1;
}
int is_hs20_network(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid,
struct wpa_bss *bss)
{
if (!wpa_s->conf->hs20 || !ssid)
return 0;
if (ssid->parent_cred)
return 1;
if (bss && !wpa_bss_get_vendor_ie(bss, HS20_IE_VENDOR_TYPE))
return 0;
/*
* This may catch some non-Hotspot 2.0 cases, but it is safer to do that
* than cause Hotspot 2.0 connections without indication element getting
* added. Non-Hotspot 2.0 APs should ignore the unknown vendor element.
*/
if (!(ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X))
return 0;
if (!(ssid->pairwise_cipher & WPA_CIPHER_CCMP))
return 0;
if (ssid->proto != WPA_PROTO_RSN)
return 0;
return 1;
}
int hs20_get_pps_mo_id(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid)
{
struct wpa_cred *cred;
if (ssid == NULL)
return 0;
if (ssid->update_identifier)
return ssid->update_identifier;
if (ssid->parent_cred == NULL)
return 0;
for (cred = wpa_s->conf->cred; cred; cred = cred->next) {
if (ssid->parent_cred == cred)
return cred->update_identifier;
}
return 0;
}
void hs20_put_anqp_req(u32 stypes, const u8 *payload, size_t payload_len,
struct wpabuf *buf)
{
u8 *len_pos;
if (buf == NULL)
return;
len_pos = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
wpabuf_put_be24(buf, OUI_WFA);
wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
if (stypes == BIT(HS20_STYPE_NAI_HOME_REALM_QUERY)) {
wpabuf_put_u8(buf, HS20_STYPE_NAI_HOME_REALM_QUERY);
wpabuf_put_u8(buf, 0); /* Reserved */
if (payload)
wpabuf_put_data(buf, payload, payload_len);
} else if (stypes == BIT(HS20_STYPE_ICON_REQUEST)) {
wpabuf_put_u8(buf, HS20_STYPE_ICON_REQUEST);
wpabuf_put_u8(buf, 0); /* Reserved */
if (payload)
wpabuf_put_data(buf, payload, payload_len);
} else {
u8 i;
wpabuf_put_u8(buf, HS20_STYPE_QUERY_LIST);
wpabuf_put_u8(buf, 0); /* Reserved */
for (i = 0; i < 32; i++) {
if (stypes & BIT(i))
wpabuf_put_u8(buf, i);
}
}
gas_anqp_set_element_len(buf, len_pos);
gas_anqp_set_len(buf);
}
static struct wpabuf * hs20_build_anqp_req(u32 stypes, const u8 *payload,
size_t payload_len)
{
struct wpabuf *buf;
buf = gas_anqp_build_initial_req(0, 100 + payload_len);
if (buf == NULL)
return NULL;
hs20_put_anqp_req(stypes, payload, payload_len, buf);
return buf;
}
int hs20_anqp_send_req(struct wpa_supplicant *wpa_s, const u8 *dst, u32 stypes,
const u8 *payload, size_t payload_len, int inmem)
{
struct wpabuf *buf;
int ret = 0;
int freq;
struct wpa_bss *bss;
int res;
struct icon_entry *icon_entry;
bss = wpa_bss_get_bssid(wpa_s, dst);
if (!bss) {
wpa_printf(MSG_WARNING,
"ANQP: Cannot send query to unknown BSS "
MACSTR, MAC2STR(dst));
return -1;
}
wpa_bss_anqp_unshare_alloc(bss);
freq = bss->freq;
wpa_printf(MSG_DEBUG, "HS20: ANQP Query Request to " MACSTR " for "
"subtypes 0x%x", MAC2STR(dst), stypes);
buf = hs20_build_anqp_req(stypes, payload, payload_len);
if (buf == NULL)
return -1;
res = gas_query_req(wpa_s->gas, dst, freq, 0, 0, buf, anqp_resp_cb,
wpa_s);
if (res < 0) {
wpa_printf(MSG_DEBUG, "ANQP: Failed to send Query Request");
wpabuf_free(buf);
return -1;
} else
wpa_printf(MSG_DEBUG, "ANQP: Query started with dialog token "
"%u", res);
if (inmem) {
icon_entry = os_zalloc(sizeof(struct icon_entry));
if (!icon_entry)
return -1;
os_memcpy(icon_entry->bssid, dst, ETH_ALEN);
icon_entry->file_name = os_malloc(payload_len + 1);
if (!icon_entry->file_name) {
os_free(icon_entry);
return -1;
}
os_memcpy(icon_entry->file_name, payload, payload_len);
icon_entry->file_name[payload_len] = '\0';
icon_entry->dialog_token = res;
dl_list_add(&wpa_s->icon_head, &icon_entry->list);
}
return ret;
}
static struct icon_entry * hs20_find_icon(struct wpa_supplicant *wpa_s,
const u8 *bssid,
const char *file_name)
{
struct icon_entry *icon;
dl_list_for_each(icon, &wpa_s->icon_head, struct icon_entry, list) {
if (os_memcmp(icon->bssid, bssid, ETH_ALEN) == 0 &&
os_strcmp(icon->file_name, file_name) == 0 && icon->image)
return icon;
}
return NULL;
}
int hs20_get_icon(struct wpa_supplicant *wpa_s, const u8 *bssid,
const char *file_name, size_t offset, size_t size,
char *reply, size_t buf_len)
{
struct icon_entry *icon;
size_t out_size;
char *b64;
size_t b64_size;
int reply_size;
wpa_printf(MSG_DEBUG, "HS20: Get icon " MACSTR " %s @ %u +%u (%u)",
MAC2STR(bssid), file_name, (unsigned int) offset,
(unsigned int) size, (unsigned int) buf_len);
icon = hs20_find_icon(wpa_s, bssid, file_name);
if (!icon || !icon->image || offset >= icon->image_len)
return -1;
if (size > icon->image_len - offset)
size = icon->image_len - offset;
out_size = buf_len - 3 /* max base64 padding */;
if (size * 4 > out_size * 3)
size = out_size * 3 / 4;
if (size == 0)
return -1;
b64 = base64_encode(&icon->image[offset], size, &b64_size);
if (b64 && buf_len >= b64_size) {
os_memcpy(reply, b64, b64_size);
reply_size = b64_size;
} else {
reply_size = -1;
}
os_free(b64);
return reply_size;
}
static void hs20_free_icon_entry(struct icon_entry *icon)
{
wpa_printf(MSG_DEBUG, "HS20: Free stored icon from " MACSTR
" dialog_token=%u file_name=%s image_len=%u",
MAC2STR(icon->bssid), icon->dialog_token,
icon->file_name ? icon->file_name : "N/A",
(unsigned int) icon->image_len);
os_free(icon->file_name);
os_free(icon->image);
os_free(icon);
}
int hs20_del_icon(struct wpa_supplicant *wpa_s, const u8 *bssid,
const char *file_name)
{
struct icon_entry *icon, *tmp;
int count = 0;
if (!bssid)
wpa_printf(MSG_DEBUG, "HS20: Delete all stored icons");
else if (!file_name)
wpa_printf(MSG_DEBUG, "HS20: Delete all stored icons for "
MACSTR, MAC2STR(bssid));
else
wpa_printf(MSG_DEBUG, "HS20: Delete stored icons for "
MACSTR " file name %s", MAC2STR(bssid), file_name);
dl_list_for_each_safe(icon, tmp, &wpa_s->icon_head, struct icon_entry,
list) {
if ((!bssid || os_memcmp(icon->bssid, bssid, ETH_ALEN) == 0) &&
(!file_name ||
os_strcmp(icon->file_name, file_name) == 0)) {
dl_list_del(&icon->list);
hs20_free_icon_entry(icon);
count++;
}
}
return count == 0 ? -1 : 0;
}
static void hs20_set_osu_access_permission(const char *osu_dir,
const char *fname)
{
struct stat statbuf;
/* Get OSU directory information */
if (stat(osu_dir, &statbuf) < 0) {
wpa_printf(MSG_WARNING, "Cannot stat the OSU directory %s",
osu_dir);
return;
}
if (chmod(fname, statbuf.st_mode) < 0) {
wpa_printf(MSG_WARNING,
"Cannot change the permissions for %s", fname);
return;
}
if (lchown(fname, statbuf.st_uid, statbuf.st_gid) < 0) {
wpa_printf(MSG_WARNING, "Cannot change the ownership for %s",
fname);
}
}
static void hs20_remove_duplicate_icons(struct wpa_supplicant *wpa_s,
struct icon_entry *new_icon)
{
struct icon_entry *icon, *tmp;
dl_list_for_each_safe(icon, tmp, &wpa_s->icon_head, struct icon_entry,
list) {
if (icon == new_icon)
continue;
if (os_memcmp(icon->bssid, new_icon->bssid, ETH_ALEN) == 0 &&
os_strcmp(icon->file_name, new_icon->file_name) == 0) {
dl_list_del(&icon->list);
hs20_free_icon_entry(icon);
}
}
}
static int hs20_process_icon_binary_file(struct wpa_supplicant *wpa_s,
const u8 *sa, const u8 *pos,
size_t slen, u8 dialog_token)
{
char fname[256];
int png;
FILE *f;
u16 data_len;
struct icon_entry *icon;
dl_list_for_each(icon, &wpa_s->icon_head, struct icon_entry, list) {
if (icon->dialog_token == dialog_token && !icon->image &&
os_memcmp(icon->bssid, sa, ETH_ALEN) == 0) {
icon->image = os_memdup(pos, slen);
if (!icon->image)
return -1;
icon->image_len = slen;
hs20_remove_duplicate_icons(wpa_s, icon);
wpa_msg(wpa_s, MSG_INFO,
RX_HS20_ICON MACSTR " %s %u",
MAC2STR(sa), icon->file_name,
(unsigned int) icon->image_len);
return 0;
}
}
wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP MACSTR " Icon Binary File",
MAC2STR(sa));
if (slen < 4) {
wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File "
"value from " MACSTR, MAC2STR(sa));
return -1;
}
wpa_printf(MSG_DEBUG, "HS 2.0: Download Status Code %u", *pos);
if (*pos != 0)
return -1;
pos++;
slen--;
if ((size_t) 1 + pos[0] > slen) {
wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File "
"value from " MACSTR, MAC2STR(sa));
return -1;
}
wpa_hexdump_ascii(MSG_DEBUG, "Icon Type", pos + 1, pos[0]);
png = os_strncasecmp((char *) pos + 1, "image/png", 9) == 0;
slen -= 1 + pos[0];
pos += 1 + pos[0];
if (slen < 2) {
wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File "
"value from " MACSTR, MAC2STR(sa));
return -1;
}
data_len = WPA_GET_LE16(pos);
pos += 2;
slen -= 2;
if (data_len > slen) {
wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short Icon Binary File "
"value from " MACSTR, MAC2STR(sa));
return -1;
}
wpa_printf(MSG_DEBUG, "Icon Binary Data: %u bytes", data_len);
if (wpa_s->conf->osu_dir == NULL)
return -1;
wpa_s->osu_icon_id++;
if (wpa_s->osu_icon_id == 0)
wpa_s->osu_icon_id++;
snprintf(fname, sizeof(fname), "%s/osu-icon-%u.%s",
wpa_s->conf->osu_dir, wpa_s->osu_icon_id,
png ? "png" : "icon");
f = fopen(fname, "wb");
if (f == NULL)
return -1;
hs20_set_osu_access_permission(wpa_s->conf->osu_dir, fname);
if (fwrite(pos, slen, 1, f) != 1) {
fclose(f);
unlink(fname);
return -1;
}
fclose(f);
wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP_ICON "%s", fname);
return 0;
}
static void hs20_continue_icon_fetch(void *eloop_ctx, void *sock_ctx)
{
struct wpa_supplicant *wpa_s = eloop_ctx;
if (wpa_s->fetch_osu_icon_in_progress)
hs20_next_osu_icon(wpa_s);
}
static void hs20_osu_icon_fetch_result(struct wpa_supplicant *wpa_s, int res)
{
size_t i, j;
struct os_reltime now, tmp;
int dur;
os_get_reltime(&now);
os_reltime_sub(&now, &wpa_s->osu_icon_fetch_start, &tmp);
dur = tmp.sec * 1000 + tmp.usec / 1000;
wpa_printf(MSG_DEBUG, "HS 2.0: Icon fetch dur=%d ms res=%d",
dur, res);
for (i = 0; i < wpa_s->osu_prov_count; i++) {
struct osu_provider *osu = &wpa_s->osu_prov[i];
for (j = 0; j < osu->icon_count; j++) {
struct osu_icon *icon = &osu->icon[j];
if (icon->id || icon->failed)
continue;
if (res < 0)
icon->failed = 1;
else
icon->id = wpa_s->osu_icon_id;
return;
}
}
}
void hs20_parse_rx_hs20_anqp_resp(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss, const u8 *sa,
const u8 *data, size_t slen, u8 dialog_token)
{
const u8 *pos = data;
u8 subtype;
struct wpa_bss_anqp *anqp = NULL;
int ret;
if (slen < 2)
return;
if (bss)
anqp = bss->anqp;
subtype = *pos++;
slen--;
pos++; /* Reserved */
slen--;
switch (subtype) {
case HS20_STYPE_CAPABILITY_LIST:
wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP MACSTR
" HS Capability List", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "HS Capability List", pos, slen);
if (anqp) {
wpabuf_free(anqp->hs20_capability_list);
anqp->hs20_capability_list =
wpabuf_alloc_copy(pos, slen);
}
break;
case HS20_STYPE_OPERATOR_FRIENDLY_NAME:
wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP MACSTR
" Operator Friendly Name", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "oper friendly name", pos, slen);
if (anqp) {
wpabuf_free(anqp->hs20_operator_friendly_name);
anqp->hs20_operator_friendly_name =
wpabuf_alloc_copy(pos, slen);
}
break;
case HS20_STYPE_WAN_METRICS:
wpa_hexdump(MSG_DEBUG, "WAN Metrics", pos, slen);
if (slen < 13) {
wpa_dbg(wpa_s, MSG_DEBUG, "HS 2.0: Too short WAN "
"Metrics value from " MACSTR, MAC2STR(sa));
break;
}
wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP MACSTR
" WAN Metrics %02x:%u:%u:%u:%u:%u", MAC2STR(sa),
pos[0], WPA_GET_LE32(pos + 1), WPA_GET_LE32(pos + 5),
pos[9], pos[10], WPA_GET_LE16(pos + 11));
if (anqp) {
wpabuf_free(anqp->hs20_wan_metrics);
anqp->hs20_wan_metrics = wpabuf_alloc_copy(pos, slen);
}
break;
case HS20_STYPE_CONNECTION_CAPABILITY:
wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP MACSTR
" Connection Capability", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "conn capability", pos, slen);
if (anqp) {
wpabuf_free(anqp->hs20_connection_capability);
anqp->hs20_connection_capability =
wpabuf_alloc_copy(pos, slen);
}
break;
case HS20_STYPE_OPERATING_CLASS:
wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP MACSTR
" Operating Class", MAC2STR(sa));
wpa_hexdump_ascii(MSG_DEBUG, "Operating Class", pos, slen);
if (anqp) {
wpabuf_free(anqp->hs20_operating_class);
anqp->hs20_operating_class =
wpabuf_alloc_copy(pos, slen);
}
break;
case HS20_STYPE_OSU_PROVIDERS_LIST:
wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP MACSTR
" OSU Providers list", MAC2STR(sa));
wpa_s->num_prov_found++;
if (anqp) {
wpabuf_free(anqp->hs20_osu_providers_list);
anqp->hs20_osu_providers_list =
wpabuf_alloc_copy(pos, slen);
}
break;
case HS20_STYPE_ICON_BINARY_FILE:
ret = hs20_process_icon_binary_file(wpa_s, sa, pos, slen,
dialog_token);
if (wpa_s->fetch_osu_icon_in_progress) {
hs20_osu_icon_fetch_result(wpa_s, ret);
eloop_cancel_timeout(hs20_continue_icon_fetch,
wpa_s, NULL);
eloop_register_timeout(0, 0, hs20_continue_icon_fetch,
wpa_s, NULL);
}
break;
case HS20_STYPE_OPERATOR_ICON_METADATA:
wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP MACSTR
" Operator Icon Metadata", MAC2STR(sa));
wpa_hexdump(MSG_DEBUG, "Operator Icon Metadata", pos, slen);
if (anqp) {
wpabuf_free(anqp->hs20_operator_icon_metadata);
anqp->hs20_operator_icon_metadata =
wpabuf_alloc_copy(pos, slen);
}
break;
case HS20_STYPE_OSU_PROVIDERS_NAI_LIST:
wpa_msg(wpa_s, MSG_INFO, RX_HS20_ANQP MACSTR
" OSU Providers NAI List", MAC2STR(sa));
if (anqp) {
wpabuf_free(anqp->hs20_osu_providers_nai_list);
anqp->hs20_osu_providers_nai_list =
wpabuf_alloc_copy(pos, slen);
}
break;
default:
wpa_printf(MSG_DEBUG, "HS20: Unsupported subtype %u", subtype);
break;
}
}
void hs20_notify_parse_done(struct wpa_supplicant *wpa_s)
{
if (!wpa_s->fetch_osu_icon_in_progress)
return;
if (eloop_is_timeout_registered(hs20_continue_icon_fetch, wpa_s, NULL))
return;
/*
* We are going through icon fetch, but no icon response was received.
* Assume this means the current AP could not provide an answer to avoid
* getting stuck in fetch iteration.
*/
hs20_icon_fetch_failed(wpa_s);
}
static void hs20_free_osu_prov_entry(struct osu_provider *prov)
{
}
void hs20_free_osu_prov(struct wpa_supplicant *wpa_s)
{
size_t i;
for (i = 0; i < wpa_s->osu_prov_count; i++)
hs20_free_osu_prov_entry(&wpa_s->osu_prov[i]);
os_free(wpa_s->osu_prov);
wpa_s->osu_prov = NULL;
wpa_s->osu_prov_count = 0;
}
static void hs20_osu_fetch_done(struct wpa_supplicant *wpa_s)
{
char fname[256];
FILE *f;
size_t i, j;
wpa_s->fetch_osu_info = 0;
wpa_s->fetch_osu_icon_in_progress = 0;
if (wpa_s->conf->osu_dir == NULL) {
hs20_free_osu_prov(wpa_s);
wpa_s->fetch_anqp_in_progress = 0;
return;
}
snprintf(fname, sizeof(fname), "%s/osu-providers.txt",
wpa_s->conf->osu_dir);
f = fopen(fname, "w");
if (f == NULL) {
wpa_msg(wpa_s, MSG_INFO,
"Could not write OSU provider information");
hs20_free_osu_prov(wpa_s);
wpa_s->fetch_anqp_in_progress = 0;
return;
}
hs20_set_osu_access_permission(wpa_s->conf->osu_dir, fname);
for (i = 0; i < wpa_s->osu_prov_count; i++) {
struct osu_provider *osu = &wpa_s->osu_prov[i];
if (i > 0)
fprintf(f, "\n");
fprintf(f, "OSU-PROVIDER " MACSTR "\n"
"uri=%s\n"
"methods=%08x\n",
MAC2STR(osu->bssid), osu->server_uri, osu->osu_methods);
if (osu->osu_ssid_len) {
fprintf(f, "osu_ssid=%s\n",
wpa_ssid_txt(osu->osu_ssid,
osu->osu_ssid_len));
}
if (osu->osu_ssid2_len) {
fprintf(f, "osu_ssid2=%s\n",
wpa_ssid_txt(osu->osu_ssid2,
osu->osu_ssid2_len));
}
if (osu->osu_nai[0])
fprintf(f, "osu_nai=%s\n", osu->osu_nai);
if (osu->osu_nai2[0])
fprintf(f, "osu_nai2=%s\n", osu->osu_nai2);
for (j = 0; j < osu->friendly_name_count; j++) {
fprintf(f, "friendly_name=%s:%s\n",
osu->friendly_name[j].lang,
osu->friendly_name[j].text);
}
for (j = 0; j < osu->serv_desc_count; j++) {
fprintf(f, "desc=%s:%s\n",
osu->serv_desc[j].lang,
osu->serv_desc[j].text);
}
for (j = 0; j < osu->icon_count; j++) {
struct osu_icon *icon = &osu->icon[j];
if (icon->failed)
continue; /* could not fetch icon */
fprintf(f, "icon=%u:%u:%u:%s:%s:%s\n",
icon->id, icon->width, icon->height, icon->lang,
icon->icon_type, icon->filename);
}
}
fclose(f);
hs20_free_osu_prov(wpa_s);
wpa_msg(wpa_s, MSG_INFO, "OSU provider fetch completed");
wpa_s->fetch_anqp_in_progress = 0;
}
void hs20_next_osu_icon(struct wpa_supplicant *wpa_s)
{
size_t i, j;
wpa_printf(MSG_DEBUG, "HS 2.0: Ready to fetch next icon");
for (i = 0; i < wpa_s->osu_prov_count; i++) {
struct osu_provider *osu = &wpa_s->osu_prov[i];
for (j = 0; j < osu->icon_count; j++) {
struct osu_icon *icon = &osu->icon[j];
if (icon->id || icon->failed)
continue;
wpa_printf(MSG_DEBUG, "HS 2.0: Try to fetch icon '%s' "
"from " MACSTR, icon->filename,
MAC2STR(osu->bssid));
os_get_reltime(&wpa_s->osu_icon_fetch_start);
if (hs20_anqp_send_req(wpa_s, osu->bssid,
BIT(HS20_STYPE_ICON_REQUEST),
(u8 *) icon->filename,
os_strlen(icon->filename),
0) < 0) {
icon->failed = 1;
continue;
}
return;
}
}
wpa_printf(MSG_DEBUG, "HS 2.0: No more icons to fetch");
hs20_osu_fetch_done(wpa_s);
}
static void hs20_osu_add_prov(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
const u8 *osu_ssid, u8 osu_ssid_len,
const u8 *osu_ssid2, u8 osu_ssid2_len,
const u8 *pos, size_t len)
{
struct osu_provider *prov;
const u8 *end = pos + len;
u16 len2;
const u8 *pos2;
u8 uri_len, osu_method_len, osu_nai_len;
wpa_hexdump(MSG_DEBUG, "HS 2.0: Parsing OSU Provider", pos, len);
prov = os_realloc_array(wpa_s->osu_prov,
wpa_s->osu_prov_count + 1,
sizeof(*prov));
if (prov == NULL)
return;
wpa_s->osu_prov = prov;
prov = &prov[wpa_s->osu_prov_count];
os_memset(prov, 0, sizeof(*prov));
os_memcpy(prov->bssid, bss->bssid, ETH_ALEN);
os_memcpy(prov->osu_ssid, osu_ssid, osu_ssid_len);
prov->osu_ssid_len = osu_ssid_len;
if (osu_ssid2)
os_memcpy(prov->osu_ssid2, osu_ssid2, osu_ssid2_len);
prov->osu_ssid2_len = osu_ssid2_len;
/* OSU Friendly Name Length */
if (end - pos < 2) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU "
"Friendly Name Length");
return;
}
len2 = WPA_GET_LE16(pos);
pos += 2;
if (len2 > end - pos) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU "
"Friendly Name Duples");
return;
}
pos2 = pos;
pos += len2;
/* OSU Friendly Name Duples */
while (pos - pos2 >= 4 && prov->friendly_name_count < OSU_MAX_ITEMS) {
struct osu_lang_string *f;
if (1 + pos2[0] > pos - pos2 || pos2[0] < 3) {
wpa_printf(MSG_DEBUG, "Invalid OSU Friendly Name");
break;
}
f = &prov->friendly_name[prov->friendly_name_count++];
os_memcpy(f->lang, pos2 + 1, 3);
os_memcpy(f->text, pos2 + 1 + 3, pos2[0] - 3);
pos2 += 1 + pos2[0];
}
/* OSU Server URI */
if (end - pos < 1) {
wpa_printf(MSG_DEBUG,
"HS 2.0: Not enough room for OSU Server URI length");
return;
}
uri_len = *pos++;
if (uri_len > end - pos) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU Server "
"URI");
return;
}
os_memcpy(prov->server_uri, pos, uri_len);
pos += uri_len;
/* OSU Method list */
if (end - pos < 1) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU Method "
"list length");
return;
}
osu_method_len = pos[0];
if (osu_method_len > end - pos - 1) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU Method "
"list");
return;
}
pos2 = pos + 1;
pos += 1 + osu_method_len;
while (pos2 < pos) {
if (*pos2 < 32)
prov->osu_methods |= BIT(*pos2);
pos2++;
}
/* Icons Available Length */
if (end - pos < 2) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for Icons "
"Available Length");
return;
}
len2 = WPA_GET_LE16(pos);
pos += 2;
if (len2 > end - pos) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for Icons "
"Available");
return;
}
pos2 = pos;
pos += len2;
/* Icons Available */
while (pos2 < pos) {
struct osu_icon *icon = &prov->icon[prov->icon_count];
u8 flen;
if (2 + 2 + 3 + 1 + 1 > pos - pos2) {
wpa_printf(MSG_DEBUG, "HS 2.0: Invalid Icon Metadata");
break;
}
icon->width = WPA_GET_LE16(pos2);
pos2 += 2;
icon->height = WPA_GET_LE16(pos2);
pos2 += 2;
os_memcpy(icon->lang, pos2, 3);
pos2 += 3;
flen = *pos2++;
if (flen > pos - pos2) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not room for Icon Type");
break;
}
os_memcpy(icon->icon_type, pos2, flen);
pos2 += flen;
if (pos - pos2 < 1) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not room for Icon "
"Filename length");
break;
}
flen = *pos2++;
if (flen > pos - pos2) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not room for Icon "
"Filename");
break;
}
os_memcpy(icon->filename, pos2, flen);
pos2 += flen;
prov->icon_count++;
}
/* OSU_NAI */
if (end - pos < 1) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU_NAI");
return;
}
osu_nai_len = *pos++;
if (osu_nai_len > end - pos) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU_NAI");
return;
}
os_memcpy(prov->osu_nai, pos, osu_nai_len);
pos += osu_nai_len;
/* OSU Service Description Length */
if (end - pos < 2) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU "
"Service Description Length");
return;
}
len2 = WPA_GET_LE16(pos);
pos += 2;
if (len2 > end - pos) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for OSU "
"Service Description Duples");
return;
}
pos2 = pos;
pos += len2;
/* OSU Service Description Duples */
while (pos - pos2 >= 4 && prov->serv_desc_count < OSU_MAX_ITEMS) {
struct osu_lang_string *f;
u8 descr_len;
descr_len = *pos2++;
if (descr_len > pos - pos2 || descr_len < 3) {
wpa_printf(MSG_DEBUG, "Invalid OSU Service "
"Description");
break;
}
f = &prov->serv_desc[prov->serv_desc_count++];
os_memcpy(f->lang, pos2, 3);
os_memcpy(f->text, pos2 + 3, descr_len - 3);
pos2 += descr_len;
}
wpa_printf(MSG_DEBUG, "HS 2.0: Added OSU Provider through " MACSTR,
MAC2STR(bss->bssid));
wpa_s->osu_prov_count++;
}
void hs20_osu_icon_fetch(struct wpa_supplicant *wpa_s)
{
struct wpa_bss *bss;
struct wpabuf *prov_anqp;
const u8 *pos, *end;
u16 len;
const u8 *osu_ssid, *osu_ssid2;
u8 osu_ssid_len, osu_ssid2_len;
u8 num_providers;
hs20_free_osu_prov(wpa_s);
dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
struct wpa_ie_data data;
const u8 *ie;
if (bss->anqp == NULL)
continue;
prov_anqp = bss->anqp->hs20_osu_providers_list;
if (prov_anqp == NULL)
continue;
ie = wpa_bss_get_ie(bss, WLAN_EID_RSN);
if (ie && wpa_parse_wpa_ie(ie, 2 + ie[1], &data) == 0 &&
(data.key_mgmt & WPA_KEY_MGMT_OSEN)) {
osu_ssid2 = bss->ssid;
osu_ssid2_len = bss->ssid_len;
} else {
osu_ssid2 = NULL;
osu_ssid2_len = 0;
}
wpa_printf(MSG_DEBUG, "HS 2.0: Parsing OSU Providers list from "
MACSTR, MAC2STR(bss->bssid));
wpa_hexdump_buf(MSG_DEBUG, "HS 2.0: OSU Providers list",
prov_anqp);
pos = wpabuf_head(prov_anqp);
end = pos + wpabuf_len(prov_anqp);
/* OSU SSID */
if (end - pos < 1)
continue;
if (1 + pos[0] > end - pos) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for "
"OSU SSID");
continue;
}
osu_ssid_len = *pos++;
if (osu_ssid_len > SSID_MAX_LEN) {
wpa_printf(MSG_DEBUG, "HS 2.0: Invalid OSU SSID "
"Length %u", osu_ssid_len);
continue;
}
osu_ssid = pos;
pos += osu_ssid_len;
if (end - pos < 1) {
wpa_printf(MSG_DEBUG, "HS 2.0: Not enough room for "
"Number of OSU Providers");
continue;
}
num_providers = *pos++;
wpa_printf(MSG_DEBUG, "HS 2.0: Number of OSU Providers: %u",
num_providers);
/* OSU Providers */
while (end - pos > 2 && num_providers > 0) {
num_providers--;
len = WPA_GET_LE16(pos);
pos += 2;
if (len > (unsigned int) (end - pos))
break;
hs20_osu_add_prov(wpa_s, bss, osu_ssid,
osu_ssid_len, osu_ssid2,
osu_ssid2_len, pos, len);
pos += len;
}
if (pos != end) {
wpa_printf(MSG_DEBUG, "HS 2.0: Ignored %d bytes of "
"extra data after OSU Providers",
(int) (end - pos));
}
prov_anqp = bss->anqp->hs20_osu_providers_nai_list;
if (!prov_anqp)
continue;
wpa_printf(MSG_DEBUG,
"HS 2.0: Parsing OSU Providers NAI List from "
MACSTR, MAC2STR(bss->bssid));
wpa_hexdump_buf(MSG_DEBUG, "HS 2.0: OSU Providers NAI List",
prov_anqp);
pos = wpabuf_head(prov_anqp);
end = pos + wpabuf_len(prov_anqp);
num_providers = 0;
while (end - pos > 0) {
len = *pos++;
if (end - pos < len) {
wpa_printf(MSG_DEBUG,
"HS 2.0: Not enough room for OSU_NAI");
break;
}
if (num_providers >= wpa_s->osu_prov_count) {
wpa_printf(MSG_DEBUG,
"HS 2.0: Ignore unexpected OSU Provider NAI List entries");
break;
}
os_memcpy(wpa_s->osu_prov[num_providers].osu_nai2,
pos, len);
pos += len;
num_providers++;
}
}
wpa_s->fetch_osu_icon_in_progress = 1;
hs20_next_osu_icon(wpa_s);
}
static void hs20_osu_scan_res_handler(struct wpa_supplicant *wpa_s,
struct wpa_scan_results *scan_res)
{
wpa_printf(MSG_DEBUG, "OSU provisioning fetch scan completed");
if (!wpa_s->fetch_osu_waiting_scan) {
wpa_printf(MSG_DEBUG, "OSU fetch have been canceled");
return;
}
wpa_s->network_select = 0;
wpa_s->fetch_all_anqp = 1;
wpa_s->fetch_osu_info = 1;
wpa_s->fetch_osu_icon_in_progress = 0;
interworking_start_fetch_anqp(wpa_s);
}
int hs20_fetch_osu(struct wpa_supplicant *wpa_s, int skip_scan)
{
if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) {
wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - "
"interface disabled");
return -1;
}
if (wpa_s->scanning) {
wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - "
"scanning");
return -1;
}
if (wpa_s->conf->osu_dir == NULL) {
wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - "
"osu_dir not configured");
return -1;
}
if (wpa_s->fetch_anqp_in_progress || wpa_s->network_select) {
wpa_printf(MSG_DEBUG, "HS 2.0: Cannot start fetch_osu - "
"fetch in progress (%d, %d)",
wpa_s->fetch_anqp_in_progress,
wpa_s->network_select);
return -1;
}
wpa_msg(wpa_s, MSG_INFO, "Starting OSU provisioning information fetch");
wpa_s->num_osu_scans = 0;
wpa_s->num_prov_found = 0;
if (skip_scan) {
wpa_s->network_select = 0;
wpa_s->fetch_all_anqp = 1;
wpa_s->fetch_osu_info = 1;
wpa_s->fetch_osu_icon_in_progress = 0;
interworking_start_fetch_anqp(wpa_s);
} else {
hs20_start_osu_scan(wpa_s);
}
return 0;
}
void hs20_start_osu_scan(struct wpa_supplicant *wpa_s)
{
wpa_s->fetch_osu_waiting_scan = 1;
wpa_s->num_osu_scans++;
wpa_s->scan_req = MANUAL_SCAN_REQ;
wpa_s->scan_res_handler = hs20_osu_scan_res_handler;
wpa_supplicant_req_scan(wpa_s, 0, 0);
}
void hs20_cancel_fetch_osu(struct wpa_supplicant *wpa_s)
{
wpa_printf(MSG_DEBUG, "Cancel OSU fetch");
interworking_stop_fetch_anqp(wpa_s);
wpa_s->fetch_osu_waiting_scan = 0;
wpa_s->network_select = 0;
wpa_s->fetch_osu_info = 0;
wpa_s->fetch_osu_icon_in_progress = 0;
}
void hs20_icon_fetch_failed(struct wpa_supplicant *wpa_s)
{
hs20_osu_icon_fetch_result(wpa_s, -1);
eloop_cancel_timeout(hs20_continue_icon_fetch, wpa_s, NULL);
eloop_register_timeout(0, 0, hs20_continue_icon_fetch, wpa_s, NULL);
}
void hs20_rx_subscription_remediation(struct wpa_supplicant *wpa_s,
const char *url, u8 osu_method)
{
if (url)
wpa_msg(wpa_s, MSG_INFO, HS20_SUBSCRIPTION_REMEDIATION "%u %s",
osu_method, url);
else
wpa_msg(wpa_s, MSG_INFO, HS20_SUBSCRIPTION_REMEDIATION);
}
void hs20_rx_deauth_imminent_notice(struct wpa_supplicant *wpa_s, u8 code,
u16 reauth_delay, const char *url)
{
if (!wpa_sm_pmf_enabled(wpa_s->wpa)) {
wpa_printf(MSG_DEBUG, "HS 2.0: Ignore deauthentication imminent notice since PMF was not enabled");
return;
}
wpa_msg(wpa_s, MSG_INFO, HS20_DEAUTH_IMMINENT_NOTICE "%u %u %s",
code, reauth_delay, url);
if (code == HS20_DEAUTH_REASON_CODE_BSS) {
wpa_printf(MSG_DEBUG, "HS 2.0: Add BSS to blacklist");
wpa_blacklist_add(wpa_s, wpa_s->bssid);
/* TODO: For now, disable full ESS since some drivers may not
* support disabling per BSS. */
if (wpa_s->current_ssid) {
struct os_reltime now;
os_get_reltime(&now);
if (now.sec + reauth_delay <=
wpa_s->current_ssid->disabled_until.sec)
return;
wpa_printf(MSG_DEBUG, "HS 2.0: Disable network for %u seconds (BSS)",
reauth_delay);
wpa_s->current_ssid->disabled_until.sec =
now.sec + reauth_delay;
}
}
if (code == HS20_DEAUTH_REASON_CODE_ESS && wpa_s->current_ssid) {
struct os_reltime now;
os_get_reltime(&now);
if (now.sec + reauth_delay <=
wpa_s->current_ssid->disabled_until.sec)
return;
wpa_printf(MSG_DEBUG, "HS 2.0: Disable network for %u seconds",
reauth_delay);
wpa_s->current_ssid->disabled_until.sec =
now.sec + reauth_delay;
}
}
void hs20_rx_t_c_acceptance(struct wpa_supplicant *wpa_s, const char *url)
{
if (!wpa_sm_pmf_enabled(wpa_s->wpa)) {
wpa_printf(MSG_DEBUG,
"HS 2.0: Ignore Terms and Conditions Acceptance since PMF was not enabled");
return;
}
wpa_msg(wpa_s, MSG_INFO, HS20_T_C_ACCEPTANCE "%s", url);
}
void hs20_init(struct wpa_supplicant *wpa_s)
{
dl_list_init(&wpa_s->icon_head);
}
void hs20_deinit(struct wpa_supplicant *wpa_s)
{
eloop_cancel_timeout(hs20_continue_icon_fetch, wpa_s, NULL);
hs20_free_osu_prov(wpa_s);
if (wpa_s->icon_head.next)
hs20_del_icon(wpa_s, NULL, NULL);
}