diff --git a/hostapd/config_file.c b/hostapd/config_file.c index be05840e8..6b1105b7c 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -1576,6 +1576,60 @@ static int hs20_parse_oper_friendly_name(struct hostapd_bss_config *bss, } return 0; } + + +static int hs20_parse_icon(struct hostapd_bss_config *bss, char *pos) +{ + struct hs20_icon *icon; + char *end; + + icon = os_realloc_array(bss->hs20_icons, bss->hs20_icons_count + 1, + sizeof(struct hs20_icon)); + if (icon == NULL) + return -1; + bss->hs20_icons = icon; + icon = &bss->hs20_icons[bss->hs20_icons_count]; + os_memset(icon, 0, sizeof(*icon)); + + icon->width = atoi(pos); + pos = os_strchr(pos, ':'); + if (pos == NULL) + return -1; + pos++; + + icon->height = atoi(pos); + pos = os_strchr(pos, ':'); + if (pos == NULL) + return -1; + pos++; + + end = os_strchr(pos, ':'); + if (end == NULL || end - pos > 3) + return -1; + os_memcpy(icon->language, pos, end - pos); + pos = end + 1; + + end = os_strchr(pos, ':'); + if (end == NULL || end - pos > 255) + return -1; + os_memcpy(icon->type, pos, end - pos); + pos = end + 1; + + end = os_strchr(pos, ':'); + if (end == NULL || end - pos > 255) + return -1; + os_memcpy(icon->name, pos, end - pos); + pos = end + 1; + + if (os_strlen(pos) > 255) + return -1; + os_memcpy(icon->file, pos, os_strlen(pos)); + + bss->hs20_icons_count++; + + return 0; +} + #endif /* CONFIG_HS20 */ @@ -2857,6 +2911,13 @@ static int hostapd_config_fill(struct hostapd_config *conf, os_free(bss->hs20_operating_class); bss->hs20_operating_class = oper_class; bss->hs20_operating_class_len = oper_class_len; + } else if (os_strcmp(buf, "hs20_icon") == 0) { + if (hs20_parse_icon(bss, pos) < 0) { + wpa_printf(MSG_ERROR, "Line %d: Invalid " + "hs20_icon '%s'", line, pos); + errors++; + return errors; + } #endif /* CONFIG_HS20 */ #ifdef CONFIG_TESTING_OPTIONS #define PARSE_TEST_PROBABILITY(_val) \ diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 2903df81c..c07913a8b 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1640,6 +1640,11 @@ own_ip_addr=127.0.0.1 # channels 36-48): #hs20_operating_class=5173 +# OSU icons +# ::::: +#hs20_icon=32:32:eng:image/png:icon32:/tmp/icon32.png +#hs20_icon=64:64:eng:image/png:icon64:/tmp/icon64.png + ##### TESTING OPTIONS ######################################################### # # The options in this section are only available when the build configuration diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c index cb5d3aab8..c48f56743 100644 --- a/src/ap/ap_config.c +++ b/src/ap/ap_config.c @@ -528,6 +528,7 @@ void hostapd_config_free_bss(struct hostapd_bss_config *conf) os_free(conf->hs20_wan_metrics); os_free(conf->hs20_connection_capability); os_free(conf->hs20_operating_class); + os_free(conf->hs20_icons); #endif /* CONFIG_HS20 */ wpabuf_free(conf->vendor_elements); diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index 2eea266f2..8d0aae5ee 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -465,6 +465,15 @@ struct hostapd_bss_config { size_t hs20_connection_capability_len; u8 *hs20_operating_class; u8 hs20_operating_class_len; + struct hs20_icon { + u16 width; + u16 height; + char language[3]; + char type[256]; + char name[256]; + char file[256]; + } *hs20_icons; + size_t hs20_icons_count; unsigned int hs20_deauth_req_timeout; #endif /* CONFIG_HS20 */ diff --git a/src/ap/gas_serv.c b/src/ap/gas_serv.c index b5fb7dfbc..835d0a8b9 100644 --- a/src/ap/gas_serv.c +++ b/src/ap/gas_serv.c @@ -159,6 +159,8 @@ static void anqp_add_hs_capab_list(struct hostapd_data *hapd, wpabuf_put_u8(buf, HS20_STYPE_NAI_HOME_REALM_QUERY); if (hapd->conf->hs20_operating_class) wpabuf_put_u8(buf, HS20_STYPE_OPERATING_CLASS); + if (hapd->conf->hs20_icons_count) + wpabuf_put_u8(buf, HS20_STYPE_ICON_REQUEST); gas_anqp_set_element_len(buf, len); } #endif /* CONFIG_HS20 */ @@ -514,6 +516,62 @@ static void anqp_add_operating_class(struct hostapd_data *hapd, } } + +static void anqp_add_icon_binary_file(struct hostapd_data *hapd, + struct wpabuf *buf, + const u8 *name, size_t name_len) +{ + struct hs20_icon *icon; + size_t i; + u8 *len; + + wpa_hexdump_ascii(MSG_DEBUG, "HS 2.0: Requested Icon Filename", + name, name_len); + for (i = 0; i < hapd->conf->hs20_icons_count; i++) { + icon = &hapd->conf->hs20_icons[i]; + if (name_len == os_strlen(icon->name) && + os_memcmp(name, icon->name, name_len) == 0) + break; + } + + if (i < hapd->conf->hs20_icons_count) + icon = &hapd->conf->hs20_icons[i]; + else + icon = NULL; + + len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC); + wpabuf_put_be24(buf, OUI_WFA); + wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE); + wpabuf_put_u8(buf, HS20_STYPE_ICON_BINARY_FILE); + wpabuf_put_u8(buf, 0); /* Reserved */ + + if (icon) { + char *data; + size_t data_len; + + data = os_readfile(icon->file, &data_len); + if (data == NULL || data_len > 65535) { + wpabuf_put_u8(buf, 2); /* Download Status: + * Unspecified file error */ + wpabuf_put_u8(buf, 0); + wpabuf_put_le16(buf, 0); + } else { + wpabuf_put_u8(buf, 0); /* Download Status: Success */ + wpabuf_put_u8(buf, os_strlen(icon->type)); + wpabuf_put_str(buf, icon->type); + wpabuf_put_le16(buf, data_len); + wpabuf_put_data(buf, data, data_len); + } + os_free(data); + } else { + wpabuf_put_u8(buf, 1); /* Download Status: File not found */ + wpabuf_put_u8(buf, 0); + wpabuf_put_le16(buf, 0); + } + + gas_anqp_set_element_len(buf, len); +} + #endif /* CONFIG_HS20 */ @@ -521,11 +579,19 @@ static struct wpabuf * gas_serv_build_gas_resp_payload(struct hostapd_data *hapd, unsigned int request, struct gas_dialog_info *di, - const u8 *home_realm, size_t home_realm_len) + const u8 *home_realm, size_t home_realm_len, + const u8 *icon_name, size_t icon_name_len) { struct wpabuf *buf; + size_t len; - buf = wpabuf_alloc(1400); + len = 1400; + if (request & (ANQP_REQ_NAI_REALM | ANQP_REQ_NAI_HOME_REALM)) + len += 1000; + if (request & ANQP_REQ_ICON_REQUEST) + len += 65536; + + buf = wpabuf_alloc(len); if (buf == NULL) return NULL; @@ -559,6 +625,8 @@ gas_serv_build_gas_resp_payload(struct hostapd_data *hapd, anqp_add_connection_capability(hapd, buf); if (request & ANQP_REQ_OPERATING_CLASS) anqp_add_operating_class(hapd, buf); + if (request & ANQP_REQ_ICON_REQUEST) + anqp_add_icon_binary_file(hapd, buf, icon_name, icon_name_len); #endif /* CONFIG_HS20 */ return buf; @@ -581,6 +649,8 @@ struct anqp_query_info { unsigned int remote_request; const u8 *home_realm_query; size_t home_realm_query_len; + const u8 *icon_name; + size_t icon_name_len; u16 remote_delay; }; @@ -725,6 +795,23 @@ static void rx_anqp_hs_nai_home_realm(struct hostapd_data *hapd, } +static void rx_anqp_hs_icon_request(struct hostapd_data *hapd, + const u8 *pos, const u8 *end, + struct anqp_query_info *qi) +{ + qi->request |= ANQP_REQ_ICON_REQUEST; + qi->icon_name = pos; + qi->icon_name_len = end - pos; + if (hapd->conf->hs20_icons_count) { + wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Icon Request Query " + "(local)"); + } else { + wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Icon Request Query not " + "available"); + } +} + + static void rx_anqp_vendor_specific(struct hostapd_data *hapd, const u8 *pos, const u8 *end, struct anqp_query_info *qi) @@ -769,6 +856,9 @@ static void rx_anqp_vendor_specific(struct hostapd_data *hapd, case HS20_STYPE_NAI_HOME_REALM_QUERY: rx_anqp_hs_nai_home_realm(hapd, pos, end, qi); break; + case HS20_STYPE_ICON_REQUEST: + rx_anqp_hs_icon_request(hapd, pos, end, qi); + break; default: wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 query subtype " "%u", subtype); @@ -787,7 +877,8 @@ static void gas_serv_req_local_processing(struct hostapd_data *hapd, buf = gas_serv_build_gas_resp_payload(hapd, qi->request, NULL, qi->home_realm_query, - qi->home_realm_query_len); + qi->home_realm_query_len, + qi->icon_name, qi->icon_name_len); wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Locally generated ANQP responses", buf); if (!buf) @@ -954,7 +1045,7 @@ void gas_serv_tx_gas_response(struct hostapd_data *hapd, const u8 *dst, if (dialog->sd_resp == NULL) { buf = gas_serv_build_gas_resp_payload(hapd, dialog->all_requested, - dialog, NULL, 0); + dialog, NULL, 0, NULL, 0); wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses", buf); if (!buf) @@ -1087,7 +1178,7 @@ static void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd, buf = gas_serv_build_gas_resp_payload(hapd, dialog->all_requested, - dialog, NULL, 0); + dialog, NULL, 0, NULL, 0); wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Generated ANQP responses", buf); if (!buf) diff --git a/src/ap/gas_serv.h b/src/ap/gas_serv.h index 74739fef1..ff32dc936 100644 --- a/src/ap/gas_serv.h +++ b/src/ap/gas_serv.h @@ -37,6 +37,8 @@ (0x10000 << HS20_STYPE_NAI_HOME_REALM_QUERY) #define ANQP_REQ_OPERATING_CLASS \ (0x10000 << HS20_STYPE_OPERATING_CLASS) +#define ANQP_REQ_ICON_REQUEST \ + (0x10000 << HS20_STYPE_ICON_REQUEST) /* To account for latencies between hostapd and external ANQP processor */ #define GAS_SERV_COMEBACK_DELAY_FUDGE 10