diff options
| author | Dmitry Shmidt <dimitrysh@google.com> | 2014-03-12 14:22:04 -0700 |
|---|---|---|
| committer | Dmitry Shmidt <dimitrysh@google.com> | 2014-03-12 14:22:04 -0700 |
| commit | d5dc24eb5fbf0e0feff214c0260cae845721d5fe (patch) | |
| tree | b825835ecf8646a80f653084479d1490897be3d7 | |
| parent | 9866086a955d00e237cc8df3722e7dff75c02532 (diff) | |
| download | android_external_wpa_supplicant_8-d5dc24eb5fbf0e0feff214c0260cae845721d5fe.tar.gz android_external_wpa_supplicant_8-d5dc24eb5fbf0e0feff214c0260cae845721d5fe.tar.bz2 android_external_wpa_supplicant_8-d5dc24eb5fbf0e0feff214c0260cae845721d5fe.zip | |
Cumulative patch from commit 39b420f7b12b152f879d1bb8a5267b9a838e04dc
39b420f HS 2.0R2: Add parse_cert command for debugging purposes
c0d701a HS 2.0R2: Add OSU client implementation
d2bb2b4 Add os_file_exists()
1cb3eda HS 2.0R2: Add wrapper functions for libcurl
2cb8f96 HS 2.0R2: Add wrapper functions for libxml2
da4ec2c HS 2.0R2: Add wrapper for system browser
db3ca20 HS 2.0R2: Add wrapper for wpadebug browser on Android
2a10e57 HS 2.0R2: Add wrapper for Android browser for user interaction
d0b79d7 HS 2.0R2: Add wrapper functions for WebKit
91728f2 Add wpa_ctrl helper functions for upper level functionality
b4c26ef Clean up hostapd_config_fill() parsers
b2e32cd Fix memory leaks on wpa_config_parse_string() error paths
a0b728b Simplify hostapd_config_fill() error reporting
599f40d Remove extra indentation level from hostapd_config_fill()
Change-Id: I17b7089210da7e083c9fe5814e79cef99bbee80b
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
| -rw-r--r-- | hostapd/config_file.c | 2544 | ||||
| -rw-r--r-- | hs20/client/Android.mk | 73 | ||||
| -rw-r--r-- | hs20/client/Makefile | 94 | ||||
| -rw-r--r-- | hs20/client/devdetail.xml | 47 | ||||
| -rw-r--r-- | hs20/client/devinfo.xml | 7 | ||||
| -rw-r--r-- | hs20/client/est.c | 700 | ||||
| -rw-r--r-- | hs20/client/oma_dm_client.c | 1374 | ||||
| -rw-r--r-- | hs20/client/osu_client.c | 3196 | ||||
| -rw-r--r-- | hs20/client/osu_client.h | 121 | ||||
| -rw-r--r-- | hs20/client/spp_client.c | 1001 | ||||
| -rw-r--r-- | src/common/wpa_helpers.c | 292 | ||||
| -rw-r--r-- | src/common/wpa_helpers.h | 37 | ||||
| -rw-r--r-- | src/utils/browser-android.c | 117 | ||||
| -rw-r--r-- | src/utils/browser-system.c | 112 | ||||
| -rw-r--r-- | src/utils/browser-wpadebug.c | 123 | ||||
| -rw-r--r-- | src/utils/browser.c | 219 | ||||
| -rw-r--r-- | src/utils/browser.h | 21 | ||||
| -rw-r--r-- | src/utils/http-utils.h | 63 | ||||
| -rw-r--r-- | src/utils/http_curl.c | 1642 | ||||
| -rw-r--r-- | src/utils/os.h | 7 | ||||
| -rw-r--r-- | src/utils/os_unix.c | 10 | ||||
| -rw-r--r-- | src/utils/xml-utils.c | 471 | ||||
| -rw-r--r-- | src/utils/xml-utils.h | 100 | ||||
| -rw-r--r-- | src/utils/xml_libxml2.c | 457 |
24 files changed, 11516 insertions, 1312 deletions
diff --git a/hostapd/config_file.c b/hostapd/config_file.c index b6f1d1b9..f018f963 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -1679,6 +1679,7 @@ static int hs20_parse_osu_ssid(struct hostapd_bss_config *bss, str = wpa_config_parse_string(pos, &slen); if (str == NULL || slen < 1 || slen > HOSTAPD_MAX_SSID_LEN) { wpa_printf(MSG_ERROR, "Line %d: Invalid SSID '%s'", line, pos); + os_free(str); return -1; } @@ -1837,1389 +1838,1308 @@ static int hostapd_config_fill(struct hostapd_config *conf, struct hostapd_bss_config *bss, char *buf, char *pos, int line) { - int errors = 0; - - { - if (os_strcmp(buf, "interface") == 0) { - os_strlcpy(conf->bss[0]->iface, pos, - sizeof(conf->bss[0]->iface)); - } else if (os_strcmp(buf, "bridge") == 0) { - os_strlcpy(bss->bridge, pos, sizeof(bss->bridge)); - } else if (os_strcmp(buf, "vlan_bridge") == 0) { - os_strlcpy(bss->vlan_bridge, pos, - sizeof(bss->vlan_bridge)); - } else if (os_strcmp(buf, "wds_bridge") == 0) { - os_strlcpy(bss->wds_bridge, pos, - sizeof(bss->wds_bridge)); - } else if (os_strcmp(buf, "driver") == 0) { - int j; - /* clear to get error below if setting is invalid */ - conf->driver = NULL; - for (j = 0; wpa_drivers[j]; j++) { - if (os_strcmp(pos, wpa_drivers[j]->name) == 0) - { - conf->driver = wpa_drivers[j]; - break; - } - } - if (conf->driver == NULL) { - wpa_printf(MSG_ERROR, "Line %d: invalid/" - "unknown driver '%s'", line, pos); - errors++; - } - } else if (os_strcmp(buf, "debug") == 0) { - wpa_printf(MSG_DEBUG, "Line %d: DEPRECATED: 'debug' " - "configuration variable is not used " - "anymore", line); - } else if (os_strcmp(buf, "logger_syslog_level") == 0) { - bss->logger_syslog_level = atoi(pos); - } else if (os_strcmp(buf, "logger_stdout_level") == 0) { - bss->logger_stdout_level = atoi(pos); - } else if (os_strcmp(buf, "logger_syslog") == 0) { - bss->logger_syslog = atoi(pos); - } else if (os_strcmp(buf, "logger_stdout") == 0) { - bss->logger_stdout = atoi(pos); - } else if (os_strcmp(buf, "dump_file") == 0) { - wpa_printf(MSG_INFO, "Line %d: DEPRECATED: 'dump_file' configuration variable is not used anymore", - line); - } else if (os_strcmp(buf, "ssid") == 0) { - bss->ssid.ssid_len = os_strlen(pos); - if (bss->ssid.ssid_len > HOSTAPD_MAX_SSID_LEN || - bss->ssid.ssid_len < 1) { - wpa_printf(MSG_ERROR, "Line %d: invalid SSID " - "'%s'", line, pos); - errors++; - } else { - os_memcpy(bss->ssid.ssid, pos, - bss->ssid.ssid_len); - bss->ssid.ssid_set = 1; - } - } else if (os_strcmp(buf, "ssid2") == 0) { - size_t slen; - char *str = wpa_config_parse_string(pos, &slen); - if (str == NULL || slen < 1 || - slen > HOSTAPD_MAX_SSID_LEN) { - wpa_printf(MSG_ERROR, "Line %d: invalid SSID " - "'%s'", line, pos); - errors++; - } else { - os_memcpy(bss->ssid.ssid, str, slen); - bss->ssid.ssid_len = slen; - bss->ssid.ssid_set = 1; + if (os_strcmp(buf, "interface") == 0) { + os_strlcpy(conf->bss[0]->iface, pos, + sizeof(conf->bss[0]->iface)); + } else if (os_strcmp(buf, "bridge") == 0) { + os_strlcpy(bss->bridge, pos, sizeof(bss->bridge)); + } else if (os_strcmp(buf, "vlan_bridge") == 0) { + os_strlcpy(bss->vlan_bridge, pos, sizeof(bss->vlan_bridge)); + } else if (os_strcmp(buf, "wds_bridge") == 0) { + os_strlcpy(bss->wds_bridge, pos, sizeof(bss->wds_bridge)); + } else if (os_strcmp(buf, "driver") == 0) { + int j; + /* clear to get error below if setting is invalid */ + conf->driver = NULL; + for (j = 0; wpa_drivers[j]; j++) { + if (os_strcmp(pos, wpa_drivers[j]->name) == 0) { + conf->driver = wpa_drivers[j]; + break; } + } + if (conf->driver == NULL) { + wpa_printf(MSG_ERROR, + "Line %d: invalid/unknown driver '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "debug") == 0) { + wpa_printf(MSG_DEBUG, "Line %d: DEPRECATED: 'debug' configuration variable is not used anymore", + line); + } else if (os_strcmp(buf, "logger_syslog_level") == 0) { + bss->logger_syslog_level = atoi(pos); + } else if (os_strcmp(buf, "logger_stdout_level") == 0) { + bss->logger_stdout_level = atoi(pos); + } else if (os_strcmp(buf, "logger_syslog") == 0) { + bss->logger_syslog = atoi(pos); + } else if (os_strcmp(buf, "logger_stdout") == 0) { + bss->logger_stdout = atoi(pos); + } else if (os_strcmp(buf, "dump_file") == 0) { + wpa_printf(MSG_INFO, "Line %d: DEPRECATED: 'dump_file' configuration variable is not used anymore", + line); + } else if (os_strcmp(buf, "ssid") == 0) { + bss->ssid.ssid_len = os_strlen(pos); + if (bss->ssid.ssid_len > HOSTAPD_MAX_SSID_LEN || + bss->ssid.ssid_len < 1) { + wpa_printf(MSG_ERROR, "Line %d: invalid SSID '%s'", + line, pos); + return 1; + } + os_memcpy(bss->ssid.ssid, pos, bss->ssid.ssid_len); + bss->ssid.ssid_set = 1; + } else if (os_strcmp(buf, "ssid2") == 0) { + size_t slen; + char *str = wpa_config_parse_string(pos, &slen); + if (str == NULL || slen < 1 || slen > HOSTAPD_MAX_SSID_LEN) { + wpa_printf(MSG_ERROR, "Line %d: invalid SSID '%s'", + line, pos); os_free(str); - } else if (os_strcmp(buf, "utf8_ssid") == 0) { - bss->ssid.utf8_ssid = atoi(pos) > 0; - } else if (os_strcmp(buf, "macaddr_acl") == 0) { - bss->macaddr_acl = atoi(pos); - if (bss->macaddr_acl != ACCEPT_UNLESS_DENIED && - bss->macaddr_acl != DENY_UNLESS_ACCEPTED && - bss->macaddr_acl != USE_EXTERNAL_RADIUS_AUTH) { - wpa_printf(MSG_ERROR, "Line %d: unknown " - "macaddr_acl %d", - line, bss->macaddr_acl); - } - } else if (os_strcmp(buf, "accept_mac_file") == 0) { - if (hostapd_config_read_maclist(pos, &bss->accept_mac, - &bss->num_accept_mac)) - { - wpa_printf(MSG_ERROR, "Line %d: Failed to " - "read accept_mac_file '%s'", - line, pos); - errors++; - } - } else if (os_strcmp(buf, "deny_mac_file") == 0) { - if (hostapd_config_read_maclist(pos, &bss->deny_mac, - &bss->num_deny_mac)) { - wpa_printf(MSG_ERROR, "Line %d: Failed to " - "read deny_mac_file '%s'", - line, pos); - errors++; - } - } else if (os_strcmp(buf, "wds_sta") == 0) { - bss->wds_sta = atoi(pos); - } else if (os_strcmp(buf, "start_disabled") == 0) { - bss->start_disabled = atoi(pos); - } else if (os_strcmp(buf, "ap_isolate") == 0) { - bss->isolate = atoi(pos); - } else if (os_strcmp(buf, "ap_max_inactivity") == 0) { - bss->ap_max_inactivity = atoi(pos); - } else if (os_strcmp(buf, "skip_inactivity_poll") == 0) { - bss->skip_inactivity_poll = atoi(pos); - } else if (os_strcmp(buf, "country_code") == 0) { - os_memcpy(conf->country, pos, 2); - /* FIX: make this configurable */ - conf->country[2] = ' '; - } else if (os_strcmp(buf, "ieee80211d") == 0) { - conf->ieee80211d = atoi(pos); - } else if (os_strcmp(buf, "ieee80211h") == 0) { - conf->ieee80211h = atoi(pos); - } else if (os_strcmp(buf, "ieee8021x") == 0) { - bss->ieee802_1x = atoi(pos); - } else if (os_strcmp(buf, "eapol_version") == 0) { - bss->eapol_version = atoi(pos); - if (bss->eapol_version < 1 || - bss->eapol_version > 2) { - wpa_printf(MSG_ERROR, "Line %d: invalid EAPOL " - "version (%d): '%s'.", - line, bss->eapol_version, pos); - errors++; - } else - wpa_printf(MSG_DEBUG, "eapol_version=%d", - bss->eapol_version); + return 1; + } + os_memcpy(bss->ssid.ssid, str, slen); + bss->ssid.ssid_len = slen; + bss->ssid.ssid_set = 1; + os_free(str); + } else if (os_strcmp(buf, "utf8_ssid") == 0) { + bss->ssid.utf8_ssid = atoi(pos) > 0; + } else if (os_strcmp(buf, "macaddr_acl") == 0) { + bss->macaddr_acl = atoi(pos); + if (bss->macaddr_acl != ACCEPT_UNLESS_DENIED && + bss->macaddr_acl != DENY_UNLESS_ACCEPTED && + bss->macaddr_acl != USE_EXTERNAL_RADIUS_AUTH) { + wpa_printf(MSG_ERROR, "Line %d: unknown macaddr_acl %d", + line, bss->macaddr_acl); + } + } else if (os_strcmp(buf, "accept_mac_file") == 0) { + if (hostapd_config_read_maclist(pos, &bss->accept_mac, + &bss->num_accept_mac)) { + wpa_printf(MSG_ERROR, "Line %d: Failed to read accept_mac_file '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "deny_mac_file") == 0) { + if (hostapd_config_read_maclist(pos, &bss->deny_mac, + &bss->num_deny_mac)) { + wpa_printf(MSG_ERROR, "Line %d: Failed to read deny_mac_file '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "wds_sta") == 0) { + bss->wds_sta = atoi(pos); + } else if (os_strcmp(buf, "start_disabled") == 0) { + bss->start_disabled = atoi(pos); + } else if (os_strcmp(buf, "ap_isolate") == 0) { + bss->isolate = atoi(pos); + } else if (os_strcmp(buf, "ap_max_inactivity") == 0) { + bss->ap_max_inactivity = atoi(pos); + } else if (os_strcmp(buf, "skip_inactivity_poll") == 0) { + bss->skip_inactivity_poll = atoi(pos); + } else if (os_strcmp(buf, "country_code") == 0) { + os_memcpy(conf->country, pos, 2); + /* FIX: make this configurable */ + conf->country[2] = ' '; + } else if (os_strcmp(buf, "ieee80211d") == 0) { + conf->ieee80211d = atoi(pos); + } else if (os_strcmp(buf, "ieee80211h") == 0) { + conf->ieee80211h = atoi(pos); + } else if (os_strcmp(buf, "ieee8021x") == 0) { + bss->ieee802_1x = atoi(pos); + } else if (os_strcmp(buf, "eapol_version") == 0) { + bss->eapol_version = atoi(pos); + if (bss->eapol_version < 1 || bss->eapol_version > 2) { + wpa_printf(MSG_ERROR, + "Line %d: invalid EAPOL version (%d): '%s'.", + line, bss->eapol_version, pos); + return 1; + } + wpa_printf(MSG_DEBUG, "eapol_version=%d", bss->eapol_version); #ifdef EAP_SERVER - } else if (os_strcmp(buf, "eap_authenticator") == 0) { - bss->eap_server = atoi(pos); - wpa_printf(MSG_ERROR, "Line %d: obsolete " - "eap_authenticator used; this has been " - "renamed to eap_server", line); - } else if (os_strcmp(buf, "eap_server") == 0) { - bss->eap_server = atoi(pos); - } else if (os_strcmp(buf, "eap_user_file") == 0) { - if (hostapd_config_read_eap_user(pos, bss)) - errors++; - } else if (os_strcmp(buf, "ca_cert") == 0) { - os_free(bss->ca_cert); - bss->ca_cert = os_strdup(pos); - } else if (os_strcmp(buf, "server_cert") == 0) { - os_free(bss->server_cert); - bss->server_cert = os_strdup(pos); - } else if (os_strcmp(buf, "private_key") == 0) { - os_free(bss->private_key); - bss->private_key = os_strdup(pos); - } else if (os_strcmp(buf, "private_key_passwd") == 0) { - os_free(bss->private_key_passwd); - bss->private_key_passwd = os_strdup(pos); - } else if (os_strcmp(buf, "check_crl") == 0) { - bss->check_crl = atoi(pos); - } else if (os_strcmp(buf, "ocsp_stapling_response") == 0) { - os_free(bss->ocsp_stapling_response); - bss->ocsp_stapling_response = os_strdup(pos); - } else if (os_strcmp(buf, "dh_file") == 0) { - os_free(bss->dh_file); - bss->dh_file = os_strdup(pos); - } else if (os_strcmp(buf, "fragment_size") == 0) { - bss->fragment_size = atoi(pos); + } else if (os_strcmp(buf, "eap_authenticator") == 0) { + bss->eap_server = atoi(pos); + wpa_printf(MSG_ERROR, "Line %d: obsolete eap_authenticator used; this has been renamed to eap_server", line); + } else if (os_strcmp(buf, "eap_server") == 0) { + bss->eap_server = atoi(pos); + } else if (os_strcmp(buf, "eap_user_file") == 0) { + if (hostapd_config_read_eap_user(pos, bss)) + return 1; + } else if (os_strcmp(buf, "ca_cert") == 0) { + os_free(bss->ca_cert); + bss->ca_cert = os_strdup(pos); + } else if (os_strcmp(buf, "server_cert") == 0) { + os_free(bss->server_cert); + bss->server_cert = os_strdup(pos); + } else if (os_strcmp(buf, "private_key") == 0) { + os_free(bss->private_key); + bss->private_key = os_strdup(pos); + } else if (os_strcmp(buf, "private_key_passwd") == 0) { + os_free(bss->private_key_passwd); + bss->private_key_passwd = os_strdup(pos); + } else if (os_strcmp(buf, "check_crl") == 0) { + bss->check_crl = atoi(pos); + } else if (os_strcmp(buf, "ocsp_stapling_response") == 0) { + os_free(bss->ocsp_stapling_response); + bss->ocsp_stapling_response = os_strdup(pos); + } else if (os_strcmp(buf, "dh_file") == 0) { + os_free(bss->dh_file); + bss->dh_file = os_strdup(pos); + } else if (os_strcmp(buf, "fragment_size") == 0) { + bss->fragment_size = atoi(pos); #ifdef EAP_SERVER_FAST - } else if (os_strcmp(buf, "pac_opaque_encr_key") == 0) { - os_free(bss->pac_opaque_encr_key); - bss->pac_opaque_encr_key = os_malloc(16); - if (bss->pac_opaque_encr_key == NULL) { - wpa_printf(MSG_ERROR, "Line %d: No memory for " - "pac_opaque_encr_key", line); - errors++; - } else if (hexstr2bin(pos, bss->pac_opaque_encr_key, - 16)) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "pac_opaque_encr_key", line); - errors++; - } - } else if (os_strcmp(buf, "eap_fast_a_id") == 0) { - size_t idlen = os_strlen(pos); - if (idlen & 1) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "eap_fast_a_id", line); - errors++; - } else { - os_free(bss->eap_fast_a_id); - bss->eap_fast_a_id = os_malloc(idlen / 2); - if (bss->eap_fast_a_id == NULL || - hexstr2bin(pos, bss->eap_fast_a_id, - idlen / 2)) { - wpa_printf(MSG_ERROR, "Line %d: " - "Failed to parse " - "eap_fast_a_id", line); - errors++; - } else - bss->eap_fast_a_id_len = idlen / 2; - } - } else if (os_strcmp(buf, "eap_fast_a_id_info") == 0) { - os_free(bss->eap_fast_a_id_info); - bss->eap_fast_a_id_info = os_strdup(pos); - } else if (os_strcmp(buf, "eap_fast_prov") == 0) { - bss->eap_fast_prov = atoi(pos); - } else if (os_strcmp(buf, "pac_key_lifetime") == 0) { - bss->pac_key_lifetime = atoi(pos); - } else if (os_strcmp(buf, "pac_key_refresh_time") == 0) { - bss->pac_key_refresh_time = atoi(pos); + } else if (os_strcmp(buf, "pac_opaque_encr_key") == 0) { + os_free(bss->pac_opaque_encr_key); + bss->pac_opaque_encr_key = os_malloc(16); + if (bss->pac_opaque_encr_key == NULL) { + wpa_printf(MSG_ERROR, + "Line %d: No memory for pac_opaque_encr_key", + line); + return 1; + } else if (hexstr2bin(pos, bss->pac_opaque_encr_key, 16)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid pac_opaque_encr_key", + line); + return 1; + } + } else if (os_strcmp(buf, "eap_fast_a_id") == 0) { + size_t idlen = os_strlen(pos); + if (idlen & 1) { + wpa_printf(MSG_ERROR, "Line %d: Invalid eap_fast_a_id", + line); + return 1; + } + os_free(bss->eap_fast_a_id); + bss->eap_fast_a_id = os_malloc(idlen / 2); + if (bss->eap_fast_a_id == NULL || + hexstr2bin(pos, bss->eap_fast_a_id, idlen / 2)) { + wpa_printf(MSG_ERROR, "Line %d: Failed to parse eap_fast_a_id", + line); + os_free(bss->eap_fast_a_id); + bss->eap_fast_a_id = NULL; + return 1; + } else { + bss->eap_fast_a_id_len = idlen / 2; + } + } else if (os_strcmp(buf, "eap_fast_a_id_info") == 0) { + os_free(bss->eap_fast_a_id_info); + bss->eap_fast_a_id_info = os_strdup(pos); + } else if (os_strcmp(buf, "eap_fast_prov") == 0) { + bss->eap_fast_prov = atoi(pos); + } else if (os_strcmp(buf, "pac_key_lifetime") == 0) { + bss->pac_key_lifetime = atoi(pos); + } else if (os_strcmp(buf, "pac_key_refresh_time") == 0) { + bss->pac_key_refresh_time = atoi(pos); #endif /* EAP_SERVER_FAST */ #ifdef EAP_SERVER_SIM - } else if (os_strcmp(buf, "eap_sim_db") == 0) { - os_free(bss->eap_sim_db); - bss->eap_sim_db = os_strdup(pos); - } else if (os_strcmp(buf, "eap_sim_aka_result_ind") == 0) { - bss->eap_sim_aka_result_ind = atoi(pos); + } else if (os_strcmp(buf, "eap_sim_db") == 0) { + os_free(bss->eap_sim_db); + bss->eap_sim_db = os_strdup(pos); + } else if (os_strcmp(buf, "eap_sim_aka_result_ind") == 0) { + bss->eap_sim_aka_result_ind = atoi(pos); #endif /* EAP_SERVER_SIM */ #ifdef EAP_SERVER_TNC - } else if (os_strcmp(buf, "tnc") == 0) { - bss->tnc = atoi(pos); + } else if (os_strcmp(buf, "tnc") == 0) { + bss->tnc = atoi(pos); #endif /* EAP_SERVER_TNC */ #ifdef EAP_SERVER_PWD - } else if (os_strcmp(buf, "pwd_group") == 0) { - bss->pwd_group = atoi(pos); + } else if (os_strcmp(buf, "pwd_group") == 0) { + bss->pwd_group = atoi(pos); #endif /* EAP_SERVER_PWD */ #endif /* EAP_SERVER */ - } else if (os_strcmp(buf, "eap_message") == 0) { - char *term; - bss->eap_req_id_text = os_strdup(pos); - if (bss->eap_req_id_text == NULL) { - wpa_printf(MSG_ERROR, "Line %d: Failed to " - "allocate memory for " - "eap_req_id_text", line); - errors++; - return errors; - } - bss->eap_req_id_text_len = - os_strlen(bss->eap_req_id_text); - term = os_strstr(bss->eap_req_id_text, "\\0"); - if (term) { - *term++ = '\0'; - os_memmove(term, term + 1, - bss->eap_req_id_text_len - - (term - bss->eap_req_id_text) - 1); - bss->eap_req_id_text_len--; - } - } else if (os_strcmp(buf, "wep_key_len_broadcast") == 0) { - bss->default_wep_key_len = atoi(pos); - if (bss->default_wep_key_len > 13) { - wpa_printf(MSG_ERROR, "Line %d: invalid WEP " - "key len %lu (= %lu bits)", line, - (unsigned long) - bss->default_wep_key_len, - (unsigned long) - bss->default_wep_key_len * 8); - errors++; - } - } else if (os_strcmp(buf, "wep_key_len_unicast") == 0) { - bss->individual_wep_key_len = atoi(pos); - if (bss->individual_wep_key_len < 0 || - bss->individual_wep_key_len > 13) { - wpa_printf(MSG_ERROR, "Line %d: invalid WEP " - "key len %d (= %d bits)", line, - bss->individual_wep_key_len, - bss->individual_wep_key_len * 8); - errors++; - } - } else if (os_strcmp(buf, "wep_rekey_period") == 0) { - bss->wep_rekeying_period = atoi(pos); - if (bss->wep_rekeying_period < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "period %d", - line, bss->wep_rekeying_period); - errors++; - } - } else if (os_strcmp(buf, "eap_reauth_period") == 0) { - bss->eap_reauth_period = atoi(pos); - if (bss->eap_reauth_period < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "period %d", - line, bss->eap_reauth_period); - errors++; - } - } else if (os_strcmp(buf, "eapol_key_index_workaround") == 0) { - bss->eapol_key_index_workaround = atoi(pos); + } else if (os_strcmp(buf, "eap_message") == 0) { + char *term; + bss->eap_req_id_text = os_strdup(pos); + if (bss->eap_req_id_text == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Failed to allocate memory for eap_req_id_text", + line); + return 1; + } + bss->eap_req_id_text_len = os_strlen(bss->eap_req_id_text); + term = os_strstr(bss->eap_req_id_text, "\\0"); + if (term) { + *term++ = '\0'; + os_memmove(term, term + 1, + bss->eap_req_id_text_len - + (term - bss->eap_req_id_text) - 1); + bss->eap_req_id_text_len--; + } + } else if (os_strcmp(buf, "wep_key_len_broadcast") == 0) { + bss->default_wep_key_len = atoi(pos); + if (bss->default_wep_key_len > 13) { + wpa_printf(MSG_ERROR, "Line %d: invalid WEP key len %lu (= %lu bits)", + line, + (unsigned long) bss->default_wep_key_len, + (unsigned long) + bss->default_wep_key_len * 8); + return 1; + } + } else if (os_strcmp(buf, "wep_key_len_unicast") == 0) { + bss->individual_wep_key_len = atoi(pos); + if (bss->individual_wep_key_len < 0 || + bss->individual_wep_key_len > 13) { + wpa_printf(MSG_ERROR, "Line %d: invalid WEP key len %d (= %d bits)", + line, bss->individual_wep_key_len, + bss->individual_wep_key_len * 8); + return 1; + } + } else if (os_strcmp(buf, "wep_rekey_period") == 0) { + bss->wep_rekeying_period = atoi(pos); + if (bss->wep_rekeying_period < 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid period %d", + line, bss->wep_rekeying_period); + return 1; + } + } else if (os_strcmp(buf, "eap_reauth_period") == 0) { + bss->eap_reauth_period = atoi(pos); + if (bss->eap_reauth_period < 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid period %d", + line, bss->eap_reauth_period); + return 1; + } + } else if (os_strcmp(buf, "eapol_key_index_workaround") == 0) { + bss->eapol_key_index_workaround = atoi(pos); #ifdef CONFIG_IAPP - } else if (os_strcmp(buf, "iapp_interface") == 0) { - bss->ieee802_11f = 1; - os_strlcpy(bss->iapp_iface, pos, - sizeof(bss->iapp_iface)); + } else if (os_strcmp(buf, "iapp_interface") == 0) { + bss->ieee802_11f = 1; + os_strlcpy(bss->iapp_iface, pos, sizeof(bss->iapp_iface)); #endif /* CONFIG_IAPP */ - } else if (os_strcmp(buf, "own_ip_addr") == 0) { - if (hostapd_parse_ip_addr(pos, &bss->own_ip_addr)) { - wpa_printf(MSG_ERROR, "Line %d: invalid IP " - "address '%s'", line, pos); - errors++; - } - } else if (os_strcmp(buf, "nas_identifier") == 0) { - bss->nas_identifier = os_strdup(pos); + } else if (os_strcmp(buf, "own_ip_addr") == 0) { + if (hostapd_parse_ip_addr(pos, &bss->own_ip_addr)) { + wpa_printf(MSG_ERROR, + "Line %d: invalid IP address '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "nas_identifier") == 0) { + bss->nas_identifier = os_strdup(pos); #ifndef CONFIG_NO_RADIUS - } else if (os_strcmp(buf, "auth_server_addr") == 0) { - if (hostapd_config_read_radius_addr( - &bss->radius->auth_servers, - &bss->radius->num_auth_servers, pos, 1812, - &bss->radius->auth_server)) { - wpa_printf(MSG_ERROR, "Line %d: invalid IP " - "address '%s'", line, pos); - errors++; - } - } else if (bss->radius->auth_server && - os_strcmp(buf, "auth_server_port") == 0) { - bss->radius->auth_server->port = atoi(pos); - } else if (bss->radius->auth_server && - os_strcmp(buf, "auth_server_shared_secret") == 0) { - int len = os_strlen(pos); - if (len == 0) { - /* RFC 2865, Ch. 3 */ - wpa_printf(MSG_ERROR, "Line %d: empty shared " - "secret is not allowed.", line); - errors++; - } - bss->radius->auth_server->shared_secret = - (u8 *) os_strdup(pos); - bss->radius->auth_server->shared_secret_len = len; - } else if (os_strcmp(buf, "acct_server_addr") == 0) { - if (hostapd_config_read_radius_addr( - &bss->radius->acct_servers, - &bss->radius->num_acct_servers, pos, 1813, - &bss->radius->acct_server)) { - wpa_printf(MSG_ERROR, "Line %d: invalid IP " - "address '%s'", line, pos); - errors++; - } - } else if (bss->radius->acct_server && - os_strcmp(buf, "acct_server_port") == 0) { - bss->radius->acct_server->port = atoi(pos); - } else if (bss->radius->acct_server && - os_strcmp(buf, "acct_server_shared_secret") == 0) { - int len = os_strlen(pos); - if (len == 0) { - /* RFC 2865, Ch. 3 */ - wpa_printf(MSG_ERROR, "Line %d: empty shared " - "secret is not allowed.", line); - errors++; - } - bss->radius->acct_server->shared_secret = - (u8 *) os_strdup(pos); - bss->radius->acct_server->shared_secret_len = len; - } else if (os_strcmp(buf, "radius_retry_primary_interval") == - 0) { - bss->radius->retry_primary_interval = atoi(pos); - } else if (os_strcmp(buf, "radius_acct_interim_interval") == 0) - { - bss->acct_interim_interval = atoi(pos); - } else if (os_strcmp(buf, "radius_request_cui") == 0) { - bss->radius_request_cui = atoi(pos); - } else if (os_strcmp(buf, "radius_auth_req_attr") == 0) { - struct hostapd_radius_attr *attr, *a; - attr = hostapd_parse_radius_attr(pos); - if (attr == NULL) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "radius_auth_req_attr", line); - errors++; - } else if (bss->radius_auth_req_attr == NULL) { - bss->radius_auth_req_attr = attr; - } else { - a = bss->radius_auth_req_attr; - while (a->next) - a = a->next; - a->next = attr; - } - } else if (os_strcmp(buf, "radius_acct_req_attr") == 0) { - struct hostapd_radius_attr *attr, *a; - attr = hostapd_parse_radius_attr(pos); - if (attr == NULL) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "radius_acct_req_attr", line); - errors++; - } else if (bss->radius_acct_req_attr == NULL) { - bss->radius_acct_req_attr = attr; - } else { - a = bss->radius_acct_req_attr; - while (a->next) - a = a->next; - a->next = attr; - } - } else if (os_strcmp(buf, "radius_das_port") == 0) { - bss->radius_das_port = atoi(pos); - } else if (os_strcmp(buf, "radius_das_client") == 0) { - if (hostapd_parse_das_client(bss, pos) < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "DAS client", line); - errors++; - } - } else if (os_strcmp(buf, "radius_das_time_window") == 0) { - bss->radius_das_time_window = atoi(pos); - } else if (os_strcmp(buf, "radius_das_require_event_timestamp") - == 0) { - bss->radius_das_require_event_timestamp = atoi(pos); + } else if (os_strcmp(buf, "auth_server_addr") == 0) { + if (hostapd_config_read_radius_addr( + &bss->radius->auth_servers, + &bss->radius->num_auth_servers, pos, 1812, + &bss->radius->auth_server)) { + wpa_printf(MSG_ERROR, + "Line %d: invalid IP address '%s'", + line, pos); + return 1; + } + } else if (bss->radius->auth_server && + os_strcmp(buf, "auth_server_port") == 0) { + bss->radius->auth_server->port = atoi(pos); + } else if (bss->radius->auth_server && + os_strcmp(buf, "auth_server_shared_secret") == 0) { + int len = os_strlen(pos); + if (len == 0) { + /* RFC 2865, Ch. 3 */ + wpa_printf(MSG_ERROR, "Line %d: empty shared secret is not allowed", + line); + return 1; + } + bss->radius->auth_server->shared_secret = (u8 *) os_strdup(pos); + bss->radius->auth_server->shared_secret_len = len; + } else if (os_strcmp(buf, "acct_server_addr") == 0) { + if (hostapd_config_read_radius_addr( + &bss->radius->acct_servers, + &bss->radius->num_acct_servers, pos, 1813, + &bss->radius->acct_server)) { + wpa_printf(MSG_ERROR, + "Line %d: invalid IP address '%s'", + line, pos); + return 1; + } + } else if (bss->radius->acct_server && + os_strcmp(buf, "acct_server_port") == 0) { + bss->radius->acct_server->port = atoi(pos); + } else if (bss->radius->acct_server && + os_strcmp(buf, "acct_server_shared_secret") == 0) { + int len = os_strlen(pos); + if (len == 0) { + /* RFC 2865, Ch. 3 */ + wpa_printf(MSG_ERROR, "Line %d: empty shared secret is not allowed", + line); + return 1; + } + bss->radius->acct_server->shared_secret = (u8 *) os_strdup(pos); + bss->radius->acct_server->shared_secret_len = len; + } else if (os_strcmp(buf, "radius_retry_primary_interval") == 0) { + bss->radius->retry_primary_interval = atoi(pos); + } else if (os_strcmp(buf, "radius_acct_interim_interval") == 0) { + bss->acct_interim_interval = atoi(pos); + } else if (os_strcmp(buf, "radius_request_cui") == 0) { + bss->radius_request_cui = atoi(pos); + } else if (os_strcmp(buf, "radius_auth_req_attr") == 0) { + struct hostapd_radius_attr *attr, *a; + attr = hostapd_parse_radius_attr(pos); + if (attr == NULL) { + wpa_printf(MSG_ERROR, + "Line %d: invalid radius_auth_req_attr", + line); + return 1; + } else if (bss->radius_auth_req_attr == NULL) { + bss->radius_auth_req_attr = attr; + } else { + a = bss->radius_auth_req_attr; + while (a->next) + a = a->next; + a->next = attr; + } + } else if (os_strcmp(buf, "radius_acct_req_attr") == 0) { + struct hostapd_radius_attr *attr, *a; + attr = hostapd_parse_radius_attr(pos); + if (attr == NULL) { + wpa_printf(MSG_ERROR, + "Line %d: invalid radius_acct_req_attr", + line); + return 1; + } else if (bss->radius_acct_req_attr == NULL) { + bss->radius_acct_req_attr = attr; + } else { + a = bss->radius_acct_req_attr; + while (a->next) + a = a->next; + a->next = attr; + } + } else if (os_strcmp(buf, "radius_das_port") == 0) { + bss->radius_das_port = atoi(pos); + } else if (os_strcmp(buf, "radius_das_client") == 0) { + if (hostapd_parse_das_client(bss, pos) < 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid DAS client", + line); + return 1; + } + } else if (os_strcmp(buf, "radius_das_time_window") == 0) { + bss->radius_das_time_window = atoi(pos); + } else if (os_strcmp(buf, "radius_das_require_event_timestamp") == 0) { + bss->radius_das_require_event_timestamp = atoi(pos); #endif /* CONFIG_NO_RADIUS */ - } else if (os_strcmp(buf, "auth_algs") == 0) { - bss->auth_algs = atoi(pos); - if (bss->auth_algs == 0) { - wpa_printf(MSG_ERROR, "Line %d: no " - "authentication algorithms allowed", - line); - errors++; - } - } else if (os_strcmp(buf, "max_num_sta") == 0) { - bss->max_num_sta = atoi(pos); - if (bss->max_num_sta < 0 || - bss->max_num_sta > MAX_STA_COUNT) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "max_num_sta=%d; allowed range " - "0..%d", line, bss->max_num_sta, - MAX_STA_COUNT); - errors++; - } - } else if (os_strcmp(buf, "wpa") == 0) { - bss->wpa = atoi(pos); - } else if (os_strcmp(buf, "wpa_group_rekey") == 0) { - bss->wpa_group_rekey = atoi(pos); - } else if (os_strcmp(buf, "wpa_strict_rekey") == 0) { - bss->wpa_strict_rekey = atoi(pos); - } else if (os_strcmp(buf, "wpa_gmk_rekey") == 0) { - bss->wpa_gmk_rekey = atoi(pos); - } else if (os_strcmp(buf, "wpa_ptk_rekey") == 0) { - bss->wpa_ptk_rekey = atoi(pos); - } else if (os_strcmp(buf, "wpa_passphrase") == 0) { - int len = os_strlen(pos); - if (len < 8 || len > 63) { - wpa_printf(MSG_ERROR, "Line %d: invalid WPA " - "passphrase length %d (expected " - "8..63)", line, len); - errors++; - } else { - os_free(bss->ssid.wpa_passphrase); - bss->ssid.wpa_passphrase = os_strdup(pos); - if (bss->ssid.wpa_passphrase) { - os_free(bss->ssid.wpa_psk); - bss->ssid.wpa_psk = NULL; - bss->ssid.wpa_passphrase_set = 1; - } - } - } else if (os_strcmp(buf, "wpa_psk") == 0) { + } else if (os_strcmp(buf, "auth_algs") == 0) { + bss->auth_algs = atoi(pos); + if (bss->auth_algs == 0) { + wpa_printf(MSG_ERROR, "Line %d: no authentication algorithms allowed", + line); + return 1; + } + } else if (os_strcmp(buf, "max_num_sta") == 0) { + bss->max_num_sta = atoi(pos); + if (bss->max_num_sta < 0 || + bss->max_num_sta > MAX_STA_COUNT) { + wpa_printf(MSG_ERROR, "Line %d: Invalid max_num_sta=%d; allowed range 0..%d", + line, bss->max_num_sta, MAX_STA_COUNT); + return 1; + } + } else if (os_strcmp(buf, "wpa") == 0) { + bss->wpa = atoi(pos); + } else if (os_strcmp(buf, "wpa_group_rekey") == 0) { + bss->wpa_group_rekey = atoi(pos); + } else if (os_strcmp(buf, "wpa_strict_rekey") == 0) { + bss->wpa_strict_rekey = atoi(pos); + } else if (os_strcmp(buf, "wpa_gmk_rekey") == 0) { + bss->wpa_gmk_rekey = atoi(pos); + } else if (os_strcmp(buf, "wpa_ptk_rekey") == 0) { + bss->wpa_ptk_rekey = atoi(pos); + } else if (os_strcmp(buf, "wpa_passphrase") == 0) { + int len = os_strlen(pos); + if (len < 8 || len > 63) { + wpa_printf(MSG_ERROR, "Line %d: invalid WPA passphrase length %d (expected 8..63)", + line, len); + return 1; + } + os_free(bss->ssid.wpa_passphrase); + bss->ssid.wpa_passphrase = os_strdup(pos); + if (bss->ssid.wpa_passphrase) { os_free(bss->ssid.wpa_psk); - bss->ssid.wpa_psk = - os_zalloc(sizeof(struct hostapd_wpa_psk)); - if (bss->ssid.wpa_psk == NULL) - errors++; - else if (hexstr2bin(pos, bss->ssid.wpa_psk->psk, - PMK_LEN) || - pos[PMK_LEN * 2] != '\0') { - wpa_printf(MSG_ERROR, "Line %d: Invalid PSK " - "'%s'.", line, pos); - errors++; - } else { - bss->ssid.wpa_psk->group = 1; - os_free(bss->ssid.wpa_passphrase); - bss->ssid.wpa_passphrase = NULL; - bss->ssid.wpa_psk_set = 1; - } - } else if (os_strcmp(buf, "wpa_psk_file") == 0) { - os_free(bss->ssid.wpa_psk_file); - bss->ssid.wpa_psk_file = os_strdup(pos); - if (!bss->ssid.wpa_psk_file) { - wpa_printf(MSG_ERROR, "Line %d: allocation " - "failed", line); - errors++; - } - } else if (os_strcmp(buf, "wpa_key_mgmt") == 0) { - bss->wpa_key_mgmt = - hostapd_config_parse_key_mgmt(line, pos); - if (bss->wpa_key_mgmt == -1) - errors++; - } else if (os_strcmp(buf, "wpa_psk_radius") == 0) { - bss->wpa_psk_radius = atoi(pos); - if (bss->wpa_psk_radius != PSK_RADIUS_IGNORED && - bss->wpa_psk_radius != PSK_RADIUS_ACCEPTED && - bss->wpa_psk_radius != PSK_RADIUS_REQUIRED) { - wpa_printf(MSG_ERROR, "Line %d: unknown " - "wpa_psk_radius %d", - line, bss->wpa_psk_radius); - errors++; - } - } else if (os_strcmp(buf, "wpa_pairwise") == 0) { - bss->wpa_pairwise = - hostapd_config_parse_cipher(line, pos); - if (bss->wpa_pairwise == -1 || - bss->wpa_pairwise == 0) - errors++; - else if (bss->wpa_pairwise & - (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | - WPA_CIPHER_WEP104)) { - wpa_printf(MSG_ERROR, "Line %d: unsupported " - "pairwise cipher suite '%s'", - bss->wpa_pairwise, pos); - errors++; - } - } else if (os_strcmp(buf, "rsn_pairwise") == 0) { - bss->rsn_pairwise = - hostapd_config_parse_cipher(line, pos); - if (bss->rsn_pairwise == -1 || - bss->rsn_pairwise == 0) - errors++; - else if (bss->rsn_pairwise & - (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | - WPA_CIPHER_WEP104)) { - wpa_printf(MSG_ERROR, "Line %d: unsupported " - "pairwise cipher suite '%s'", - bss->rsn_pairwise, pos); - errors++; - } + bss->ssid.wpa_psk = NULL; + bss->ssid.wpa_passphrase_set = 1; + } + } else if (os_strcmp(buf, "wpa_psk") == 0) { + os_free(bss->ssid.wpa_psk); + bss->ssid.wpa_psk = os_zalloc(sizeof(struct hostapd_wpa_psk)); + if (bss->ssid.wpa_psk == NULL) + return 1; + if (hexstr2bin(pos, bss->ssid.wpa_psk->psk, PMK_LEN) || + pos[PMK_LEN * 2] != '\0') { + wpa_printf(MSG_ERROR, "Line %d: Invalid PSK '%s'.", + line, pos); + os_free(bss->ssid.wpa_psk); + bss->ssid.wpa_psk = NULL; + return 1; + } + bss->ssid.wpa_psk->group = 1; + os_free(bss->ssid.wpa_passphrase); + bss->ssid.wpa_passphrase = NULL; + bss->ssid.wpa_psk_set = 1; + } else if (os_strcmp(buf, "wpa_psk_file") == 0) { + os_free(bss->ssid.wpa_psk_file); + bss->ssid.wpa_psk_file = os_strdup(pos); + if (!bss->ssid.wpa_psk_file) { + wpa_printf(MSG_ERROR, "Line %d: allocation failed", + line); + return 1; + } + } else if (os_strcmp(buf, "wpa_key_mgmt") == 0) { + bss->wpa_key_mgmt = hostapd_config_parse_key_mgmt(line, pos); + if (bss->wpa_key_mgmt == -1) + return 1; + } else if (os_strcmp(buf, "wpa_psk_radius") == 0) { + bss->wpa_psk_radius = atoi(pos); + if (bss->wpa_psk_radius != PSK_RADIUS_IGNORED && + bss->wpa_psk_radius != PSK_RADIUS_ACCEPTED && + bss->wpa_psk_radius != PSK_RADIUS_REQUIRED) { + wpa_printf(MSG_ERROR, + "Line %d: unknown wpa_psk_radius %d", + line, bss->wpa_psk_radius); + return 1; + } + } else if (os_strcmp(buf, "wpa_pairwise") == 0) { + bss->wpa_pairwise = hostapd_config_parse_cipher(line, pos); + if (bss->wpa_pairwise == -1 || bss->wpa_pairwise == 0) + return 1; + if (bss->wpa_pairwise & + (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104)) { + wpa_printf(MSG_ERROR, "Line %d: unsupported pairwise cipher suite '%s'", + bss->wpa_pairwise, pos); + return 1; + } + } else if (os_strcmp(buf, "rsn_pairwise") == 0) { + bss->rsn_pairwise = hostapd_config_parse_cipher(line, pos); + if (bss->rsn_pairwise == -1 || bss->rsn_pairwise == 0) + return 1; + if (bss->rsn_pairwise & + (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104)) { + wpa_printf(MSG_ERROR, "Line %d: unsupported pairwise cipher suite '%s'", + bss->rsn_pairwise, pos); + return 1; + } #ifdef CONFIG_RSN_PREAUTH - } else if (os_strcmp(buf, "rsn_preauth") == 0) { - bss->rsn_preauth = atoi(pos); - } else if (os_strcmp(buf, "rsn_preauth_interfaces") == 0) { - bss->rsn_preauth_interfaces = os_strdup(pos); + } else if (os_strcmp(buf, "rsn_preauth") == 0) { + bss->rsn_preauth = atoi(pos); + } else if (os_strcmp(buf, "rsn_preauth_interfaces") == 0) { + bss->rsn_preauth_interfaces = os_strdup(pos); #endif /* CONFIG_RSN_PREAUTH */ #ifdef CONFIG_PEERKEY - } else if (os_strcmp(buf, "peerkey") == 0) { - bss->peerkey = atoi(pos); + } else if (os_strcmp(buf, "peerkey") == 0) { + bss->peerkey = atoi(pos); #endif /* CONFIG_PEERKEY */ #ifdef CONFIG_IEEE80211R - } else if (os_strcmp(buf, "mobility_domain") == 0) { - if (os_strlen(pos) != 2 * MOBILITY_DOMAIN_ID_LEN || - hexstr2bin(pos, bss->mobility_domain, - MOBILITY_DOMAIN_ID_LEN) != 0) { - wpa_printf(MSG_DEBUG, "Line %d: Invalid " - "mobility_domain '%s'", line, pos); - errors++; - return errors; - } - } else if (os_strcmp(buf, "r1_key_holder") == 0) { - if (os_strlen(pos) != 2 * FT_R1KH_ID_LEN || - hexstr2bin(pos, bss->r1_key_holder, - FT_R1KH_ID_LEN) != 0) { - wpa_printf(MSG_DEBUG, "Line %d: Invalid " - "r1_key_holder '%s'", line, pos); - errors++; - return errors; - } - } else if (os_strcmp(buf, "r0_key_lifetime") == 0) { - bss->r0_key_lifetime = atoi(pos); - } else if (os_strcmp(buf, "reassociation_deadline") == 0) { - bss->reassociation_deadline = atoi(pos); - } else if (os_strcmp(buf, "r0kh") == 0) { - if (add_r0kh(bss, pos) < 0) { - wpa_printf(MSG_DEBUG, "Line %d: Invalid " - "r0kh '%s'", line, pos); - errors++; - return errors; - } - } else if (os_strcmp(buf, "r1kh") == 0) { - if (add_r1kh(bss, pos) < 0) { - wpa_printf(MSG_DEBUG, "Line %d: Invalid " - "r1kh '%s'", line, pos); - errors++; - return errors; - } - } else if (os_strcmp(buf, "pmk_r1_push") == 0) { - bss->pmk_r1_push = atoi(pos); - } else if (os_strcmp(buf, "ft_over_ds") == 0) { - bss->ft_over_ds = atoi(pos); + } else if (os_strcmp(buf, "mobility_domain") == 0) { + if (os_strlen(pos) != 2 * MOBILITY_DOMAIN_ID_LEN || + hexstr2bin(pos, bss->mobility_domain, + MOBILITY_DOMAIN_ID_LEN) != 0) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid mobility_domain '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "r1_key_holder") == 0) { + if (os_strlen(pos) != 2 * FT_R1KH_ID_LEN || + hexstr2bin(pos, bss->r1_key_holder, FT_R1KH_ID_LEN) != 0) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid r1_key_holder '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "r0_key_lifetime") == 0) { + bss->r0_key_lifetime = atoi(pos); + } else if (os_strcmp(buf, "reassociation_deadline") == 0) { + bss->reassociation_deadline = atoi(pos); + } else if (os_strcmp(buf, "r0kh") == 0) { + if (add_r0kh(bss, pos) < 0) { + wpa_printf(MSG_DEBUG, "Line %d: Invalid r0kh '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "r1kh") == 0) { + if (add_r1kh(bss, pos) < 0) { + wpa_printf(MSG_DEBUG, "Line %d: Invalid r1kh '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "pmk_r1_push") == 0) { + bss->pmk_r1_push = atoi(pos); + } else if (os_strcmp(buf, "ft_over_ds") == 0) { + bss->ft_over_ds = atoi(pos); #endif /* CONFIG_IEEE80211R */ #ifndef CONFIG_NO_CTRL_IFACE - } else if (os_strcmp(buf, "ctrl_interface") == 0) { - os_free(bss->ctrl_interface); - bss->ctrl_interface = os_strdup(pos); - } else if (os_strcmp(buf, "ctrl_interface_group") == 0) { + } else if (os_strcmp(buf, "ctrl_interface") == 0) { + os_free(bss->ctrl_interface); + bss->ctrl_interface = os_strdup(pos); + } else if (os_strcmp(buf, "ctrl_interface_group") == 0) { #ifndef CONFIG_NATIVE_WINDOWS - struct group *grp; - char *endp; - const char *group = pos; - - grp = getgrnam(group); - if (grp) { - bss->ctrl_interface_gid = grp->gr_gid; - bss->ctrl_interface_gid_set = 1; - wpa_printf(MSG_DEBUG, "ctrl_interface_group=%d" - " (from group name '%s')", - bss->ctrl_interface_gid, group); - return errors; - } + struct group *grp; + char *endp; + const char *group = pos; - /* Group name not found - try to parse this as gid */ - bss->ctrl_interface_gid = strtol(group, &endp, 10); - if (*group == '\0' || *endp != '\0') { - wpa_printf(MSG_DEBUG, "Line %d: Invalid group " - "'%s'", line, group); - errors++; - return errors; - } + grp = getgrnam(group); + if (grp) { + bss->ctrl_interface_gid = grp->gr_gid; bss->ctrl_interface_gid_set = 1; - wpa_printf(MSG_DEBUG, "ctrl_interface_group=%d", - bss->ctrl_interface_gid); + wpa_printf(MSG_DEBUG, "ctrl_interface_group=%d (from group name '%s')", + bss->ctrl_interface_gid, group); + return 0; + } + + /* Group name not found - try to parse this as gid */ + bss->ctrl_interface_gid = strtol(group, &endp, 10); + if (*group == '\0' || *endp != '\0') { + wpa_printf(MSG_DEBUG, "Line %d: Invalid group '%s'", + line, group); + return 1; + } + bss->ctrl_interface_gid_set = 1; + wpa_printf(MSG_DEBUG, "ctrl_interface_group=%d", + bss->ctrl_interface_gid); #endif /* CONFIG_NATIVE_WINDOWS */ #endif /* CONFIG_NO_CTRL_IFACE */ #ifdef RADIUS_SERVER - } else if (os_strcmp(buf, "radius_server_clients") == 0) { - os_free(bss->radius_server_clients); - bss->radius_server_clients = os_strdup(pos); - } else if (os_strcmp(buf, "radius_server_auth_port") == 0) { - bss->radius_server_auth_port = atoi(pos); - } else if (os_strcmp(buf, "radius_server_acct_port") == 0) { - bss->radius_server_acct_port = atoi(pos); - } else if (os_strcmp(buf, "radius_server_ipv6") == 0) { - bss->radius_server_ipv6 = atoi(pos); + } else if (os_strcmp(buf, "radius_server_clients") == 0) { + os_free(bss->radius_server_clients); + bss->radius_server_clients = os_strdup(pos); + } else if (os_strcmp(buf, "radius_server_auth_port") == 0) { + bss->radius_server_auth_port = atoi(pos); + } else if (os_strcmp(buf, "radius_server_acct_port") == 0) { + bss->radius_server_acct_port = atoi(pos); + } else if (os_strcmp(buf, "radius_server_ipv6") == 0) { + bss->radius_server_ipv6 = atoi(pos); #endif /* RADIUS_SERVER */ - } else if (os_strcmp(buf, "test_socket") == 0) { - os_free(bss->test_socket); - bss->test_socket = os_strdup(pos); - } else if (os_strcmp(buf, "use_pae_group_addr") == 0) { - bss->use_pae_group_addr = atoi(pos); - } else if (os_strcmp(buf, "hw_mode") == 0) { - if (os_strcmp(pos, "a") == 0) - conf->hw_mode = HOSTAPD_MODE_IEEE80211A; - else if (os_strcmp(pos, "b") == 0) - conf->hw_mode = HOSTAPD_MODE_IEEE80211B; - else if (os_strcmp(pos, "g") == 0) - conf->hw_mode = HOSTAPD_MODE_IEEE80211G; - else if (os_strcmp(pos, "ad") == 0) - conf->hw_mode = HOSTAPD_MODE_IEEE80211AD; - else { - wpa_printf(MSG_ERROR, "Line %d: unknown " - "hw_mode '%s'", line, pos); - errors++; - } - } else if (os_strcmp(buf, "wps_rf_bands") == 0) { - if (os_strcmp(pos, "a") == 0) - bss->wps_rf_bands = WPS_RF_50GHZ; - else if (os_strcmp(pos, "g") == 0 || - os_strcmp(pos, "b") == 0) - bss->wps_rf_bands = WPS_RF_24GHZ; - else if (os_strcmp(pos, "ag") == 0 || - os_strcmp(pos, "ga") == 0) - bss->wps_rf_bands = - WPS_RF_24GHZ | WPS_RF_50GHZ; - else { - wpa_printf(MSG_ERROR, "Line %d: unknown " - "wps_rf_band '%s'", line, pos); - errors++; - } - } else if (os_strcmp(buf, "channel") == 0) { - if (os_strcmp(pos, "acs_survey") == 0) { + } else if (os_strcmp(buf, "test_socket") == 0) { + os_free(bss->test_socket); + bss->test_socket = os_strdup(pos); + } else if (os_strcmp(buf, "use_pae_group_addr") == 0) { + bss->use_pae_group_addr = atoi(pos); + } else if (os_strcmp(buf, "hw_mode") == 0) { + if (os_strcmp(pos, "a") == 0) + conf->hw_mode = HOSTAPD_MODE_IEEE80211A; + else if (os_strcmp(pos, "b") == 0) + conf->hw_mode = HOSTAPD_MODE_IEEE80211B; + else if (os_strcmp(pos, "g") == 0) + conf->hw_mode = HOSTAPD_MODE_IEEE80211G; + else if (os_strcmp(pos, "ad") == 0) + conf->hw_mode = HOSTAPD_MODE_IEEE80211AD; + else { + wpa_printf(MSG_ERROR, "Line %d: unknown hw_mode '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "wps_rf_bands") == 0) { + if (os_strcmp(pos, "a") == 0) + bss->wps_rf_bands = WPS_RF_50GHZ; + else if (os_strcmp(pos, "g") == 0 || + os_strcmp(pos, "b") == 0) + bss->wps_rf_bands = WPS_RF_24GHZ; + else if (os_strcmp(pos, "ag") == 0 || + os_strcmp(pos, "ga") == 0) + bss->wps_rf_bands = WPS_RF_24GHZ | WPS_RF_50GHZ; + else { + wpa_printf(MSG_ERROR, + "Line %d: unknown wps_rf_band '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "channel") == 0) { + if (os_strcmp(pos, "acs_survey") == 0) { #ifndef CONFIG_ACS - wpa_printf(MSG_ERROR, "Line %d: tries to enable ACS but CONFIG_ACS disabled", - line); - errors++; + wpa_printf(MSG_ERROR, "Line %d: tries to enable ACS but CONFIG_ACS disabled", + line); + return 1; #endif /* CONFIG_ACS */ - conf->channel = 0; - } else - conf->channel = atoi(pos); - } else if (os_strcmp(buf, "chanlist") == 0) { - if (hostapd_parse_intlist(&conf->chanlist, pos)) { - wpa_printf(MSG_ERROR, - "Line %d: invalid channel list", - line); - errors++; - } - } else if (os_strcmp(buf, "beacon_int") == 0) { - int val = atoi(pos); - /* MIB defines range as 1..65535, but very small values - * cause problems with the current implementation. - * Since it is unlikely that this small numbers are - * useful in real life scenarios, do not allow beacon - * period to be set below 15 TU. */ - if (val < 15 || val > 65535) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "beacon_int %d (expected " - "15..65535)", line, val); - errors++; - } else - conf->beacon_int = val; + conf->channel = 0; + } else + conf->channel = atoi(pos); + } else if (os_strcmp(buf, "chanlist") == 0) { + if (hostapd_parse_intlist(&conf->chanlist, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid channel list", + line); + return 1; + } + } else if (os_strcmp(buf, "beacon_int") == 0) { + int val = atoi(pos); + /* MIB defines range as 1..65535, but very small values + * cause problems with the current implementation. + * Since it is unlikely that this small numbers are + * useful in real life scenarios, do not allow beacon + * period to be set below 15 TU. */ + if (val < 15 || val > 65535) { + wpa_printf(MSG_ERROR, "Line %d: invalid beacon_int %d (expected 15..65535)", + line, val); + return 1; + } + conf->beacon_int = val; #ifdef CONFIG_ACS - } else if (os_strcmp(buf, "acs_num_scans") == 0) { - int val = atoi(pos); - if (val <= 0 || val > 100) { - wpa_printf(MSG_ERROR, "Line %d: invalid acs_num_scans %d (expected 1..100)", - line, val); - errors++; - } else - conf->acs_num_scans = val; + } else if (os_strcmp(buf, "acs_num_scans") == 0) { + int val = atoi(pos); + if (val <= 0 || val > 100) { + wpa_printf(MSG_ERROR, "Line %d: invalid acs_num_scans %d (expected 1..100)", + line, val); + return 1; + } + conf->acs_num_scans = val; #endif /* CONFIG_ACS */ - } else if (os_strcmp(buf, "dtim_period") == 0) { - bss->dtim_period = atoi(pos); - if (bss->dtim_period < 1 || bss->dtim_period > 255) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "dtim_period %d", - line, bss->dtim_period); - errors++; - } - } else if (os_strcmp(buf, "rts_threshold") == 0) { - conf->rts_threshold = atoi(pos); - if (conf->rts_threshold < 0 || - conf->rts_threshold > 2347) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "rts_threshold %d", - line, conf->rts_threshold); - errors++; - } - } else if (os_strcmp(buf, "fragm_threshold") == 0) { - conf->fragm_threshold = atoi(pos); - if (conf->fragm_threshold < 256 || - conf->fragm_threshold > 2346) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "fragm_threshold %d", - line, conf->fragm_threshold); - errors++; - } - } else if (os_strcmp(buf, "send_probe_response") == 0) { - int val = atoi(pos); - if (val != 0 && val != 1) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "send_probe_response %d (expected " - "0 or 1)", line, val); - } else - conf->send_probe_response = val; - } else if (os_strcmp(buf, "supported_rates") == 0) { - if (hostapd_parse_intlist(&conf->supported_rates, pos)) - { - wpa_printf(MSG_ERROR, "Line %d: invalid rate " - "list", line); - errors++; - } - } else if (os_strcmp(buf, "basic_rates") == 0) { - if (hostapd_parse_intlist(&conf->basic_rates, pos)) { - wpa_printf(MSG_ERROR, "Line %d: invalid rate " - "list", line); - errors++; - } - } else if (os_strcmp(buf, "preamble") == 0) { - if (atoi(pos)) - conf->preamble = SHORT_PREAMBLE; - else - conf->preamble = LONG_PREAMBLE; - } else if (os_strcmp(buf, "ignore_broadcast_ssid") == 0) { - bss->ignore_broadcast_ssid = atoi(pos); - } else if (os_strcmp(buf, "wep_default_key") == 0) { - bss->ssid.wep.idx = atoi(pos); - if (bss->ssid.wep.idx > 3) { - wpa_printf(MSG_ERROR, "Invalid " - "wep_default_key index %d", - bss->ssid.wep.idx); - errors++; - } - } else if (os_strcmp(buf, "wep_key0") == 0 || - os_strcmp(buf, "wep_key1") == 0 || - os_strcmp(buf, "wep_key2") == 0 || - os_strcmp(buf, "wep_key3") == 0) { - if (hostapd_config_read_wep(&bss->ssid.wep, - buf[7] - '0', pos)) { - wpa_printf(MSG_ERROR, "Line %d: invalid WEP " - "key '%s'", line, buf); - errors++; - } + } else if (os_strcmp(buf, "dtim_period") == 0) { + bss->dtim_period = atoi(pos); + if (bss->dtim_period < 1 || bss->dtim_period > 255) { + wpa_printf(MSG_ERROR, "Line %d: invalid dtim_period %d", + line, bss->dtim_period); + return 1; + } + } else if (os_strcmp(buf, "rts_threshold") == 0) { + conf->rts_threshold = atoi(pos); + if (conf->rts_threshold < 0 || conf->rts_threshold > 2347) { + wpa_printf(MSG_ERROR, + "Line %d: invalid rts_threshold %d", + line, conf->rts_threshold); + return 1; + } + } else if (os_strcmp(buf, "fragm_threshold") == 0) { + conf->fragm_threshold = atoi(pos); + if (conf->fragm_threshold < 256 || + conf->fragm_threshold > 2346) { + wpa_printf(MSG_ERROR, + "Line %d: invalid fragm_threshold %d", + line, conf->fragm_threshold); + return 1; + } + } else if (os_strcmp(buf, "send_probe_response") == 0) { + int val = atoi(pos); + if (val != 0 && val != 1) { + wpa_printf(MSG_ERROR, "Line %d: invalid send_probe_response %d (expected 0 or 1)", + line, val); + return 1; + } + conf->send_probe_response = val; + } else if (os_strcmp(buf, "supported_rates") == 0) { + if (hostapd_parse_intlist(&conf->supported_rates, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid rate list", + line); + return 1; + } + } else if (os_strcmp(buf, "basic_rates") == 0) { + if (hostapd_parse_intlist(&conf->basic_rates, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid rate list", + line); + return 1; + } + } else if (os_strcmp(buf, "preamble") == 0) { + if (atoi(pos)) + conf->preamble = SHORT_PREAMBLE; + else + conf->preamble = LONG_PREAMBLE; + } else if (os_strcmp(buf, "ignore_broadcast_ssid") == 0) { + bss->ignore_broadcast_ssid = atoi(pos); + } else if (os_strcmp(buf, "wep_default_key") == 0) { + bss->ssid.wep.idx = atoi(pos); + if (bss->ssid.wep.idx > 3) { + wpa_printf(MSG_ERROR, + "Invalid wep_default_key index %d", + bss->ssid.wep.idx); + return 1; + } + } else if (os_strcmp(buf, "wep_key0") == 0 || + os_strcmp(buf, "wep_key1") == 0 || + os_strcmp(buf, "wep_key2") == 0 || + os_strcmp(buf, "wep_key3") == 0) { + if (hostapd_config_read_wep(&bss->ssid.wep, + buf[7] - '0', pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid WEP key '%s'", + line, buf); + return 1; + } #ifndef CONFIG_NO_VLAN - } else if (os_strcmp(buf, "dynamic_vlan") == 0) { - bss->ssid.dynamic_vlan = atoi(pos); - } else if (os_strcmp(buf, "vlan_file") == 0) { - if (hostapd_config_read_vlan_file(bss, pos)) { - wpa_printf(MSG_ERROR, "Line %d: failed to " - "read VLAN file '%s'", line, pos); - errors++; - } - } else if (os_strcmp(buf, "vlan_naming") == 0) { - bss->ssid.vlan_naming = atoi(pos); - if (bss->ssid.vlan_naming >= DYNAMIC_VLAN_NAMING_END || - bss->ssid.vlan_naming < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "naming scheme %d", line, - bss->ssid.vlan_naming); - errors++; - } + } else if (os_strcmp(buf, "dynamic_vlan") == 0) { + bss->ssid.dynamic_vlan = atoi(pos); + } else if (os_strcmp(buf, "vlan_file") == 0) { + if (hostapd_config_read_vlan_file(bss, pos)) { + wpa_printf(MSG_ERROR, "Line %d: failed to read VLAN file '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "vlan_naming") == 0) { + bss->ssid.vlan_naming = atoi(pos); + if (bss->ssid.vlan_naming >= DYNAMIC_VLAN_NAMING_END || + bss->ssid.vlan_naming < 0) { + wpa_printf(MSG_ERROR, + "Line %d: invalid naming scheme %d", + line, bss->ssid.vlan_naming); + return 1; + } #ifdef CONFIG_FULL_DYNAMIC_VLAN - } else if (os_strcmp(buf, "vlan_tagged_interface") == 0) { - bss->ssid.vlan_tagged_interface = os_strdup(pos); + } else if (os_strcmp(buf, "vlan_tagged_interface") == 0) { + bss->ssid.vlan_tagged_interface = os_strdup(pos); #endif /* CONFIG_FULL_DYNAMIC_VLAN */ #endif /* CONFIG_NO_VLAN */ - } else if (os_strcmp(buf, "ap_table_max_size") == 0) { - conf->ap_table_max_size = atoi(pos); - } else if (os_strcmp(buf, "ap_table_expiration_time") == 0) { - conf->ap_table_expiration_time = atoi(pos); - } else if (os_strncmp(buf, "tx_queue_", 9) == 0) { - if (hostapd_config_tx_queue(conf, buf, pos)) { - wpa_printf(MSG_ERROR, "Line %d: invalid TX " - "queue item", line); - errors++; - } - } else if (os_strcmp(buf, "wme_enabled") == 0 || - os_strcmp(buf, "wmm_enabled") == 0) { - bss->wmm_enabled = atoi(pos); - } else if (os_strcmp(buf, "uapsd_advertisement_enabled") == 0) { - bss->wmm_uapsd = atoi(pos); - } else if (os_strncmp(buf, "wme_ac_", 7) == 0 || - os_strncmp(buf, "wmm_ac_", 7) == 0) { - if (hostapd_config_wmm_ac(conf->wmm_ac_params, buf, - pos)) { - wpa_printf(MSG_ERROR, "Line %d: invalid WMM " - "ac item", line); - errors++; - } - } else if (os_strcmp(buf, "bss") == 0) { - if (hostapd_config_bss(conf, pos)) { - wpa_printf(MSG_ERROR, "Line %d: invalid bss " - "item", line); - errors++; - } - } else if (os_strcmp(buf, "bssid") == 0) { - if (hwaddr_aton(pos, bss->bssid)) { - wpa_printf(MSG_ERROR, "Line %d: invalid bssid " - "item", line); - errors++; - } + } else if (os_strcmp(buf, "ap_table_max_size") == 0) { + conf->ap_table_max_size = atoi(pos); + } else if (os_strcmp(buf, "ap_table_expiration_time") == 0) { + conf->ap_table_expiration_time = atoi(pos); + } else if (os_strncmp(buf, "tx_queue_", 9) == 0) { + if (hostapd_config_tx_queue(conf, buf, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid TX queue item", + line); + return 1; + } + } else if (os_strcmp(buf, "wme_enabled") == 0 || + os_strcmp(buf, "wmm_enabled") == 0) { + bss->wmm_enabled = atoi(pos); + } else if (os_strcmp(buf, "uapsd_advertisement_enabled") == 0) { + bss->wmm_uapsd = atoi(pos); + } else if (os_strncmp(buf, "wme_ac_", 7) == 0 || + os_strncmp(buf, "wmm_ac_", 7) == 0) { + if (hostapd_config_wmm_ac(conf->wmm_ac_params, buf, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid WMM ac item", + line); + return 1; + } + } else if (os_strcmp(buf, "bss") == 0) { + if (hostapd_config_bss(conf, pos)) { + wpa_printf(MSG_ERROR, "Line %d: invalid bss item", + line); + return 1; + } + } else if (os_strcmp(buf, "bssid") == 0) { + if (hwaddr_aton(pos, bss->bssid)) { + wpa_printf(MSG_ERROR, "Line %d: invalid bssid item", + line); + return 1; + } #ifdef CONFIG_IEEE80211W - } else if (os_strcmp(buf, "ieee80211w") == 0) { - bss->ieee80211w = atoi(pos); - } else if (os_strcmp(buf, "assoc_sa_query_max_timeout") == 0) { - bss->assoc_sa_query_max_timeout = atoi(pos); - if (bss->assoc_sa_query_max_timeout == 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "assoc_sa_query_max_timeout", line); - errors++; - } - } else if (os_strcmp(buf, "assoc_sa_query_retry_timeout") == 0) - { - bss->assoc_sa_query_retry_timeout = atoi(pos); - if (bss->assoc_sa_query_retry_timeout == 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "assoc_sa_query_retry_timeout", - line); - errors++; - } + } else if (os_strcmp(buf, "ieee80211w") == 0) { + bss->ieee80211w = atoi(pos); + } else if (os_strcmp(buf, "assoc_sa_query_max_timeout") == 0) { + bss->assoc_sa_query_max_timeout = atoi(pos); + if (bss->assoc_sa_query_max_timeout == 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid assoc_sa_query_max_timeout", + line); + return 1; + } + } else if (os_strcmp(buf, "assoc_sa_query_retry_timeout") == 0) { + bss->assoc_sa_query_retry_timeout = atoi(pos); + if (bss->assoc_sa_query_retry_timeout == 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid assoc_sa_query_retry_timeout", + line); + return 1; + } #endif /* CONFIG_IEEE80211W */ #ifdef CONFIG_IEEE80211N - } else if (os_strcmp(buf, "ieee80211n") == 0) { - conf->ieee80211n = atoi(pos); - } else if (os_strcmp(buf, "ht_capab") == 0) { - if (hostapd_config_ht_capab(conf, pos) < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "ht_capab", line); - errors++; - } - } else if (os_strcmp(buf, "require_ht") == 0) { - conf->require_ht = atoi(pos); - } else if (os_strcmp(buf, "obss_interval") == 0) { - conf->obss_interval = atoi(pos); + } else if (os_strcmp(buf, "ieee80211n") == 0) { + conf->ieee80211n = atoi(pos); + } else if (os_strcmp(buf, "ht_capab") == 0) { + if (hostapd_config_ht_capab(conf, pos) < 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid ht_capab", + line); + return 1; + } + } else if (os_strcmp(buf, "require_ht") == 0) { + conf->require_ht = atoi(pos); + } else if (os_strcmp(buf, "obss_interval") == 0) { + conf->obss_interval = atoi(pos); #endif /* CONFIG_IEEE80211N */ #ifdef CONFIG_IEEE80211AC - } else if (os_strcmp(buf, "ieee80211ac") == 0) { - conf->ieee80211ac = atoi(pos); - } else if (os_strcmp(buf, "vht_capab") == 0) { - if (hostapd_config_vht_capab(conf, pos) < 0) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "vht_capab", line); - errors++; - } - } else if (os_strcmp(buf, "require_vht") == 0) { - conf->require_vht = atoi(pos); - } else if (os_strcmp(buf, "vht_oper_chwidth") == 0) { - conf->vht_oper_chwidth = atoi(pos); - } else if (os_strcmp(buf, "vht_oper_centr_freq_seg0_idx") == 0) - { - conf->vht_oper_centr_freq_seg0_idx = atoi(pos); - } else if (os_strcmp(buf, "vht_oper_centr_freq_seg1_idx") == 0) - { - conf->vht_oper_centr_freq_seg1_idx = atoi(pos); + } else if (os_strcmp(buf, "ieee80211ac") == 0) { + conf->ieee80211ac = atoi(pos); + } else if (os_strcmp(buf, "vht_capab") == 0) { + if (hostapd_config_vht_capab(conf, pos) < 0) { + wpa_printf(MSG_ERROR, "Line %d: invalid vht_capab", + line); + return 1; + } + } else if (os_strcmp(buf, "require_vht") == 0) { + conf->require_vht = atoi(pos); + } else if (os_strcmp(buf, "vht_oper_chwidth") == 0) { + conf->vht_oper_chwidth = atoi(pos); + } else if (os_strcmp(buf, "vht_oper_centr_freq_seg0_idx") == 0) { + conf->vht_oper_centr_freq_seg0_idx = atoi(pos); + } else if (os_strcmp(buf, "vht_oper_centr_freq_seg1_idx") == 0) { + conf->vht_oper_centr_freq_seg1_idx = atoi(pos); #endif /* CONFIG_IEEE80211AC */ - } else if (os_strcmp(buf, "max_listen_interval") == 0) { - bss->max_listen_interval = atoi(pos); - } else if (os_strcmp(buf, "disable_pmksa_caching") == 0) { - bss->disable_pmksa_caching = atoi(pos); - } else if (os_strcmp(buf, "okc") == 0) { - bss->okc = atoi(pos); + } else if (os_strcmp(buf, "max_listen_interval") == 0) { + bss->max_listen_interval = atoi(pos); + } else if (os_strcmp(buf, "disable_pmksa_caching") == 0) { + bss->disable_pmksa_caching = atoi(pos); + } else if (os_strcmp(buf, "okc") == 0) { + bss->okc = atoi(pos); #ifdef CONFIG_WPS - } else if (os_strcmp(buf, "wps_state") == 0) { - bss->wps_state = atoi(pos); - if (bss->wps_state < 0 || bss->wps_state > 2) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "wps_state", line); - errors++; - } - } else if (os_strcmp(buf, "wps_independent") == 0) { - bss->wps_independent = atoi(pos); - } else if (os_strcmp(buf, "ap_setup_locked") == 0) { - bss->ap_setup_locked = atoi(pos); - } else if (os_strcmp(buf, "uuid") == 0) { - if (uuid_str2bin(pos, bss->uuid)) { - wpa_printf(MSG_ERROR, "Line %d: invalid UUID", - line); - errors++; - } - } else if (os_strcmp(buf, "wps_pin_requests") == 0) { - os_free(bss->wps_pin_requests); - bss->wps_pin_requests = os_strdup(pos); - } else if (os_strcmp(buf, "device_name") == 0) { - if (os_strlen(pos) > 32) { - wpa_printf(MSG_ERROR, "Line %d: Too long " - "device_name", line); - errors++; - } - os_free(bss->device_name); - bss->device_name = os_strdup(pos); - } else if (os_strcmp(buf, "manufacturer") == 0) { - if (os_strlen(pos) > 64) { - wpa_printf(MSG_ERROR, "Line %d: Too long " - "manufacturer", line); - errors++; - } - os_free(bss->manufacturer); - bss->manufacturer = os_strdup(pos); - } else if (os_strcmp(buf, "model_name") == 0) { - if (os_strlen(pos) > 32) { - wpa_printf(MSG_ERROR, "Line %d: Too long " - "model_name", line); - errors++; - } - os_free(bss->model_name); - bss->model_name = os_strdup(pos); - } else if (os_strcmp(buf, "model_number") == 0) { - if (os_strlen(pos) > 32) { - wpa_printf(MSG_ERROR, "Line %d: Too long " - "model_number", line); - errors++; - } - os_free(bss->model_number); - bss->model_number = os_strdup(pos); - } else if (os_strcmp(buf, "serial_number") == 0) { - if (os_strlen(pos) > 32) { - wpa_printf(MSG_ERROR, "Line %d: Too long " - "serial_number", line); - errors++; - } - os_free(bss->serial_number); - bss->serial_number = os_strdup(pos); - } else if (os_strcmp(buf, "device_type") == 0) { - if (wps_dev_type_str2bin(pos, bss->device_type)) - errors++; - } else if (os_strcmp(buf, "config_methods") == 0) { - os_free(bss->config_methods); - bss->config_methods = os_strdup(pos); - } else if (os_strcmp(buf, "os_version") == 0) { - if (hexstr2bin(pos, bss->os_version, 4)) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "os_version", line); - errors++; - } - } else if (os_strcmp(buf, "ap_pin") == 0) { - os_free(bss->ap_pin); - bss->ap_pin = os_strdup(pos); - } else if (os_strcmp(buf, "skip_cred_build") == 0) { - bss->skip_cred_build = atoi(pos); - } else if (os_strcmp(buf, "extra_cred") == 0) { - os_free(bss->extra_cred); - bss->extra_cred = - (u8 *) os_readfile(pos, &bss->extra_cred_len); - if (bss->extra_cred == NULL) { - wpa_printf(MSG_ERROR, "Line %d: could not " - "read Credentials from '%s'", - line, pos); - errors++; - } - } else if (os_strcmp(buf, "wps_cred_processing") == 0) { - bss->wps_cred_processing = atoi(pos); - } else if (os_strcmp(buf, "ap_settings") == 0) { - os_free(bss->ap_settings); - bss->ap_settings = - (u8 *) os_readfile(pos, &bss->ap_settings_len); - if (bss->ap_settings == NULL) { - wpa_printf(MSG_ERROR, "Line %d: could not " - "read AP Settings from '%s'", - line, pos); - errors++; - } - } else if (os_strcmp(buf, "upnp_iface") == 0) { - bss->upnp_iface = os_strdup(pos); - } else if (os_strcmp(buf, "friendly_name") == 0) { - os_free(bss->friendly_name); - bss->friendly_name = os_strdup(pos); - } else if (os_strcmp(buf, "manufacturer_url") == 0) { - os_free(bss->manufacturer_url); - bss->manufacturer_url = os_strdup(pos); - } else if (os_strcmp(buf, "model_description") == 0) { - os_free(bss->model_description); - bss->model_description = os_strdup(pos); - } else if (os_strcmp(buf, "model_url") == 0) { - os_free(bss->model_url); - bss->model_url = os_strdup(pos); - } else if (os_strcmp(buf, "upc") == 0) { - os_free(bss->upc); - bss->upc = os_strdup(pos); - } else if (os_strcmp(buf, "pbc_in_m1") == 0) { - bss->pbc_in_m1 = atoi(pos); - } else if (os_strcmp(buf, "server_id") == 0) { - os_free(bss->server_id); - bss->server_id = os_strdup(pos); + } else if (os_strcmp(buf, "wps_state") == 0) { + bss->wps_state = atoi(pos); + if (bss->wps_state < 0 || bss->wps_state > 2) { + wpa_printf(MSG_ERROR, "Line %d: invalid wps_state", + line); + return 1; + } + } else if (os_strcmp(buf, "wps_independent") == 0) { + bss->wps_independent = atoi(pos); + } else if (os_strcmp(buf, "ap_setup_locked") == 0) { + bss->ap_setup_locked = atoi(pos); + } else if (os_strcmp(buf, "uuid") == 0) { + if (uuid_str2bin(pos, bss->uuid)) { + wpa_printf(MSG_ERROR, "Line %d: invalid UUID", line); + return 1; + } + } else if (os_strcmp(buf, "wps_pin_requests") == 0) { + os_free(bss->wps_pin_requests); + bss->wps_pin_requests = os_strdup(pos); + } else if (os_strcmp(buf, "device_name") == 0) { + if (os_strlen(pos) > 32) { + wpa_printf(MSG_ERROR, "Line %d: Too long " + "device_name", line); + return 1; + } + os_free(bss->device_name); + bss->device_name = os_strdup(pos); + } else if (os_strcmp(buf, "manufacturer") == 0) { + if (os_strlen(pos) > 64) { + wpa_printf(MSG_ERROR, "Line %d: Too long manufacturer", + line); + return 1; + } + os_free(bss->manufacturer); + bss->manufacturer = os_strdup(pos); + } else if (os_strcmp(buf, "model_name") == 0) { + if (os_strlen(pos) > 32) { + wpa_printf(MSG_ERROR, "Line %d: Too long model_name", + line); + return 1; + } + os_free(bss->model_name); + bss->model_name = os_strdup(pos); + } else if (os_strcmp(buf, "model_number") == 0) { + if (os_strlen(pos) > 32) { + wpa_printf(MSG_ERROR, "Line %d: Too long model_number", + line); + return 1; + } + os_free(bss->model_number); + bss->model_number = os_strdup(pos); + } else if (os_strcmp(buf, "serial_number") == 0) { + if (os_strlen(pos) > 32) { + wpa_printf(MSG_ERROR, "Line %d: Too long serial_number", + line); + return 1; + } + os_free(bss->serial_number); + bss->serial_number = os_strdup(pos); + } else if (os_strcmp(buf, "device_type") == 0) { + if (wps_dev_type_str2bin(pos, bss->device_type)) + return 1; + } else if (os_strcmp(buf, "config_methods") == 0) { + os_free(bss->config_methods); + bss->config_methods = os_strdup(pos); + } else if (os_strcmp(buf, "os_version") == 0) { + if (hexstr2bin(pos, bss->os_version, 4)) { + wpa_printf(MSG_ERROR, "Line %d: invalid os_version", + line); + return 1; + } + } else if (os_strcmp(buf, "ap_pin") == 0) { + os_free(bss->ap_pin); + bss->ap_pin = os_strdup(pos); + } else if (os_strcmp(buf, "skip_cred_build") == 0) { + bss->skip_cred_build = atoi(pos); + } else if (os_strcmp(buf, "extra_cred") == 0) { + os_free(bss->extra_cred); + bss->extra_cred = (u8 *) os_readfile(pos, &bss->extra_cred_len); + if (bss->extra_cred == NULL) { + wpa_printf(MSG_ERROR, "Line %d: could not read Credentials from '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "wps_cred_processing") == 0) { + bss->wps_cred_processing = atoi(pos); + } else if (os_strcmp(buf, "ap_settings") == 0) { + os_free(bss->ap_settings); + bss->ap_settings = + (u8 *) os_readfile(pos, &bss->ap_settings_len); + if (bss->ap_settings == NULL) { + wpa_printf(MSG_ERROR, "Line %d: could not read AP Settings from '%s'", + line, pos); + return 1; + } + } else if (os_strcmp(buf, "upnp_iface") == 0) { + bss->upnp_iface = os_strdup(pos); + } else if (os_strcmp(buf, "friendly_name") == 0) { + os_free(bss->friendly_name); + bss->friendly_name = os_strdup(pos); + } else if (os_strcmp(buf, "manufacturer_url") == 0) { + os_free(bss->manufacturer_url); + bss->manufacturer_url = os_strdup(pos); + } else if (os_strcmp(buf, "model_description") == 0) { + os_free(bss->model_description); + bss->model_description = os_strdup(pos); + } else if (os_strcmp(buf, "model_url") == 0) { + os_free(bss->model_url); + bss->model_url = os_strdup(pos); + } else if (os_strcmp(buf, "upc") == 0) { + os_free(bss->upc); + bss->upc = os_strdup(pos); + } else if (os_strcmp(buf, "pbc_in_m1") == 0) { + bss->pbc_in_m1 = atoi(pos); + } else if (os_strcmp(buf, "server_id") == 0) { + os_free(bss->server_id); + bss->server_id = os_strdup(pos); #ifdef CONFIG_WPS_NFC - } else if (os_strcmp(buf, "wps_nfc_dev_pw_id") == 0) { - bss->wps_nfc_dev_pw_id = atoi(pos); - if (bss->wps_nfc_dev_pw_id < 0x10 || - bss->wps_nfc_dev_pw_id > 0xffff) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "wps_nfc_dev_pw_id value", line); - errors++; - } - bss->wps_nfc_pw_from_config = 1; - } else if (os_strcmp(buf, "wps_nfc_dh_pubkey") == 0) { - wpabuf_free(bss->wps_nfc_dh_pubkey); - bss->wps_nfc_dh_pubkey = hostapd_parse_bin(pos); - bss->wps_nfc_pw_from_config = 1; - } else if (os_strcmp(buf, "wps_nfc_dh_privkey") == 0) { - wpabuf_free(bss->wps_nfc_dh_privkey); - bss->wps_nfc_dh_privkey = hostapd_parse_bin(pos); - bss->wps_nfc_pw_from_config = 1; - } else if (os_strcmp(buf, "wps_nfc_dev_pw") == 0) { - wpabuf_free(bss->wps_nfc_dev_pw); - bss->wps_nfc_dev_pw = hostapd_parse_bin(pos); - bss->wps_nfc_pw_from_config = 1; + } else if (os_strcmp(buf, "wps_nfc_dev_pw_id") == 0) { + bss->wps_nfc_dev_pw_id = atoi(pos); + if (bss->wps_nfc_dev_pw_id < 0x10 || + bss->wps_nfc_dev_pw_id > 0xffff) { + wpa_printf(MSG_ERROR, "Line %d: Invalid wps_nfc_dev_pw_id value", + line); + return 1; + } + bss->wps_nfc_pw_from_config = 1; + } else if (os_strcmp(buf, "wps_nfc_dh_pubkey") == 0) { + wpabuf_free(bss->wps_nfc_dh_pubkey); + bss->wps_nfc_dh_pubkey = hostapd_parse_bin(pos); + bss->wps_nfc_pw_from_config = 1; + } else if (os_strcmp(buf, "wps_nfc_dh_privkey") == 0) { + wpabuf_free(bss->wps_nfc_dh_privkey); + bss->wps_nfc_dh_privkey = hostapd_parse_bin(pos); + bss->wps_nfc_pw_from_config = 1; + } else if (os_strcmp(buf, "wps_nfc_dev_pw") == 0) { + wpabuf_free(bss->wps_nfc_dev_pw); + bss->wps_nfc_dev_pw = hostapd_parse_bin(pos); + bss->wps_nfc_pw_from_config = 1; #endif /* CONFIG_WPS_NFC */ #endif /* CONFIG_WPS */ #ifdef CONFIG_P2P_MANAGER - } else if (os_strcmp(buf, "manage_p2p") == 0) { - int manage = atoi(pos); - if (manage) - bss->p2p |= P2P_MANAGE; - else - bss->p2p &= ~P2P_MANAGE; - } else if (os_strcmp(buf, "allow_cross_connection") == 0) { - if (atoi(pos)) - bss->p2p |= P2P_ALLOW_CROSS_CONNECTION; - else - bss->p2p &= ~P2P_ALLOW_CROSS_CONNECTION; + } else if (os_strcmp(buf, "manage_p2p") == 0) { + if (atoi(pos)) + bss->p2p |= P2P_MANAGE; + else + bss->p2p &= ~P2P_MANAGE; + } else if (os_strcmp(buf, "allow_cross_connection") == 0) { + if (atoi(pos)) + bss->p2p |= P2P_ALLOW_CROSS_CONNECTION; + else + bss->p2p &= ~P2P_ALLOW_CROSS_CONNECTION; #endif /* CONFIG_P2P_MANAGER */ - } else if (os_strcmp(buf, "disassoc_low_ack") == 0) { - bss->disassoc_low_ack = atoi(pos); - } else if (os_strcmp(buf, "tdls_prohibit") == 0) { - int val = atoi(pos); - if (val) - bss->tdls |= TDLS_PROHIBIT; - else - bss->tdls &= ~TDLS_PROHIBIT; - } else if (os_strcmp(buf, "tdls_prohibit_chan_switch") == 0) { - int val = atoi(pos); - if (val) - bss->tdls |= TDLS_PROHIBIT_CHAN_SWITCH; - else - bss->tdls &= ~TDLS_PROHIBIT_CHAN_SWITCH; + } else if (os_strcmp(buf, "disassoc_low_ack") == 0) { + bss->disassoc_low_ack = atoi(pos); + } else if (os_strcmp(buf, "tdls_prohibit") == 0) { + if (atoi(pos)) + bss->tdls |= TDLS_PROHIBIT; + else + bss->tdls &= ~TDLS_PROHIBIT; + } else if (os_strcmp(buf, "tdls_prohibit_chan_switch") == 0) { + if (atoi(pos)) + bss->tdls |= TDLS_PROHIBIT_CHAN_SWITCH; + else + bss->tdls &= ~TDLS_PROHIBIT_CHAN_SWITCH; #ifdef CONFIG_RSN_TESTING - } else if (os_strcmp(buf, "rsn_testing") == 0) { - extern int rsn_testing; - rsn_testing = atoi(pos); + } else if (os_strcmp(buf, "rsn_testing") == 0) { + extern int rsn_testing; + rsn_testing = atoi(pos); #endif /* CONFIG_RSN_TESTING */ - } else if (os_strcmp(buf, "time_advertisement") == 0) { - bss->time_advertisement = atoi(pos); - } else if (os_strcmp(buf, "time_zone") == 0) { - size_t tz_len = os_strlen(pos); - if (tz_len < 4 || tz_len > 255) { - wpa_printf(MSG_DEBUG, "Line %d: invalid " - "time_zone", line); - errors++; - return errors; - } - os_free(bss->time_zone); - bss->time_zone = os_strdup(pos); - if (bss->time_zone == NULL) - errors++; + } else if (os_strcmp(buf, "time_advertisement") == 0) { + bss->time_advertisement = atoi(pos); + } else if (os_strcmp(buf, "time_zone") == 0) { + size_t tz_len = os_strlen(pos); + if (tz_len < 4 || tz_len > 255) { + wpa_printf(MSG_DEBUG, "Line %d: invalid time_zone", + line); + return 1; + } + os_free(bss->time_zone); + bss->time_zone = os_strdup(pos); + if (bss->time_zone == NULL) + return 1; #ifdef CONFIG_WNM - } else if (os_strcmp(buf, "wnm_sleep_mode") == 0) { - bss->wnm_sleep_mode = atoi(pos); - } else if (os_strcmp(buf, "bss_transition") == 0) { - bss->bss_transition = atoi(pos); + } else if (os_strcmp(buf, "wnm_sleep_mode") == 0) { + bss->wnm_sleep_mode = atoi(pos); + } else if (os_strcmp(buf, "bss_transition") == 0) { + bss->bss_transition = atoi(pos); #endif /* CONFIG_WNM */ #ifdef CONFIG_INTERWORKING - } else if (os_strcmp(buf, "interworking") == 0) { - bss->interworking = atoi(pos); - } else if (os_strcmp(buf, "access_network_type") == 0) { - bss->access_network_type = atoi(pos); - if (bss->access_network_type < 0 || - bss->access_network_type > 15) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "access_network_type", line); - errors++; - } - } else if (os_strcmp(buf, "internet") == 0) { - bss->internet = atoi(pos); - } else if (os_strcmp(buf, "asra") == 0) { - bss->asra = atoi(pos); - } else if (os_strcmp(buf, "esr") == 0) { - bss->esr = atoi(pos); - } else if (os_strcmp(buf, "uesa") == 0) { - bss->uesa = atoi(pos); - } else if (os_strcmp(buf, "venue_group") == 0) { - bss->venue_group = atoi(pos); - bss->venue_info_set = 1; - } else if (os_strcmp(buf, "venue_type") == 0) { - bss->venue_type = atoi(pos); - bss->venue_info_set = 1; - } else if (os_strcmp(buf, "hessid") == 0) { - if (hwaddr_aton(pos, bss->hessid)) { - wpa_printf(MSG_ERROR, "Line %d: invalid " - "hessid", line); - errors++; - } - } else if (os_strcmp(buf, "roaming_consortium") == 0) { - if (parse_roaming_consortium(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "venue_name") == 0) { - if (parse_venue_name(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "network_auth_type") == 0) { - u8 auth_type; - u16 redirect_url_len; - if (hexstr2bin(pos, &auth_type, 1)) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "network_auth_type '%s'", - line, pos); - errors++; - return errors; - } - if (auth_type == 0 || auth_type == 2) - redirect_url_len = os_strlen(pos + 2); - else - redirect_url_len = 0; - os_free(bss->network_auth_type); - bss->network_auth_type = - os_malloc(redirect_url_len + 3 + 1); - if (bss->network_auth_type == NULL) { - errors++; - return errors; - } - *bss->network_auth_type = auth_type; - WPA_PUT_LE16(bss->network_auth_type + 1, - redirect_url_len); - if (redirect_url_len) - os_memcpy(bss->network_auth_type + 3, - pos + 2, redirect_url_len); - bss->network_auth_type_len = 3 + redirect_url_len; - } else if (os_strcmp(buf, "ipaddr_type_availability") == 0) { - if (hexstr2bin(pos, &bss->ipaddr_type_availability, 1)) - { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "ipaddr_type_availability '%s'", - line, pos); - bss->ipaddr_type_configured = 0; - errors++; - return errors; - } - bss->ipaddr_type_configured = 1; - } else if (os_strcmp(buf, "domain_name") == 0) { - int j, num_domains, domain_len, domain_list_len = 0; - char *tok_start, *tok_prev; - u8 *domain_list, *domain_ptr; - - domain_list_len = os_strlen(pos) + 1; - domain_list = os_malloc(domain_list_len); - if (domain_list == NULL) { - errors++; - return errors; - } - - domain_ptr = domain_list; - tok_prev = pos; - num_domains = 1; - while ((tok_prev = os_strchr(tok_prev, ','))) { - num_domains++; - tok_prev++; - } - tok_prev = pos; - for (j = 0; j < num_domains; j++) { - tok_start = os_strchr(tok_prev, ','); - if (tok_start) { - domain_len = tok_start - tok_prev; - *domain_ptr = domain_len; - os_memcpy(domain_ptr + 1, tok_prev, - domain_len); - domain_ptr += domain_len + 1; - tok_prev = ++tok_start; - } else { - domain_len = os_strlen(tok_prev); - *domain_ptr = domain_len; - os_memcpy(domain_ptr + 1, tok_prev, - domain_len); - domain_ptr += domain_len + 1; - } + } else if (os_strcmp(buf, "interworking") == 0) { + bss->interworking = atoi(pos); + } else if (os_strcmp(buf, "access_network_type") == 0) { + bss->access_network_type = atoi(pos); + if (bss->access_network_type < 0 || + bss->access_network_type > 15) { + wpa_printf(MSG_ERROR, + "Line %d: invalid access_network_type", + line); + return 1; + } + } else if (os_strcmp(buf, "internet") == 0) { + bss->internet = atoi(pos); + } else if (os_strcmp(buf, "asra") == 0) { + bss->asra = atoi(pos); + } else if (os_strcmp(buf, "esr") == 0) { + bss->esr = atoi(pos); + } else if (os_strcmp(buf, "uesa") == 0) { + bss->uesa = atoi(pos); + } else if (os_strcmp(buf, "venue_group") == 0) { + bss->venue_group = atoi(pos); + bss->venue_info_set = 1; + } else if (os_strcmp(buf, "venue_type") == 0) { + bss->venue_type = atoi(pos); + bss->venue_info_set = 1; + } else if (os_strcmp(buf, "hessid") == 0) { + if (hwaddr_aton(pos, bss->hessid)) { + wpa_printf(MSG_ERROR, "Line %d: invalid hessid", line); + return 1; + } + } else if (os_strcmp(buf, "roaming_consortium") == 0) { + if (parse_roaming_consortium(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "venue_name") == 0) { + if (parse_venue_name(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "network_auth_type") == 0) { + u8 auth_type; + u16 redirect_url_len; + if (hexstr2bin(pos, &auth_type, 1)) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid network_auth_type '%s'", + line, pos); + return 1; + } + if (auth_type == 0 || auth_type == 2) + redirect_url_len = os_strlen(pos + 2); + else + redirect_url_len = 0; + os_free(bss->network_auth_type); + bss->network_auth_type = os_malloc(redirect_url_len + 3 + 1); + if (bss->network_auth_type == NULL) + return 1; + *bss->network_auth_type = auth_type; + WPA_PUT_LE16(bss->network_auth_type + 1, redirect_url_len); + if (redirect_url_len) + os_memcpy(bss->network_auth_type + 3, pos + 2, + redirect_url_len); + bss->network_auth_type_len = 3 + redirect_url_len; + } else if (os_strcmp(buf, "ipaddr_type_availability") == 0) { + if (hexstr2bin(pos, &bss->ipaddr_type_availability, 1)) { + wpa_printf(MSG_ERROR, "Line %d: Invalid ipaddr_type_availability '%s'", + line, pos); + bss->ipaddr_type_configured = 0; + return 1; + } + bss->ipaddr_type_configured = 1; + } else if (os_strcmp(buf, "domain_name") == 0) { + int j, num_domains, domain_len, domain_list_len = 0; + char *tok_start, *tok_prev; + u8 *domain_list, *domain_ptr; + + domain_list_len = os_strlen(pos) + 1; + domain_list = os_malloc(domain_list_len); + if (domain_list == NULL) + return 1; + + domain_ptr = domain_list; + tok_prev = pos; + num_domains = 1; + while ((tok_prev = os_strchr(tok_prev, ','))) { + num_domains++; + tok_prev++; + } + tok_prev = pos; + for (j = 0; j < num_domains; j++) { + tok_start = os_strchr(tok_prev, ','); + if (tok_start) { + domain_len = tok_start - tok_prev; + *domain_ptr = domain_len; + os_memcpy(domain_ptr + 1, tok_prev, domain_len); + domain_ptr += domain_len + 1; + tok_prev = ++tok_start; + } else { + domain_len = os_strlen(tok_prev); + *domain_ptr = domain_len; + os_memcpy(domain_ptr + 1, tok_prev, domain_len); + domain_ptr += domain_len + 1; } + } - os_free(bss->domain_name); - bss->domain_name = domain_list; - bss->domain_name_len = domain_list_len; - } else if (os_strcmp(buf, "anqp_3gpp_cell_net") == 0) { - if (parse_3gpp_cell_net(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "nai_realm") == 0) { - if (parse_nai_realm(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "gas_frag_limit") == 0) { - bss->gas_frag_limit = atoi(pos); - } else if (os_strcmp(buf, "gas_comeback_delay") == 0) { - bss->gas_comeback_delay = atoi(pos); - } else if (os_strcmp(buf, "qos_map_set") == 0) { - if (parse_qos_map_set(bss, pos, line) < 0) - errors++; + os_free(bss->domain_name); + bss->domain_name = domain_list; + bss->domain_name_len = domain_list_len; + } else if (os_strcmp(buf, "anqp_3gpp_cell_net") == 0) { + if (parse_3gpp_cell_net(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "nai_realm") == 0) { + if (parse_nai_realm(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "gas_frag_limit") == 0) { + bss->gas_frag_limit = atoi(pos); + } else if (os_strcmp(buf, "gas_comeback_delay") == 0) { + bss->gas_comeback_delay = atoi(pos); + } else if (os_strcmp(buf, "qos_map_set") == 0) { + if (parse_qos_map_set(bss, pos, line) < 0) + return 1; #endif /* CONFIG_INTERWORKING */ #ifdef CONFIG_RADIUS_TEST - } else if (os_strcmp(buf, "dump_msk_file") == 0) { - os_free(bss->dump_msk_file); - bss->dump_msk_file = os_strdup(pos); + } else if (os_strcmp(buf, "dump_msk_file") == 0) { + os_free(bss->dump_msk_file); + bss->dump_msk_file = os_strdup(pos); #endif /* CONFIG_RADIUS_TEST */ #ifdef CONFIG_HS20 - } else if (os_strcmp(buf, "hs20") == 0) { - bss->hs20 = atoi(pos); - } else if (os_strcmp(buf, "disable_dgaf") == 0) { - bss->disable_dgaf = atoi(pos); - } else if (os_strcmp(buf, "osen") == 0) { - bss->osen = atoi(pos); - } else if (os_strcmp(buf, "anqp_domain_id") == 0) { - bss->anqp_domain_id = atoi(pos); - } else if (os_strcmp(buf, "hs20_deauth_req_timeout") == 0) { - bss->hs20_deauth_req_timeout = atoi(pos); - } else if (os_strcmp(buf, "hs20_oper_friendly_name") == 0) { - if (hs20_parse_oper_friendly_name(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "hs20_wan_metrics") == 0) { - if (hs20_parse_wan_metrics(bss, pos, line) < 0) { - errors++; - return errors; - } - } else if (os_strcmp(buf, "hs20_conn_capab") == 0) { - if (hs20_parse_conn_capab(bss, pos, line) < 0) { - errors++; - return errors; - } - } else if (os_strcmp(buf, "hs20_operating_class") == 0) { - u8 *oper_class; - size_t oper_class_len; - oper_class_len = os_strlen(pos); - if (oper_class_len < 2 || (oper_class_len & 0x01)) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "hs20_operating_class '%s'", - line, pos); - errors++; - return errors; - } - oper_class_len /= 2; - oper_class = os_malloc(oper_class_len); - if (oper_class == NULL) { - errors++; - return errors; - } - if (hexstr2bin(pos, oper_class, oper_class_len)) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "hs20_operating_class '%s'", - line, pos); - os_free(oper_class); - errors++; - return errors; - } - 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; - } - } else if (os_strcmp(buf, "osu_ssid") == 0) { - if (hs20_parse_osu_ssid(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "osu_server_uri") == 0) { - if (hs20_parse_osu_server_uri(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "osu_friendly_name") == 0) { - if (hs20_parse_osu_friendly_name(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "osu_nai") == 0) { - if (hs20_parse_osu_nai(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "osu_method_list") == 0) { - if (hs20_parse_osu_method_list(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "osu_icon") == 0) { - if (hs20_parse_osu_icon(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "osu_service_desc") == 0) { - if (hs20_parse_osu_service_desc(bss, pos, line) < 0) - errors++; - } else if (os_strcmp(buf, "subscr_remediation_url") == 0) { - os_free(bss->subscr_remediation_url); - bss->subscr_remediation_url = os_strdup(pos); - } else if (os_strcmp(buf, "subscr_remediation_method") == 0) { - bss->subscr_remediation_method = atoi(pos); + } else if (os_strcmp(buf, "hs20") == 0) { + bss->hs20 = atoi(pos); + } else if (os_strcmp(buf, "disable_dgaf") == 0) { + bss->disable_dgaf = atoi(pos); + } else if (os_strcmp(buf, "osen") == 0) { + bss->osen = atoi(pos); + } else if (os_strcmp(buf, "anqp_domain_id") == 0) { + bss->anqp_domain_id = atoi(pos); + } else if (os_strcmp(buf, "hs20_deauth_req_timeout") == 0) { + bss->hs20_deauth_req_timeout = atoi(pos); + } else if (os_strcmp(buf, "hs20_oper_friendly_name") == 0) { + if (hs20_parse_oper_friendly_name(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "hs20_wan_metrics") == 0) { + if (hs20_parse_wan_metrics(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "hs20_conn_capab") == 0) { + if (hs20_parse_conn_capab(bss, pos, line) < 0) { + return 1; + } + } else if (os_strcmp(buf, "hs20_operating_class") == 0) { + u8 *oper_class; + size_t oper_class_len; + oper_class_len = os_strlen(pos); + if (oper_class_len < 2 || (oper_class_len & 0x01)) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid hs20_operating_class '%s'", + line, pos); + return 1; + } + oper_class_len /= 2; + oper_class = os_malloc(oper_class_len); + if (oper_class == NULL) + return 1; + if (hexstr2bin(pos, oper_class, oper_class_len)) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid hs20_operating_class '%s'", + line, pos); + os_free(oper_class); + return 1; + } + 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); + return 1; + } + } else if (os_strcmp(buf, "osu_ssid") == 0) { + if (hs20_parse_osu_ssid(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_server_uri") == 0) { + if (hs20_parse_osu_server_uri(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_friendly_name") == 0) { + if (hs20_parse_osu_friendly_name(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_nai") == 0) { + if (hs20_parse_osu_nai(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_method_list") == 0) { + if (hs20_parse_osu_method_list(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_icon") == 0) { + if (hs20_parse_osu_icon(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "osu_service_desc") == 0) { + if (hs20_parse_osu_service_desc(bss, pos, line) < 0) + return 1; + } else if (os_strcmp(buf, "subscr_remediation_url") == 0) { + os_free(bss->subscr_remediation_url); + bss->subscr_remediation_url = os_strdup(pos); + } else if (os_strcmp(buf, "subscr_remediation_method") == 0) { + bss->subscr_remediation_method = atoi(pos); #endif /* CONFIG_HS20 */ #ifdef CONFIG_TESTING_OPTIONS -#define PARSE_TEST_PROBABILITY(_val) \ - } else if (os_strcmp(buf, #_val) == 0) { \ - char *end; \ - \ - conf->_val = strtod(pos, &end); \ - if (*end || conf->_val < 0.0d || \ - conf->_val > 1.0d) { \ - wpa_printf(MSG_ERROR, \ - "Line %d: Invalid value '%s'", \ - line, pos); \ - errors++; \ - return errors; \ - } - PARSE_TEST_PROBABILITY(ignore_probe_probability) - PARSE_TEST_PROBABILITY(ignore_auth_probability) - PARSE_TEST_PROBABILITY(ignore_assoc_probability) - PARSE_TEST_PROBABILITY(ignore_reassoc_probability) - PARSE_TEST_PROBABILITY(corrupt_gtk_rekey_mic_probability) - } else if (os_strcmp(buf, "bss_load_test") == 0) { - WPA_PUT_LE16(bss->bss_load_test, atoi(pos)); - pos = os_strchr(pos, ':'); - if (pos == NULL) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "bss_load_test", line); - return 1; - } - pos++; - bss->bss_load_test[2] = atoi(pos); - pos = os_strchr(pos, ':'); - if (pos == NULL) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "bss_load_test", line); - return 1; - } - pos++; - WPA_PUT_LE16(&bss->bss_load_test[3], atoi(pos)); - bss->bss_load_test_set = 1; +#define PARSE_TEST_PROBABILITY(_val) \ + } else if (os_strcmp(buf, #_val) == 0) { \ + char *end; \ + \ + conf->_val = strtod(pos, &end); \ + if (*end || conf->_val < 0.0d || \ + conf->_val > 1.0d) { \ + wpa_printf(MSG_ERROR, \ + "Line %d: Invalid value '%s'", \ + line, pos); \ + return 1; \ + } + PARSE_TEST_PROBABILITY(ignore_probe_probability) + PARSE_TEST_PROBABILITY(ignore_auth_probability) + PARSE_TEST_PROBABILITY(ignore_assoc_probability) + PARSE_TEST_PROBABILITY(ignore_reassoc_probability) + PARSE_TEST_PROBABILITY(corrupt_gtk_rekey_mic_probability) + } else if (os_strcmp(buf, "bss_load_test") == 0) { + WPA_PUT_LE16(bss->bss_load_test, atoi(pos)); + pos = os_strchr(pos, ':'); + if (pos == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Invalid bss_load_test", + line); + return 1; + } + pos++; + bss->bss_load_test[2] = atoi(pos); + pos = os_strchr(pos, ':'); + if (pos == NULL) { + wpa_printf(MSG_ERROR, "Line %d: Invalid bss_load_test", + line); + return 1; + } + pos++; + WPA_PUT_LE16(&bss->bss_load_test[3], atoi(pos)); + bss->bss_load_test_set = 1; #endif /* CONFIG_TESTING_OPTIONS */ - } else if (os_strcmp(buf, "vendor_elements") == 0) { - struct wpabuf *elems; - size_t len = os_strlen(pos); - if (len & 0x01) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "vendor_elements '%s'", line, pos); - return 1; - } - len /= 2; - if (len == 0) { - wpabuf_free(bss->vendor_elements); - bss->vendor_elements = NULL; - return 0; - } + } else if (os_strcmp(buf, "vendor_elements") == 0) { + struct wpabuf *elems; + size_t len = os_strlen(pos); + if (len & 0x01) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid vendor_elements '%s'", + line, pos); + return 1; + } + len /= 2; + if (len == 0) { + wpabuf_free(bss->vendor_elements); + bss->vendor_elements = NULL; + return 0; + } - elems = wpabuf_alloc(len); - if (elems == NULL) - return 1; + elems = wpabuf_alloc(len); + if (elems == NULL) + return 1; - if (hexstr2bin(pos, wpabuf_put(elems, len), len)) { - wpabuf_free(elems); - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "vendor_elements '%s'", line, pos); - return 1; - } + if (hexstr2bin(pos, wpabuf_put(elems, len), len)) { + wpabuf_free(elems); + wpa_printf(MSG_ERROR, + "Line %d: Invalid vendor_elements '%s'", + line, pos); + return 1; + } - wpabuf_free(bss->vendor_elements); - bss->vendor_elements = elems; - } else if (os_strcmp(buf, "sae_anti_clogging_threshold") == 0) { - bss->sae_anti_clogging_threshold = atoi(pos); - } else if (os_strcmp(buf, "sae_groups") == 0) { - if (hostapd_parse_intlist(&bss->sae_groups, pos)) { - wpa_printf(MSG_ERROR, "Line %d: Invalid " - "sae_groups value '%s'", line, pos); - return 1; - } - } else if (os_strcmp(buf, "local_pwr_constraint") == 0) { - int val = atoi(pos); - if (val < 0 || val > 255) { - wpa_printf(MSG_ERROR, "Line %d: Invalid local_pwr_constraint %d (expected 0..255)", - line, val); - return 1; - } - conf->local_pwr_constraint = val; - } else if (os_strcmp(buf, "spectrum_mgmt_required") == 0) { - conf->spectrum_mgmt_required = atoi(pos); - } else { - wpa_printf(MSG_ERROR, "Line %d: unknown configuration " - "item '%s'", line, buf); - errors++; + wpabuf_free(bss->vendor_elements); + bss->vendor_elements = elems; + } else if (os_strcmp(buf, "sae_anti_clogging_threshold") == 0) { + bss->sae_anti_clogging_threshold = atoi(pos); + } else if (os_strcmp(buf, "sae_groups") == 0) { + if (hostapd_parse_intlist(&bss->sae_groups, pos)) { + wpa_printf(MSG_ERROR, + "Line %d: Invalid sae_groups value '%s'", + line, pos); + return 1; } + } else if (os_strcmp(buf, "local_pwr_constraint") == 0) { + int val = atoi(pos); + if (val < 0 || val > 255) { + wpa_printf(MSG_ERROR, "Line %d: Invalid local_pwr_constraint %d (expected 0..255)", + line, val); + return 1; + } + conf->local_pwr_constraint = val; + } else if (os_strcmp(buf, "spectrum_mgmt_required") == 0) { + conf->spectrum_mgmt_required = atoi(pos); + } else { + wpa_printf(MSG_ERROR, + "Line %d: unknown configuration item '%s'", + line, buf); + return 1; } - return errors; + return 0; } diff --git a/hs20/client/Android.mk b/hs20/client/Android.mk new file mode 100644 index 00000000..b7bd9325 --- /dev/null +++ b/hs20/client/Android.mk @@ -0,0 +1,73 @@ +LOCAL_PATH := $(call my-dir) + +INCLUDES = $(LOCAL_PATH) +INCLUDES += $(LOCAL_PATH)/../../src/utils +INCLUDES += $(LOCAL_PATH)/../../src/common +INCLUDES += $(LOCAL_PATH)/../../src +INCLUDES += external/openssl/include +INCLUDES += external/libxml2/include +INCLUDES += external/curl/include +INCLUDES += external/webkit/Source/WebKit/gtk +INCLUDES += external/icu4c/common + + +#GTKCFLAGS := $(shell pkg-config --cflags gtk+-2.0 webkit-1.0) +#GTKLIBS := $(shell pkg-config --libs gtk+-2.0 webkit-1.0) +#CFLAGS += $(GTKCFLAGS) +#LIBS += $(GTKLIBS) + +L_CFLAGS += -DCONFIG_CTRL_IFACE +L_CFLAGS += -DCONFIG_CTRL_IFACE_UNIX +L_CFLAGS += -DCONFIG_CTRL_IFACE_CLIENT_DIR=\"/data/misc/wifi/sockets\" +L_CFLAGS += -DLIBXML_SCHEMAS_ENABLED +L_CFLAGS += -DLIBXML_REGEXP_ENABLED + +OBJS = spp_client.c +OBJS += oma_dm_client.c +OBJS += osu_client.c +OBJS += est.c +OBJS += ../../src/common/wpa_ctrl.c +OBJS += ../../src/common/wpa_helpers.c +OBJS += ../../src/utils/xml-utils.c +#OBJS += ../../src/utils/browser-android.c +OBJS += ../../src/utils/browser-wpadebug.c +OBJS += ../../src/utils/wpabuf.c +OBJS += ../../src/utils/eloop.c +OBJS += ../../src/wps/httpread.c +OBJS += ../../src/wps/http_server.c +OBJS += ../../src/utils/xml_libxml2.c +OBJS += ../../src/utils/http_curl.c +OBJS += ../../src/utils/base64.c +OBJS += ../../src/utils/os_unix.c +L_CFLAGS += -DCONFIG_DEBUG_FILE +OBJS += ../../src/utils/wpa_debug.c +OBJS += ../../src/utils/common.c +OBJS += ../../src/crypto/crypto_internal.c +OBJS += ../../src/crypto/md5-internal.c +OBJS += ../../src/crypto/sha1-internal.c +OBJS += ../../src/crypto/sha256-internal.c + +L_CFLAGS += -DEAP_TLS_OPENSSL + +#CFLAGS += $(shell xml2-config --cflags) +#LIBS += $(shell xml2-config --libs) + + +######################## +include $(CLEAR_VARS) +LOCAL_MODULE := hs20-osu-client +LOCAL_MODULE_TAGS := optional + +LOCAL_SHARED_LIBRARIES := libc libcutils +LOCAL_SHARED_LIBRARIES += libcrypto libssl +#LOCAL_SHARED_LIBRARIES += libxml2 +LOCAL_STATIC_LIBRARIES += libxml2 +LOCAL_SHARED_LIBRARIES += libicuuc +LOCAL_SHARED_LIBRARIES += libcurl + +LOCAL_CFLAGS := $(L_CFLAGS) +LOCAL_SRC_FILES := $(OBJS) +LOCAL_C_INCLUDES := $(INCLUDES) +include $(BUILD_EXECUTABLE) + +######################## diff --git a/hs20/client/Makefile b/hs20/client/Makefile new file mode 100644 index 00000000..ca67b54d --- /dev/null +++ b/hs20/client/Makefile @@ -0,0 +1,94 @@ +all: hs20-osu-client + +ifndef CC +CC=gcc +endif + +ifndef LDO +LDO=$(CC) +endif + +Q=@ +E=echo +ifeq ($(V), 1) +Q= +E=true +endif + +ifndef CFLAGS +CFLAGS = -MMD -O2 -Wall -g +endif + +CFLAGS += -I../../src/utils +CFLAGS += -I../../src/common +CFLAGS += -I../../src + +ifndef CONFIG_NO_BROWSER +ifndef CONFIG_BROWSER_SYSTEM +GTKCFLAGS := $(shell pkg-config --cflags gtk+-3.0 webkitgtk-3.0) +GTKLIBS := $(shell pkg-config --libs gtk+-3.0 webkitgtk-3.0) +CFLAGS += $(GTKCFLAGS) +LIBS += $(GTKLIBS) +endif +endif + +OBJS=spp_client.o +OBJS += oma_dm_client.o +OBJS += osu_client.o +OBJS += est.o +OBJS += ../../src/utils/xml-utils.o +CFLAGS += -DCONFIG_CTRL_IFACE +CFLAGS += -DCONFIG_CTRL_IFACE_UNIX +OBJS += ../../src/common/wpa_ctrl.o ../../src/common/wpa_helpers.o +ifdef CONFIG_NO_BROWSER +CFLAGS += -DCONFIG_NO_BROWSER +else +ifdef CONFIG_BROWSER_SYSTEM +OBJS += ../../src/utils/eloop.o +OBJS += ../../src/utils/wpabuf.o +OBJS += ../../src/wps/httpread.o +OBJS += ../../src/wps/http_server.o +OBJS += ../../src/utils/browser-system.o +else +OBJS += ../../src/utils/browser.o +endif +endif +OBJS += ../../src/utils/xml_libxml2.o +OBJS += ../../src/utils/http_curl.o +OBJS += ../../src/utils/base64.o +OBJS += ../../src/utils/os_unix.o +CFLAGS += -DCONFIG_DEBUG_FILE +OBJS += ../../src/utils/wpa_debug.o +OBJS += ../../src/utils/common.o +OBJS += ../../src/crypto/crypto_internal.o +OBJS += ../../src/crypto/md5-internal.o +OBJS += ../../src/crypto/sha1-internal.o +OBJS += ../../src/crypto/sha256-internal.o + +CFLAGS += $(shell xml2-config --cflags) +LIBS += $(shell xml2-config --libs) +LIBS += -lcurl + +CFLAGS += -DEAP_TLS_OPENSSL +LIBS += -lssl -lcrypto + +hs20-osu-client: $(OBJS) + $(Q)$(LDO) $(LDFLAGS) -o hs20-osu-client $(OBJS) $(LIBS) + @$(E) " LD " $@ + +%.o: %.c + $(Q)$(CC) -c -o $@ $(CFLAGS) $< + @$(E) " CC " $< + +clean: + rm -f core *~ *.o *.d hs20-osu-client + rm -f ../../src/utils/*.o + rm -f ../../src/utils/*.d + rm -f ../../src/common/*.o + rm -f ../../src/common/*.d + rm -f ../../src/crypto/*.o + rm -f ../../src/crypto/*.d + rm -f ../../src/wps/*.o + rm -f ../../src/wps/*.d + +-include $(OBJS:%.o=%.d) diff --git a/hs20/client/devdetail.xml b/hs20/client/devdetail.xml new file mode 100644 index 00000000..6d0389e8 --- /dev/null +++ b/hs20/client/devdetail.xml @@ -0,0 +1,47 @@ +<DevDetail xmlns="urn:oma:mo:oma-dm-devdetail:1.0"> + <Ext> + <org.wi-fi> + <Wi-Fi> + <EAPMethodList> + <EAPMethod1> + <EAPType>13</EAPType> + </EAPMethod1> + <EAPMethod2> + <EAPType>21</EAPType> + <InnerMethod>MS-CHAP-V2</InnerMethod> + </EAPMethod2> + <EAPMethod3> + <EAPType>18</EAPType> + </EAPMethod3> + <EAPMethod4> + <EAPType>23</EAPType> + </EAPMethod4> + <EAPMethod5> + <EAPType>50</EAPType> + </EAPMethod5> + </EAPMethodList> + <ManufacturingCertificate>false</ManufacturingCertificate> + <Wi-FiMACAddress>020102030405</Wi-FiMACAddress> + <IMSI>310026000000000</IMSI> + <IMEI_MEID>imei:490123456789012</IMEI_MEID> + <ClientTriggerRedirectURI>http://localhost:12345/</ClientTriggerRedirectURI> + <Ops> + <launchBrowserToURI></launchBrowserToURI> + <negotiateClientCertTLS></negotiateClientCertTLS> + <getCertificate></getCertificate> + </Ops> + </Wi-Fi> + </org.wi-fi> + </Ext> + <URI> + <MaxDepth>0</MaxDepth> + <MaxTotLen>0</MaxTotLen> + <MaxSegLen>0</MaxSegLen> + </URI> + <DevType>MobilePhone</DevType> + <OEM>Manufacturer</OEM> + <FwV>1.0</FwV> + <SwV>1.0</SwV> + <HwV>1.0</HwV> + <LrgObj>false</LrgObj> +</DevDetail> diff --git a/hs20/client/devinfo.xml b/hs20/client/devinfo.xml new file mode 100644 index 00000000..d48a520a --- /dev/null +++ b/hs20/client/devinfo.xml @@ -0,0 +1,7 @@ +<DevInfo xmlns="urn:oma:mo:oma-dm-devinfo:1.0"> + <DevId>urn:Example:HS20-station:123456</DevId> + <Man>Manufacturer</Man> + <Mod>HS20-station</Mod> + <DmV>1.2</DmV> + <Lang>en</Lang> +</DevInfo> diff --git a/hs20/client/est.c b/hs20/client/est.c new file mode 100644 index 00000000..c1d514a9 --- /dev/null +++ b/hs20/client/est.c @@ -0,0 +1,700 @@ +/* + * Hotspot 2.0 OSU client - EST client + * Copyright (c) 2012-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 <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/pkcs7.h> +#include <openssl/rsa.h> +#include <openssl/asn1.h> +#include <openssl/asn1t.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> + +#include "common.h" +#include "utils/base64.h" +#include "utils/xml-utils.h" +#include "utils/http-utils.h" +#include "osu_client.h" + + +static int pkcs7_to_cert(struct hs20_osu_client *ctx, const u8 *pkcs7, + size_t len, char *pem_file, char *der_file) +{ + PKCS7 *p7 = NULL; + const unsigned char *p = pkcs7; + STACK_OF(X509) *certs; + int i, num, ret = -1; + BIO *out = NULL; + + p7 = d2i_PKCS7(NULL, &p, len); + if (p7 == NULL) { + wpa_printf(MSG_INFO, "Could not parse PKCS#7 object: %s", + ERR_error_string(ERR_get_error(), NULL)); + write_result(ctx, "Could not parse PKCS#7 object from EST"); + goto fail; + } + + switch (OBJ_obj2nid(p7->type)) { + case NID_pkcs7_signed: + certs = p7->d.sign->cert; + break; + case NID_pkcs7_signedAndEnveloped: + certs = p7->d.signed_and_enveloped->cert; + break; + default: + certs = NULL; + break; + } + + if (!certs || ((num = sk_X509_num(certs)) == 0)) { + wpa_printf(MSG_INFO, "No certificates found in PKCS#7 object"); + write_result(ctx, "No certificates found in PKCS#7 object"); + goto fail; + } + + if (der_file) { + FILE *f = fopen(der_file, "wb"); + if (f == NULL) + goto fail; + i2d_X509_fp(f, sk_X509_value(certs, 0)); + fclose(f); + } + + if (pem_file) { + out = BIO_new(BIO_s_file()); + if (out == NULL || + BIO_write_filename(out, pem_file) <= 0) + goto fail; + + for (i = 0; i < num; i++) { + X509 *cert = sk_X509_value(certs, i); + X509_print(out, cert); + PEM_write_bio_X509(out, cert); + BIO_puts(out, "\n"); + } + } + + ret = 0; + +fail: + PKCS7_free(p7); + if (out) + BIO_free_all(out); + + return ret; +} + + +int est_load_cacerts(struct hs20_osu_client *ctx, const char *url) +{ + char *buf, *resp; + size_t buflen; + unsigned char *pkcs7; + size_t pkcs7_len, resp_len; + int res; + + buflen = os_strlen(url) + 100; + buf = os_malloc(buflen); + if (buf == NULL) + return -1; + + os_snprintf(buf, buflen, "%s/cacerts", url); + wpa_printf(MSG_INFO, "Download EST cacerts from %s", buf); + write_summary(ctx, "Download EST cacerts from %s", buf); + res = http_download_file(ctx->http, buf, "Cert/est-cacerts.txt", + ctx->ca_fname); + if (res < 0) { + wpa_printf(MSG_INFO, "Failed to download EST cacerts from %s", + buf); + write_result(ctx, "Failed to download EST cacerts from %s", + buf); + os_free(buf); + return -1; + } + os_free(buf); + + resp = os_readfile("Cert/est-cacerts.txt", &resp_len); + if (resp == NULL) { + wpa_printf(MSG_INFO, "Could not read Cert/est-cacerts.txt"); + write_result(ctx, "Could not read EST cacerts"); + return -1; + } + + pkcs7 = base64_decode((unsigned char *) resp, resp_len, &pkcs7_len); + if (pkcs7 && pkcs7_len < resp_len / 2) { + wpa_printf(MSG_INFO, "Too short base64 decode (%u bytes; downloaded %u bytes) - assume this was binary", + (unsigned int) pkcs7_len, (unsigned int) resp_len); + os_free(pkcs7); + pkcs7 = NULL; + } + if (pkcs7 == NULL) { + wpa_printf(MSG_INFO, "EST workaround - Could not decode base64, assume this is DER encoded PKCS7"); + pkcs7 = os_malloc(resp_len); + if (pkcs7) { + os_memcpy(pkcs7, resp, resp_len); + pkcs7_len = resp_len; + } + } + os_free(resp); + + if (pkcs7 == NULL) { + wpa_printf(MSG_INFO, "Could not fetch PKCS7 cacerts"); + write_result(ctx, "Could not fetch EST PKCS#7 cacerts"); + return -1; + } + + res = pkcs7_to_cert(ctx, pkcs7, pkcs7_len, "Cert/est-cacerts.pem", + NULL); + os_free(pkcs7); + if (res < 0) { + wpa_printf(MSG_INFO, "Could not parse CA certs from PKCS#7 cacerts response"); + write_result(ctx, "Could not parse CA certs from EST PKCS#7 cacerts response"); + return -1; + } + unlink("Cert/est-cacerts.txt"); + + return 0; +} + + +/* + * CsrAttrs ::= SEQUENCE SIZE (0..MAX) OF AttrOrOID + * + * AttrOrOID ::= CHOICE { + * oid OBJECT IDENTIFIER, + * attribute Attribute } + * + * Attribute ::= SEQUENCE { + * type OBJECT IDENTIFIER, + * values SET SIZE(1..MAX) OF OBJECT IDENTIFIER } + */ + +typedef struct { + ASN1_OBJECT *type; + STACK_OF(ASN1_OBJECT) *values; +} Attribute; + +typedef struct { + int type; + union { + ASN1_OBJECT *oid; + Attribute *attribute; + } d; +} AttrOrOID; + +typedef struct { + int type; + STACK_OF(AttrOrOID) *attrs; +} CsrAttrs; + +ASN1_SEQUENCE(Attribute) = { + ASN1_SIMPLE(Attribute, type, ASN1_OBJECT), + ASN1_SET_OF(Attribute, values, ASN1_OBJECT) +} ASN1_SEQUENCE_END(Attribute); + +ASN1_CHOICE(AttrOrOID) = { + ASN1_SIMPLE(AttrOrOID, d.oid, ASN1_OBJECT), + ASN1_SIMPLE(AttrOrOID, d.attribute, Attribute) +} ASN1_CHOICE_END(AttrOrOID); + +ASN1_CHOICE(CsrAttrs) = { + ASN1_SEQUENCE_OF(CsrAttrs, attrs, AttrOrOID) +} ASN1_CHOICE_END(CsrAttrs); + +IMPLEMENT_ASN1_FUNCTIONS(CsrAttrs); + + +static void add_csrattrs_oid(struct hs20_osu_client *ctx, ASN1_OBJECT *oid, + STACK_OF(X509_EXTENSION) *exts) +{ + char txt[100]; + int res; + + if (!oid) + return; + + res = OBJ_obj2txt(txt, sizeof(txt), oid, 1); + if (res < 0 || res >= (int) sizeof(txt)) + return; + + if (os_strcmp(txt, "1.2.840.113549.1.9.7") == 0) { + wpa_printf(MSG_INFO, "TODO: csrattr challengePassword"); + } else if (os_strcmp(txt, "1.2.840.113549.1.1.11") == 0) { + wpa_printf(MSG_INFO, "csrattr sha256WithRSAEncryption"); + } else { + wpa_printf(MSG_INFO, "Ignore unsupported csrattr oid %s", txt); + } +} + + +static void add_csrattrs_ext_req(struct hs20_osu_client *ctx, + STACK_OF(ASN1_OBJECT) *values, + STACK_OF(X509_EXTENSION) *exts) +{ + char txt[100]; + int i, num, res; + + num = sk_ASN1_OBJECT_num(values); + for (i = 0; i < num; i++) { + ASN1_OBJECT *oid = sk_ASN1_OBJECT_value(values, i); + + res = OBJ_obj2txt(txt, sizeof(txt), oid, 1); + if (res < 0 || res >= (int) sizeof(txt)) + continue; + + if (os_strcmp(txt, "1.3.6.1.1.1.1.22") == 0) { + wpa_printf(MSG_INFO, "TODO: extReq macAddress"); + } else if (os_strcmp(txt, "1.3.6.1.4.1.40808.1.1.3") == 0) { + wpa_printf(MSG_INFO, "TODO: extReq imei"); + } else if (os_strcmp(txt, "1.3.6.1.4.1.40808.1.1.4") == 0) { + wpa_printf(MSG_INFO, "TODO: extReq meid"); + } else if (os_strcmp(txt, "1.3.6.1.4.1.40808.1.1.5") == 0) { + wpa_printf(MSG_INFO, "TODO: extReq DevId"); + } else { + wpa_printf(MSG_INFO, "Ignore unsupported cstattr extensionsRequest %s", + txt); + } + } +} + + +static void add_csrattrs_attr(struct hs20_osu_client *ctx, Attribute *attr, + STACK_OF(X509_EXTENSION) *exts) +{ + char txt[100], txt2[100]; + int i, num, res; + + if (!attr || !attr->type || !attr->values) + return; + + res = OBJ_obj2txt(txt, sizeof(txt), attr->type, 1); + if (res < 0 || res >= (int) sizeof(txt)) + return; + + if (os_strcmp(txt, "1.2.840.113549.1.9.14") == 0) { + add_csrattrs_ext_req(ctx, attr->values, exts); + return; + } + + num = sk_ASN1_OBJECT_num(attr->values); + for (i = 0; i < num; i++) { + ASN1_OBJECT *oid = sk_ASN1_OBJECT_value(attr->values, i); + + res = OBJ_obj2txt(txt2, sizeof(txt2), oid, 1); + if (res < 0 || res >= (int) sizeof(txt2)) + continue; + + wpa_printf(MSG_INFO, "Ignore unsupported cstattr::attr %s oid %s", + txt, txt2); + } +} + + +static void add_csrattrs(struct hs20_osu_client *ctx, CsrAttrs *csrattrs, + STACK_OF(X509_EXTENSION) *exts) +{ + int i, num; + + if (!csrattrs || ! csrattrs->attrs) + return; + + num = SKM_sk_num(AttrOrOID, csrattrs->attrs); + for (i = 0; i < num; i++) { + AttrOrOID *ao = SKM_sk_value(AttrOrOID, csrattrs->attrs, i); + switch (ao->type) { + case 0: + add_csrattrs_oid(ctx, ao->d.oid, exts); + break; + case 1: + add_csrattrs_attr(ctx, ao->d.attribute, exts); + break; + } + } +} + + +static int generate_csr(struct hs20_osu_client *ctx, char *key_pem, + char *csr_pem, char *est_req, char *old_cert, + CsrAttrs *csrattrs) +{ + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL; + RSA *rsa; + X509_REQ *req = NULL; + int ret = -1; + unsigned int val; + X509_NAME *subj = NULL; + char name[100]; + STACK_OF(X509_EXTENSION) *exts = NULL; + X509_EXTENSION *ex; + BIO *out; + + wpa_printf(MSG_INFO, "Generate RSA private key"); + write_summary(ctx, "Generate RSA private key"); + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!pctx) + return -1; + + if (EVP_PKEY_keygen_init(pctx) <= 0) + goto fail; + + if (EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048) <= 0) + goto fail; + + if (EVP_PKEY_keygen(pctx, &pkey) <= 0) + goto fail; + EVP_PKEY_CTX_free(pctx); + pctx = NULL; + + rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) + goto fail; + + if (key_pem) { + FILE *f = fopen(key_pem, "wb"); + if (f == NULL) + goto fail; + if (!PEM_write_RSAPrivateKey(f, rsa, NULL, NULL, 0, NULL, + NULL)) { + wpa_printf(MSG_INFO, "Could not write private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + fclose(f); + goto fail; + } + fclose(f); + } + + wpa_printf(MSG_INFO, "Generate CSR"); + write_summary(ctx, "Generate CSR"); + req = X509_REQ_new(); + if (req == NULL) + goto fail; + + if (old_cert) { + FILE *f; + X509 *cert; + int res; + + f = fopen(old_cert, "r"); + if (f == NULL) + goto fail; + cert = PEM_read_X509(f, NULL, NULL, NULL); + fclose(f); + + if (cert == NULL) + goto fail; + res = X509_REQ_set_subject_name(req, + X509_get_subject_name(cert)); + X509_free(cert); + if (!res) + goto fail; + } else { + os_get_random((u8 *) &val, sizeof(val)); + os_snprintf(name, sizeof(name), "cert-user-%u", val); + subj = X509_NAME_new(); + if (subj == NULL || + !X509_NAME_add_entry_by_txt(subj, "CN", MBSTRING_ASC, + (unsigned char *) name, + -1, -1, 0) || + !X509_REQ_set_subject_name(req, subj)) + goto fail; + X509_NAME_free(subj); + subj = NULL; + } + + if (!X509_REQ_set_pubkey(req, pkey)) + goto fail; + + exts = sk_X509_EXTENSION_new_null(); + if (!exts) + goto fail; + + ex = X509V3_EXT_conf_nid(NULL, NULL, NID_basic_constraints, + "CA:FALSE"); + if (ex == NULL || + !sk_X509_EXTENSION_push(exts, ex)) + goto fail; + + ex = X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, + "nonRepudiation,digitalSignature,keyEncipherment"); + if (ex == NULL || + !sk_X509_EXTENSION_push(exts, ex)) + goto fail; + + ex = X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, + "1.3.6.1.4.1.40808.1.1.2"); + if (ex == NULL || + !sk_X509_EXTENSION_push(exts, ex)) + goto fail; + + add_csrattrs(ctx, csrattrs, exts); + + if (!X509_REQ_add_extensions(req, exts)) + goto fail; + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + exts = NULL; + + if (!X509_REQ_sign(req, pkey, EVP_sha256())) + goto fail; + + out = BIO_new(BIO_s_mem()); + if (out) { + char *txt; + size_t rlen; + + X509_REQ_print(out, req); + rlen = BIO_ctrl_pending(out); + txt = os_malloc(rlen + 1); + if (txt) { + int res = BIO_read(out, txt, rlen); + if (res > 0) { + txt[res] = '\0'; + wpa_printf(MSG_MSGDUMP, "OpenSSL: Certificate request:\n%s", + txt); + } + os_free(txt); + } + BIO_free(out); + } + + if (csr_pem) { + FILE *f = fopen(csr_pem, "w"); + if (f == NULL) + goto fail; + X509_REQ_print_fp(f, req); + if (!PEM_write_X509_REQ(f, req)) { + fclose(f); + goto fail; + } + fclose(f); + } + + if (est_req) { + BIO *mem = BIO_new(BIO_s_mem()); + BUF_MEM *ptr; + char *pos, *end, *buf_end; + FILE *f; + + if (mem == NULL) + goto fail; + if (!PEM_write_bio_X509_REQ(mem, req)) { + BIO_free(mem); + goto fail; + } + + BIO_get_mem_ptr(mem, &ptr); + pos = ptr->data; + buf_end = pos + ptr->length; + + /* Remove START/END lines */ + while (pos < buf_end && *pos != '\n') + pos++; + if (pos == buf_end) { + BIO_free(mem); + goto fail; + } + pos++; + + end = pos; + while (end < buf_end && *end != '-') + end++; + + f = fopen(est_req, "w"); + if (f == NULL) { + BIO_free(mem); + goto fail; + } + fwrite(pos, end - pos, 1, f); + fclose(f); + + BIO_free(mem); + } + + ret = 0; +fail: + if (exts) + sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free); + if (subj) + X509_NAME_free(subj); + if (req) + X509_REQ_free(req); + if (pkey) + EVP_PKEY_free(pkey); + if (pctx) + EVP_PKEY_CTX_free(pctx); + return ret; +} + + +int est_build_csr(struct hs20_osu_client *ctx, const char *url) +{ + char *buf; + size_t buflen; + int res; + char old_cert_buf[200]; + char *old_cert = NULL; + CsrAttrs *csrattrs = NULL; + + buflen = os_strlen(url) + 100; + buf = os_malloc(buflen); + if (buf == NULL) + return -1; + + os_snprintf(buf, buflen, "%s/csrattrs", url); + wpa_printf(MSG_INFO, "Download csrattrs from %s", buf); + write_summary(ctx, "Download EST csrattrs from %s", buf); + res = http_download_file(ctx->http, buf, "Cert/est-csrattrs.txt", + ctx->ca_fname); + os_free(buf); + if (res < 0) { + wpa_printf(MSG_INFO, "Failed to download EST csrattrs - assume no extra attributes are needed"); + } else { + size_t resp_len; + char *resp; + unsigned char *attrs; + const unsigned char *pos; + size_t attrs_len; + + resp = os_readfile("Cert/est-csrattrs.txt", &resp_len); + if (resp == NULL) { + wpa_printf(MSG_INFO, "Could not read csrattrs"); + return -1; + } + + attrs = base64_decode((unsigned char *) resp, resp_len, + &attrs_len); + os_free(resp); + + if (attrs == NULL) { + wpa_printf(MSG_INFO, "Could not base64 decode csrattrs"); + return -1; + } + unlink("Cert/est-csrattrs.txt"); + + pos = attrs; + csrattrs = d2i_CsrAttrs(NULL, &pos, attrs_len); + os_free(attrs); + if (csrattrs == NULL) { + wpa_printf(MSG_INFO, "Failed to parse csrattrs ASN.1"); + /* Continue assuming no additional requirements */ + } + } + + if (ctx->client_cert_present) { + os_snprintf(old_cert_buf, sizeof(old_cert_buf), + "SP/%s/client-cert.pem", ctx->fqdn); + old_cert = old_cert_buf; + } + + res = generate_csr(ctx, "Cert/privkey-plain.pem", "Cert/est-req.pem", + "Cert/est-req.b64", old_cert, csrattrs); + if (csrattrs) + CsrAttrs_free(csrattrs); + + return res; +} + + +int est_simple_enroll(struct hs20_osu_client *ctx, const char *url, + const char *user, const char *pw) +{ + char *buf, *resp, *req, *req2; + size_t buflen, resp_len, len, pkcs7_len; + unsigned char *pkcs7; + FILE *f; + char client_cert_buf[200]; + char client_key_buf[200]; + const char *client_cert = NULL, *client_key = NULL; + int res; + + req = os_readfile("Cert/est-req.b64", &len); + if (req == NULL) { + wpa_printf(MSG_INFO, "Could not read Cert/req.b64"); + return -1; + } + req2 = os_realloc(req, len + 1); + if (req2 == NULL) { + os_free(req); + return -1; + } + req2[len] = '\0'; + req = req2; + wpa_printf(MSG_DEBUG, "EST simpleenroll request: %s", req); + + buflen = os_strlen(url) + 100; + buf = os_malloc(buflen); + if (buf == NULL) { + os_free(req); + return -1; + } + + if (ctx->client_cert_present) { + os_snprintf(buf, buflen, "%s/simplereenroll", url); + os_snprintf(client_cert_buf, sizeof(client_cert_buf), + "SP/%s/client-cert.pem", ctx->fqdn); + client_cert = client_cert_buf; + os_snprintf(client_key_buf, sizeof(client_key_buf), + "SP/%s/client-key.pem", ctx->fqdn); + client_key = client_key_buf; + } else + os_snprintf(buf, buflen, "%s/simpleenroll", url); + wpa_printf(MSG_INFO, "EST simpleenroll URL: %s", buf); + write_summary(ctx, "EST simpleenroll URL: %s", buf); + resp = http_post(ctx->http, buf, req, "application/pkcs10", + "Content-Transfer-Encoding: base64", + ctx->ca_fname, user, pw, client_cert, client_key, + &resp_len); + os_free(buf); + if (resp == NULL) { + wpa_printf(MSG_INFO, "EST certificate enrollment failed"); + write_result(ctx, "EST certificate enrollment failed"); + return -1; + } + wpa_printf(MSG_DEBUG, "EST simpleenroll response: %s", resp); + f = fopen("Cert/est-resp.raw", "w"); + if (f) { + fwrite(resp, resp_len, 1, f); + fclose(f); + } + + pkcs7 = base64_decode((unsigned char *) resp, resp_len, &pkcs7_len); + if (pkcs7 == NULL) { + wpa_printf(MSG_INFO, "EST workaround - Could not decode base64, assume this is DER encoded PKCS7"); + pkcs7 = os_malloc(resp_len); + if (pkcs7) { + os_memcpy(pkcs7, resp, resp_len); + pkcs7_len = resp_len; + } + } + os_free(resp); + + if (pkcs7 == NULL) { + wpa_printf(MSG_INFO, "Failed to parse simpleenroll base64 response"); + write_result(ctx, "Failed to parse EST simpleenroll base64 response"); + return -1; + } + + res = pkcs7_to_cert(ctx, pkcs7, pkcs7_len, "Cert/est_cert.pem", + "Cert/est_cert.der"); + os_free(pkcs7); + + if (res < 0) { + wpa_printf(MSG_INFO, "EST: Failed to extract certificate from PKCS7 file"); + write_result(ctx, "EST: Failed to extract certificate from EST PKCS7 file"); + return -1; + } + + wpa_printf(MSG_INFO, "EST simple%senroll completed successfully", + ctx->client_cert_present ? "re" : ""); + write_summary(ctx, "EST simple%senroll completed successfully", + ctx->client_cert_present ? "re" : ""); + + return 0; +} diff --git a/hs20/client/oma_dm_client.c b/hs20/client/oma_dm_client.c new file mode 100644 index 00000000..8fc350b1 --- /dev/null +++ b/hs20/client/oma_dm_client.c @@ -0,0 +1,1374 @@ +/* + * Hotspot 2.0 - OMA DM client + * Copyright (c) 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 "common.h" +#include "wpa_helpers.h" +#include "xml-utils.h" +#include "http-utils.h" +#include "utils/browser.h" +#include "osu_client.h" + + +#define DM_SERVER_INITIATED_MGMT 1200 +#define DM_CLIENT_INITIATED_MGMT 1201 +#define DM_GENERIC_ALERT 1226 + +/* OMA-TS-SyncML-RepPro-V1_2_2 - 10. Response Status Codes */ +#define DM_RESP_OK 200 +#define DM_RESP_AUTH_ACCEPTED 212 +#define DM_RESP_CHUNKED_ITEM_ACCEPTED 213 +#define DM_RESP_NOT_EXECUTED 215 +#define DM_RESP_ATOMIC_ROLL_BACK_OK 216 +#define DM_RESP_NOT_MODIFIED 304 +#define DM_RESP_BAD_REQUEST 400 +#define DM_RESP_UNAUTHORIZED 401 +#define DM_RESP_FORBIDDEN 403 +#define DM_RESP_NOT_FOUND 404 +#define DM_RESP_COMMAND_NOT_ALLOWED 405 +#define DM_RESP_OPTIONAL_FEATURE_NOT_SUPPORTED 406 +#define DM_RESP_MISSING_CREDENTIALS 407 +#define DM_RESP_CONFLICT 409 +#define DM_RESP_GONE 410 +#define DM_RESP_INCOMPLETE_COMMAND 412 +#define DM_RESP_REQ_ENTITY_TOO_LARGE 413 +#define DM_RESP_URI_TOO_LONG 414 +#define DM_RESP_UNSUPPORTED_MEDIA_TYPE_OR_FORMAT 415 +#define DM_RESP_REQ_TOO_BIG 416 +#define DM_RESP_ALREADY_EXISTS 418 +#define DM_RESP_DEVICE_FULL 420 +#define DM_RESP_SIZE_MISMATCH 424 +#define DM_RESP_PERMISSION_DENIED 425 +#define DM_RESP_COMMAND_FAILED 500 +#define DM_RESP_COMMAND_NOT_IMPLEMENTED 501 +#define DM_RESP_ATOMIC_ROLL_BACK_FAILED 516 + +#define DM_HS20_SUBSCRIPTION_CREATION \ + "org.wi-fi.hotspot2dot0.SubscriptionCreation" +#define DM_HS20_SUBSCRIPTION_PROVISIONING \ + "org.wi-fi.hotspot2dot0.SubscriptionProvisioning" +#define DM_HS20_SUBSCRIPTION_REMEDIATION \ + "org.wi-fi.hotspot2dot0.SubscriptionRemediation" +#define DM_HS20_POLICY_UPDATE \ + "org.wi-fi.hotspot2dot0.PolicyUpdate" + +#define DM_URI_PPS "./Wi-Fi/org.wi-fi/PerProviderSubscription" +#define DM_URI_LAUNCH_BROWSER \ + "./DevDetail/Ext/org.wi-fi/Wi-Fi/Ops/launchBrowserToURI" + + +static void add_item(struct hs20_osu_client *ctx, xml_node_t *parent, + const char *locuri, const char *data); + + +static const char * int2str(int val) +{ + static char buf[20]; + snprintf(buf, sizeof(buf), "%d", val); + return buf; +} + + +static char * oma_dm_get_target_locuri(struct hs20_osu_client *ctx, + xml_node_t *node) +{ + xml_node_t *locuri; + char *uri, *ret = NULL; + + locuri = get_node(ctx->xml, node, "Item/Target/LocURI"); + if (locuri == NULL) + return NULL; + + uri = xml_node_get_text(ctx->xml, locuri); + if (uri) + ret = os_strdup(uri); + xml_node_get_text_free(ctx->xml, uri); + return ret; +} + + +static void oma_dm_add_locuri(struct hs20_osu_client *ctx, xml_node_t *parent, + const char *element, const char *uri) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, element); + if (node == NULL) + return; + xml_node_create_text(ctx->xml, node, NULL, "LocURI", uri); +} + + +static xml_node_t * oma_dm_build_hdr(struct hs20_osu_client *ctx, + const char *url, int msgid) +{ + xml_node_t *syncml, *synchdr; + xml_namespace_t *ns; + + syncml = xml_node_create_root(ctx->xml, "SYNCML:SYNCML1.2", NULL, &ns, + "SyncML"); + + synchdr = xml_node_create(ctx->xml, syncml, NULL, "SyncHdr"); + xml_node_create_text(ctx->xml, synchdr, NULL, "VerDTD", "1.2"); + xml_node_create_text(ctx->xml, synchdr, NULL, "VerProto", "DM/1.2"); + xml_node_create_text(ctx->xml, synchdr, NULL, "SessionID", "1"); + xml_node_create_text(ctx->xml, synchdr, NULL, "MsgID", int2str(msgid)); + + oma_dm_add_locuri(ctx, synchdr, "Target", url); + oma_dm_add_locuri(ctx, synchdr, "Source", ctx->devid); + + return syncml; +} + + +static void oma_dm_add_cmdid(struct hs20_osu_client *ctx, xml_node_t *parent, + int cmdid) +{ + xml_node_create_text(ctx->xml, parent, NULL, "CmdID", int2str(cmdid)); +} + + +static xml_node_t * add_alert(struct hs20_osu_client *ctx, xml_node_t *parent, + int cmdid, int data) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, "Alert"); + if (node == NULL) + return NULL; + oma_dm_add_cmdid(ctx, node, cmdid); + xml_node_create_text(ctx->xml, node, NULL, "Data", int2str(data)); + + return node; +} + + +static xml_node_t * add_status(struct hs20_osu_client *ctx, xml_node_t *parent, + int msgref, int cmdref, int cmdid, + const char *cmd, int data, const char *targetref) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, "Status"); + if (node == NULL) + return NULL; + oma_dm_add_cmdid(ctx, node, cmdid); + xml_node_create_text(ctx->xml, node, NULL, "MsgRef", int2str(msgref)); + if (cmdref) + xml_node_create_text(ctx->xml, node, NULL, "CmdRef", + int2str(cmdref)); + xml_node_create_text(ctx->xml, node, NULL, "Cmd", cmd); + xml_node_create_text(ctx->xml, node, NULL, "Data", int2str(data)); + if (targetref) { + xml_node_create_text(ctx->xml, node, NULL, "TargetRef", + targetref); + } + + return node; +} + + +static xml_node_t * add_results(struct hs20_osu_client *ctx, xml_node_t *parent, + int msgref, int cmdref, int cmdid, + const char *locuri, const char *data) +{ + xml_node_t *node; + + node = xml_node_create(ctx->xml, parent, NULL, "Results"); + if (node == NULL) + return NULL; + + oma_dm_add_cmdid(ctx, node, cmdid); + xml_node_create_text(ctx->xml, node, NULL, "MsgRef", int2str(msgref)); + xml_node_create_text(ctx->xml, node, NULL, "CmdRef", int2str(cmdref)); + add_item(ctx, node, locuri, data); + + return node; +} + + +static char * mo_str(struct hs20_osu_client *ctx, const char *urn, + const char *fname) +{ + xml_node_t *fnode, *tnds; + char *str; + + fnode = node_from_file(ctx->xml, fname); + if (!fnode) + return NULL; + tnds = mo_to_tnds(ctx->xml, fnode, 0, urn, "syncml:dmddf1.2"); + xml_node_free(ctx->xml, fnode); + if (!tnds) + return NULL; + + str = xml_node_to_str(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (str == NULL) + return NULL; + wpa_printf(MSG_INFO, "MgmtTree: %s", str); + + return str; +} + + +static void add_item(struct hs20_osu_client *ctx, xml_node_t *parent, + const char *locuri, const char *data) +{ + xml_node_t *item, *node; + + item = xml_node_create(ctx->xml, parent, NULL, "Item"); + oma_dm_add_locuri(ctx, item, "Source", locuri); + node = xml_node_create(ctx->xml, item, NULL, "Meta"); + xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Format", + "Chr"); + xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Type", + "text/plain"); + xml_node_create_text(ctx->xml, item, NULL, "Data", data); +} + + +static void add_replace_devinfo(struct hs20_osu_client *ctx, xml_node_t *parent, + int cmdid) +{ + xml_node_t *info, *child, *replace; + const char *name; + char locuri[200], *txt; + + info = node_from_file(ctx->xml, "devinfo.xml"); + if (info == NULL) { + wpa_printf(MSG_INFO, "Could not read devinfo.xml"); + return; + } + + replace = xml_node_create(ctx->xml, parent, NULL, "Replace"); + if (replace == NULL) { + xml_node_free(ctx->xml, info); + return; + } + oma_dm_add_cmdid(ctx, replace, cmdid); + + xml_node_for_each_child(ctx->xml, child, info) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + os_snprintf(locuri, sizeof(locuri), "./DevInfo/%s", name); + txt = xml_node_get_text(ctx->xml, child); + if (txt) { + add_item(ctx, replace, locuri, txt); + xml_node_get_text_free(ctx->xml, txt); + } + } + + xml_node_free(ctx->xml, info); +} + + +static void oma_dm_add_hs20_generic_alert(struct hs20_osu_client *ctx, + xml_node_t *syncbody, + int cmdid, const char *oper, + const char *data) +{ + xml_node_t *node, *item; + char buf[200]; + + node = add_alert(ctx, syncbody, cmdid, DM_GENERIC_ALERT); + + item = xml_node_create(ctx->xml, node, NULL, "Item"); + oma_dm_add_locuri(ctx, item, "Source", DM_URI_PPS); + node = xml_node_create(ctx->xml, item, NULL, "Meta"); + snprintf(buf, sizeof(buf), "Reversed-Domain-Name: %s", oper); + xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Type", buf); + xml_node_create_text_ns(ctx->xml, node, "syncml:metinf", "Format", + "xml"); + xml_node_create_text(ctx->xml, item, NULL, "Data", data); +} + + +static xml_node_t * build_oma_dm_1(struct hs20_osu_client *ctx, + const char *url, int msgid, const char *oper) +{ + xml_node_t *syncml, *syncbody; + char *str; + int cmdid = 0; + + syncml = oma_dm_build_hdr(ctx, url, msgid); + if (syncml == NULL) + return NULL; + + syncbody = xml_node_create(ctx->xml, syncml, NULL, "SyncBody"); + if (syncbody == NULL) { + xml_node_free(ctx->xml, syncml); + return NULL; + } + + cmdid++; + add_alert(ctx, syncbody, cmdid, DM_CLIENT_INITIATED_MGMT); + + str = mo_str(ctx, NULL, "devdetail.xml"); + if (str == NULL) { + xml_node_free(ctx->xml, syncml); + return NULL; + } + cmdid++; + oma_dm_add_hs20_generic_alert(ctx, syncbody, cmdid, oper, str); + os_free(str); + + cmdid++; + add_replace_devinfo(ctx, syncbody, cmdid); + + xml_node_create(ctx->xml, syncbody, NULL, "Final"); + + return syncml; +} + + +static xml_node_t * build_oma_dm_1_sub_reg(struct hs20_osu_client *ctx, + const char *url, int msgid) +{ + xml_node_t *syncml; + + syncml = build_oma_dm_1(ctx, url, msgid, DM_HS20_SUBSCRIPTION_CREATION); + if (syncml) + debug_dump_node(ctx, "OMA-DM Package 1 (sub reg)", syncml); + + return syncml; +} + + +static xml_node_t * build_oma_dm_1_sub_prov(struct hs20_osu_client *ctx, + const char *url, int msgid) +{ + xml_node_t *syncml; + + syncml = build_oma_dm_1(ctx, url, msgid, + DM_HS20_SUBSCRIPTION_PROVISIONING); + if (syncml) + debug_dump_node(ctx, "OMA-DM Package 1 (sub prov)", syncml); + + return syncml; +} + + +static xml_node_t * build_oma_dm_1_pol_upd(struct hs20_osu_client *ctx, + const char *url, int msgid) +{ + xml_node_t *syncml; + + syncml = build_oma_dm_1(ctx, url, msgid, DM_HS20_POLICY_UPDATE); + if (syncml) + debug_dump_node(ctx, "OMA-DM Package 1 (pol upd)", syncml); + + return syncml; +} + + +static xml_node_t * build_oma_dm_1_sub_rem(struct hs20_osu_client *ctx, + const char *url, int msgid) +{ + xml_node_t *syncml; + + syncml = build_oma_dm_1(ctx, url, msgid, + DM_HS20_SUBSCRIPTION_REMEDIATION); + if (syncml) + debug_dump_node(ctx, "OMA-DM Package 1 (sub rem)", syncml); + + return syncml; +} + + +static int oma_dm_exec_browser(struct hs20_osu_client *ctx, xml_node_t *exec) +{ + xml_node_t *node; + char *data; + int res; + + node = get_node(ctx->xml, exec, "Item/Data"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Data node found"); + return DM_RESP_BAD_REQUEST; + } + + data = xml_node_get_text(ctx->xml, node); + wpa_printf(MSG_INFO, "Data: %s", data); + wpa_printf(MSG_INFO, "Launch browser to URI '%s'", data); + write_summary(ctx, "Launch browser to URI '%s'", data); + res = hs20_web_browser(data); + xml_node_get_text_free(ctx->xml, data); + if (res > 0) { + wpa_printf(MSG_INFO, "User response in browser completed successfully"); + write_summary(ctx, "User response in browser completed successfully"); + return DM_RESP_OK; + } else { + wpa_printf(MSG_INFO, "Failed to receive user response"); + write_summary(ctx, "Failed to receive user response"); + return DM_RESP_COMMAND_FAILED; + } +} + + +static int oma_dm_exec_get_cert(struct hs20_osu_client *ctx, xml_node_t *exec) +{ + xml_node_t *node, *getcert; + char *data; + const char *name; + int res; + + wpa_printf(MSG_INFO, "Client certificate enrollment"); + write_summary(ctx, "Client certificate enrollment"); + + node = get_node(ctx->xml, exec, "Item/Data"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Data node found"); + return DM_RESP_BAD_REQUEST; + } + + data = xml_node_get_text(ctx->xml, node); + wpa_printf(MSG_INFO, "Data: %s", data); + getcert = xml_node_from_buf(ctx->xml, data); + xml_node_get_text_free(ctx->xml, data); + + if (getcert == NULL) { + wpa_printf(MSG_INFO, "Could not parse Item/Data node contents"); + return DM_RESP_BAD_REQUEST; + } + + debug_dump_node(ctx, "OMA-DM getCertificate", getcert); + + name = xml_node_get_localname(ctx->xml, getcert); + if (name == NULL || os_strcasecmp(name, "getCertificate") != 0) { + wpa_printf(MSG_INFO, "Unexpected getCertificate node name '%s'", + name); + return DM_RESP_BAD_REQUEST; + } + + res = osu_get_certificate(ctx, getcert); + + xml_node_free(ctx->xml, getcert); + + return res == 0 ? DM_RESP_OK : DM_RESP_COMMAND_FAILED; +} + + +static int oma_dm_exec(struct hs20_osu_client *ctx, xml_node_t *exec) +{ + char *locuri; + int ret; + + locuri = oma_dm_get_target_locuri(ctx, exec); + if (locuri == NULL) { + wpa_printf(MSG_INFO, "No Target LocURI node found"); + return DM_RESP_BAD_REQUEST; + } + + wpa_printf(MSG_INFO, "Target LocURI: %s", locuri); + + if (os_strcasecmp(locuri, "./DevDetail/Ext/org.wi-fi/Wi-Fi/Ops/" + "launchBrowserToURI") == 0) { + ret = oma_dm_exec_browser(ctx, exec); + } else if (os_strcasecmp(locuri, "./DevDetail/Ext/org.wi-fi/Wi-Fi/Ops/" + "getCertificate") == 0) { + ret = oma_dm_exec_get_cert(ctx, exec); + } else { + wpa_printf(MSG_INFO, "Unsupported exec Target LocURI"); + ret = DM_RESP_NOT_FOUND; + } + os_free(locuri); + + return ret; +} + + +static int oma_dm_run_add(struct hs20_osu_client *ctx, const char *locuri, + xml_node_t *add, xml_node_t *pps, + const char *pps_fname) +{ + const char *pos; + size_t fqdn_len; + xml_node_t *node, *tnds, *unode, *pps_node; + char *data, *uri, *upos, *end; + int use_tnds = 0; + size_t uri_len; + + wpa_printf(MSG_INFO, "Add command target LocURI: %s", locuri); + + if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Do not allow Add outside ./Wi-Fi"); + return DM_RESP_PERMISSION_DENIED; + } + pos = locuri + 8; + + if (ctx->fqdn == NULL) + return DM_RESP_COMMAND_FAILED; + fqdn_len = os_strlen(ctx->fqdn); + if (os_strncasecmp(pos, ctx->fqdn, fqdn_len) != 0 || + pos[fqdn_len] != '/') { + wpa_printf(MSG_INFO, "Do not allow Add outside ./Wi-Fi/%s", + ctx->fqdn); + return DM_RESP_PERMISSION_DENIED; + } + pos += fqdn_len + 1; + + if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) { + wpa_printf(MSG_INFO, + "Do not allow Add outside ./Wi-Fi/%s/PerProviderSubscription", + ctx->fqdn); + return DM_RESP_PERMISSION_DENIED; + } + pos += 24; + + wpa_printf(MSG_INFO, "Add command for PPS node %s", pos); + + pps_node = get_node(ctx->xml, pps, pos); + if (pps_node) { + wpa_printf(MSG_INFO, "Specified PPS node exists already"); + return DM_RESP_ALREADY_EXISTS; + } + + uri = os_strdup(pos); + if (uri == NULL) + return DM_RESP_COMMAND_FAILED; + while (!pps_node) { + upos = os_strrchr(uri, '/'); + if (!upos) + break; + upos[0] = '\0'; + pps_node = get_node(ctx->xml, pps, uri); + wpa_printf(MSG_INFO, "Node %s %s", uri, + pps_node ? "exists" : "does not exist"); + } + + wpa_printf(MSG_INFO, "Parent URI: %s", uri); + + if (!pps_node) { + /* Add at root of PPS MO */ + pps_node = pps; + } + + uri_len = os_strlen(uri); + os_strlcpy(uri, pos + uri_len, os_strlen(pos)); + upos = uri; + while (*upos == '/') + upos++; + wpa_printf(MSG_INFO, "Nodes to add: %s", upos); + + for (;;) { + end = os_strchr(upos, '/'); + if (!end) + break; + *end = '\0'; + wpa_printf(MSG_INFO, "Adding interim node %s", upos); + pps_node = xml_node_create(ctx->xml, pps_node, NULL, upos); + if (pps_node == NULL) { + os_free(uri); + return DM_RESP_COMMAND_FAILED; + } + upos = end + 1; + } + + wpa_printf(MSG_INFO, "Adding node %s", upos); + + node = get_node(ctx->xml, add, "Item/Meta/Type"); + if (node) { + char *type; + type = xml_node_get_text(ctx->xml, node); + use_tnds = node && + os_strstr(type, "application/vnd.syncml.dmtnds+xml"); + } + + node = get_node(ctx->xml, add, "Item/Data"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Add/Item/Data found"); + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + + data = xml_node_get_text(ctx->xml, node); + if (data == NULL) { + wpa_printf(MSG_INFO, "Could not get Add/Item/Data text"); + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + + wpa_printf(MSG_DEBUG, "Add/Item/Data: %s", data); + + if (use_tnds) { + tnds = xml_node_from_buf(ctx->xml, data); + xml_node_get_text_free(ctx->xml, data); + if (tnds == NULL) { + wpa_printf(MSG_INFO, + "Could not parse Add/Item/Data text"); + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + + unode = tnds_to_mo(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (unode == NULL) { + wpa_printf(MSG_INFO, "Could not parse TNDS text"); + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + + debug_dump_node(ctx, "Parsed TNDS", unode); + + xml_node_add_child(ctx->xml, pps_node, unode); + } else { + /* TODO: What to do here? */ + os_free(uri); + return DM_RESP_BAD_REQUEST; + } + + os_free(uri); + + if (update_pps_file(ctx, pps_fname, pps) < 0) + return DM_RESP_COMMAND_FAILED; + + ctx->pps_updated = 1; + + return DM_RESP_OK; +} + + +static int oma_dm_add(struct hs20_osu_client *ctx, xml_node_t *add, + xml_node_t *pps, const char *pps_fname) +{ + xml_node_t *node; + char *locuri; + char fname[300]; + int ret; + + node = get_node(ctx->xml, add, "Item/Target/LocURI"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Target LocURI node found"); + return DM_RESP_BAD_REQUEST; + } + locuri = xml_node_get_text(ctx->xml, node); + wpa_printf(MSG_INFO, "Target LocURI: %s", locuri); + if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Unsupported Add Target LocURI"); + xml_node_get_text_free(ctx->xml, locuri); + return DM_RESP_PERMISSION_DENIED; + } + + node = get_node(ctx->xml, add, "Item/Data"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Data node found"); + xml_node_get_text_free(ctx->xml, locuri); + return DM_RESP_BAD_REQUEST; + } + + if (pps_fname && os_file_exists(pps_fname)) { + ret = oma_dm_run_add(ctx, locuri, add, pps, pps_fname); + if (ret != DM_RESP_OK) { + xml_node_get_text_free(ctx->xml, locuri); + return ret; + } + ret = 0; + os_strlcpy(fname, pps_fname, sizeof(fname)); + } else + ret = hs20_add_pps_mo(ctx, locuri, node, fname, sizeof(fname)); + xml_node_get_text_free(ctx->xml, locuri); + if (ret < 0) + return ret == -2 ? DM_RESP_ALREADY_EXISTS : + DM_RESP_COMMAND_FAILED; + + if (ctx->no_reconnect == 2) { + os_snprintf(ctx->pps_fname, sizeof(ctx->pps_fname), "%s", + fname); + ctx->pps_cred_set = 1; + return DM_RESP_OK; + } + + wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials"); + cmd_set_pps(ctx, fname); + + if (ctx->no_reconnect) + return DM_RESP_OK; + + wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration"); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) + wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect"); + + return DM_RESP_OK; +} + + +static int oma_dm_replace(struct hs20_osu_client *ctx, xml_node_t *replace, + xml_node_t *pps, const char *pps_fname) +{ + char *locuri, *pos; + size_t fqdn_len; + xml_node_t *node, *tnds, *unode, *pps_node, *parent; + char *data; + int use_tnds = 0; + + locuri = oma_dm_get_target_locuri(ctx, replace); + if (locuri == NULL) + return DM_RESP_BAD_REQUEST; + + wpa_printf(MSG_INFO, "Replace command target LocURI: %s", locuri); + if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Do not allow Replace outside ./Wi-Fi"); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos = locuri + 8; + + if (ctx->fqdn == NULL) { + os_free(locuri); + return DM_RESP_COMMAND_FAILED; + } + fqdn_len = os_strlen(ctx->fqdn); + if (os_strncasecmp(pos, ctx->fqdn, fqdn_len) != 0 || + pos[fqdn_len] != '/') { + wpa_printf(MSG_INFO, "Do not allow Replace outside ./Wi-Fi/%s", + ctx->fqdn); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos += fqdn_len + 1; + + if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) { + wpa_printf(MSG_INFO, + "Do not allow Replace outside ./Wi-Fi/%s/PerProviderSubscription", + ctx->fqdn); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos += 24; + + wpa_printf(MSG_INFO, "Replace command for PPS node %s", pos); + + pps_node = get_node(ctx->xml, pps, pos); + if (pps_node == NULL) { + wpa_printf(MSG_INFO, "Specified PPS node not found"); + os_free(locuri); + return DM_RESP_NOT_FOUND; + } + + node = get_node(ctx->xml, replace, "Item/Meta/Type"); + if (node) { + char *type; + type = xml_node_get_text(ctx->xml, node); + use_tnds = node && + os_strstr(type, "application/vnd.syncml.dmtnds+xml"); + } + + node = get_node(ctx->xml, replace, "Item/Data"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No Replace/Item/Data found"); + os_free(locuri); + return DM_RESP_BAD_REQUEST; + } + + data = xml_node_get_text(ctx->xml, node); + if (data == NULL) { + wpa_printf(MSG_INFO, "Could not get Replace/Item/Data text"); + os_free(locuri); + return DM_RESP_BAD_REQUEST; + } + + wpa_printf(MSG_DEBUG, "Replace/Item/Data: %s", data); + + if (use_tnds) { + tnds = xml_node_from_buf(ctx->xml, data); + xml_node_get_text_free(ctx->xml, data); + if (tnds == NULL) { + wpa_printf(MSG_INFO, + "Could not parse Replace/Item/Data text"); + os_free(locuri); + return DM_RESP_BAD_REQUEST; + } + + unode = tnds_to_mo(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (unode == NULL) { + wpa_printf(MSG_INFO, "Could not parse TNDS text"); + os_free(locuri); + return DM_RESP_BAD_REQUEST; + } + + debug_dump_node(ctx, "Parsed TNDS", unode); + + parent = xml_node_get_parent(ctx->xml, pps_node); + xml_node_detach(ctx->xml, pps_node); + xml_node_add_child(ctx->xml, parent, unode); + } else { + xml_node_set_text(ctx->xml, pps_node, data); + xml_node_get_text_free(ctx->xml, data); + } + + os_free(locuri); + + if (update_pps_file(ctx, pps_fname, pps) < 0) + return DM_RESP_COMMAND_FAILED; + + ctx->pps_updated = 1; + + return DM_RESP_OK; +} + + +static int oma_dm_get(struct hs20_osu_client *ctx, xml_node_t *get, + xml_node_t *pps, const char *pps_fname, char **value) +{ + char *locuri, *pos; + size_t fqdn_len; + xml_node_t *pps_node; + const char *name; + + *value = NULL; + + locuri = oma_dm_get_target_locuri(ctx, get); + if (locuri == NULL) + return DM_RESP_BAD_REQUEST; + + wpa_printf(MSG_INFO, "Get command target LocURI: %s", locuri); + if (os_strncasecmp(locuri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Do not allow Get outside ./Wi-Fi"); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos = locuri + 8; + + if (ctx->fqdn == NULL) + return DM_RESP_COMMAND_FAILED; + fqdn_len = os_strlen(ctx->fqdn); + if (os_strncasecmp(pos, ctx->fqdn, fqdn_len) != 0 || + pos[fqdn_len] != '/') { + wpa_printf(MSG_INFO, "Do not allow Get outside ./Wi-Fi/%s", + ctx->fqdn); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos += fqdn_len + 1; + + if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) { + wpa_printf(MSG_INFO, + "Do not allow Get outside ./Wi-Fi/%s/PerProviderSubscription", + ctx->fqdn); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + pos += 24; + + wpa_printf(MSG_INFO, "Get command for PPS node %s", pos); + + pps_node = get_node(ctx->xml, pps, pos); + if (pps_node == NULL) { + wpa_printf(MSG_INFO, "Specified PPS node not found"); + os_free(locuri); + return DM_RESP_NOT_FOUND; + } + + name = xml_node_get_localname(ctx->xml, pps_node); + wpa_printf(MSG_INFO, "Get command returned node with name '%s'", name); + if (os_strcasecmp(name, "Password") == 0) { + wpa_printf(MSG_INFO, "Do not allow Get for Password node"); + os_free(locuri); + return DM_RESP_PERMISSION_DENIED; + } + + /* + * TODO: No support for DMTNDS, so if interior node, reply with a + * list of children node names in Results element. The child list type is + * defined in [DMTND]. + */ + + *value = xml_node_get_text(ctx->xml, pps_node); + if (*value == NULL) + return DM_RESP_COMMAND_FAILED; + + return DM_RESP_OK; +} + + +static int oma_dm_get_cmdid(struct hs20_osu_client *ctx, xml_node_t *node) +{ + xml_node_t *cnode; + char *str; + int ret; + + cnode = get_node(ctx->xml, node, "CmdID"); + if (cnode == NULL) + return 0; + + str = xml_node_get_text(ctx->xml, cnode); + if (str == NULL) + return 0; + ret = atoi(str); + xml_node_get_text_free(ctx->xml, str); + return ret; +} + + +static xml_node_t * oma_dm_send_recv(struct hs20_osu_client *ctx, + const char *url, xml_node_t *syncml, + const char *ext_hdr, const char *ca_fname, + const char *username, const char *password, + const char *client_cert, + const char *client_key) +{ + xml_node_t *resp; + char *str, *res; + char *resp_uri = NULL; + + str = xml_node_to_str(ctx->xml, syncml); + xml_node_free(ctx->xml, syncml); + if (str == NULL) + return NULL; + + wpa_printf(MSG_INFO, "Send OMA DM Package"); + write_summary(ctx, "Send OMA DM Package"); + os_free(ctx->server_url); + ctx->server_url = os_strdup(url); + res = http_post(ctx->http, url, str, "application/vnd.syncml.dm+xml", + ext_hdr, ca_fname, username, password, + client_cert, client_key, NULL); + os_free(str); + os_free(resp_uri); + resp_uri = NULL; + + if (res == NULL) { + const char *err = http_get_err(ctx->http); + if (err) { + wpa_printf(MSG_INFO, "HTTP error: %s", err); + write_result(ctx, "HTTP error: %s", err); + } else { + write_summary(ctx, "Failed to send OMA DM Package"); + } + return NULL; + } + wpa_printf(MSG_DEBUG, "Server response: %s", res); + + wpa_printf(MSG_INFO, "Process OMA DM Package"); + write_summary(ctx, "Process received OMA DM Package"); + resp = xml_node_from_buf(ctx->xml, res); + os_free(res); + if (resp == NULL) { + wpa_printf(MSG_INFO, "Failed to parse OMA DM response"); + return NULL; + } + + debug_dump_node(ctx, "OMA DM Package", resp); + + return resp; +} + + +static xml_node_t * oma_dm_process(struct hs20_osu_client *ctx, const char *url, + xml_node_t *resp, int msgid, + char **ret_resp_uri, + xml_node_t *pps, const char *pps_fname) +{ + xml_node_t *syncml, *syncbody, *hdr, *body, *child; + const char *name; + char *resp_uri = NULL; + int server_msgid = 0; + int cmdid = 0; + int server_cmdid; + int resp_needed = 0; + char *tmp; + int final = 0; + char *locuri; + + *ret_resp_uri = NULL; + + name = xml_node_get_localname(ctx->xml, resp); + if (name == NULL || os_strcasecmp(name, "SyncML") != 0) { + wpa_printf(MSG_INFO, "SyncML node not found"); + return NULL; + } + + hdr = get_node(ctx->xml, resp, "SyncHdr"); + body = get_node(ctx->xml, resp, "SyncBody"); + if (hdr == NULL || body == NULL) { + wpa_printf(MSG_INFO, "Could not find SyncHdr or SyncBody"); + return NULL; + } + + xml_node_for_each_child(ctx->xml, child, hdr) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + wpa_printf(MSG_INFO, "SyncHdr %s", name); + if (os_strcasecmp(name, "RespURI") == 0) { + tmp = xml_node_get_text(ctx->xml, child); + if (tmp) + resp_uri = os_strdup(tmp); + xml_node_get_text_free(ctx->xml, tmp); + } else if (os_strcasecmp(name, "MsgID") == 0) { + tmp = xml_node_get_text(ctx->xml, child); + if (tmp) + server_msgid = atoi(tmp); + xml_node_get_text_free(ctx->xml, tmp); + } + } + + wpa_printf(MSG_INFO, "Server MsgID: %d", server_msgid); + if (resp_uri) + wpa_printf(MSG_INFO, "RespURI: %s", resp_uri); + + syncml = oma_dm_build_hdr(ctx, resp_uri ? resp_uri : url, msgid); + if (syncml == NULL) { + os_free(resp_uri); + return NULL; + } + + syncbody = xml_node_create(ctx->xml, syncml, NULL, "SyncBody"); + cmdid++; + add_status(ctx, syncbody, server_msgid, 0, cmdid, "SyncHdr", + DM_RESP_AUTH_ACCEPTED, NULL); + + xml_node_for_each_child(ctx->xml, child, body) { + xml_node_for_each_check(ctx->xml, child); + server_cmdid = oma_dm_get_cmdid(ctx, child); + name = xml_node_get_localname(ctx->xml, child); + wpa_printf(MSG_INFO, "SyncBody CmdID=%d - %s", + server_cmdid, name); + if (os_strcasecmp(name, "Exec") == 0) { + int res = oma_dm_exec(ctx, child); + cmdid++; + locuri = oma_dm_get_target_locuri(ctx, child); + if (locuri == NULL) + res = DM_RESP_BAD_REQUEST; + add_status(ctx, syncbody, server_msgid, server_cmdid, + cmdid, name, res, locuri); + os_free(locuri); + resp_needed = 1; + } else if (os_strcasecmp(name, "Add") == 0) { + int res = oma_dm_add(ctx, child, pps, pps_fname); + cmdid++; + locuri = oma_dm_get_target_locuri(ctx, child); + if (locuri == NULL) + res = DM_RESP_BAD_REQUEST; + add_status(ctx, syncbody, server_msgid, server_cmdid, + cmdid, name, res, locuri); + os_free(locuri); + resp_needed = 1; + } else if (os_strcasecmp(name, "Replace") == 0) { + int res; + res = oma_dm_replace(ctx, child, pps, pps_fname); + cmdid++; + locuri = oma_dm_get_target_locuri(ctx, child); + if (locuri == NULL) + res = DM_RESP_BAD_REQUEST; + add_status(ctx, syncbody, server_msgid, server_cmdid, + cmdid, name, res, locuri); + os_free(locuri); + resp_needed = 1; + } else if (os_strcasecmp(name, "Status") == 0) { + /* TODO: Verify success */ + } else if (os_strcasecmp(name, "Get") == 0) { + int res; + char *value; + res = oma_dm_get(ctx, child, pps, pps_fname, &value); + cmdid++; + locuri = oma_dm_get_target_locuri(ctx, child); + if (locuri == NULL) + res = DM_RESP_BAD_REQUEST; + add_status(ctx, syncbody, server_msgid, server_cmdid, + cmdid, name, res, locuri); + if (res == DM_RESP_OK && value) { + cmdid++; + add_results(ctx, syncbody, server_msgid, + server_cmdid, cmdid, locuri, value); + } + os_free(locuri); + xml_node_get_text_free(ctx->xml, value); + resp_needed = 1; +#if 0 /* TODO: MUST support */ + } else if (os_strcasecmp(name, "Delete") == 0) { +#endif +#if 0 /* TODO: MUST support */ + } else if (os_strcasecmp(name, "Sequence") == 0) { +#endif + } else if (os_strcasecmp(name, "Final") == 0) { + final = 1; + break; + } else { + locuri = oma_dm_get_target_locuri(ctx, child); + add_status(ctx, syncbody, server_msgid, server_cmdid, + cmdid, name, DM_RESP_COMMAND_NOT_IMPLEMENTED, + locuri); + os_free(locuri); + resp_needed = 1; + } + } + + if (!final) { + wpa_printf(MSG_INFO, "Final node not found"); + xml_node_free(ctx->xml, syncml); + os_free(resp_uri); + return NULL; + } + + if (!resp_needed) { + wpa_printf(MSG_INFO, "Exchange completed - no response needed"); + xml_node_free(ctx->xml, syncml); + os_free(resp_uri); + return NULL; + } + + xml_node_create(ctx->xml, syncbody, NULL, "Final"); + + debug_dump_node(ctx, "OMA-DM Package 3", syncml); + + *ret_resp_uri = resp_uri; + return syncml; +} + + +int cmd_oma_dm_prov(struct hs20_osu_client *ctx, const char *url, + const char *ca_fname) +{ + xml_node_t *syncml, *resp; + char *resp_uri = NULL; + int msgid = 0; + + if (url == NULL) { + wpa_printf(MSG_INFO, "Invalid prov command (missing URL)"); + return -1; + } + + wpa_printf(MSG_INFO, "OMA-DM credential provisioning requested"); + write_summary(ctx, "OMA-DM credential provisioning"); + + msgid++; + syncml = build_oma_dm_1_sub_reg(ctx, url, msgid); + if (syncml == NULL) + return -1; + + while (syncml) { + resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : url, + syncml, NULL, ca_fname, NULL, NULL, + NULL, NULL); + if (resp == NULL) + return -1; + + msgid++; + syncml = oma_dm_process(ctx, url, resp, msgid, &resp_uri, + NULL, NULL); + xml_node_free(ctx->xml, resp); + } + + os_free(resp_uri); + + return ctx->pps_cred_set ? 0 : -1; +} + + +int cmd_oma_dm_sim_prov(struct hs20_osu_client *ctx, const char *url, + const char *ca_fname) +{ + xml_node_t *syncml, *resp; + char *resp_uri = NULL; + int msgid = 0; + + if (url == NULL) { + wpa_printf(MSG_INFO, "Invalid prov command (missing URL)"); + return -1; + } + + wpa_printf(MSG_INFO, "OMA-DM SIM provisioning requested"); + ctx->no_reconnect = 2; + + wpa_printf(MSG_INFO, "Wait for IP address before starting SIM provisioning"); + write_summary(ctx, "Wait for IP address before starting SIM provisioning"); + + if (wait_ip_addr(ctx->ifname, 15) < 0) { + wpa_printf(MSG_INFO, "Could not get IP address for WLAN - try connection anyway"); + } + write_summary(ctx, "OMA-DM SIM provisioning"); + + msgid++; + syncml = build_oma_dm_1_sub_prov(ctx, url, msgid); + if (syncml == NULL) + return -1; + + while (syncml) { + resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : url, + syncml, NULL, ca_fname, NULL, NULL, + NULL, NULL); + if (resp == NULL) + return -1; + + msgid++; + syncml = oma_dm_process(ctx, url, resp, msgid, &resp_uri, + NULL, NULL); + xml_node_free(ctx->xml, resp); + } + + os_free(resp_uri); + + if (ctx->pps_cred_set) { + wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials"); + cmd_set_pps(ctx, ctx->pps_fname); + + wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration"); + write_summary(ctx, "Requesting reconnection with updated configuration"); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) { + wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect"); + write_summary(ctx, "Failed to request wpa_supplicant to reconnect"); + return -1; + } + } + + return ctx->pps_cred_set ? 0 : -1; +} + + +void oma_dm_pol_upd(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps) +{ + xml_node_t *syncml, *resp; + char *resp_uri = NULL; + int msgid = 0; + + wpa_printf(MSG_INFO, "OMA-DM policy update"); + write_summary(ctx, "OMA-DM policy update"); + + msgid++; + syncml = build_oma_dm_1_pol_upd(ctx, address, msgid); + if (syncml == NULL) + return; + + while (syncml) { + resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : address, + syncml, NULL, ca_fname, cred_username, + cred_password, client_cert, client_key); + if (resp == NULL) + return; + + msgid++; + syncml = oma_dm_process(ctx, address, resp, msgid, &resp_uri, + pps, pps_fname); + xml_node_free(ctx->xml, resp); + } + + os_free(resp_uri); + + if (ctx->pps_updated) { + wpa_printf(MSG_INFO, "Update wpa_supplicant credential based on updated PPS MO"); + write_summary(ctx, "Update wpa_supplicant credential based on updated PPS MO and request connection"); + cmd_set_pps(ctx, pps_fname); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) { + wpa_printf(MSG_INFO, + "Failed to request wpa_supplicant to reconnect"); + write_summary(ctx, + "Failed to request wpa_supplicant to reconnect"); + } + } +} + + +void oma_dm_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps) +{ + xml_node_t *syncml, *resp; + char *resp_uri = NULL; + int msgid = 0; + + wpa_printf(MSG_INFO, "OMA-DM subscription remediation"); + write_summary(ctx, "OMA-DM subscription remediation"); + + msgid++; + syncml = build_oma_dm_1_sub_rem(ctx, address, msgid); + if (syncml == NULL) + return; + + while (syncml) { + resp = oma_dm_send_recv(ctx, resp_uri ? resp_uri : address, + syncml, NULL, ca_fname, cred_username, + cred_password, client_cert, client_key); + if (resp == NULL) + return; + + msgid++; + syncml = oma_dm_process(ctx, address, resp, msgid, &resp_uri, + pps, pps_fname); + xml_node_free(ctx->xml, resp); + } + + os_free(resp_uri); + + wpa_printf(MSG_INFO, "Update wpa_supplicant credential based on updated PPS MO and request reconnection"); + write_summary(ctx, "Update wpa_supplicant credential based on updated PPS MO and request reconnection"); + cmd_set_pps(ctx, pps_fname); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) { + wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect"); + write_summary(ctx, "Failed to request wpa_supplicant to reconnect"); + } +} + + +void cmd_oma_dm_add(struct hs20_osu_client *ctx, const char *pps_fname, + const char *add_fname) +{ + xml_node_t *pps, *add; + int res; + + ctx->fqdn = os_strdup("wi-fi.org"); + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "PPS file %s could not be parsed", + pps_fname); + return; + } + + add = node_from_file(ctx->xml, add_fname); + if (add == NULL) { + wpa_printf(MSG_INFO, "Add file %s could not be parsed", + add_fname); + xml_node_free(ctx->xml, pps); + return; + } + + res = oma_dm_add(ctx, add, pps, pps_fname); + wpa_printf(MSG_INFO, "oma_dm_add --> %d", res); + + xml_node_free(ctx->xml, pps); + xml_node_free(ctx->xml, add); +} + + +void cmd_oma_dm_replace(struct hs20_osu_client *ctx, const char *pps_fname, + const char *replace_fname) +{ + xml_node_t *pps, *replace; + int res; + + ctx->fqdn = os_strdup("wi-fi.org"); + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "PPS file %s could not be parsed", + pps_fname); + return; + } + + replace = node_from_file(ctx->xml, replace_fname); + if (replace == NULL) { + wpa_printf(MSG_INFO, "Replace file %s could not be parsed", + replace_fname); + xml_node_free(ctx->xml, pps); + return; + } + + res = oma_dm_replace(ctx, replace, pps, pps_fname); + wpa_printf(MSG_INFO, "oma_dm_replace --> %d", res); + + xml_node_free(ctx->xml, pps); + xml_node_free(ctx->xml, replace); +} diff --git a/hs20/client/osu_client.c b/hs20/client/osu_client.c new file mode 100644 index 00000000..2175fc35 --- /dev/null +++ b/hs20/client/osu_client.c @@ -0,0 +1,3196 @@ +/* + * Hotspot 2.0 OSU client + * Copyright (c) 2012-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 <time.h> +#include <sys/stat.h> + +#include "common.h" +#include "utils/browser.h" +#include "utils/base64.h" +#include "utils/xml-utils.h" +#include "utils/http-utils.h" +#include "common/wpa_ctrl.h" +#include "common/wpa_helpers.h" +#include "eap_common/eap_defs.h" +#include "crypto/crypto.h" +#include "crypto/sha256.h" +#include "osu_client.h" + + +void write_result(struct hs20_osu_client *ctx, const char *fmt, ...) +{ + va_list ap; + FILE *f; + char buf[500]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + write_summary(ctx, "%s", buf); + + if (!ctx->result_file) + return; + + f = fopen(ctx->result_file, "w"); + if (f == NULL) + return; + + va_start(ap, fmt); + vfprintf(f, fmt, ap); + va_end(ap); + fprintf(f, "\n"); + fclose(f); +} + + +void write_summary(struct hs20_osu_client *ctx, const char *fmt, ...) +{ + va_list ap; + FILE *f; + + if (!ctx->summary_file) + return; + + f = fopen(ctx->summary_file, "a"); + if (f == NULL) + return; + + va_start(ap, fmt); + vfprintf(f, fmt, ap); + va_end(ap); + fprintf(f, "\n"); + fclose(f); +} + + +void debug_dump_node(struct hs20_osu_client *ctx, const char *title, + xml_node_t *node) +{ + char *str = xml_node_to_str(ctx->xml, node); + wpa_printf(MSG_DEBUG, "[hs20] %s: '%s'", title, str); + free(str); +} + + +static int valid_fqdn(const char *fqdn) +{ + const char *pos; + + /* TODO: could make this more complete.. */ + if (strchr(fqdn, '.') == 0 || strlen(fqdn) > 255) + return 0; + for (pos = fqdn; *pos; pos++) { + if (*pos >= 'a' && *pos <= 'z') + continue; + if (*pos >= 'A' && *pos <= 'Z') + continue; + if (*pos >= '0' && *pos <= '9') + continue; + if (*pos == '-' || *pos == '.' || *pos == '_') + continue; + return 0; + } + return 1; +} + + +int osu_get_certificate(struct hs20_osu_client *ctx, xml_node_t *getcert) +{ + xml_node_t *node; + char *url, *user = NULL, *pw = NULL; + char *proto; + int ret = -1; + + proto = xml_node_get_attr_value(ctx->xml, getcert, + "enrollmentProtocol"); + if (!proto) + return -1; + wpa_printf(MSG_INFO, "getCertificate - enrollmentProtocol=%s", proto); + write_summary(ctx, "getCertificate - enrollmentProtocol=%s", proto); + if (os_strcasecmp(proto, "EST") != 0) { + wpa_printf(MSG_INFO, "Unsupported enrollmentProtocol"); + xml_node_get_attr_value_free(ctx->xml, proto); + return -1; + } + xml_node_get_attr_value_free(ctx->xml, proto); + + node = get_node(ctx->xml, getcert, "enrollmentServerURI"); + if (node == NULL) { + wpa_printf(MSG_INFO, "Could not find enrollmentServerURI node"); + xml_node_get_attr_value_free(ctx->xml, proto); + return -1; + } + url = xml_node_get_text(ctx->xml, node); + if (url == NULL) { + wpa_printf(MSG_INFO, "Could not get URL text"); + return -1; + } + wpa_printf(MSG_INFO, "enrollmentServerURI: %s", url); + write_summary(ctx, "enrollmentServerURI: %s", url); + + node = get_node(ctx->xml, getcert, "estUserID"); + if (node == NULL && !ctx->client_cert_present) { + wpa_printf(MSG_INFO, "Could not find estUserID node"); + goto fail; + } + if (node) { + user = xml_node_get_text(ctx->xml, node); + if (user == NULL) { + wpa_printf(MSG_INFO, "Could not get estUserID text"); + goto fail; + } + wpa_printf(MSG_INFO, "estUserID: %s", user); + write_summary(ctx, "estUserID: %s", user); + } + + node = get_node(ctx->xml, getcert, "estPassword"); + if (node == NULL && !ctx->client_cert_present) { + wpa_printf(MSG_INFO, "Could not find estPassword node"); + goto fail; + } + if (node) { + pw = xml_node_get_base64_text(ctx->xml, node, NULL); + if (pw == NULL) { + wpa_printf(MSG_INFO, "Could not get estPassword text"); + goto fail; + } + wpa_printf(MSG_INFO, "estPassword: %s", pw); + } + + mkdir("Cert", S_IRWXU); + if (est_load_cacerts(ctx, url) < 0 || + est_build_csr(ctx, url) < 0 || + est_simple_enroll(ctx, url, user, pw) < 0) + goto fail; + + ret = 0; +fail: + xml_node_get_text_free(ctx->xml, url); + xml_node_get_text_free(ctx->xml, user); + xml_node_get_text_free(ctx->xml, pw); + + return ret; +} + + +static int process_est_cert(struct hs20_osu_client *ctx, xml_node_t *cert, + const char *fqdn) +{ + u8 digest1[SHA256_MAC_LEN], digest2[SHA256_MAC_LEN]; + char *der, *pem; + size_t der_len, pem_len; + char *fingerprint; + char buf[200]; + + wpa_printf(MSG_INFO, "PPS for certificate credential - fqdn=%s", fqdn); + + fingerprint = xml_node_get_text(ctx->xml, cert); + if (fingerprint == NULL) + return -1; + if (hexstr2bin(fingerprint, digest1, SHA256_MAC_LEN) < 0) { + wpa_printf(MSG_INFO, "Invalid SHA256 hash value"); + write_result(ctx, "Invalid client certificate SHA256 hash value in PPS"); + xml_node_get_text_free(ctx->xml, fingerprint); + return -1; + } + xml_node_get_text_free(ctx->xml, fingerprint); + + der = os_readfile("Cert/est_cert.der", &der_len); + if (der == NULL) { + wpa_printf(MSG_INFO, "Could not find client certificate from EST"); + write_result(ctx, "Could not find client certificate from EST"); + return -1; + } + + if (sha256_vector(1, (const u8 **) &der, &der_len, digest2) < 0) { + os_free(der); + return -1; + } + os_free(der); + + if (os_memcmp(digest1, digest2, sizeof(digest1)) != 0) { + wpa_printf(MSG_INFO, "Client certificate from EST does not match fingerprint from PPS MO"); + write_result(ctx, "Client certificate from EST does not match fingerprint from PPS MO"); + return -1; + } + + wpa_printf(MSG_INFO, "Client certificate from EST matches PPS MO"); + unlink("Cert/est_cert.der"); + + os_snprintf(buf, sizeof(buf), "SP/%s/client-ca.pem", fqdn); + if (rename("Cert/est-cacerts.pem", buf) < 0) { + wpa_printf(MSG_INFO, "Could not move est-cacerts.pem to client-ca.pem: %s", + strerror(errno)); + return -1; + } + pem = os_readfile(buf, &pem_len); + + os_snprintf(buf, sizeof(buf), "SP/%s/client-cert.pem", fqdn); + if (rename("Cert/est_cert.pem", buf) < 0) { + wpa_printf(MSG_INFO, "Could not move est_cert.pem to client-cert.pem: %s", + strerror(errno)); + os_free(pem); + return -1; + } + + if (pem) { + FILE *f = fopen(buf, "a"); + if (f) { + fwrite(pem, pem_len, 1, f); + fclose(f); + } + os_free(pem); + } + + os_snprintf(buf, sizeof(buf), "SP/%s/client-key.pem", fqdn); + if (rename("Cert/privkey-plain.pem", buf) < 0) { + wpa_printf(MSG_INFO, "Could not move privkey-plain.pem to client-key.pem: %s", + strerror(errno)); + return -1; + } + + unlink("Cert/est-req.b64"); + unlink("Cert/est-req.pem"); + unlink("Cert/est-resp.raw"); + rmdir("Cert"); + + return 0; +} + + +#define TMP_CERT_DL_FILE "tmp-cert-download" + +static int download_cert(struct hs20_osu_client *ctx, xml_node_t *params, + const char *fname) +{ + xml_node_t *url_node, *hash_node; + char *url, *hash; + char *cert; + size_t len; + u8 digest1[SHA256_MAC_LEN], digest2[SHA256_MAC_LEN]; + int res; + unsigned char *b64; + FILE *f; + + url_node = get_node(ctx->xml, params, "CertURL"); + hash_node = get_node(ctx->xml, params, "CertSHA256Fingerprint"); + if (url_node == NULL || hash_node == NULL) + return -1; + url = xml_node_get_text(ctx->xml, url_node); + hash = xml_node_get_text(ctx->xml, hash_node); + if (url == NULL || hash == NULL) { + xml_node_get_text_free(ctx->xml, url); + xml_node_get_text_free(ctx->xml, hash); + return -1; + } + + wpa_printf(MSG_INFO, "CertURL: %s", url); + wpa_printf(MSG_INFO, "SHA256 hash: %s", hash); + + if (hexstr2bin(hash, digest1, SHA256_MAC_LEN) < 0) { + wpa_printf(MSG_INFO, "Invalid SHA256 hash value"); + write_result(ctx, "Invalid SHA256 hash value for downloaded certificate"); + xml_node_get_text_free(ctx->xml, hash); + return -1; + } + xml_node_get_text_free(ctx->xml, hash); + + write_summary(ctx, "Download certificate from %s", url); + res = http_download_file(ctx->http, url, TMP_CERT_DL_FILE, NULL); + xml_node_get_text_free(ctx->xml, url); + if (res < 0) + return -1; + + cert = os_readfile(TMP_CERT_DL_FILE, &len); + remove(TMP_CERT_DL_FILE); + if (cert == NULL) + return -1; + + if (sha256_vector(1, (const u8 **) &cert, &len, digest2) < 0) { + os_free(cert); + return -1; + } + + if (os_memcmp(digest1, digest2, sizeof(digest1)) != 0) { + wpa_printf(MSG_INFO, "Downloaded certificate fingerprint did not match"); + write_result(ctx, "Downloaded certificate fingerprint did not match"); + os_free(cert); + return -1; + } + + b64 = base64_encode((unsigned char *) cert, len, NULL); + os_free(cert); + if (b64 == NULL) + return -1; + + f = fopen(fname, "wb"); + if (f == NULL) { + os_free(b64); + return -1; + } + + fprintf(f, "-----BEGIN CERTIFICATE-----\n" + "%s" + "-----END CERTIFICATE-----\n", + b64); + + os_free(b64); + fclose(f); + + wpa_printf(MSG_INFO, "Downloaded certificate into %s and validated fingerprint", + fname); + write_summary(ctx, "Downloaded certificate into %s and validated fingerprint", + fname); + + return 0; +} + + +static int cmd_dl_osu_ca(struct hs20_osu_client *ctx, const char *pps_fname, + const char *ca_fname) +{ + xml_node_t *pps, *node; + int ret; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", pps_fname); + return -1; + } + + node = get_child_node(ctx->xml, pps, + "SubscriptionUpdate/TrustRoot"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No SubscriptionUpdate/TrustRoot/CertURL found from PPS"); + xml_node_free(ctx->xml, pps); + return -1; + } + + ret = download_cert(ctx, node, ca_fname); + xml_node_free(ctx->xml, pps); + + return ret; +} + + +static int cmd_dl_polupd_ca(struct hs20_osu_client *ctx, const char *pps_fname, + const char *ca_fname) +{ + xml_node_t *pps, *node; + int ret; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", pps_fname); + return -1; + } + + node = get_child_node(ctx->xml, pps, + "PolicyUpdate/TrustRoot"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No PolicyUpdate/TrustRoot/CertURL found from PPS"); + xml_node_free(ctx->xml, pps); + return -1; + } + + ret = download_cert(ctx, node, ca_fname); + xml_node_free(ctx->xml, pps); + + return ret; +} + + +static int cmd_dl_aaa_ca(struct hs20_osu_client *ctx, const char *pps_fname, + const char *ca_fname) +{ + xml_node_t *pps, *node, *aaa; + int ret; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", pps_fname); + return -1; + } + + node = get_child_node(ctx->xml, pps, + "AAAServerTrustRoot"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No AAAServerTrustRoot/CertURL found from PPS"); + xml_node_free(ctx->xml, pps); + return -1; + } + + aaa = xml_node_first_child(ctx->xml, node); + if (aaa == NULL) { + wpa_printf(MSG_INFO, "No AAAServerTrustRoot/CertURL found from PPS"); + xml_node_free(ctx->xml, pps); + return -1; + } + + ret = download_cert(ctx, aaa, ca_fname); + xml_node_free(ctx->xml, pps); + + return ret; +} + + +static int download_trust_roots(struct hs20_osu_client *ctx, + const char *pps_fname) +{ + char *dir, *pos; + char fname[300]; + int ret; + + dir = os_strdup(pps_fname); + if (dir == NULL) + return -1; + pos = os_strrchr(dir, '/'); + if (pos == NULL) { + os_free(dir); + return -1; + } + *pos = '\0'; + + snprintf(fname, sizeof(fname), "%s/ca.pem", dir); + ret = cmd_dl_osu_ca(ctx, pps_fname, fname); + snprintf(fname, sizeof(fname), "%s/polupd-ca.pem", dir); + cmd_dl_polupd_ca(ctx, pps_fname, fname); + snprintf(fname, sizeof(fname), "%s/aaa-ca.pem", dir); + cmd_dl_aaa_ca(ctx, pps_fname, fname); + + os_free(dir); + + return ret; +} + + +static int server_dnsname_suffix_match(struct hs20_osu_client *ctx, + const char *fqdn) +{ + size_t match_len, len, i; + const char *val; + + match_len = os_strlen(fqdn); + + for (i = 0; i < ctx->server_dnsname_count; i++) { + wpa_printf(MSG_INFO, + "Checking suffix match against server dNSName %s", + ctx->server_dnsname[i]); + val = ctx->server_dnsname[i]; + len = os_strlen(val); + + if (match_len > len) + continue; + + if (os_strncasecmp(val + len - match_len, fqdn, match_len) != 0) + continue; /* no match */ + + if (match_len == len) + return 1; /* exact match */ + + if (val[len - match_len - 1] == '.') + return 1; /* full label match completes suffix match */ + + /* Reject due to incomplete label match */ + } + + /* None of the dNSName(s) matched */ + return 0; +} + + +int hs20_add_pps_mo(struct hs20_osu_client *ctx, const char *uri, + xml_node_t *add_mo, char *fname, size_t fname_len) +{ + char *str; + char *fqdn, *pos; + xml_node_t *tnds, *mo, *cert; + const char *name; + int ret; + + if (strncmp(uri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Unsupported location for addMO to add PPS MO: '%s'", + uri); + write_result(ctx, "Unsupported location for addMO to add PPS MO: '%s'", + uri); + return -1; + } + + fqdn = strdup(uri + 8); + if (fqdn == NULL) + return -1; + pos = strchr(fqdn, '/'); + if (pos) { + if (os_strcasecmp(pos, "/PerProviderSubscription") != 0) { + wpa_printf(MSG_INFO, "Unsupported location for addMO to add PPS MO (extra directory): '%s'", + uri); + write_result(ctx, "Unsupported location for addMO to " + "add PPS MO (extra directory): '%s'", uri); + return -1; + } + *pos = '\0'; /* remove trailing slash and PPS node name */ + } + wpa_printf(MSG_INFO, "SP FQDN: %s", fqdn); + + if (!server_dnsname_suffix_match(ctx, fqdn)) { + wpa_printf(MSG_INFO, "FQDN '%s' for new PPS MO did not have suffix match with server's dNSName values", + fqdn); + write_result(ctx, "FQDN '%s' for new PPS MO did not have suffix match with server's dNSName values", + fqdn); + free(fqdn); + return -1; + } + + if (!valid_fqdn(fqdn)) { + wpa_printf(MSG_INFO, "Invalid FQDN '%s'", fqdn); + write_result(ctx, "Invalid FQDN '%s'", fqdn); + free(fqdn); + return -1; + } + + mkdir("SP", S_IRWXU); + snprintf(fname, fname_len, "SP/%s", fqdn); + if (mkdir(fname, S_IRWXU) < 0) { + if (errno != EEXIST) { + int err = errno; + wpa_printf(MSG_INFO, "mkdir(%s) failed: %s", + fname, strerror(err)); + free(fqdn); + return -1; + } + } + + snprintf(fname, fname_len, "SP/%s/pps.xml", fqdn); + + if (os_file_exists(fname)) { + wpa_printf(MSG_INFO, "PPS file '%s' exists - reject addMO", + fname); + write_result(ctx, "PPS file '%s' exists - reject addMO", + fname); + free(fqdn); + return -2; + } + wpa_printf(MSG_INFO, "Using PPS file: %s", fname); + + str = xml_node_get_text(ctx->xml, add_mo); + if (str == NULL) { + wpa_printf(MSG_INFO, "Could not extract MO text"); + free(fqdn); + return -1; + } + wpa_printf(MSG_DEBUG, "[hs20] addMO text: '%s'", str); + + tnds = xml_node_from_buf(ctx->xml, str); + xml_node_get_text_free(ctx->xml, str); + if (tnds == NULL) { + wpa_printf(MSG_INFO, "[hs20] Could not parse addMO text"); + free(fqdn); + return -1; + } + + mo = tnds_to_mo(ctx->xml, tnds); + if (mo == NULL) { + wpa_printf(MSG_INFO, "[hs20] Could not parse addMO TNDS text"); + free(fqdn); + return -1; + } + + debug_dump_node(ctx, "Parsed TNDS", mo); + + name = xml_node_get_localname(ctx->xml, mo); + if (os_strcasecmp(name, "PerProviderSubscription") != 0) { + wpa_printf(MSG_INFO, "[hs20] Unexpected PPS MO root node name '%s'", + name); + free(fqdn); + return -1; + } + + cert = get_child_node(ctx->xml, mo, + "Credential/DigitalCertificate/" + "CertSHA256Fingerprint"); + if (cert && process_est_cert(ctx, cert, fqdn) < 0) { + xml_node_free(ctx->xml, mo); + free(fqdn); + return -1; + } + free(fqdn); + + if (node_to_file(ctx->xml, fname, mo) < 0) { + wpa_printf(MSG_INFO, "Could not write MO to file"); + xml_node_free(ctx->xml, mo); + return -1; + } + xml_node_free(ctx->xml, mo); + + wpa_printf(MSG_INFO, "A new PPS MO added as '%s'", fname); + write_summary(ctx, "A new PPS MO added as '%s'", fname); + + ret = download_trust_roots(ctx, fname); + if (ret < 0) { + wpa_printf(MSG_INFO, "Remove invalid PPS MO file"); + write_summary(ctx, "Remove invalid PPS MO file"); + unlink(fname); + } + + return ret; +} + + +int update_pps_file(struct hs20_osu_client *ctx, const char *pps_fname, + xml_node_t *pps) +{ + char *str; + FILE *f; + char backup[300]; + + if (ctx->client_cert_present) { + xml_node_t *cert; + cert = get_child_node(ctx->xml, pps, + "Credential/DigitalCertificate/" + "CertSHA256Fingerprint"); + if (cert && os_file_exists("Cert/est_cert.der") && + process_est_cert(ctx, cert, ctx->fqdn) < 0) { + wpa_printf(MSG_INFO, "EST certificate update processing failed on PPS MO update"); + return -1; + } + } + + wpa_printf(MSG_INFO, "Updating PPS MO %s", pps_fname); + + str = xml_node_to_str(ctx->xml, pps); + wpa_printf(MSG_MSGDUMP, "[hs20] Updated PPS: '%s'", str); + + snprintf(backup, sizeof(backup), "%s.bak", pps_fname); + rename(pps_fname, backup); + f = fopen(pps_fname, "w"); + if (f == NULL) { + wpa_printf(MSG_INFO, "Could not write PPS"); + rename(backup, pps_fname); + free(str); + return -1; + } + fprintf(f, "%s\n", str); + fclose(f); + + free(str); + + return 0; +} + + +void get_user_pw(struct hs20_osu_client *ctx, xml_node_t *pps, + const char *alt_loc, char **user, char **pw) +{ + xml_node_t *node; + + node = get_child_node(ctx->xml, pps, + "Credential/UsernamePassword/Username"); + if (node) + *user = xml_node_get_text(ctx->xml, node); + + node = get_child_node(ctx->xml, pps, + "Credential/UsernamePassword/Password"); + if (node) + *pw = xml_node_get_base64_text(ctx->xml, node, NULL); + + node = get_child_node(ctx->xml, pps, alt_loc); + if (node) { + xml_node_t *a; + a = get_node(ctx->xml, node, "Username"); + if (a) { + xml_node_get_text_free(ctx->xml, *user); + *user = xml_node_get_text(ctx->xml, a); + wpa_printf(MSG_INFO, "Use OSU username '%s'", *user); + } + + a = get_node(ctx->xml, node, "Password"); + if (a) { + free(*pw); + *pw = xml_node_get_base64_text(ctx->xml, a, NULL); + wpa_printf(MSG_INFO, "Use OSU password"); + } + } +} + + +/* Remove old credentials based on HomeSP/FQDN */ +static void remove_sp_creds(struct hs20_osu_client *ctx, const char *fqdn) +{ + char cmd[300]; + os_snprintf(cmd, sizeof(cmd), "REMOVE_CRED provisioning_sp=%s", fqdn); + if (wpa_command(ctx->ifname, cmd) < 0) + wpa_printf(MSG_INFO, "Failed to remove old credential(s)"); +} + + +static void set_pps_cred_policy_spe(struct hs20_osu_client *ctx, int id, + xml_node_t *spe) +{ + xml_node_t *ssid; + char *txt; + + ssid = get_node(ctx->xml, spe, "SSID"); + if (ssid == NULL) + return; + txt = xml_node_get_text(ctx->xml, ssid); + if (txt == NULL) + return; + wpa_printf(MSG_DEBUG, "- Policy/SPExclusionList/<X+>/SSID = %s", txt); + if (set_cred_quoted(ctx->ifname, id, "excluded_ssid", txt) < 0) + wpa_printf(MSG_INFO, "Failed to set cred excluded_ssid"); + xml_node_get_text_free(ctx->xml, txt); +} + + +static void set_pps_cred_policy_spel(struct hs20_osu_client *ctx, int id, + xml_node_t *spel) +{ + xml_node_t *child; + + xml_node_for_each_child(ctx->xml, child, spel) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_policy_spe(ctx, id, child); + } +} + + +static void set_pps_cred_policy_prp(struct hs20_osu_client *ctx, int id, + xml_node_t *prp) +{ + xml_node_t *node; + char *txt = NULL, *pos; + char *prio, *country_buf = NULL; + const char *country; + char val[200]; + int priority; + + node = get_node(ctx->xml, prp, "Priority"); + if (node == NULL) + return; + prio = xml_node_get_text(ctx->xml, node); + if (prio == NULL) + return; + wpa_printf(MSG_INFO, "- Policy/PreferredRoamingPartnerList/<X+>/Priority = %s", + prio); + priority = atoi(prio); + xml_node_get_text_free(ctx->xml, prio); + + node = get_node(ctx->xml, prp, "Country"); + if (node) { + country_buf = xml_node_get_text(ctx->xml, node); + if (country_buf == NULL) + return; + country = country_buf; + wpa_printf(MSG_INFO, "- Policy/PreferredRoamingPartnerList/<X+>/Country = %s", + country); + } else { + country = "*"; + } + + node = get_node(ctx->xml, prp, "FQDN_Match"); + if (node == NULL) + goto out; + txt = xml_node_get_text(ctx->xml, node); + if (txt == NULL) + goto out; + wpa_printf(MSG_INFO, "- Policy/PreferredRoamingPartnerList/<X+>/FQDN_Match = %s", + txt); + pos = strrchr(txt, ','); + if (pos == NULL) + goto out; + *pos++ = '\0'; + + snprintf(val, sizeof(val), "%s,%d,%d,%s", txt, + strcmp(pos, "includeSubdomains") != 0, priority, country); + if (set_cred_quoted(ctx->ifname, id, "roaming_partner", val) < 0) + wpa_printf(MSG_INFO, "Failed to set cred roaming_partner"); +out: + xml_node_get_text_free(ctx->xml, country_buf); + xml_node_get_text_free(ctx->xml, txt); +} + + +static void set_pps_cred_policy_prpl(struct hs20_osu_client *ctx, int id, + xml_node_t *prpl) +{ + xml_node_t *child; + + xml_node_for_each_child(ctx->xml, child, prpl) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_policy_prp(ctx, id, child); + } +} + + +static void set_pps_cred_policy_min_backhaul(struct hs20_osu_client *ctx, int id, + xml_node_t *min_backhaul) +{ + xml_node_t *node; + char *type, *dl = NULL, *ul = NULL; + int home; + + node = get_node(ctx->xml, min_backhaul, "NetworkType"); + if (node == NULL) { + wpa_printf(MSG_INFO, "Ignore MinBackhaulThreshold without mandatory NetworkType node"); + return; + } + + type = xml_node_get_text(ctx->xml, node); + if (type == NULL) + return; + wpa_printf(MSG_INFO, "- Policy/MinBackhaulThreshold/<X+>/NetworkType = %s", + type); + if (os_strcasecmp(type, "home") == 0) + home = 1; + else if (os_strcasecmp(type, "roaming") == 0) + home = 0; + else { + wpa_printf(MSG_INFO, "Ignore MinBackhaulThreshold with invalid NetworkType"); + xml_node_get_text_free(ctx->xml, type); + return; + } + xml_node_get_text_free(ctx->xml, type); + + node = get_node(ctx->xml, min_backhaul, "DLBandwidth"); + if (node) + dl = xml_node_get_text(ctx->xml, node); + + node = get_node(ctx->xml, min_backhaul, "ULBandwidth"); + if (node) + ul = xml_node_get_text(ctx->xml, node); + + if (dl == NULL && ul == NULL) { + wpa_printf(MSG_INFO, "Ignore MinBackhaulThreshold without either DLBandwidth or ULBandwidth nodes"); + return; + } + + if (dl) + wpa_printf(MSG_INFO, "- Policy/MinBackhaulThreshold/<X+>/DLBandwidth = %s", + dl); + if (ul) + wpa_printf(MSG_INFO, "- Policy/MinBackhaulThreshold/<X+>/ULBandwidth = %s", + ul); + + if (home) { + if (dl && + set_cred(ctx->ifname, id, "min_dl_bandwidth_home", dl) < 0) + wpa_printf(MSG_INFO, "Failed to set cred bandwidth limit"); + if (ul && + set_cred(ctx->ifname, id, "min_ul_bandwidth_home", ul) < 0) + wpa_printf(MSG_INFO, "Failed to set cred bandwidth limit"); + } else { + if (dl && + set_cred(ctx->ifname, id, "min_dl_bandwidth_roaming", dl) < + 0) + wpa_printf(MSG_INFO, "Failed to set cred bandwidth limit"); + if (ul && + set_cred(ctx->ifname, id, "min_ul_bandwidth_roaming", ul) < + 0) + wpa_printf(MSG_INFO, "Failed to set cred bandwidth limit"); + } + + xml_node_get_text_free(ctx->xml, dl); + xml_node_get_text_free(ctx->xml, ul); +} + + +static void set_pps_cred_policy_min_backhaul_list(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + xml_node_t *child; + + wpa_printf(MSG_INFO, "- Policy/MinBackhaulThreshold"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_policy_min_backhaul(ctx, id, child); + } +} + + +static void set_pps_cred_policy_update(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + wpa_printf(MSG_INFO, "- Policy/PolicyUpdate"); + /* Not used in wpa_supplicant */ +} + + +static void set_pps_cred_policy_required_proto_port(struct hs20_osu_client *ctx, + int id, xml_node_t *tuple) +{ + xml_node_t *node; + char *proto, *port; + char *buf; + size_t buflen; + + node = get_node(ctx->xml, tuple, "IPProtocol"); + if (node == NULL) { + wpa_printf(MSG_INFO, "Ignore RequiredProtoPortTuple without mandatory IPProtocol node"); + return; + } + + proto = xml_node_get_text(ctx->xml, node); + if (proto == NULL) + return; + + wpa_printf(MSG_INFO, "- Policy/RequiredProtoPortTuple/<X+>/IPProtocol = %s", + proto); + + node = get_node(ctx->xml, tuple, "PortNumber"); + port = node ? xml_node_get_text(ctx->xml, node) : NULL; + if (port) { + wpa_printf(MSG_INFO, "- Policy/RequiredProtoPortTuple/<X+>/PortNumber = %s", + port); + buflen = os_strlen(proto) + os_strlen(port) + 10; + buf = os_malloc(buflen); + if (buf) + os_snprintf(buf, buflen, "%s:%s", proto, port); + xml_node_get_text_free(ctx->xml, port); + } else { + buflen = os_strlen(proto) + 10; + buf = os_malloc(buflen); + if (buf) + os_snprintf(buf, buflen, "%s", proto); + } + + xml_node_get_text_free(ctx->xml, proto); + + if (buf == NULL) + return; + + if (set_cred(ctx->ifname, id, "req_conn_capab", buf) < 0) + wpa_printf(MSG_INFO, "Could not set req_conn_capab"); + + os_free(buf); +} + + +static void set_pps_cred_policy_required_proto_ports(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + xml_node_t *child; + + wpa_printf(MSG_INFO, "- Policy/RequiredProtoPortTuple"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_policy_required_proto_port(ctx, id, child); + } +} + + +static void set_pps_cred_policy_max_bss_load(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Policy/MaximumBSSLoadValue - %s", str); + if (set_cred(ctx->ifname, id, "max_bss_load", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred max_bss_load limit"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_policy(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + const char *name; + + wpa_printf(MSG_INFO, "- Policy"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "PreferredRoamingPartnerList") == 0) + set_pps_cred_policy_prpl(ctx, id, child); + else if (os_strcasecmp(name, "MinBackhaulThreshold") == 0) + set_pps_cred_policy_min_backhaul_list(ctx, id, child); + else if (os_strcasecmp(name, "PolicyUpdate") == 0) + set_pps_cred_policy_update(ctx, id, child); + else if (os_strcasecmp(name, "SPExclusionList") == 0) + set_pps_cred_policy_spel(ctx, id, child); + else if (os_strcasecmp(name, "RequiredProtoPortTuple") == 0) + set_pps_cred_policy_required_proto_ports(ctx, id, child); + else if (os_strcasecmp(name, "MaximumBSSLoadValue") == 0) + set_pps_cred_policy_max_bss_load(ctx, id, child); + else + wpa_printf(MSG_INFO, "Unknown Policy node '%s'", name); + } +} + + +static void set_pps_cred_priority(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- CredentialPriority = %s", str); + if (set_cred(ctx->ifname, id, "sp_priority", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred sp_priority"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_aaa_server_trust_root(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + wpa_printf(MSG_INFO, "- AAAServerTrustRoot - TODO"); +} + + +static void set_pps_cred_sub_update(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + wpa_printf(MSG_INFO, "- SubscriptionUpdate"); + /* not used within wpa_supplicant */ +} + + +static void set_pps_cred_home_sp_network_id(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + xml_node_t *ssid_node, *hessid_node; + char *ssid, *hessid; + + ssid_node = get_node(ctx->xml, node, "SSID"); + if (ssid_node == NULL) { + wpa_printf(MSG_INFO, "Ignore HomeSP/NetworkID without mandatory SSID node"); + return; + } + + hessid_node = get_node(ctx->xml, node, "HESSID"); + + ssid = xml_node_get_text(ctx->xml, ssid_node); + if (ssid == NULL) + return; + hessid = hessid_node ? xml_node_get_text(ctx->xml, hessid_node) : NULL; + + wpa_printf(MSG_INFO, "- HomeSP/NetworkID/<X+>/SSID = %s", ssid); + if (hessid) + wpa_printf(MSG_INFO, "- HomeSP/NetworkID/<X+>/HESSID = %s", + hessid); + + /* TODO: Configure to wpa_supplicant */ + + xml_node_get_text_free(ctx->xml, ssid); + xml_node_get_text_free(ctx->xml, hessid); +} + + +static void set_pps_cred_home_sp_network_ids(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + xml_node_t *child; + + wpa_printf(MSG_INFO, "- HomeSP/NetworkID"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_home_sp_network_id(ctx, id, child); + } +} + + +static void set_pps_cred_home_sp_friendly_name(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- HomeSP/FriendlyName = %s", str); + /* not used within wpa_supplicant(?) */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_home_sp_icon_url(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- HomeSP/IconURL = %s", str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_home_sp_fqdn(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- HomeSP/FQDN = %s", str); + if (set_cred_quoted(ctx->ifname, id, "domain", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred domain"); + if (set_cred_quoted(ctx->ifname, id, "domain_suffix_match", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred domain_suffix_match"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_home_sp_oi(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + const char *name; + char *homeoi = NULL; + int required = 0; + char *str; + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (strcasecmp(name, "HomeOI") == 0 && !homeoi) { + homeoi = xml_node_get_text(ctx->xml, child); + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList/<X+>/HomeOI = %s", + homeoi); + } else if (strcasecmp(name, "HomeOIRequired") == 0) { + str = xml_node_get_text(ctx->xml, child); + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList/<X+>/HomeOIRequired = '%s'", + str); + if (str == NULL) + continue; + required = strcasecmp(str, "true") == 0; + xml_node_get_text_free(ctx->xml, str); + } else + wpa_printf(MSG_INFO, "Unknown HomeOIList node '%s'", + name); + } + + if (homeoi == NULL) { + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList/<X+> without HomeOI ignored"); + return; + } + + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList/<X+> '%s' required=%d", + homeoi, required); + + if (required) { + if (set_cred(ctx->ifname, id, "required_roaming_consortium", + homeoi) < 0) + wpa_printf(MSG_INFO, "Failed to set cred required_roaming_consortium"); + } else { + if (set_cred_quoted(ctx->ifname, id, "roaming_consortium", + homeoi) < 0) + wpa_printf(MSG_INFO, "Failed to set cred roaming_consortium"); + } + + xml_node_get_text_free(ctx->xml, homeoi); +} + + +static void set_pps_cred_home_sp_oi_list(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + + wpa_printf(MSG_INFO, "- HomeSP/HomeOIList"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_home_sp_oi(ctx, id, child); + } +} + + +static void set_pps_cred_home_sp_other_partner(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + xml_node_t *child; + const char *name; + char *fqdn = NULL; + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "FQDN") == 0 && !fqdn) { + fqdn = xml_node_get_text(ctx->xml, child); + wpa_printf(MSG_INFO, "- HomeSP/OtherHomePartners/<X+>/FQDN = %s", + fqdn); + } else + wpa_printf(MSG_INFO, "Unknown OtherHomePartners node '%s'", + name); + } + + if (fqdn == NULL) { + wpa_printf(MSG_INFO, "- HomeSP/OtherHomePartners/<X+> without FQDN ignored"); + return; + } + + if (set_cred_quoted(ctx->ifname, id, "domain", fqdn) < 0) + wpa_printf(MSG_INFO, "Failed to set cred domain for OtherHomePartners node"); + + xml_node_get_text_free(ctx->xml, fqdn); +} + + +static void set_pps_cred_home_sp_other_partners(struct hs20_osu_client *ctx, + int id, + xml_node_t *node) +{ + xml_node_t *child; + + wpa_printf(MSG_INFO, "- HomeSP/OtherHomePartners"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + set_pps_cred_home_sp_other_partner(ctx, id, child); + } +} + + +static void set_pps_cred_home_sp_roaming_consortium_oi( + struct hs20_osu_client *ctx, int id, xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- HomeSP/RoamingConsortiumOI = %s", str); + /* TODO: Set to wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_home_sp(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + const char *name; + + wpa_printf(MSG_INFO, "- HomeSP"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "NetworkID") == 0) + set_pps_cred_home_sp_network_ids(ctx, id, child); + else if (os_strcasecmp(name, "FriendlyName") == 0) + set_pps_cred_home_sp_friendly_name(ctx, id, child); + else if (os_strcasecmp(name, "IconURL") == 0) + set_pps_cred_home_sp_icon_url(ctx, id, child); + else if (os_strcasecmp(name, "FQDN") == 0) + set_pps_cred_home_sp_fqdn(ctx, id, child); + else if (os_strcasecmp(name, "HomeOIList") == 0) + set_pps_cred_home_sp_oi_list(ctx, id, child); + else if (os_strcasecmp(name, "OtherHomePartners") == 0) + set_pps_cred_home_sp_other_partners(ctx, id, child); + else if (os_strcasecmp(name, "RoamingConsortiumOI") == 0) + set_pps_cred_home_sp_roaming_consortium_oi(ctx, id, + child); + else + wpa_printf(MSG_INFO, "Unknown HomeSP node '%s'", name); + } +} + + +static void set_pps_cred_sub_params(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + wpa_printf(MSG_INFO, "- SubscriptionParameters"); + /* not used within wpa_supplicant */ +} + + +static void set_pps_cred_creation_date(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/CreationDate = %s", str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_expiration_date(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/ExpirationDate = %s", str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_username(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/Username = %s", + str); + if (set_cred_quoted(ctx->ifname, id, "username", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred username"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_password(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + int len, i; + char *pw, *hex, *pos, *end; + + pw = xml_node_get_base64_text(ctx->xml, node, &len); + if (pw == NULL) + return; + + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/Password = %s", pw); + + hex = malloc(len * 2 + 1); + if (hex == NULL) { + free(pw); + return; + } + end = hex + len * 2 + 1; + pos = hex; + for (i = 0; i < len; i++) { + snprintf(pos, end - pos, "%02x", pw[i]); + pos += 2; + } + free(pw); + + if (set_cred(ctx->ifname, id, "password", hex) < 0) + wpa_printf(MSG_INFO, "Failed to set cred password"); + free(hex); +} + + +static void set_pps_cred_machine_managed(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/MachineManaged = %s", + str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_soft_token_app(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/SoftTokenApp = %s", + str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_able_to_share(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + if (str == NULL) + return; + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/AbleToShare = %s", + str); + /* not used within wpa_supplicant */ + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_eap_method(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + wpa_printf(MSG_INFO, "- Credential/UsernamePassword/EAPMethod - TODO"); +} + + +static void set_pps_cred_username_password(struct hs20_osu_client *ctx, int id, + xml_node_t *node) +{ + xml_node_t *child; + const char *name; + + wpa_printf(MSG_INFO, "- Credential/UsernamePassword"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "Username") == 0) + set_pps_cred_username(ctx, id, child); + else if (os_strcasecmp(name, "Password") == 0) + set_pps_cred_password(ctx, id, child); + else if (os_strcasecmp(name, "MachineManaged") == 0) + set_pps_cred_machine_managed(ctx, id, child); + else if (os_strcasecmp(name, "SoftTokenApp") == 0) + set_pps_cred_soft_token_app(ctx, id, child); + else if (os_strcasecmp(name, "AbleToShare") == 0) + set_pps_cred_able_to_share(ctx, id, child); + else if (os_strcasecmp(name, "EAPMethod") == 0) + set_pps_cred_eap_method(ctx, id, child); + else + wpa_printf(MSG_INFO, "Unknown Credential/UsernamePassword node '%s'", + name); + } +} + + +static void set_pps_cred_digital_cert(struct hs20_osu_client *ctx, int id, + xml_node_t *node, const char *fqdn) +{ + char buf[200], dir[200]; + + wpa_printf(MSG_INFO, "- Credential/DigitalCertificate"); + + if (getcwd(dir, sizeof(dir)) == NULL) + return; + + /* TODO: could build username from Subject of Subject AltName */ + if (set_cred_quoted(ctx->ifname, id, "username", "cert") < 0) { + wpa_printf(MSG_INFO, "Failed to set username"); + } + + snprintf(buf, sizeof(buf), "%s/SP/%s/client-cert.pem", dir, fqdn); + if (os_file_exists(buf)) { + if (set_cred_quoted(ctx->ifname, id, "client_cert", buf) < 0) { + wpa_printf(MSG_INFO, "Failed to set client_cert"); + } + } + + snprintf(buf, sizeof(buf), "%s/SP/%s/client-key.pem", dir, fqdn); + if (os_file_exists(buf)) { + if (set_cred_quoted(ctx->ifname, id, "private_key", buf) < 0) { + wpa_printf(MSG_INFO, "Failed to set private_key"); + } + } +} + + +static void set_pps_cred_realm(struct hs20_osu_client *ctx, int id, + xml_node_t *node, const char *fqdn, int sim) +{ + char *str = xml_node_get_text(ctx->xml, node); + char buf[200], dir[200]; + + if (str == NULL) + return; + + wpa_printf(MSG_INFO, "- Credential/Realm = %s", str); + if (set_cred_quoted(ctx->ifname, id, "realm", str) < 0) + wpa_printf(MSG_INFO, "Failed to set cred realm"); + xml_node_get_text_free(ctx->xml, str); + + if (sim) + return; + + if (getcwd(dir, sizeof(dir)) == NULL) + return; + snprintf(buf, sizeof(buf), "%s/SP/%s/aaa-ca.pem", dir, fqdn); + if (os_file_exists(buf)) { + if (set_cred_quoted(ctx->ifname, id, "ca_cert", buf) < 0) { + wpa_printf(MSG_INFO, "Failed to set CA cert"); + } + } +} + + +static void set_pps_cred_check_aaa_cert_status(struct hs20_osu_client *ctx, + int id, xml_node_t *node) +{ + char *str = xml_node_get_text(ctx->xml, node); + + if (str == NULL) + return; + + wpa_printf(MSG_INFO, "- Credential/CheckAAAServerCertStatus = %s", str); + if (os_strcasecmp(str, "true") == 0 && + set_cred(ctx->ifname, id, "ocsp", "2") < 0) + wpa_printf(MSG_INFO, "Failed to set cred ocsp"); + xml_node_get_text_free(ctx->xml, str); +} + + +static void set_pps_cred_sim(struct hs20_osu_client *ctx, int id, + xml_node_t *sim, xml_node_t *realm) +{ + xml_node_t *node; + char *imsi, *eaptype, *str, buf[20]; + int type; + int mnc_len = 3; + size_t imsi_len; + + node = get_node(ctx->xml, sim, "EAPType"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No SIM/EAPType node in credential"); + return; + } + eaptype = xml_node_get_text(ctx->xml, node); + if (eaptype == NULL) { + wpa_printf(MSG_INFO, "Could not extract SIM/EAPType"); + return; + } + wpa_printf(MSG_INFO, " - Credential/SIM/EAPType = %s", eaptype); + type = atoi(eaptype); + xml_node_get_text_free(ctx->xml, eaptype); + + switch (type) { + case EAP_TYPE_SIM: + if (set_cred(ctx->ifname, id, "eap", "SIM") < 0) + wpa_printf(MSG_INFO, "Could not set eap=SIM"); + break; + case EAP_TYPE_AKA: + if (set_cred(ctx->ifname, id, "eap", "AKA") < 0) + wpa_printf(MSG_INFO, "Could not set eap=SIM"); + break; + case EAP_TYPE_AKA_PRIME: + if (set_cred(ctx->ifname, id, "eap", "AKA'") < 0) + wpa_printf(MSG_INFO, "Could not set eap=SIM"); + break; + default: + wpa_printf(MSG_INFO, "Unsupported SIM/EAPType %d", type); + return; + } + + node = get_node(ctx->xml, sim, "IMSI"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No SIM/IMSI node in credential"); + return; + } + imsi = xml_node_get_text(ctx->xml, node); + if (imsi == NULL) { + wpa_printf(MSG_INFO, "Could not extract SIM/IMSI"); + return; + } + wpa_printf(MSG_INFO, " - Credential/SIM/IMSI = %s", imsi); + imsi_len = os_strlen(imsi); + if (imsi_len < 7 || imsi_len + 2 > sizeof(buf)) { + wpa_printf(MSG_INFO, "Invalid IMSI length"); + xml_node_get_text_free(ctx->xml, imsi); + return; + } + + str = xml_node_get_text(ctx->xml, node); + if (str) { + char *pos; + pos = os_strstr(str, "mnc"); + if (pos && os_strlen(pos) >= 6) { + if (os_strncmp(imsi + 3, pos + 3, 3) == 0) + mnc_len = 3; + else if (os_strncmp(imsi + 3, pos + 4, 2) == 0) + mnc_len = 2; + } + xml_node_get_text_free(ctx->xml, str); + } + + os_memcpy(buf, imsi, 3 + mnc_len); + buf[3 + mnc_len] = '-'; + os_strlcpy(buf + 3 + mnc_len + 1, imsi + 3 + mnc_len, + sizeof(buf) - 3 - mnc_len - 1); + + xml_node_get_text_free(ctx->xml, imsi); + + if (set_cred_quoted(ctx->ifname, id, "imsi", buf) < 0) + wpa_printf(MSG_INFO, "Could not set IMSI"); + + if (set_cred_quoted(ctx->ifname, id, "milenage", + "90dca4eda45b53cf0f12d7c9c3bc6a89:" + "cb9cccc4b9258e6dca4760379fb82581:000000000123") < + 0) + wpa_printf(MSG_INFO, "Could not set Milenage parameters"); +} + + +static void set_pps_cred_credential(struct hs20_osu_client *ctx, int id, + xml_node_t *node, const char *fqdn) +{ + xml_node_t *child, *sim, *realm; + const char *name; + + wpa_printf(MSG_INFO, "- Credential"); + + sim = get_node(ctx->xml, node, "SIM"); + realm = get_node(ctx->xml, node, "Realm"); + + xml_node_for_each_child(ctx->xml, child, node) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "CreationDate") == 0) + set_pps_cred_creation_date(ctx, id, child); + else if (os_strcasecmp(name, "ExpirationDate") == 0) + set_pps_cred_expiration_date(ctx, id, child); + else if (os_strcasecmp(name, "UsernamePassword") == 0) + set_pps_cred_username_password(ctx, id, child); + else if (os_strcasecmp(name, "DigitalCertificate") == 0) + set_pps_cred_digital_cert(ctx, id, child, fqdn); + else if (os_strcasecmp(name, "Realm") == 0) + set_pps_cred_realm(ctx, id, child, fqdn, sim != NULL); + else if (os_strcasecmp(name, "CheckAAAServerCertStatus") == 0) + set_pps_cred_check_aaa_cert_status(ctx, id, child); + else if (os_strcasecmp(name, "SIM") == 0) + set_pps_cred_sim(ctx, id, child, realm); + else + wpa_printf(MSG_INFO, "Unknown Credential node '%s'", + name); + } +} + + +static void set_pps_credential(struct hs20_osu_client *ctx, int id, + xml_node_t *cred, const char *fqdn) +{ + xml_node_t *child; + const char *name; + + xml_node_for_each_child(ctx->xml, child, cred) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "Policy") == 0) + set_pps_cred_policy(ctx, id, child); + else if (os_strcasecmp(name, "CredentialPriority") == 0) + set_pps_cred_priority(ctx, id, child); + else if (os_strcasecmp(name, "AAAServerTrustRoot") == 0) + set_pps_cred_aaa_server_trust_root(ctx, id, child); + else if (os_strcasecmp(name, "SubscriptionUpdate") == 0) + set_pps_cred_sub_update(ctx, id, child); + else if (os_strcasecmp(name, "HomeSP") == 0) + set_pps_cred_home_sp(ctx, id, child); + else if (os_strcasecmp(name, "SubscriptionParameters") == 0) + set_pps_cred_sub_params(ctx, id, child); + else if (os_strcasecmp(name, "Credential") == 0) + set_pps_cred_credential(ctx, id, child, fqdn); + else + wpa_printf(MSG_INFO, "Unknown credential node '%s'", + name); + } +} + + +static void set_pps(struct hs20_osu_client *ctx, xml_node_t *pps, + const char *fqdn) +{ + xml_node_t *child; + const char *name; + int id; + char *update_identifier = NULL; + + /* + * TODO: Could consider more complex mechanism that would remove + * credentials only if there are changes in the information sent to + * wpa_supplicant. + */ + remove_sp_creds(ctx, fqdn); + + xml_node_for_each_child(ctx->xml, child, pps) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "UpdateIdentifier") == 0) { + update_identifier = xml_node_get_text(ctx->xml, child); + if (update_identifier) { + wpa_printf(MSG_INFO, "- UpdateIdentifier = %s", + update_identifier); + break; + } + } + } + + xml_node_for_each_child(ctx->xml, child, pps) { + xml_node_for_each_check(ctx->xml, child); + name = xml_node_get_localname(ctx->xml, child); + if (os_strcasecmp(name, "UpdateIdentifier") == 0) + continue; + id = add_cred(ctx->ifname); + if (id < 0) { + wpa_printf(MSG_INFO, "Failed to add credential to wpa_supplicant"); + write_summary(ctx, "Failed to add credential to wpa_supplicant"); + break; + } + write_summary(ctx, "Add a credential to wpa_supplicant"); + if (update_identifier && + set_cred(ctx->ifname, id, "update_identifier", + update_identifier) < 0) + wpa_printf(MSG_INFO, "Failed to set update_identifier"); + if (set_cred_quoted(ctx->ifname, id, "provisioning_sp", fqdn) < + 0) + wpa_printf(MSG_INFO, "Failed to set provisioning_sp"); + wpa_printf(MSG_INFO, "credential localname: '%s'", name); + set_pps_credential(ctx, id, child, fqdn); + ctx->pps_cred_set = 1; + } + + xml_node_get_text_free(ctx->xml, update_identifier); +} + + +void cmd_set_pps(struct hs20_osu_client *ctx, const char *pps_fname) +{ + xml_node_t *pps; + const char *fqdn; + char *fqdn_buf = NULL, *pos; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", pps_fname); + return; + } + + fqdn = os_strstr(pps_fname, "SP/"); + if (fqdn) { + fqdn_buf = os_strdup(fqdn + 3); + if (fqdn_buf == NULL) + return; + pos = os_strchr(fqdn_buf, '/'); + if (pos) + *pos = '\0'; + fqdn = fqdn_buf; + } else + fqdn = "wi-fi.org"; + + wpa_printf(MSG_INFO, "Set PPS MO info to wpa_supplicant - SP FQDN %s", + fqdn); + set_pps(ctx, pps, fqdn); + + os_free(fqdn_buf); + xml_node_free(ctx->xml, pps); +} + + +static int cmd_get_fqdn(struct hs20_osu_client *ctx, const char *pps_fname) +{ + xml_node_t *pps, *node; + char *fqdn = NULL; + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", pps_fname); + return -1; + } + + node = get_child_node(ctx->xml, pps, "HomeSP/FQDN"); + if (node) + fqdn = xml_node_get_text(ctx->xml, node); + + xml_node_free(ctx->xml, pps); + + if (fqdn) { + FILE *f = fopen("pps-fqdn", "w"); + if (f) { + fprintf(f, "%s", fqdn); + fclose(f); + } + xml_node_get_text_free(ctx->xml, fqdn); + return 0; + } + + xml_node_get_text_free(ctx->xml, fqdn); + return -1; +} + + +static void cmd_to_tnds(struct hs20_osu_client *ctx, const char *in_fname, + const char *out_fname, const char *urn, int use_path) +{ + xml_node_t *mo, *node; + + mo = node_from_file(ctx->xml, in_fname); + if (mo == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", in_fname); + return; + } + + node = mo_to_tnds(ctx->xml, mo, use_path, urn, NULL); + if (node) { + node_to_file(ctx->xml, out_fname, node); + xml_node_free(ctx->xml, node); + } + + xml_node_free(ctx->xml, mo); +} + + +static void cmd_from_tnds(struct hs20_osu_client *ctx, const char *in_fname, + const char *out_fname) +{ + xml_node_t *tnds, *mo; + + tnds = node_from_file(ctx->xml, in_fname); + if (tnds == NULL) { + wpa_printf(MSG_INFO, "Could not read or parse '%s'", in_fname); + return; + } + + mo = tnds_to_mo(ctx->xml, tnds); + if (mo) { + node_to_file(ctx->xml, out_fname, mo); + xml_node_free(ctx->xml, mo); + } + + xml_node_free(ctx->xml, tnds); +} + + +struct osu_icon { + int id; + char lang[4]; + char mime_type[256]; + char filename[256]; +}; + +struct osu_data { + char bssid[20]; + char url[256]; + unsigned int methods; + char osu_ssid[33]; + char osu_nai[256]; + struct osu_lang_text friendly_name[MAX_OSU_VALS]; + size_t friendly_name_count; + struct osu_lang_text serv_desc[MAX_OSU_VALS]; + size_t serv_desc_count; + struct osu_icon icon[MAX_OSU_VALS]; + size_t icon_count; +}; + + +static struct osu_data * parse_osu_providers(const char *fname, size_t *count) +{ + FILE *f; + char buf[1000]; + struct osu_data *osu = NULL, *last = NULL; + size_t osu_count = 0; + char *pos, *end; + + f = fopen(fname, "r"); + if (f == NULL) { + wpa_printf(MSG_ERROR, "Could not open %s", fname); + return NULL; + } + + while (fgets(buf, sizeof(buf), f)) { + pos = strchr(buf, '\n'); + if (pos) + *pos = '\0'; + + if (strncmp(buf, "OSU-PROVIDER ", 13) == 0) { + last = realloc(osu, (osu_count + 1) * sizeof(*osu)); + if (last == NULL) + break; + osu = last; + last = &osu[osu_count++]; + memset(last, 0, sizeof(*last)); + snprintf(last->bssid, sizeof(last->bssid), "%s", + buf + 13); + continue; + } + if (!last) + continue; + + if (strncmp(buf, "uri=", 4) == 0) { + snprintf(last->url, sizeof(last->url), "%s", buf + 4); + continue; + } + + if (strncmp(buf, "methods=", 8) == 0) { + last->methods = strtol(buf + 8, NULL, 16); + continue; + } + + if (strncmp(buf, "osu_ssid=", 9) == 0) { + snprintf(last->osu_ssid, sizeof(last->osu_ssid), + "%s", buf + 9); + continue; + } + + if (os_strncmp(buf, "osu_nai=", 8) == 0) { + os_snprintf(last->osu_nai, sizeof(last->osu_nai), + "%s", buf + 8); + continue; + } + + if (strncmp(buf, "friendly_name=", 14) == 0) { + struct osu_lang_text *txt; + if (last->friendly_name_count == MAX_OSU_VALS) + continue; + pos = strchr(buf + 14, ':'); + if (pos == NULL) + continue; + *pos++ = '\0'; + txt = &last->friendly_name[last->friendly_name_count++]; + snprintf(txt->lang, sizeof(txt->lang), "%s", buf + 14); + snprintf(txt->text, sizeof(txt->text), "%s", pos); + } + + if (strncmp(buf, "desc=", 5) == 0) { + struct osu_lang_text *txt; + if (last->serv_desc_count == MAX_OSU_VALS) + continue; + pos = strchr(buf + 5, ':'); + if (pos == NULL) + continue; + *pos++ = '\0'; + txt = &last->serv_desc[last->serv_desc_count++]; + snprintf(txt->lang, sizeof(txt->lang), "%s", buf + 5); + snprintf(txt->text, sizeof(txt->text), "%s", pos); + } + + if (strncmp(buf, "icon=", 5) == 0) { + struct osu_icon *icon; + if (last->icon_count == MAX_OSU_VALS) + continue; + icon = &last->icon[last->icon_count++]; + icon->id = atoi(buf + 5); + pos = strchr(buf, ':'); + if (pos == NULL) + continue; + pos = strchr(pos + 1, ':'); + if (pos == NULL) + continue; + pos = strchr(pos + 1, ':'); + if (pos == NULL) + continue; + pos++; + end = strchr(pos, ':'); + if (!end) + continue; + *end = '\0'; + snprintf(icon->lang, sizeof(icon->lang), "%s", pos); + pos = end + 1; + + end = strchr(pos, ':'); + if (end) + *end = '\0'; + snprintf(icon->mime_type, sizeof(icon->mime_type), + "%s", pos); + if (!pos) + continue; + pos = end + 1; + + end = strchr(pos, ':'); + if (end) + *end = '\0'; + snprintf(icon->filename, sizeof(icon->filename), + "%s", pos); + continue; + } + } + + fclose(f); + + *count = osu_count; + return osu; +} + + +static int osu_connect(struct hs20_osu_client *ctx, const char *bssid, + const char *ssid, const char *url, const char *ca_fname, + unsigned int methods, int no_prod_assoc, + const char *osu_nai) +{ + int id; + const char *ifname = ctx->ifname; + char buf[200]; + struct wpa_ctrl *mon; + int res; + + id = add_network(ifname); + if (id < 0) + return -1; + if (set_network_quoted(ifname, id, "ssid", ssid) < 0) + return -1; + if (osu_nai && os_strlen(osu_nai) > 0) { + char dir[255], fname[300]; + if (getcwd(dir, sizeof(dir)) == NULL) + return -1; + os_snprintf(fname, sizeof(fname), "%s/osu-ca.pem", dir); + + if (set_network(ifname, id, "proto", "OSEN") < 0 || + set_network(ifname, id, "key_mgmt", "OSEN") < 0 || + set_network(ifname, id, "pairwise", "CCMP") < 0 || + set_network(ifname, id, "group", "GTK_NOT_USED") < 0 || + set_network(ifname, id, "eap", "WFA-UNAUTH-TLS") < 0 || + set_network(ifname, id, "ocsp", "2") < 0 || + set_network_quoted(ifname, id, "identity", osu_nai) < 0 || + set_network_quoted(ifname, id, "ca_cert", fname) < 0) + return -1; + } else { + if (set_network(ifname, id, "key_mgmt", "NONE") < 0) + return -1; + } + + mon = open_wpa_mon(ifname); + if (mon == NULL) + return -1; + + wpa_printf(MSG_INFO, "Associate with OSU SSID"); + write_summary(ctx, "Associate with OSU SSID"); + snprintf(buf, sizeof(buf), "SELECT_NETWORK %d", id); + if (wpa_command(ifname, buf) < 0) + return -1; + + res = get_wpa_cli_event(mon, "CTRL-EVENT-CONNECTED", + buf, sizeof(buf)); + + wpa_ctrl_detach(mon); + wpa_ctrl_close(mon); + + if (res < 0) { + wpa_printf(MSG_INFO, "Could not connect"); + write_summary(ctx, "Could not connect to OSU network"); + wpa_printf(MSG_INFO, "Remove OSU network connection"); + snprintf(buf, sizeof(buf), "REMOVE_NETWORK %d", id); + wpa_command(ifname, buf); + return -1; + } + + write_summary(ctx, "Waiting for IP address for subscription registration"); + if (wait_ip_addr(ifname, 15) < 0) { + wpa_printf(MSG_INFO, "Could not get IP address for WLAN - try connection anyway"); + } + + if (no_prod_assoc) { + if (res < 0) + return -1; + wpa_printf(MSG_INFO, "No production connection used for testing purposes"); + write_summary(ctx, "No production connection used for testing purposes"); + return 0; + } + + ctx->no_reconnect = 1; + if (methods & 0x02) + res = cmd_prov(ctx, url, ca_fname); + else if (methods & 0x01) + res = cmd_oma_dm_prov(ctx, url, ca_fname); + + wpa_printf(MSG_INFO, "Remove OSU network connection"); + write_summary(ctx, "Remove OSU network connection"); + snprintf(buf, sizeof(buf), "REMOVE_NETWORK %d", id); + wpa_command(ifname, buf); + + if (res < 0) + return -1; + + wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration"); + write_summary(ctx, "Requesting reconnection with updated configuration"); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) { + wpa_printf(MSG_INFO, "Failed to request wpa_supplicant to reconnect"); + write_summary(ctx, "Failed to request wpa_supplicant to reconnect"); + return -1; + } + + return 0; +} + + +static int cmd_osu_select(struct hs20_osu_client *ctx, const char *dir, + int connect, const char *ca_fname, int no_prod_assoc, + const char *friendly_name) +{ + char fname[255]; + FILE *f; + struct osu_data *osu = NULL, *last = NULL; + size_t osu_count, i, j; + int ret; + + write_summary(ctx, "OSU provider selection"); + + if (dir == NULL) { + wpa_printf(MSG_INFO, "Missing dir parameter to osu_select"); + return -1; + } + + snprintf(fname, sizeof(fname), "%s/osu-providers.txt", dir); + osu = parse_osu_providers(fname, &osu_count); + if (osu == NULL) { + wpa_printf(MSG_INFO, "Could not any OSU providers from %s", + fname); + write_result(ctx, "No OSU providers available"); + return -1; + } + + if (friendly_name) { + for (i = 0; i < osu_count; i++) { + last = &osu[i]; + for (j = 0; j < last->friendly_name_count; j++) { + if (os_strcmp(last->friendly_name[j].text, + friendly_name) == 0) + break; + } + if (j < last->friendly_name_count) + break; + } + if (i == osu_count) { + wpa_printf(MSG_INFO, "Requested operator friendly name '%s' not found in the list of available providers", + friendly_name); + write_summary(ctx, "Requested operator friendly name '%s' not found in the list of available providers", + friendly_name); + free(osu); + return -1; + } + + wpa_printf(MSG_INFO, "OSU Provider selected based on requested operator friendly name '%s'", + friendly_name); + write_summary(ctx, "OSU Provider selected based on requested operator friendly name '%s'", + friendly_name); + ret = i + 1; + goto selected; + } + + snprintf(fname, sizeof(fname), "%s/osu-providers.html", dir); + f = fopen(fname, "w"); + if (f == NULL) { + wpa_printf(MSG_INFO, "Could not open %s", fname); + free(osu); + return -1; + } + + fprintf(f, "<html><head>" + "<meta http-equiv=\"Content-type\" content=\"text/html; " + "charset=utf-8\"<title>Select service operator</title>" + "</head><body><h1>Select service operator</h1>\n"); + + if (osu_count == 0) + fprintf(f, "No online signup available\n"); + + for (i = 0; i < osu_count; i++) { + last = &osu[i]; +#ifdef ANDROID + fprintf(f, "<p>\n" + "<a href=\"http://localhost:12345/osu/%d\">" + "<table><tr><td>", (int) i + 1); +#else /* ANDROID */ + fprintf(f, "<p>\n" + "<a href=\"osu://%d\">" + "<table><tr><td>", (int) i + 1); +#endif /* ANDROID */ + for (j = 0; j < last->icon_count; j++) { + fprintf(f, "<img src=\"osu-icon-%d.%s\">\n", + last->icon[j].id, + strcasecmp(last->icon[j].mime_type, + "image/png") == 0 ? "png" : "icon"); + } + fprintf(f, "<td>"); + for (j = 0; j < last->friendly_name_count; j++) { + fprintf(f, "<small>[%s]</small> %s<br>\n", + last->friendly_name[j].lang, + last->friendly_name[j].text); + } + fprintf(f, "<tr><td colspan=2>"); + for (j = 0; j < last->serv_desc_count; j++) { + fprintf(f, "<small>[%s]</small> %s<br>\n", + last->serv_desc[j].lang, + last->serv_desc[j].text); + } + fprintf(f, "</table></a><br><small>BSSID: %s<br>\n" + "SSID: %s<br>\n", + last->bssid, last->osu_ssid); + if (last->osu_nai) + fprintf(f, "NAI: %s<br>\n", last->osu_nai); + fprintf(f, "URL: %s<br>\n" + "methods:%s%s<br>\n" + "</small></p>\n", + last->url, + last->methods & 0x01 ? " OMA-DM" : "", + last->methods & 0x02 ? " SOAP-XML-SPP" : ""); + } + + fprintf(f, "</body></html>\n"); + + fclose(f); + + snprintf(fname, sizeof(fname), "file://%s/osu-providers.html", dir); + write_summary(ctx, "Start web browser with OSU provider selection page"); + ret = hs20_web_browser(fname); + +selected: + if (ret > 0 && (size_t) ret <= osu_count) { + char *data; + size_t data_len; + + wpa_printf(MSG_INFO, "Selected OSU id=%d", ret); + last = &osu[ret - 1]; + ret = 0; + wpa_printf(MSG_INFO, "BSSID: %s", last->bssid); + wpa_printf(MSG_INFO, "SSID: %s", last->osu_ssid); + wpa_printf(MSG_INFO, "URL: %s", last->url); + write_summary(ctx, "Selected OSU provider id=%d BSSID=%s SSID=%s URL=%s", + ret, last->bssid, last->osu_ssid, last->url); + + ctx->friendly_name_count = last->friendly_name_count; + for (j = 0; j < last->friendly_name_count; j++) { + wpa_printf(MSG_INFO, "FRIENDLY_NAME: [%s]%s", + last->friendly_name[j].lang, + last->friendly_name[j].text); + os_strlcpy(ctx->friendly_name[j].lang, + last->friendly_name[j].lang, + sizeof(ctx->friendly_name[j].lang)); + os_strlcpy(ctx->friendly_name[j].text, + last->friendly_name[j].text, + sizeof(ctx->friendly_name[j].text)); + } + + ctx->icon_count = last->icon_count; + for (j = 0; j < last->icon_count; j++) { + char fname[256]; + + os_snprintf(fname, sizeof(fname), "%s/osu-icon-%d.%s", + dir, last->icon[j].id, + strcasecmp(last->icon[j].mime_type, + "image/png") == 0 ? + "png" : "icon"); + wpa_printf(MSG_INFO, "ICON: %s (%s)", + fname, last->icon[j].filename); + os_strlcpy(ctx->icon_filename[j], + last->icon[j].filename, + sizeof(ctx->icon_filename[j])); + + data = os_readfile(fname, &data_len); + if (data) { + sha256_vector(1, (const u8 **) &data, &data_len, + ctx->icon_hash[j]); + os_free(data); + } + } + + if (connect == 2) { + if (last->methods & 0x02) + ret = cmd_prov(ctx, last->url, ca_fname); + else if (last->methods & 0x01) + ret = cmd_oma_dm_prov(ctx, last->url, ca_fname); + else + ret = -1; + } else if (connect) + ret = osu_connect(ctx, last->bssid, last->osu_ssid, + last->url, ca_fname, last->methods, + no_prod_assoc, last->osu_nai); + } else + ret = -1; + + free(osu); + + return ret; +} + + +static int cmd_signup(struct hs20_osu_client *ctx, const char *ca_fname, + int no_prod_assoc, const char *friendly_name) +{ + char dir[255]; + char fname[300], buf[400]; + struct wpa_ctrl *mon; + const char *ifname; + int res; + + ifname = ctx->ifname; + + if (getcwd(dir, sizeof(dir)) == NULL) + return -1; + + snprintf(fname, sizeof(fname), "%s/osu-info", dir); + if (mkdir(fname, S_IRWXU | S_IRWXG) < 0 && errno != EEXIST) { + wpa_printf(MSG_INFO, "mkdir(%s) failed: %s", + fname, strerror(errno)); + return -1; + } + + snprintf(buf, sizeof(buf), "SET osu_dir %s", fname); + if (wpa_command(ifname, buf) < 0) { + wpa_printf(MSG_INFO, "Failed to configure osu_dir to wpa_supplicant"); + return -1; + } + + mon = open_wpa_mon(ifname); + if (mon == NULL) + return -1; + + wpa_printf(MSG_INFO, "Starting OSU fetch"); + write_summary(ctx, "Starting OSU provider information fetch"); + if (wpa_command(ifname, "FETCH_OSU") < 0) { + wpa_printf(MSG_INFO, "Could not start OSU fetch"); + wpa_ctrl_detach(mon); + wpa_ctrl_close(mon); + return -1; + } + res = get_wpa_cli_event(mon, "OSU provider fetch completed", + buf, sizeof(buf)); + + wpa_ctrl_detach(mon); + wpa_ctrl_close(mon); + + if (res < 0) { + wpa_printf(MSG_INFO, "OSU fetch did not complete"); + write_summary(ctx, "OSU fetch did not complete"); + return -1; + } + wpa_printf(MSG_INFO, "OSU provider fetch completed"); + + return cmd_osu_select(ctx, fname, 1, ca_fname, no_prod_assoc, + friendly_name); +} + + +static void cmd_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname) +{ + xml_node_t *pps, *node; + char pps_fname_buf[300]; + char ca_fname_buf[200]; + char *cred_username = NULL; + char *cred_password = NULL; + char *sub_rem_uri = NULL; + char client_cert_buf[200]; + char *client_cert = NULL; + char client_key_buf[200]; + char *client_key = NULL; + int spp; + + ctx->ca_fname = ca_fname; + + wpa_printf(MSG_INFO, "Subscription remediation requested with Server URL: %s", + address); + + if (!pps_fname) { + char buf[256]; + wpa_printf(MSG_INFO, "Determining PPS file based on Home SP information"); + if (os_strncmp(address, "fqdn=", 5) == 0) { + wpa_printf(MSG_INFO, "Use requested FQDN from command line"); + os_snprintf(buf, sizeof(buf), "%s", address + 5); + address = NULL; + } else if (get_wpa_status(ctx->ifname, "provisioning_sp", buf, + sizeof(buf)) < 0) { + wpa_printf(MSG_INFO, "Could not get provisioning Home SP FQDN from wpa_supplicant"); + return; + } + os_free(ctx->fqdn); + ctx->fqdn = os_strdup(buf); + if (ctx->fqdn == NULL) + return; + wpa_printf(MSG_INFO, "Home SP FQDN for current credential: %s", + buf); + os_snprintf(pps_fname_buf, sizeof(pps_fname_buf), + "SP/%s/pps.xml", ctx->fqdn); + pps_fname = pps_fname_buf; + + os_snprintf(ca_fname_buf, sizeof(ca_fname_buf), "SP/%s/ca.pem", + ctx->fqdn); + ca_fname = ca_fname_buf; + } + + if (!os_file_exists(pps_fname)) { + wpa_printf(MSG_INFO, "PPS file '%s' does not exist or is not accessible", + pps_fname); + return; + } + wpa_printf(MSG_INFO, "Using PPS file: %s", pps_fname); + + if (ca_fname && !os_file_exists(ca_fname)) { + wpa_printf(MSG_INFO, "CA file '%s' does not exist or is not accessible", + ca_fname); + return; + } + wpa_printf(MSG_INFO, "Using server trust root: %s", ca_fname); + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read PPS MO"); + return; + } + + if (!ctx->fqdn) { + char *tmp; + node = get_child_node(ctx->xml, pps, "HomeSP/FQDN"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No HomeSP/FQDN found from PPS"); + return; + } + tmp = xml_node_get_text(ctx->xml, node); + if (tmp == NULL) { + wpa_printf(MSG_INFO, "No HomeSP/FQDN text found from PPS"); + return; + } + ctx->fqdn = os_strdup(tmp); + xml_node_get_text_free(ctx->xml, tmp); + if (!ctx->fqdn) { + wpa_printf(MSG_INFO, "No FQDN known"); + return; + } + } + + node = get_child_node(ctx->xml, pps, + "SubscriptionUpdate/UpdateMethod"); + if (node) { + char *tmp; + tmp = xml_node_get_text(ctx->xml, node); + if (tmp && os_strcasecmp(tmp, "OMA-DM-ClientInitiated") == 0) + spp = 0; + else + spp = 1; + } else { + wpa_printf(MSG_INFO, "No UpdateMethod specified - assume SPP"); + spp = 1; + } + + get_user_pw(ctx, pps, "SubscriptionUpdate/UsernamePassword", + &cred_username, &cred_password); + if (cred_username) + wpa_printf(MSG_INFO, "Using username: %s", cred_username); + if (cred_password) + wpa_printf(MSG_DEBUG, "Using password: %s", cred_password); + + if (cred_username == NULL && cred_password == NULL && + get_child_node(ctx->xml, pps, "Credential/DigitalCertificate")) { + wpa_printf(MSG_INFO, "Using client certificate"); + os_snprintf(client_cert_buf, sizeof(client_cert_buf), + "SP/%s/client-cert.pem", ctx->fqdn); + client_cert = client_cert_buf; + os_snprintf(client_key_buf, sizeof(client_key_buf), + "SP/%s/client-key.pem", ctx->fqdn); + client_key = client_key_buf; + ctx->client_cert_present = 1; + } + + node = get_child_node(ctx->xml, pps, "SubscriptionUpdate/URI"); + if (node) { + sub_rem_uri = xml_node_get_text(ctx->xml, node); + if (sub_rem_uri && + (!address || os_strcmp(address, sub_rem_uri) != 0)) { + wpa_printf(MSG_INFO, "Override sub rem URI based on PPS: %s", + sub_rem_uri); + address = sub_rem_uri; + } + } + if (!address) { + wpa_printf(MSG_INFO, "Server URL not known"); + return; + } + + write_summary(ctx, "Wait for IP address for subscriptiom remediation"); + wpa_printf(MSG_INFO, "Wait for IP address before starting subscription remediation"); + + if (wait_ip_addr(ctx->ifname, 15) < 0) { + wpa_printf(MSG_INFO, "Could not get IP address for WLAN - try connection anyway"); + } + + if (spp) + spp_sub_rem(ctx, address, pps_fname, ca_fname, + client_cert, client_key, + cred_username, cred_password, pps); + else + oma_dm_sub_rem(ctx, address, pps_fname, ca_fname, + client_cert, client_key, + cred_username, cred_password, pps); + + xml_node_get_text_free(ctx->xml, sub_rem_uri); + xml_node_get_text_free(ctx->xml, cred_username); + os_free(cred_password); + xml_node_free(ctx->xml, pps); +} + + +static int cmd_pol_upd(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname) +{ + xml_node_t *pps; + xml_node_t *node; + char pps_fname_buf[300]; + char ca_fname_buf[200]; + char *uri = NULL; + char *cred_username = NULL; + char *cred_password = NULL; + char client_cert_buf[200]; + char *client_cert = NULL; + char client_key_buf[200]; + char *client_key = NULL; + int spp; + + wpa_printf(MSG_INFO, "Policy update requested"); + + if (!pps_fname) { + char buf[256]; + wpa_printf(MSG_INFO, "Determining PPS file based on Home SP information"); + if (os_strncmp(address, "fqdn=", 5) == 0) { + wpa_printf(MSG_INFO, "Use requested FQDN from command line"); + os_snprintf(buf, sizeof(buf), "%s", address + 5); + address = NULL; + } else if (get_wpa_status(ctx->ifname, "provisioning_sp", buf, + sizeof(buf)) < 0) { + wpa_printf(MSG_INFO, "Could not get provisioning Home SP FQDN from wpa_supplicant"); + return -1; + } + os_free(ctx->fqdn); + ctx->fqdn = os_strdup(buf); + if (ctx->fqdn == NULL) + return -1; + wpa_printf(MSG_INFO, "Home SP FQDN for current credential: %s", + buf); + os_snprintf(pps_fname_buf, sizeof(pps_fname_buf), + "SP/%s/pps.xml", ctx->fqdn); + pps_fname = pps_fname_buf; + + os_snprintf(ca_fname_buf, sizeof(ca_fname_buf), "SP/%s/ca.pem", + buf); + ca_fname = ca_fname_buf; + } + + if (!os_file_exists(pps_fname)) { + wpa_printf(MSG_INFO, "PPS file '%s' does not exist or is not accessible", + pps_fname); + return -1; + } + wpa_printf(MSG_INFO, "Using PPS file: %s", pps_fname); + + if (ca_fname && !os_file_exists(ca_fname)) { + wpa_printf(MSG_INFO, "CA file '%s' does not exist or is not accessible", + ca_fname); + return -1; + } + wpa_printf(MSG_INFO, "Using server trust root: %s", ca_fname); + + pps = node_from_file(ctx->xml, pps_fname); + if (pps == NULL) { + wpa_printf(MSG_INFO, "Could not read PPS MO"); + return -1; + } + + if (!ctx->fqdn) { + char *tmp; + node = get_child_node(ctx->xml, pps, "HomeSP/FQDN"); + if (node == NULL) { + wpa_printf(MSG_INFO, "No HomeSP/FQDN found from PPS"); + return -1; + } + tmp = xml_node_get_text(ctx->xml, node); + if (tmp == NULL) { + wpa_printf(MSG_INFO, "No HomeSP/FQDN text found from PPS"); + return -1; + } + ctx->fqdn = os_strdup(tmp); + xml_node_get_text_free(ctx->xml, tmp); + if (!ctx->fqdn) { + wpa_printf(MSG_INFO, "No FQDN known"); + return -1; + } + } + + node = get_child_node(ctx->xml, pps, + "Policy/PolicyUpdate/UpdateMethod"); + if (node) { + char *tmp; + tmp = xml_node_get_text(ctx->xml, node); + if (tmp && os_strcasecmp(tmp, "OMA-DM-ClientInitiated") == 0) + spp = 0; + else + spp = 1; + } else { + wpa_printf(MSG_INFO, "No UpdateMethod specified - assume SPP"); + spp = 1; + } + + get_user_pw(ctx, pps, "Policy/PolicyUpdate/UsernamePassword", + &cred_username, &cred_password); + if (cred_username) + wpa_printf(MSG_INFO, "Using username: %s", cred_username); + if (cred_password) + wpa_printf(MSG_DEBUG, "Using password: %s", cred_password); + + if (cred_username == NULL && cred_password == NULL && + get_child_node(ctx->xml, pps, "Credential/DigitalCertificate")) { + wpa_printf(MSG_INFO, "Using client certificate"); + os_snprintf(client_cert_buf, sizeof(client_cert_buf), + "SP/%s/client-cert.pem", ctx->fqdn); + client_cert = client_cert_buf; + os_snprintf(client_key_buf, sizeof(client_key_buf), + "SP/%s/client-key.pem", ctx->fqdn); + client_key = client_key_buf; + } + + if (!address) { + node = get_child_node(ctx->xml, pps, "Policy/PolicyUpdate/URI"); + if (node) { + uri = xml_node_get_text(ctx->xml, node); + wpa_printf(MSG_INFO, "URI based on PPS: %s", uri); + address = uri; + } + } + if (!address) { + wpa_printf(MSG_INFO, "Server URL not known"); + return -1; + } + + if (spp) + spp_pol_upd(ctx, address, pps_fname, ca_fname, + client_cert, client_key, + cred_username, cred_password, pps); + else + oma_dm_pol_upd(ctx, address, pps_fname, ca_fname, + client_cert, client_key, + cred_username, cred_password, pps); + + xml_node_get_text_free(ctx->xml, uri); + xml_node_get_text_free(ctx->xml, cred_username); + os_free(cred_password); + xml_node_free(ctx->xml, pps); + + return 0; +} + + +static char * get_hostname(const char *url) +{ + const char *pos, *end, *end2; + char *ret; + + if (url == NULL) + return NULL; + + pos = os_strchr(url, '/'); + if (pos == NULL) + return NULL; + pos++; + if (*pos != '/') + return NULL; + pos++; + + end = os_strchr(pos, '/'); + end2 = os_strchr(pos, ':'); + if (end && end2 && end2 < end) + end = end2; + if (end) + end--; + else { + end = pos; + while (*end) + end++; + if (end > pos) + end--; + } + + ret = os_malloc(end - pos + 2); + if (ret == NULL) + return NULL; + + os_memcpy(ret, pos, end - pos + 1); + ret[end - pos + 1] = '\0'; + + return ret; +} + + +static int osu_cert_cb(void *_ctx, struct http_cert *cert) +{ + struct hs20_osu_client *ctx = _ctx; + unsigned int i, j; + int found; + char *host = NULL; + + wpa_printf(MSG_INFO, "osu_cert_cb"); + + host = get_hostname(ctx->server_url); + + for (i = 0; i < ctx->server_dnsname_count; i++) + os_free(ctx->server_dnsname[i]); + os_free(ctx->server_dnsname); + ctx->server_dnsname = os_calloc(cert->num_dnsname, sizeof(char *)); + ctx->server_dnsname_count = 0; + + found = 0; + for (i = 0; i < cert->num_dnsname; i++) { + if (ctx->server_dnsname) { + ctx->server_dnsname[ctx->server_dnsname_count] = + os_strdup(cert->dnsname[i]); + if (ctx->server_dnsname[ctx->server_dnsname_count]) + ctx->server_dnsname_count++; + } + if (host && os_strcasecmp(host, cert->dnsname[i]) == 0) + found = 1; + wpa_printf(MSG_INFO, "dNSName '%s'", cert->dnsname[i]); + } + + if (host && !found) { + wpa_printf(MSG_INFO, "Server name from URL (%s) did not match any dNSName - abort connection", + host); + write_result(ctx, "Server name from URL (%s) did not match any dNSName - abort connection", + host); + os_free(host); + return -1; + } + + os_free(host); + + for (i = 0; i < cert->num_othername; i++) { + if (os_strcmp(cert->othername[i].oid, + "1.3.6.1.4.1.40808.1.1.1") == 0) { + wpa_hexdump_ascii(MSG_INFO, + "id-wfa-hotspot-friendlyName", + cert->othername[i].data, + cert->othername[i].len); + } + } + + for (j = 0; j < ctx->friendly_name_count; j++) { + int found = 0; + for (i = 0; i < cert->num_othername; i++) { + if (os_strcmp(cert->othername[i].oid, + "1.3.6.1.4.1.40808.1.1.1") != 0) + continue; + if (cert->othername[i].len < 3) + continue; + if (os_strncasecmp((char *) cert->othername[i].data, + ctx->friendly_name[j].lang, 3) != 0) + continue; + if (os_strncmp((char *) cert->othername[i].data + 3, + ctx->friendly_name[j].text, + cert->othername[i].len - 3) == 0) { + found = 1; + break; + } + } + + if (!found) { + wpa_printf(MSG_INFO, "No friendly name match found for '[%s]%s'", + ctx->friendly_name[j].lang, + ctx->friendly_name[j].text); + write_result(ctx, "No friendly name match found for '[%s]%s'", + ctx->friendly_name[j].lang, + ctx->friendly_name[j].text); + return -1; + } + } + + for (i = 0; i < cert->num_logo; i++) { + struct http_logo *logo = &cert->logo[i]; + + wpa_printf(MSG_INFO, "logo hash alg %s uri '%s'", + logo->alg_oid, logo->uri); + wpa_hexdump_ascii(MSG_INFO, "hashValue", + logo->hash, logo->hash_len); + } + + for (j = 0; j < ctx->icon_count; j++) { + int found = 0; + char *name = ctx->icon_filename[j]; + size_t name_len = os_strlen(name); + + wpa_printf(MSG_INFO, "Looking for icon file name '%s' match", + name); + for (i = 0; i < cert->num_logo; i++) { + struct http_logo *logo = &cert->logo[i]; + size_t uri_len = os_strlen(logo->uri); + char *pos; + + wpa_printf(MSG_INFO, "Comparing to '%s' uri_len=%d name_len=%d", + logo->uri, (int) uri_len, (int) name_len); + if (uri_len < 1 + name_len) + continue; + pos = &logo->uri[uri_len - name_len - 1]; + if (*pos != '/') + continue; + pos++; + if (os_strcmp(pos, name) == 0) { + found = 1; + break; + } + } + + if (!found) { + wpa_printf(MSG_INFO, "No icon filename match found for '%s'", + name); + write_result(ctx, + "No icon filename match found for '%s'", + name); + return -1; + } + } + + for (j = 0; j < ctx->icon_count; j++) { + int found = 0; + + for (i = 0; i < cert->num_logo; i++) { + struct http_logo *logo = &cert->logo[i]; + + if (logo->hash_len != 32) + continue; + if (os_memcmp(logo->hash, ctx->icon_hash[j], 32) == 0) { + found = 1; + break; + } + } + + if (!found) { + wpa_printf(MSG_INFO, "No icon hash match found"); + write_result(ctx, "No icon hash match found"); + return -1; + } + } + + return 0; +} + + +static int init_ctx(struct hs20_osu_client *ctx) +{ + xml_node_t *devinfo, *devid; + + os_memset(ctx, 0, sizeof(*ctx)); + ctx->ifname = "wlan0"; + ctx->xml = xml_node_init_ctx(ctx, NULL); + if (ctx->xml == NULL) + return -1; + + devinfo = node_from_file(ctx->xml, "devinfo.xml"); + if (!devinfo) { + wpa_printf(MSG_ERROR, "devinfo.xml not found"); + return -1; + } + + devid = get_node(ctx->xml, devinfo, "DevId"); + if (devid) { + char *tmp = xml_node_get_text(ctx->xml, devid); + if (tmp) { + ctx->devid = os_strdup(tmp); + xml_node_get_text_free(ctx->xml, tmp); + } + } + xml_node_free(ctx->xml, devinfo); + + if (ctx->devid == NULL) { + wpa_printf(MSG_ERROR, "Could not fetch DevId from devinfo.xml"); + return -1; + } + + ctx->http = http_init_ctx(ctx, ctx->xml); + if (ctx->http == NULL) { + xml_node_deinit_ctx(ctx->xml); + return -1; + } + http_ocsp_set(ctx->http, 2); + http_set_cert_cb(ctx->http, osu_cert_cb, ctx); + + return 0; +} + + +static void deinit_ctx(struct hs20_osu_client *ctx) +{ + size_t i; + + http_deinit_ctx(ctx->http); + xml_node_deinit_ctx(ctx->xml); + os_free(ctx->fqdn); + os_free(ctx->server_url); + os_free(ctx->devid); + + for (i = 0; i < ctx->server_dnsname_count; i++) + os_free(ctx->server_dnsname[i]); + os_free(ctx->server_dnsname); +} + + +static void check_workarounds(struct hs20_osu_client *ctx) +{ + FILE *f; + char buf[100]; + unsigned long int val = 0; + + f = fopen("hs20-osu-client.workarounds", "r"); + if (f == NULL) + return; + + if (fgets(buf, sizeof(buf), f)) + val = strtoul(buf, NULL, 16); + + fclose(f); + + if (val) { + wpa_printf(MSG_INFO, "Workarounds enabled: 0x%lx", val); + ctx->workarounds = val; + if (ctx->workarounds & WORKAROUND_OCSP_OPTIONAL) + http_ocsp_set(ctx->http, 1); + } +} + + +static void usage(void) +{ + printf("usage: hs20-osu-client [-dddqqKt] [-S<station ifname>] \\\n" + " [-w<wpa_supplicant ctrl_iface dir>] " + "[-r<result file>] [-f<debug file>] \\\n" + " [-s<summary file>] \\\n" + " <command> [arguments..]\n" + "commands:\n" + "- to_tnds <XML MO> <XML MO in TNDS format> [URN]\n" + "- to_tnds2 <XML MO> <XML MO in TNDS format (Path) " + "[URN]>\n" + "- from_tnds <XML MO in TNDS format> <XML MO>\n" + "- set_pps <PerProviderSubscription XML file name>\n" + "- get_fqdn <PerProviderSubscription XML file name>\n" + "- pol_upd [Server URL] [PPS] [CA cert]\n" + "- sub_rem <Server URL> [PPS] [CA cert]\n" + "- prov <Server URL> [CA cert]\n" + "- oma_dm_prov <Server URL> [CA cert]\n" + "- sim_prov <Server URL> [CA cert]\n" + "- oma_dm_sim_prov <Server URL> [CA cert]\n" + "- signup [CA cert]\n" + "- dl_osu_ca <PPS> <CA file>\n" + "- dl_polupd_ca <PPS> <CA file>\n" + "- dl_aaa_ca <PPS> <CA file>\n" + "- browser <URL>\n" + "- parse_cert <X.509 certificate (DER)>\n" + "- osu_select <OSU info directory> [CA cert]\n"); +} + + +int main(int argc, char *argv[]) +{ + struct hs20_osu_client ctx; + int c; + int ret = 0; + int no_prod_assoc = 0; + const char *friendly_name = NULL; + const char *wpa_debug_file_path = NULL; + extern char *wpas_ctrl_path; + extern int wpa_debug_level; + extern int wpa_debug_show_keys; + extern int wpa_debug_timestamp; + + if (init_ctx(&ctx) < 0) + return -1; + + for (;;) { + c = getopt(argc, argv, "df:hi:KNO:qr:s:S:tw:"); + if (c < 0) + break; + switch (c) { + case 'd': + if (wpa_debug_level > 0) + wpa_debug_level--; + break; + case 'f': + wpa_debug_file_path = optarg; + break; + case 'K': + wpa_debug_show_keys++; + break; + case 'N': + no_prod_assoc = 1; + break; + case 'O': + friendly_name = optarg; + break; + case 'q': + wpa_debug_level++; + break; + case 'r': + ctx.result_file = optarg; + break; + case 's': + ctx.summary_file = optarg; + break; + case 'S': + ctx.ifname = optarg; + break; + case 't': + wpa_debug_timestamp++; + break; + case 'w': + wpas_ctrl_path = optarg; + break; + case 'h': + default: + usage(); + exit(0); + break; + } + } + + if (argc - optind < 1) { + usage(); + exit(0); + } + + wpa_debug_open_file(wpa_debug_file_path); + +#ifdef __linux__ + setlinebuf(stdout); +#endif /* __linux__ */ + + if (ctx.result_file) + unlink(ctx.result_file); + wpa_printf(MSG_DEBUG, "===[hs20-osu-client START - command: %s ]======" + "================", argv[optind]); + check_workarounds(&ctx); + + if (strcmp(argv[optind], "to_tnds") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_to_tnds(&ctx, argv[optind + 1], argv[optind + 2], + argc > optind + 3 ? argv[optind + 3] : NULL, + 0); + } else if (strcmp(argv[optind], "to_tnds2") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_to_tnds(&ctx, argv[optind + 1], argv[optind + 2], + argc > optind + 3 ? argv[optind + 3] : NULL, + 1); + } else if (strcmp(argv[optind], "from_tnds") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_from_tnds(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "sub_rem") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + if (argc - optind < 2) + wpa_printf(MSG_ERROR, "Server URL missing from command line"); + else + cmd_sub_rem(&ctx, argv[optind + 1], + argc > optind + 2 ? argv[optind + 2] : NULL, + argc > optind + 3 ? argv[optind + 3] : + NULL); + } else if (strcmp(argv[optind], "pol_upd") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + ret = cmd_pol_upd(&ctx, argc > 2 ? argv[optind + 1] : NULL, + argc > optind + 2 ? argv[optind + 2] : NULL, + argc > optind + 3 ? argv[optind + 3] : NULL); + } else if (strcmp(argv[optind], "prov") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_prov(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "sim_prov") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_sim_prov(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "dl_osu_ca") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_dl_osu_ca(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "dl_polupd_ca") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_dl_polupd_ca(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "dl_aaa_ca") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_dl_aaa_ca(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "osu_select") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_osu_select(&ctx, argv[optind + 1], 2, + argc > optind + 2 ? argv[optind + 2] : NULL, + 1, NULL); + } else if (strcmp(argv[optind], "signup") == 0) { + ret = cmd_signup(&ctx, + argc > optind + 1 ? argv[optind + 1] : NULL, + no_prod_assoc, friendly_name); + } else if (strcmp(argv[optind], "set_pps") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_set_pps(&ctx, argv[optind + 1]); + } else if (strcmp(argv[optind], "get_fqdn") == 0) { + if (argc - optind < 1) { + usage(); + exit(0); + } + ret = cmd_get_fqdn(&ctx, argv[optind + 1]); + } else if (strcmp(argv[optind], "oma_dm_prov") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_oma_dm_prov(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "oma_dm_sim_prov") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + if (cmd_oma_dm_sim_prov(&ctx, argv[optind + 1], + argv[optind + 2]) < 0) { + write_summary(&ctx, "Failed to complete OMA DM SIM provisioning"); + return -1; + } + } else if (strcmp(argv[optind], "oma_dm_add") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_oma_dm_add(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "oma_dm_replace") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + cmd_oma_dm_replace(&ctx, argv[optind + 1], argv[optind + 2]); + } else if (strcmp(argv[optind], "est_csr") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + mkdir("Cert", S_IRWXU); + est_build_csr(&ctx, argv[optind + 1]); + } else if (strcmp(argv[optind], "browser") == 0) { + int ret; + + if (argc - optind < 2) { + usage(); + exit(0); + } + + wpa_printf(MSG_INFO, "Launch web browser to URL %s", + argv[optind + 1]); + ret = hs20_web_browser(argv[optind + 1]); + wpa_printf(MSG_INFO, "Web browser result: %d", ret); + } else if (strcmp(argv[optind], "parse_cert") == 0) { + if (argc - optind < 2) { + usage(); + exit(0); + } + + wpa_debug_level = MSG_MSGDUMP; + http_parse_x509_certificate(ctx.http, argv[optind + 1]); + wpa_debug_level = MSG_INFO; + } else { + wpa_printf(MSG_INFO, "Unknown command '%s'", argv[optind]); + } + + wpa_printf(MSG_DEBUG, + "===[hs20-osu-client END ]======================"); + + wpa_debug_close_file(); + deinit_ctx(&ctx); + + return ret; +} diff --git a/hs20/client/osu_client.h b/hs20/client/osu_client.h new file mode 100644 index 00000000..ef568b49 --- /dev/null +++ b/hs20/client/osu_client.h @@ -0,0 +1,121 @@ +/* + * Hotspot 2.0 - OSU client + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef OSU_CLIENT_H +#define OSU_CLIENT_H + +#define SPP_NS_URI "http://www.wi-fi.org/specifications/hotspot2dot0/v1.0/spp" + +#define URN_OMA_DM_DEVINFO "urn:oma:mo:oma-dm-devinfo:1.0" +#define URN_OMA_DM_DEVDETAIL "urn:oma:mo:oma-dm-devdetail:1.0" +#define URN_HS20_DEVDETAIL_EXT "urn:wfa:mo-ext:hotspot2dot0-devdetail-ext:1.0" +#define URN_HS20_PPS "urn:wfa:mo:hotspot2dot0-perprovidersubscription:1.0" + + +#define MAX_OSU_VALS 10 + +struct osu_lang_text { + char lang[4]; + char text[253]; +}; + +struct hs20_osu_client { + struct xml_node_ctx *xml; + struct http_ctx *http; + int no_reconnect; + char pps_fname[300]; + char *devid; + const char *result_file; + const char *summary_file; + const char *ifname; + const char *ca_fname; + char *fqdn; + char *server_url; + struct osu_lang_text friendly_name[MAX_OSU_VALS]; + size_t friendly_name_count; + size_t icon_count; + char icon_filename[MAX_OSU_VALS][256]; + u8 icon_hash[MAX_OSU_VALS][32]; + int pps_cred_set; + int pps_updated; + int client_cert_present; + char **server_dnsname; + size_t server_dnsname_count; +#define WORKAROUND_OCSP_OPTIONAL 0x00000001 + unsigned long int workarounds; +}; + + +/* osu_client.c */ + +void write_result(struct hs20_osu_client *ctx, const char *fmt, ...) + __attribute__ ((format (printf, 2, 3))); +void write_summary(struct hs20_osu_client *ctx, const char *fmt, ...) + __attribute__ ((format (printf, 2, 3))); + +void debug_dump_node(struct hs20_osu_client *ctx, const char *title, + xml_node_t *node); +int osu_get_certificate(struct hs20_osu_client *ctx, xml_node_t *getcert); +int hs20_add_pps_mo(struct hs20_osu_client *ctx, const char *uri, + xml_node_t *add_mo, char *fname, size_t fname_len); +void get_user_pw(struct hs20_osu_client *ctx, xml_node_t *pps, + const char *alt_loc, char **user, char **pw); +int update_pps_file(struct hs20_osu_client *ctx, const char *pps_fname, + xml_node_t *pps); +void cmd_set_pps(struct hs20_osu_client *ctx, const char *pps_fname); + + +/* spp_client.c */ + +void spp_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps); +void spp_pol_upd(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps); +int cmd_prov(struct hs20_osu_client *ctx, const char *url, + const char *ca_fname); +int cmd_sim_prov(struct hs20_osu_client *ctx, const char *url, + const char *ca_fname); + + +/* oma_dm_client.c */ + +int cmd_oma_dm_prov(struct hs20_osu_client *ctx, const char *url, + const char *ca_fname); +int cmd_oma_dm_sim_prov(struct hs20_osu_client *ctx, const char *url, + const char *ca_fname); +void oma_dm_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps); +void oma_dm_pol_upd(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps); +void cmd_oma_dm_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname); +void cmd_oma_dm_add(struct hs20_osu_client *ctx, const char *pps_fname, + const char *add_fname); +void cmd_oma_dm_replace(struct hs20_osu_client *ctx, const char *pps_fname, + const char *replace_fname); + +/* est.c */ + +int est_load_cacerts(struct hs20_osu_client *ctx, const char *url); +int est_build_csr(struct hs20_osu_client *ctx, const char *url); +int est_simple_enroll(struct hs20_osu_client *ctx, const char *url, + const char *user, const char *pw); + +#endif /* OSU_CLIENT_H */ diff --git a/hs20/client/spp_client.c b/hs20/client/spp_client.c new file mode 100644 index 00000000..8022ccbb --- /dev/null +++ b/hs20/client/spp_client.c @@ -0,0 +1,1001 @@ +/* + * Hotspot 2.0 SPP client + * Copyright (c) 2012-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 "browser.h" +#include "wpa_ctrl.h" +#include "wpa_helpers.h" +#include "xml-utils.h" +#include "http-utils.h" +#include "utils/base64.h" +#include "crypto/crypto.h" +#include "crypto/sha256.h" +#include "osu_client.h" + + +static int hs20_spp_update_response(struct hs20_osu_client *ctx, + const char *session_id, + const char *spp_status, + const char *error_code); +static void hs20_policy_update_complete( + struct hs20_osu_client *ctx, const char *pps_fname); + + +static char * get_spp_attr_value(struct xml_node_ctx *ctx, xml_node_t *node, + char *attr_name) +{ + return xml_node_get_attr_value_ns(ctx, node, SPP_NS_URI, attr_name); +} + + +static int hs20_spp_validate(struct hs20_osu_client *ctx, xml_node_t *node, + const char *expected_name) +{ + struct xml_node_ctx *xctx = ctx->xml; + const char *name; + char *err; + int ret; + + if (!xml_node_is_element(xctx, node)) + return -1; + + name = xml_node_get_localname(xctx, node); + if (name == NULL) + return -1; + + if (strcmp(expected_name, name) != 0) { + wpa_printf(MSG_INFO, "Unexpected SOAP method name '%s' (expected '%s')", + name, expected_name); + write_summary(ctx, "Unexpected SOAP method name '%s' (expected '%s')", + name, expected_name); + return -1; + } + + ret = xml_validate(xctx, node, "spp.xsd", &err); + if (ret < 0) { + wpa_printf(MSG_INFO, "XML schema validation error(s)\n%s", err); + write_summary(ctx, "SPP XML schema validation failed"); + os_free(err); + } + return ret; +} + + +static void add_mo_container(struct xml_node_ctx *ctx, xml_namespace_t *ns, + xml_node_t *parent, const char *urn, + const char *fname) +{ + xml_node_t *node; + xml_node_t *fnode, *tnds; + char *str; + + fnode = node_from_file(ctx, fname); + if (!fnode) + return; + tnds = mo_to_tnds(ctx, fnode, 0, urn, "syncml:dmddf1.2"); + xml_node_free(ctx, fnode); + if (!tnds) + return; + + str = xml_node_to_str(ctx, tnds); + xml_node_free(ctx, tnds); + if (str == NULL) + return; + + node = xml_node_create_text(ctx, parent, ns, "moContainer", str); + if (node) + xml_node_add_attr(ctx, node, ns, "moURN", urn); + os_free(str); +} + + +static xml_node_t * build_spp_post_dev_data(struct hs20_osu_client *ctx, + xml_namespace_t **ret_ns, + const char *session_id, + const char *reason) +{ + xml_namespace_t *ns; + xml_node_t *spp_node; + + write_summary(ctx, "Building sppPostDevData requestReason='%s'", + reason); + spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, + "sppPostDevData"); + if (spp_node == NULL) + return NULL; + if (ret_ns) + *ret_ns = ns; + + xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0"); + xml_node_add_attr(ctx->xml, spp_node, NULL, "requestReason", reason); + if (session_id) + xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", + session_id); + xml_node_add_attr(ctx->xml, spp_node, NULL, "redirectURI", + "http://localhost:12345/"); + + xml_node_create_text(ctx->xml, spp_node, ns, "supportedSPPVersions", + "1.0"); + xml_node_create_text(ctx->xml, spp_node, ns, "supportedMOList", + URN_HS20_PPS " " URN_OMA_DM_DEVINFO " " + URN_OMA_DM_DEVDETAIL " " URN_HS20_DEVDETAIL_EXT); + + add_mo_container(ctx->xml, ns, spp_node, URN_OMA_DM_DEVINFO, + "devinfo.xml"); + add_mo_container(ctx->xml, ns, spp_node, URN_OMA_DM_DEVDETAIL, + "devdetail.xml"); + + return spp_node; +} + + +static int process_update_node(struct hs20_osu_client *ctx, xml_node_t *pps, + xml_node_t *update) +{ + xml_node_t *node, *parent, *tnds, *unode; + char *str; + const char *name; + char *uri, *pos; + char *cdata, *cdata_end; + size_t fqdn_len; + + wpa_printf(MSG_INFO, "Processing updateNode"); + debug_dump_node(ctx, "updateNode", update); + + uri = get_spp_attr_value(ctx->xml, update, "managementTreeURI"); + if (uri == NULL) { + wpa_printf(MSG_INFO, "No managementTreeURI present"); + return -1; + } + wpa_printf(MSG_INFO, "managementTreeUri: '%s'", uri); + + name = os_strrchr(uri, '/'); + if (name == NULL) { + wpa_printf(MSG_INFO, "Unexpected URI"); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + name++; + wpa_printf(MSG_INFO, "Update interior node: '%s'", name); + + str = xml_node_get_text(ctx->xml, update); + if (str == NULL) { + wpa_printf(MSG_INFO, "Could not extract MO text"); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + wpa_printf(MSG_DEBUG, "[hs20] nodeContainer text: '%s'", str); + cdata = strstr(str, "<![CDATA["); + cdata_end = strstr(str, "]]>"); + if (cdata && cdata_end && cdata_end > cdata && + cdata < strstr(str, "MgmtTree") && + cdata_end > strstr(str, "/MgmtTree")) { + char *tmp; + wpa_printf(MSG_DEBUG, "[hs20] Removing extra CDATA container"); + tmp = strdup(cdata + 9); + if (tmp) { + cdata_end = strstr(tmp, "]]>"); + if (cdata_end) + *cdata_end = '\0'; + wpa_printf(MSG_DEBUG, "[hs20] nodeContainer text with CDATA container removed: '%s'", + tmp); + tnds = xml_node_from_buf(ctx->xml, tmp); + free(tmp); + } else + tnds = NULL; + } else + tnds = xml_node_from_buf(ctx->xml, str); + xml_node_get_text_free(ctx->xml, str); + if (tnds == NULL) { + wpa_printf(MSG_INFO, "[hs20] Could not parse nodeContainer text"); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + + unode = tnds_to_mo(ctx->xml, tnds); + xml_node_free(ctx->xml, tnds); + if (unode == NULL) { + wpa_printf(MSG_INFO, "[hs20] Could not parse nodeContainer TNDS text"); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + + debug_dump_node(ctx, "Parsed TNDS", unode); + + if (get_node_uri(ctx->xml, unode, name) == NULL) { + wpa_printf(MSG_INFO, "[hs20] %s node not found", name); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + + if (os_strncasecmp(uri, "./Wi-Fi/", 8) != 0) { + wpa_printf(MSG_INFO, "Do not allow update outside ./Wi-Fi"); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + pos = uri + 8; + + if (ctx->fqdn == NULL) { + wpa_printf(MSG_INFO, "FQDN not known"); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + fqdn_len = os_strlen(ctx->fqdn); + if (os_strncasecmp(pos, ctx->fqdn, fqdn_len) != 0 || + pos[fqdn_len] != '/') { + wpa_printf(MSG_INFO, "Do not allow update outside ./Wi-Fi/%s", + ctx->fqdn); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + pos += fqdn_len + 1; + + if (os_strncasecmp(pos, "PerProviderSubscription/", 24) != 0) { + wpa_printf(MSG_INFO, "Do not allow update outside ./Wi-Fi/%s/PerProviderSubscription", + ctx->fqdn); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + pos += 24; + + wpa_printf(MSG_INFO, "Update command for PPS node %s", pos); + + node = get_node(ctx->xml, pps, pos); + if (node) { + parent = xml_node_get_parent(ctx->xml, node); + xml_node_detach(ctx->xml, node); + wpa_printf(MSG_INFO, "Replace '%s' node", name); + } else { + char *pos2; + pos2 = os_strrchr(pos, '/'); + if (pos2 == NULL) { + parent = pps; + } else { + *pos2 = '\0'; + parent = get_node(ctx->xml, pps, pos); + } + if (parent == NULL) { + wpa_printf(MSG_INFO, "Could not find parent %s", pos); + xml_node_free(ctx->xml, unode); + xml_node_get_attr_value_free(ctx->xml, uri); + return -1; + } + wpa_printf(MSG_INFO, "Add '%s' node", name); + } + xml_node_add_child(ctx->xml, parent, unode); + + xml_node_get_attr_value_free(ctx->xml, uri); + + return 0; +} + + +static int update_pps(struct hs20_osu_client *ctx, xml_node_t *update, + const char *pps_fname, xml_node_t *pps) +{ + wpa_printf(MSG_INFO, "Updating PPS based on updateNode element(s)"); + xml_node_for_each_sibling(ctx->xml, update) { + xml_node_for_each_check(ctx->xml, update); + if (process_update_node(ctx, pps, update) < 0) + return -1; + } + + return update_pps_file(ctx, pps_fname, pps); +} + + +static void hs20_sub_rem_complete(struct hs20_osu_client *ctx, + const char *pps_fname) +{ + /* + * Update wpa_supplicant credentials and reconnect using updated + * information. + */ + wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials"); + cmd_set_pps(ctx, pps_fname); + + if (ctx->no_reconnect) + return; + + wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration"); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) + wpa_printf(MSG_ERROR, "Failed to request wpa_supplicant to reconnect"); +} + + +static xml_node_t * hs20_spp_upload_mo(struct hs20_osu_client *ctx, + xml_node_t *cmd, + const char *session_id, + const char *pps_fname) +{ + xml_namespace_t *ns; + xml_node_t *node, *ret_node; + char *urn; + + urn = get_spp_attr_value(ctx->xml, cmd, "moURN"); + if (!urn) { + wpa_printf(MSG_INFO, "No URN included"); + return NULL; + } + wpa_printf(MSG_INFO, "Upload MO request - URN=%s", urn); + if (strcasecmp(urn, URN_HS20_PPS) != 0) { + wpa_printf(MSG_INFO, "Unsupported moURN"); + xml_node_get_attr_value_free(ctx->xml, urn); + return NULL; + } + xml_node_get_attr_value_free(ctx->xml, urn); + + if (!pps_fname) { + wpa_printf(MSG_INFO, "PPS file name no known"); + return NULL; + } + + node = build_spp_post_dev_data(ctx, &ns, session_id, + "MO upload"); + if (node == NULL) + return NULL; + add_mo_container(ctx->xml, ns, node, URN_HS20_PPS, pps_fname); + + ret_node = soap_send_receive(ctx->http, node); + if (ret_node == NULL) + return NULL; + + debug_dump_node(ctx, "Received response to MO upload", ret_node); + + if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) { + wpa_printf(MSG_INFO, "SPP validation failed"); + xml_node_free(ctx->xml, ret_node); + return NULL; + } + + return ret_node; +} + + +static int hs20_add_mo(struct hs20_osu_client *ctx, xml_node_t *add_mo, + char *fname, size_t fname_len) +{ + char *uri, *urn; + int ret; + + debug_dump_node(ctx, "Received addMO", add_mo); + + urn = get_spp_attr_value(ctx->xml, add_mo, "moURN"); + if (urn == NULL) { + wpa_printf(MSG_INFO, "[hs20] No moURN in addMO"); + return -1; + } + wpa_printf(MSG_INFO, "addMO - moURN: '%s'", urn); + if (strcasecmp(urn, URN_HS20_PPS) != 0) { + wpa_printf(MSG_INFO, "[hs20] Unsupported MO in addMO"); + xml_node_get_attr_value_free(ctx->xml, urn); + return -1; + } + xml_node_get_attr_value_free(ctx->xml, urn); + + uri = get_spp_attr_value(ctx->xml, add_mo, "managementTreeURI"); + if (uri == NULL) { + wpa_printf(MSG_INFO, "[hs20] No managementTreeURI in addMO"); + return -1; + } + wpa_printf(MSG_INFO, "addMO - managementTreeURI: '%s'", uri); + + ret = hs20_add_pps_mo(ctx, uri, add_mo, fname, fname_len); + xml_node_get_attr_value_free(ctx->xml, uri); + return ret; +} + + +static int process_spp_user_input_response(struct hs20_osu_client *ctx, + const char *session_id, + xml_node_t *add_mo) +{ + int ret; + char fname[300]; + + debug_dump_node(ctx, "addMO", add_mo); + + wpa_printf(MSG_INFO, "Subscription registration completed"); + + if (hs20_add_mo(ctx, add_mo, fname, sizeof(fname)) < 0) { + wpa_printf(MSG_INFO, "Could not add MO"); + ret = hs20_spp_update_response( + ctx, session_id, + "Error occurred", + "MO addition or update failed"); + return 0; + } + + ret = hs20_spp_update_response(ctx, session_id, "OK", NULL); + if (ret == 0) + hs20_sub_rem_complete(ctx, fname); + + return 0; +} + + +static xml_node_t * hs20_spp_user_input_completed(struct hs20_osu_client *ctx, + const char *session_id) +{ + xml_node_t *node, *ret_node; + + node = build_spp_post_dev_data(ctx, NULL, session_id, + "User input completed"); + if (node == NULL) + return NULL; + + ret_node = soap_send_receive(ctx->http, node); + if (!ret_node) { + if (soap_reinit_client(ctx->http) < 0) + return NULL; + wpa_printf(MSG_INFO, "Try to finish with re-opened connection"); + node = build_spp_post_dev_data(ctx, NULL, session_id, + "User input completed"); + if (node == NULL) + return NULL; + ret_node = soap_send_receive(ctx->http, node); + if (ret_node == NULL) + return NULL; + wpa_printf(MSG_INFO, "Continue with new connection"); + } + + if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) { + wpa_printf(MSG_INFO, "SPP validation failed"); + xml_node_free(ctx->xml, ret_node); + return NULL; + } + + return ret_node; +} + + +static xml_node_t * hs20_spp_get_certificate(struct hs20_osu_client *ctx, + xml_node_t *cmd, + const char *session_id, + const char *pps_fname) +{ + xml_namespace_t *ns; + xml_node_t *node, *ret_node; + int res; + + wpa_printf(MSG_INFO, "Client certificate enrollment"); + + res = osu_get_certificate(ctx, cmd); + if (res < 0) + wpa_printf(MSG_INFO, "EST simpleEnroll failed"); + + node = build_spp_post_dev_data(ctx, &ns, session_id, + res == 0 ? + "Certificate enrollment completed" : + "Certificate enrollment failed"); + if (node == NULL) + return NULL; + + ret_node = soap_send_receive(ctx->http, node); + if (ret_node == NULL) + return NULL; + + debug_dump_node(ctx, "Received response to certificate enrollment " + "completed", ret_node); + + if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) { + wpa_printf(MSG_INFO, "SPP validation failed"); + xml_node_free(ctx->xml, ret_node); + return NULL; + } + + return ret_node; +} + + +static int hs20_spp_exec(struct hs20_osu_client *ctx, xml_node_t *exec, + const char *session_id, const char *pps_fname, + xml_node_t *pps, xml_node_t **ret_node) +{ + xml_node_t *cmd; + const char *name; + char *uri; + char *id = strdup(session_id); + + if (id == NULL) + return -1; + + *ret_node = NULL; + + debug_dump_node(ctx, "exec", exec); + + xml_node_for_each_child(ctx->xml, cmd, exec) { + xml_node_for_each_check(ctx->xml, cmd); + break; + } + if (!cmd) { + wpa_printf(MSG_INFO, "exec command element not found (cmd=%p)", + cmd); + free(id); + return -1; + } + + name = xml_node_get_localname(ctx->xml, cmd); + + if (strcasecmp(name, "launchBrowserToURI") == 0) { + int res; + uri = xml_node_get_text(ctx->xml, cmd); + if (!uri) { + wpa_printf(MSG_INFO, "No URI found"); + free(id); + return -1; + } + wpa_printf(MSG_INFO, "Launch browser to URI '%s'", uri); + write_summary(ctx, "Launch browser to URI '%s'", uri); + res = hs20_web_browser(uri); + xml_node_get_text_free(ctx->xml, uri); + if (res > 0) { + wpa_printf(MSG_INFO, "User response in browser completed successfully - sessionid='%s'", + id); + write_summary(ctx, "User response in browser completed successfully"); + *ret_node = hs20_spp_user_input_completed(ctx, id); + free(id); + return *ret_node ? 0 : -1; + } else { + wpa_printf(MSG_INFO, "Failed to receive user response"); + write_summary(ctx, "Failed to receive user response"); + hs20_spp_update_response( + ctx, id, "Error occurred", "Other"); + free(id); + return -1; + } + return 0; + } + + if (strcasecmp(name, "uploadMO") == 0) { + if (pps_fname == NULL) + return -1; + *ret_node = hs20_spp_upload_mo(ctx, cmd, id, + pps_fname); + free(id); + return *ret_node ? 0 : -1; + } + + if (strcasecmp(name, "getCertificate") == 0) { + *ret_node = hs20_spp_get_certificate(ctx, cmd, id, + pps_fname); + free(id); + return *ret_node ? 0 : -1; + } + + wpa_printf(MSG_INFO, "Unsupported exec command: '%s'", name); + free(id); + return -1; +} + + +enum spp_post_dev_data_use { + SPP_SUBSCRIPTION_REMEDIATION, + SPP_POLICY_UPDATE, + SPP_SUBSCRIPTION_REGISTRATION, +}; + +static void process_spp_post_dev_data_response( + struct hs20_osu_client *ctx, + enum spp_post_dev_data_use use, xml_node_t *node, + const char *pps_fname, xml_node_t *pps) +{ + xml_node_t *child; + char *status = NULL; + xml_node_t *update = NULL, *exec = NULL, *add_mo = NULL, *no_mo = NULL; + char *session_id = NULL; + + debug_dump_node(ctx, "sppPostDevDataResponse node", node); + + status = get_spp_attr_value(ctx->xml, node, "sppStatus"); + if (status == NULL) { + wpa_printf(MSG_INFO, "No sppStatus attribute"); + goto out; + } + write_summary(ctx, "Received sppPostDevDataResponse sppStatus='%s'", + status); + + session_id = get_spp_attr_value(ctx->xml, node, "sessionID"); + if (session_id == NULL) { + wpa_printf(MSG_INFO, "No sessionID attribute"); + goto out; + } + + wpa_printf(MSG_INFO, "[hs20] sppPostDevDataResponse - sppStatus: '%s' sessionID: '%s'", + status, session_id); + + xml_node_for_each_child(ctx->xml, child, node) { + const char *name; + xml_node_for_each_check(ctx->xml, child); + debug_dump_node(ctx, "child", child); + name = xml_node_get_localname(ctx->xml, child); + wpa_printf(MSG_INFO, "localname: '%s'", name); + if (!update && strcasecmp(name, "updateNode") == 0) + update = child; + if (!exec && strcasecmp(name, "exec") == 0) + exec = child; + if (!add_mo && strcasecmp(name, "addMO") == 0) + add_mo = child; + if (!no_mo && strcasecmp(name, "noMOUpdate") == 0) + no_mo = child; + } + + if (use == SPP_SUBSCRIPTION_REMEDIATION && + strcasecmp(status, + "Remediation complete, request sppUpdateResponse") == 0) + { + int res, ret; + if (!update && !no_mo) { + wpa_printf(MSG_INFO, "No updateNode or noMOUpdate element"); + goto out; + } + wpa_printf(MSG_INFO, "Subscription remediation completed"); + res = update_pps(ctx, update, pps_fname, pps); + if (res < 0) + wpa_printf(MSG_INFO, "Failed to update PPS MO"); + ret = hs20_spp_update_response( + ctx, session_id, + res < 0 ? "Error occurred" : "OK", + res < 0 ? "MO addition or update failed" : NULL); + if (res == 0 && ret == 0) + hs20_sub_rem_complete(ctx, pps_fname); + goto out; + } + + if (use == SPP_SUBSCRIPTION_REMEDIATION && + strcasecmp(status, "Exchange complete, release TLS connection") == + 0) { + if (!no_mo) { + wpa_printf(MSG_INFO, "No noMOUpdate element"); + goto out; + } + wpa_printf(MSG_INFO, "Subscription remediation completed (no MO update)"); + goto out; + } + + if (use == SPP_POLICY_UPDATE && + strcasecmp(status, "Update complete, request sppUpdateResponse") == + 0) { + int res, ret; + wpa_printf(MSG_INFO, "Policy update received - update PPS"); + res = update_pps(ctx, update, pps_fname, pps); + ret = hs20_spp_update_response( + ctx, session_id, + res < 0 ? "Error occurred" : "OK", + res < 0 ? "MO addition or update failed" : NULL); + if (res == 0 && ret == 0) + hs20_policy_update_complete(ctx, pps_fname); + goto out; + } + + if (use == SPP_SUBSCRIPTION_REGISTRATION && + strcasecmp(status, "Provisioning complete, request " + "sppUpdateResponse") == 0) { + if (!add_mo) { + wpa_printf(MSG_INFO, "No addMO element - not sure what to do next"); + goto out; + } + process_spp_user_input_response(ctx, session_id, add_mo); + node = NULL; + goto out; + } + + if (strcasecmp(status, "No update available at this time") == 0) { + wpa_printf(MSG_INFO, "No update available at this time"); + goto out; + } + + if (strcasecmp(status, "OK") == 0) { + int res; + xml_node_t *ret; + + if (!exec) { + wpa_printf(MSG_INFO, "No exec element - not sure what to do next"); + goto out; + } + res = hs20_spp_exec(ctx, exec, session_id, + pps_fname, pps, &ret); + /* xml_node_free(ctx->xml, node); */ + node = NULL; + if (res == 0 && ret) + process_spp_post_dev_data_response(ctx, use, + ret, pps_fname, pps); + goto out; + } + + if (strcasecmp(status, "Error occurred") == 0) { + xml_node_t *err; + char *code = NULL; + err = get_node(ctx->xml, node, "sppError"); + if (err) + code = xml_node_get_attr_value(ctx->xml, err, + "errorCode"); + wpa_printf(MSG_INFO, "Error occurred - errorCode=%s", + code ? code : "N/A"); + xml_node_get_attr_value_free(ctx->xml, code); + goto out; + } + + wpa_printf(MSG_INFO, + "[hs20] Unsupported sppPostDevDataResponse sppStatus '%s'", + status); +out: + xml_node_get_attr_value_free(ctx->xml, status); + xml_node_get_attr_value_free(ctx->xml, session_id); + xml_node_free(ctx->xml, node); +} + + +static int spp_post_dev_data(struct hs20_osu_client *ctx, + enum spp_post_dev_data_use use, + const char *reason, + const char *pps_fname, xml_node_t *pps) +{ + xml_node_t *payload; + xml_node_t *ret_node; + + payload = build_spp_post_dev_data(ctx, NULL, NULL, reason); + if (payload == NULL) + return -1; + + ret_node = soap_send_receive(ctx->http, payload); + if (!ret_node) { + const char *err = http_get_err(ctx->http); + if (err) { + wpa_printf(MSG_INFO, "HTTP error: %s", err); + write_result(ctx, "HTTP error: %s", err); + } else { + write_summary(ctx, "Failed to send SOAP message"); + } + return -1; + } + + if (hs20_spp_validate(ctx, ret_node, "sppPostDevDataResponse") < 0) { + wpa_printf(MSG_INFO, "SPP validation failed"); + xml_node_free(ctx->xml, ret_node); + return -1; + } + + process_spp_post_dev_data_response(ctx, use, ret_node, + pps_fname, pps); + return 0; +} + + +void spp_sub_rem(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps) +{ + wpa_printf(MSG_INFO, "SPP subscription remediation"); + write_summary(ctx, "SPP subscription remediation"); + + os_free(ctx->server_url); + ctx->server_url = os_strdup(address); + + if (soap_init_client(ctx->http, address, ca_fname, + cred_username, cred_password, client_cert, + client_key) == 0) { + spp_post_dev_data(ctx, SPP_SUBSCRIPTION_REMEDIATION, + "Subscription remediation", pps_fname, pps); + } +} + + +static void hs20_policy_update_complete(struct hs20_osu_client *ctx, + const char *pps_fname) +{ + wpa_printf(MSG_INFO, "Policy update completed"); + + /* + * Update wpa_supplicant credentials and reconnect using updated + * information. + */ + wpa_printf(MSG_INFO, "Updating wpa_supplicant credentials"); + cmd_set_pps(ctx, pps_fname); + + wpa_printf(MSG_INFO, "Requesting reconnection with updated configuration"); + if (wpa_command(ctx->ifname, "INTERWORKING_SELECT auto") < 0) + wpa_printf(MSG_ERROR, "Failed to request wpa_supplicant to reconnect"); +} + + +static int process_spp_exchange_complete(struct hs20_osu_client *ctx, + xml_node_t *node) +{ + char *status, *session_id; + + debug_dump_node(ctx, "sppExchangeComplete", node); + + status = get_spp_attr_value(ctx->xml, node, "sppStatus"); + if (status == NULL) { + wpa_printf(MSG_INFO, "No sppStatus attribute"); + return -1; + } + write_summary(ctx, "Received sppExchangeComplete sppStatus='%s'", + status); + + session_id = get_spp_attr_value(ctx->xml, node, "sessionID"); + if (session_id == NULL) { + wpa_printf(MSG_INFO, "No sessionID attribute"); + xml_node_get_attr_value_free(ctx->xml, status); + return -1; + } + + wpa_printf(MSG_INFO, "[hs20] sppStatus: '%s' sessionID: '%s'", + status, session_id); + xml_node_get_attr_value_free(ctx->xml, session_id); + + if (strcasecmp(status, "Exchange complete, release TLS connection") == + 0) { + xml_node_get_attr_value_free(ctx->xml, status); + return 0; + } + + wpa_printf(MSG_INFO, "Unexpected sppStatus '%s'", status); + write_summary(ctx, "Unexpected sppStatus '%s'", status); + xml_node_get_attr_value_free(ctx->xml, status); + return -1; +} + + +static xml_node_t * build_spp_update_response(struct hs20_osu_client *ctx, + const char *session_id, + const char *spp_status, + const char *error_code) +{ + xml_namespace_t *ns; + xml_node_t *spp_node, *node; + + spp_node = xml_node_create_root(ctx->xml, SPP_NS_URI, "spp", &ns, + "sppUpdateResponse"); + if (spp_node == NULL) + return NULL; + + xml_node_add_attr(ctx->xml, spp_node, ns, "sppVersion", "1.0"); + xml_node_add_attr(ctx->xml, spp_node, ns, "sessionID", session_id); + xml_node_add_attr(ctx->xml, spp_node, ns, "sppStatus", spp_status); + + if (error_code) { + node = xml_node_create(ctx->xml, spp_node, ns, "sppError"); + if (node) + xml_node_add_attr(ctx->xml, node, NULL, "errorCode", + error_code); + } + + return spp_node; +} + + +static int hs20_spp_update_response(struct hs20_osu_client *ctx, + const char *session_id, + const char *spp_status, + const char *error_code) +{ + xml_node_t *node, *ret_node; + int ret; + + write_summary(ctx, "Building sppUpdateResponse sppStatus='%s' error_code='%s'", + spp_status, error_code); + node = build_spp_update_response(ctx, session_id, spp_status, + error_code); + if (node == NULL) + return -1; + ret_node = soap_send_receive(ctx->http, node); + if (!ret_node) { + if (soap_reinit_client(ctx->http) < 0) + return -1; + wpa_printf(MSG_INFO, "Try to finish with re-opened connection"); + node = build_spp_update_response(ctx, session_id, spp_status, + error_code); + if (node == NULL) + return -1; + ret_node = soap_send_receive(ctx->http, node); + if (ret_node == NULL) + return -1; + wpa_printf(MSG_INFO, "Continue with new connection"); + } + + if (hs20_spp_validate(ctx, ret_node, "sppExchangeComplete") < 0) { + wpa_printf(MSG_INFO, "SPP validation failed"); + xml_node_free(ctx->xml, ret_node); + return -1; + } + + ret = process_spp_exchange_complete(ctx, ret_node); + xml_node_free(ctx->xml, ret_node); + return ret; +} + + +void spp_pol_upd(struct hs20_osu_client *ctx, const char *address, + const char *pps_fname, const char *ca_fname, + const char *client_cert, const char *client_key, + const char *cred_username, const char *cred_password, + xml_node_t *pps) +{ + wpa_printf(MSG_INFO, "SPP policy update"); + write_summary(ctx, "SPP policy update"); + + os_free(ctx->server_url); + ctx->server_url = os_strdup(address); + + if (soap_init_client(ctx->http, address, ca_fname, cred_username, + cred_password, client_cert, client_key) == 0) { + spp_post_dev_data(ctx, SPP_POLICY_UPDATE, "Policy update", + pps_fname, pps); + } +} + + +int cmd_prov(struct hs20_osu_client *ctx, const char *url, + const char *ca_fname) +{ + unlink("Cert/est_cert.der"); + unlink("Cert/est_cert.pem"); + + ctx->ca_fname = ca_fname; + + if (url == NULL) { + wpa_printf(MSG_INFO, "Invalid prov command (missing URL)"); + return -1; + } + + wpa_printf(MSG_INFO, "Credential provisioning requested"); + + os_free(ctx->server_url); + ctx->server_url = os_strdup(url); + + if (soap_init_client(ctx->http, url, ca_fname, NULL, NULL, NULL, NULL) < + 0) + return -1; + spp_post_dev_data(ctx, SPP_SUBSCRIPTION_REGISTRATION, + "Subscription registration", NULL, NULL); + + return ctx->pps_cred_set ? 0 : -1; +} + + +int cmd_sim_prov(struct hs20_osu_client *ctx, const char *url, + const char *ca_fname) +{ + ctx->ca_fname = ca_fname; + + if (url == NULL) { + wpa_printf(MSG_INFO, "Invalid prov command (missing URL)"); + return -1; + } + + wpa_printf(MSG_INFO, "SIM provisioning requested"); + + os_free(ctx->server_url); + ctx->server_url = os_strdup(url); + + wpa_printf(MSG_INFO, "Wait for IP address before starting SIM provisioning"); + + if (wait_ip_addr(ctx->ifname, 15) < 0) { + wpa_printf(MSG_INFO, "Could not get IP address for WLAN - try connection anyway"); + } + + if (soap_init_client(ctx->http, url, ca_fname, NULL, NULL, NULL, NULL) < + 0) + return -1; + spp_post_dev_data(ctx, SPP_SUBSCRIPTION_REGISTRATION, + "Subscription provisioning", NULL, NULL); + + return ctx->pps_cred_set ? 0 : -1; +} diff --git a/src/common/wpa_helpers.c b/src/common/wpa_helpers.c new file mode 100644 index 00000000..28913b91 --- /dev/null +++ b/src/common/wpa_helpers.c @@ -0,0 +1,292 @@ +/* + * wpa_supplicant ctrl_iface helpers + * Copyright (c) 2010-2011, Atheros Communications, Inc. + * 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 <time.h> + +#include "common.h" +#include "wpa_ctrl.h" +#include "wpa_helpers.h" + + +char *wpas_ctrl_path = "/var/run/wpa_supplicant/"; +static int default_timeout = 60; + + +static struct wpa_ctrl * wpa_open_ctrl(const char *ifname) +{ + char buf[128]; + struct wpa_ctrl *ctrl; + + os_snprintf(buf, sizeof(buf), "%s%s", wpas_ctrl_path, ifname); + ctrl = wpa_ctrl_open(buf); + if (ctrl == NULL) + printf("wpa_command: wpa_ctrl_open(%s) failed\n", buf); + return ctrl; +} + + +int wpa_command(const char *ifname, const char *cmd) +{ + struct wpa_ctrl *ctrl; + char buf[128]; + size_t len; + + printf("wpa_command(ifname='%s', cmd='%s')\n", ifname, cmd); + ctrl = wpa_open_ctrl(ifname); + if (ctrl == NULL) + return -1; + len = sizeof(buf); + if (wpa_ctrl_request(ctrl, cmd, strlen(cmd), buf, &len, NULL) < 0) { + printf("wpa_command: wpa_ctrl_request failed\n"); + wpa_ctrl_close(ctrl); + return -1; + } + wpa_ctrl_close(ctrl); + buf[len] = '\0'; + if (strncmp(buf, "FAIL", 4) == 0) { + printf("wpa_command: Command failed (FAIL received)\n"); + return -1; + } + return 0; +} + + +int wpa_command_resp(const char *ifname, const char *cmd, + char *resp, size_t resp_size) +{ + struct wpa_ctrl *ctrl; + size_t len; + + printf("wpa_command(ifname='%s', cmd='%s')\n", ifname, cmd); + ctrl = wpa_open_ctrl(ifname); + if (ctrl == NULL) + return -1; + len = resp_size; + if (wpa_ctrl_request(ctrl, cmd, strlen(cmd), resp, &len, NULL) < 0) { + printf("wpa_command: wpa_ctrl_request failed\n"); + wpa_ctrl_close(ctrl); + return -1; + } + wpa_ctrl_close(ctrl); + resp[len] = '\0'; + return 0; +} + + +struct wpa_ctrl * open_wpa_mon(const char *ifname) +{ + struct wpa_ctrl *ctrl; + + ctrl = wpa_open_ctrl(ifname); + if (ctrl == NULL) + return NULL; + if (wpa_ctrl_attach(ctrl) < 0) { + wpa_ctrl_close(ctrl); + return NULL; + } + + return ctrl; +} + + +int get_wpa_cli_event2(struct wpa_ctrl *mon, + const char *event, const char *event2, + char *buf, size_t buf_size) +{ + int fd, ret; + fd_set rfd; + char *pos; + struct timeval tv; + time_t start, now; + + printf("Waiting for wpa_cli event %s\n", event); + fd = wpa_ctrl_get_fd(mon); + if (fd < 0) + return -1; + + time(&start); + while (1) { + size_t len; + + FD_ZERO(&rfd); + FD_SET(fd, &rfd); + tv.tv_sec = default_timeout; + tv.tv_usec = 0; + ret = select(fd + 1, &rfd, NULL, NULL, &tv); + if (ret == 0) { + printf("Timeout on waiting for event %s\n", event); + return -1; + } + if (ret < 0) { + printf("select: %s\n", strerror(errno)); + return -1; + } + len = buf_size; + if (wpa_ctrl_recv(mon, buf, &len) < 0) { + printf("Failure while waiting for event %s\n", event); + return -1; + } + if (len == buf_size) + len--; + buf[len] = '\0'; + + pos = strchr(buf, '>'); + if (pos && + (strncmp(pos + 1, event, strlen(event)) == 0 || + (event2 && + strncmp(pos + 1, event2, strlen(event2)) == 0))) + return 0; /* Event found */ + + time(&now); + if ((int) (now - start) > default_timeout) { + printf("Timeout on waiting for event %s\n", event); + return -1; + } + } +} + + +int get_wpa_cli_event(struct wpa_ctrl *mon, + const char *event, char *buf, size_t buf_size) +{ + return get_wpa_cli_event2(mon, event, NULL, buf, buf_size); +} + + +int get_wpa_status(const char *ifname, const char *field, char *obuf, + size_t obuf_size) +{ + struct wpa_ctrl *ctrl; + char buf[4096]; + char *pos, *end; + size_t len, flen; + + ctrl = wpa_open_ctrl(ifname); + if (ctrl == NULL) + return -1; + len = sizeof(buf); + if (wpa_ctrl_request(ctrl, "STATUS", 6, buf, &len, NULL) < 0) { + wpa_ctrl_close(ctrl); + return -1; + } + wpa_ctrl_close(ctrl); + buf[len] = '\0'; + + flen = strlen(field); + pos = buf; + while (pos + flen < buf + len) { + if (pos > buf) { + if (*pos != '\n') { + pos++; + continue; + } + pos++; + } + if (strncmp(pos, field, flen) != 0 || pos[flen] != '=') { + pos++; + continue; + } + pos += flen + 1; + end = strchr(pos, '\n'); + if (end == NULL) + return -1; + *end++ = '\0'; + if (end - pos > (int) obuf_size) + return -1; + memcpy(obuf, pos, end - pos); + return 0; + } + + return -1; +} + + +int wait_ip_addr(const char *ifname, int timeout) +{ + char ip[30]; + int count = timeout; + struct wpa_ctrl *ctrl; + + while (count > 0) { + printf("%s: ifname='%s' - %d seconds remaining\n", + __func__, ifname, count); + count--; + if (get_wpa_status(ifname, "ip_address", ip, sizeof(ip)) == 0 + && strlen(ip) > 0) { + printf("IP address found: '%s'\n", ip); + return 0; + } + ctrl = wpa_open_ctrl(ifname); + if (ctrl == NULL) + return -1; + wpa_ctrl_close(ctrl); + sleep(1); + } + printf("%s: Could not get IP address for ifname='%s'", __func__, + ifname); + return -1; +} + + +int add_network(const char *ifname) +{ + char res[30]; + + if (wpa_command_resp(ifname, "ADD_NETWORK", res, sizeof(res)) < 0) + return -1; + return atoi(res); +} + + +int set_network(const char *ifname, int id, const char *field, + const char *value) +{ + char buf[200]; + snprintf(buf, sizeof(buf), "SET_NETWORK %d %s %s", id, field, value); + return wpa_command(ifname, buf); +} + + +int set_network_quoted(const char *ifname, int id, const char *field, + const char *value) +{ + char buf[200]; + snprintf(buf, sizeof(buf), "SET_NETWORK %d %s \"%s\"", + id, field, value); + return wpa_command(ifname, buf); +} + + +int add_cred(const char *ifname) +{ + char res[30]; + + if (wpa_command_resp(ifname, "ADD_CRED", res, sizeof(res)) < 0) + return -1; + return atoi(res); +} + + +int set_cred(const char *ifname, int id, const char *field, const char *value) +{ + char buf[200]; + snprintf(buf, sizeof(buf), "SET_CRED %d %s %s", id, field, value); + return wpa_command(ifname, buf); +} + + +int set_cred_quoted(const char *ifname, int id, const char *field, + const char *value) +{ + char buf[200]; + snprintf(buf, sizeof(buf), "SET_CRED %d %s \"%s\"", + id, field, value); + return wpa_command(ifname, buf); +} diff --git a/src/common/wpa_helpers.h b/src/common/wpa_helpers.h new file mode 100644 index 00000000..54c2872f --- /dev/null +++ b/src/common/wpa_helpers.h @@ -0,0 +1,37 @@ +/* + * wpa_supplicant ctrl_iface helpers + * Copyright (c) 2010-2011, Atheros Communications, Inc. + * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WPA_HELPERS_H +#define WPA_HELPERS_H + +int wpa_command(const char *ifname, const char *cmd); +int wpa_command_resp(const char *ifname, const char *cmd, + char *resp, size_t resp_size); +int get_wpa_status(const char *ifname, const char *field, char *obuf, + size_t obuf_size); + +struct wpa_ctrl * open_wpa_mon(const char *ifname); +int wait_ip_addr(const char *ifname, int timeout); +int get_wpa_cli_event(struct wpa_ctrl *mon, + const char *event, char *buf, size_t buf_size); +int get_wpa_cli_event2(struct wpa_ctrl *mon, + const char *event, const char *event2, + char *buf, size_t buf_size); + +int add_network(const char *ifname); +int set_network(const char *ifname, int id, const char *field, + const char *value); +int set_network_quoted(const char *ifname, int id, const char *field, + const char *value); +int add_cred(const char *ifname); +int set_cred(const char *ifname, int id, const char *field, const char *value); +int set_cred_quoted(const char *ifname, int id, const char *field, + const char *value); + +#endif /* WPA_HELPERS_H */ diff --git a/src/utils/browser-android.c b/src/utils/browser-android.c new file mode 100644 index 00000000..a0663925 --- /dev/null +++ b/src/utils/browser-android.c @@ -0,0 +1,117 @@ +/* + * Hotspot 2.0 client - Web browser using Android browser + * Copyright (c) 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 "common.h" +#include "utils/eloop.h" +#include "wps/http_server.h" +#include "browser.h" + + +struct browser_data { + int success; +}; + + +static void browser_timeout(void *eloop_data, void *user_ctx) +{ + wpa_printf(MSG_INFO, "Timeout on waiting browser interaction to " + "complete"); + eloop_terminate(); +} + + +static void http_req(void *ctx, struct http_request *req) +{ + struct browser_data *data = ctx; + struct wpabuf *resp; + const char *url; + int done = 0; + + url = http_request_get_uri(req); + wpa_printf(MSG_INFO, "Browser response received: %s", url); + + if (os_strcmp(url, "/") == 0) { + data->success = 1; + done = 1; + } else if (os_strncmp(url, "/osu/", 5) == 0) { + data->success = atoi(url + 5); + done = 1; + } + + resp = wpabuf_alloc(1); + if (resp == NULL) { + http_request_deinit(req); + if (done) + eloop_terminate(); + return; + } + + if (done) { + eloop_cancel_timeout(browser_timeout, NULL, NULL); + eloop_register_timeout(0, 500000, browser_timeout, &data, NULL); + } + + http_request_send_and_deinit(req, resp); +} + + +int hs20_web_browser(const char *url) +{ + char cmd[2000]; + int ret; + struct http_server *http; + struct in_addr addr; + struct browser_data data; + + wpa_printf(MSG_INFO, "Launching Android browser to %s", url); + + os_memset(&data, 0, sizeof(data)); + + ret = os_snprintf(cmd, sizeof(cmd), + "am start -a android.intent.action.VIEW -d '%s' " + "-n com.android.browser/.BrowserActivity", url); + if (ret < 0 || (size_t) ret >= sizeof(cmd)) { + wpa_printf(MSG_ERROR, "Too long URL"); + return -1; + } + + if (eloop_init() < 0) { + wpa_printf(MSG_ERROR, "eloop_init failed"); + return -1; + } + addr.s_addr = htonl((127 << 24) | 1); + http = http_server_init(&addr, 12345, http_req, &data); + if (http == NULL) { + wpa_printf(MSG_ERROR, "http_server_init failed"); + eloop_destroy(); + return -1; + } + + if (system(cmd) != 0) { + wpa_printf(MSG_INFO, "Failed to launch Android browser"); + eloop_cancel_timeout(browser_timeout, NULL, NULL); + http_server_deinit(http); + eloop_destroy(); + return -1; + } + + eloop_register_timeout(30, 0, browser_timeout, &data, NULL); + eloop_run(); + eloop_cancel_timeout(browser_timeout, &data, NULL); + http_server_deinit(http); + eloop_destroy(); + + wpa_printf(MSG_INFO, "Closing Android browser"); + if (system("input keyevent 3") != 0) { + wpa_printf(MSG_INFO, "Failed to inject keyevent"); + } + + return data.success; +} diff --git a/src/utils/browser-system.c b/src/utils/browser-system.c new file mode 100644 index 00000000..2884d341 --- /dev/null +++ b/src/utils/browser-system.c @@ -0,0 +1,112 @@ +/* + * Hotspot 2.0 client - Web browser using system browser + * Copyright (c) 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 "common.h" +#include "utils/eloop.h" +#include "wps/http_server.h" +#include "browser.h" + + +struct browser_data { + int success; +}; + + +static void browser_timeout(void *eloop_data, void *user_ctx) +{ + wpa_printf(MSG_INFO, "Timeout on waiting browser interaction to " + "complete"); + eloop_terminate(); +} + + +static void http_req(void *ctx, struct http_request *req) +{ + struct browser_data *data = ctx; + struct wpabuf *resp; + const char *url; + int done = 0; + + url = http_request_get_uri(req); + wpa_printf(MSG_INFO, "Browser response received: %s", url); + + if (os_strcmp(url, "/") == 0) { + data->success = 1; + done = 1; + } else if (os_strncmp(url, "/osu/", 5) == 0) { + data->success = atoi(url + 5); + done = 1; + } + + resp = wpabuf_alloc(1); + if (resp == NULL) { + http_request_deinit(req); + if (done) + eloop_terminate(); + return; + } + + if (done) { + eloop_cancel_timeout(browser_timeout, NULL, NULL); + eloop_register_timeout(0, 500000, browser_timeout, &data, NULL); + } + + http_request_send_and_deinit(req, resp); +} + + +int hs20_web_browser(const char *url) +{ + char cmd[2000]; + int ret; + struct http_server *http; + struct in_addr addr; + struct browser_data data; + + wpa_printf(MSG_INFO, "Launching Android browser to %s", url); + + os_memset(&data, 0, sizeof(data)); + + ret = os_snprintf(cmd, sizeof(cmd), "x-www-browser '%s' &", url); + if (ret < 0 || (size_t) ret >= sizeof(cmd)) { + wpa_printf(MSG_ERROR, "Too long URL"); + return -1; + } + + if (eloop_init() < 0) { + wpa_printf(MSG_ERROR, "eloop_init failed"); + return -1; + } + addr.s_addr = htonl((127 << 24) | 1); + http = http_server_init(&addr, 12345, http_req, &data); + if (http == NULL) { + wpa_printf(MSG_ERROR, "http_server_init failed"); + eloop_destroy(); + return -1; + } + + if (system(cmd) != 0) { + wpa_printf(MSG_INFO, "Failed to launch browser"); + eloop_cancel_timeout(browser_timeout, NULL, NULL); + http_server_deinit(http); + eloop_destroy(); + return -1; + } + + eloop_register_timeout(120, 0, browser_timeout, &data, NULL); + eloop_run(); + eloop_cancel_timeout(browser_timeout, &data, NULL); + http_server_deinit(http); + eloop_destroy(); + + /* TODO: Close browser */ + + return data.success; +} diff --git a/src/utils/browser-wpadebug.c b/src/utils/browser-wpadebug.c new file mode 100644 index 00000000..eeb8f650 --- /dev/null +++ b/src/utils/browser-wpadebug.c @@ -0,0 +1,123 @@ +/* + * Hotspot 2.0 client - Web browser using wpadebug on Android + * Copyright (c) 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 "common.h" +#include "utils/eloop.h" +#include "wps/http_server.h" +#include "browser.h" + + +struct browser_data { + int success; +}; + + +static void browser_timeout(void *eloop_data, void *user_ctx) +{ + wpa_printf(MSG_INFO, "Timeout on waiting browser interaction to " + "complete"); + eloop_terminate(); +} + + +static void http_req(void *ctx, struct http_request *req) +{ + struct browser_data *data = ctx; + struct wpabuf *resp; + const char *url; + int done = 0; + + url = http_request_get_uri(req); + wpa_printf(MSG_INFO, "Browser response received: %s", url); + + if (os_strcmp(url, "/") == 0) { + data->success = 1; + done = 1; + } else if (os_strncmp(url, "/osu/", 5) == 0) { + data->success = atoi(url + 5); + done = 1; + } + + resp = wpabuf_alloc(100); + if (resp == NULL) { + http_request_deinit(req); + if (done) + eloop_terminate(); + return; + } + wpabuf_put_str(resp, "User input completed"); + + if (done) { + eloop_cancel_timeout(browser_timeout, NULL, NULL); + eloop_register_timeout(0, 500000, browser_timeout, &data, NULL); + } + + http_request_send_and_deinit(req, resp); +} + + +int hs20_web_browser(const char *url) +{ + char cmd[2000]; + int ret; + struct http_server *http; + struct in_addr addr; + struct browser_data data; + + wpa_printf(MSG_INFO, "Launching wpadebug browser to %s", url); + + os_memset(&data, 0, sizeof(data)); + + ret = os_snprintf(cmd, sizeof(cmd), + "am start -a android.action.MAIN " + "-c android.intent.category.LAUNCHER " + "-n w1.fi.wpadebug/.WpaWebViewActivity " + "-e w1.fi.wpadebug.URL '%s'", url); + if (ret < 0 || (size_t) ret >= sizeof(cmd)) { + wpa_printf(MSG_ERROR, "Too long URL"); + return -1; + } + + if (eloop_init() < 0) { + wpa_printf(MSG_ERROR, "eloop_init failed"); + return -1; + } + addr.s_addr = htonl((127 << 24) | 1); + http = http_server_init(&addr, 12345, http_req, &data); + if (http == NULL) { + wpa_printf(MSG_ERROR, "http_server_init failed"); + eloop_destroy(); + return -1; + } + + if (system(cmd) != 0) { + wpa_printf(MSG_INFO, "Failed to launch wpadebug browser"); + eloop_cancel_timeout(browser_timeout, NULL, NULL); + http_server_deinit(http); + eloop_destroy(); + return -1; + } + + eloop_register_timeout(300, 0, browser_timeout, &data, NULL); + eloop_run(); + eloop_cancel_timeout(browser_timeout, &data, NULL); + http_server_deinit(http); + eloop_destroy(); + + wpa_printf(MSG_INFO, "Closing Android browser"); + if (system("am start -a android.action.MAIN " + "-c android.intent.category.LAUNCHER " + "-n w1.fi.wpadebug/.WpaWebViewActivity " + "-e w1.fi.wpadebug.URL FINISH") != 0) { + wpa_printf(MSG_INFO, "Failed to close wpadebug browser"); + } + + return data.success; +} diff --git a/src/utils/browser.c b/src/utils/browser.c new file mode 100644 index 00000000..9cf6152d --- /dev/null +++ b/src/utils/browser.c @@ -0,0 +1,219 @@ +/* + * Hotspot 2.0 client - Web browser using WebKit + * Copyright (c) 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 <webkit/webkit.h> + +#include "common.h" +#include "browser.h" + + +struct browser_context { + GtkWidget *win; + int success; + int progress; + char *hover_link; + char *title; +}; + +static void win_cb_destroy(GtkWidget *win, struct browser_context *ctx) +{ + wpa_printf(MSG_DEBUG, "BROWSER:%s", __func__); + gtk_main_quit(); +} + + +static void browser_update_title(struct browser_context *ctx) +{ + char buf[100]; + + if (ctx->hover_link) { + gtk_window_set_title(GTK_WINDOW(ctx->win), ctx->hover_link); + return; + } + + if (ctx->progress == 100) { + gtk_window_set_title(GTK_WINDOW(ctx->win), + ctx->title ? ctx->title : + "Hotspot 2.0 client"); + return; + } + + snprintf(buf, sizeof(buf), "[%d%%] %s", ctx->progress, + ctx->title ? ctx->title : "Hotspot 2.0 client"); + gtk_window_set_title(GTK_WINDOW(ctx->win), buf); +} + + +static void view_cb_notify_progress(WebKitWebView *view, GParamSpec *pspec, + struct browser_context *ctx) +{ + ctx->progress = 100 * webkit_web_view_get_progress(view); + wpa_printf(MSG_DEBUG, "BROWSER:%s progress=%d", __func__, + ctx->progress); + browser_update_title(ctx); +} + + +static void view_cb_notify_load_status(WebKitWebView *view, GParamSpec *pspec, + struct browser_context *ctx) +{ + int status = webkit_web_view_get_load_status(view); + wpa_printf(MSG_DEBUG, "BROWSER:%s load-status=%d uri=%s", + __func__, status, webkit_web_view_get_uri(view)); +} + + +static void view_cb_resource_request_starting(WebKitWebView *view, + WebKitWebFrame *frame, + WebKitWebResource *res, + WebKitNetworkRequest *req, + WebKitNetworkResponse *resp, + struct browser_context *ctx) +{ + const gchar *uri = webkit_network_request_get_uri(req); + wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri); + if (g_str_has_suffix(uri, "/favicon.ico")) + webkit_network_request_set_uri(req, "about:blank"); + if (g_str_has_prefix(uri, "osu://")) { + ctx->success = atoi(uri + 6); + gtk_main_quit(); + } + if (g_str_has_prefix(uri, "http://localhost:12345")) { + /* + * This is used as a special trigger to indicate that the + * user exchange has been completed. + */ + ctx->success = 1; + gtk_main_quit(); + } +} + + +static gboolean view_cb_mime_type_policy_decision( + WebKitWebView *view, WebKitWebFrame *frame, WebKitNetworkRequest *req, + gchar *mime, WebKitWebPolicyDecision *policy, + struct browser_context *ctx) +{ + wpa_printf(MSG_DEBUG, "BROWSER:%s mime=%s", __func__, mime); + + if (!webkit_web_view_can_show_mime_type(view, mime)) { + webkit_web_policy_decision_download(policy); + return TRUE; + } + + return FALSE; +} + + +static gboolean view_cb_download_requested(WebKitWebView *view, + WebKitDownload *dl, + struct browser_context *ctx) +{ + const gchar *uri; + uri = webkit_download_get_uri(dl); + wpa_printf(MSG_DEBUG, "BROWSER:%s uri=%s", __func__, uri); + return FALSE; +} + + +static void view_cb_hovering_over_link(WebKitWebView *view, gchar *title, + gchar *uri, struct browser_context *ctx) +{ + wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s uri=%s", __func__, title, + uri); + os_free(ctx->hover_link); + if (uri) + ctx->hover_link = os_strdup(uri); + else + ctx->hover_link = NULL; + + browser_update_title(ctx); +} + + +static void view_cb_title_changed(WebKitWebView *view, WebKitWebFrame *frame, + const char *title, + struct browser_context *ctx) +{ + wpa_printf(MSG_DEBUG, "BROWSER:%s title=%s", __func__, title); + os_free(ctx->title); + ctx->title = os_strdup(title); + browser_update_title(ctx); +} + + +int hs20_web_browser(const char *url) +{ + GtkWidget *scroll; + SoupSession *s; + WebKitWebView *view; + WebKitWebSettings *settings; + struct browser_context ctx; + + memset(&ctx, 0, sizeof(ctx)); + if (!gtk_init_check(NULL, NULL)) + return -1; + + s = webkit_get_default_session(); + g_object_set(G_OBJECT(s), "ssl-ca-file", + "/etc/ssl/certs/ca-certificates.crt", NULL); + g_object_set(G_OBJECT(s), "ssl-strict", FALSE, NULL); + + ctx.win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_wmclass(GTK_WINDOW(ctx.win), "Hotspot 2.0 client", + "Hotspot 2.0 client"); + gtk_window_set_default_size(GTK_WINDOW(ctx.win), 800, 600); + + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_NEVER, GTK_POLICY_NEVER); + + g_signal_connect(G_OBJECT(ctx.win), "destroy", + G_CALLBACK(win_cb_destroy), &ctx); + + view = WEBKIT_WEB_VIEW(webkit_web_view_new()); + g_signal_connect(G_OBJECT(view), "notify::progress", + G_CALLBACK(view_cb_notify_progress), &ctx); + g_signal_connect(G_OBJECT(view), "notify::load-status", + G_CALLBACK(view_cb_notify_load_status), &ctx); + g_signal_connect(G_OBJECT(view), "resource-request-starting", + G_CALLBACK(view_cb_resource_request_starting), &ctx); + g_signal_connect(G_OBJECT(view), "mime-type-policy-decision-requested", + G_CALLBACK(view_cb_mime_type_policy_decision), &ctx); + g_signal_connect(G_OBJECT(view), "download-requested", + G_CALLBACK(view_cb_download_requested), &ctx); + g_signal_connect(G_OBJECT(view), "hovering-over-link", + G_CALLBACK(view_cb_hovering_over_link), &ctx); + g_signal_connect(G_OBJECT(view), "title-changed", + G_CALLBACK(view_cb_title_changed), &ctx); + + gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(ctx.win), GTK_WIDGET(scroll)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); + gtk_widget_show_all(ctx.win); + + settings = webkit_web_view_get_settings(view); + g_object_set(G_OBJECT(settings), "user-agent", + "Mozilla/5.0 (X11; U; Unix; en-US) " + "AppleWebKit/537.15 (KHTML, like Gecko) " + "hs20-client/1.0", NULL); + g_object_set(G_OBJECT(settings), "auto-load-images", TRUE, NULL); + + webkit_web_view_load_uri(view, url); + + gtk_main(); + gtk_widget_destroy(ctx.win); + while (gtk_events_pending()) + gtk_main_iteration(); + + free(ctx.hover_link); + free(ctx.title); + return ctx.success; +} diff --git a/src/utils/browser.h b/src/utils/browser.h new file mode 100644 index 00000000..aaa0eed2 --- /dev/null +++ b/src/utils/browser.h @@ -0,0 +1,21 @@ +/* + * Hotspot 2.0 client - Web browser + * Copyright (c) 2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef BROWSER_H +#define BROWSER_H + +#ifdef CONFIG_NO_BROWSER +static inline int hs20_web_browser(const char *url) +{ + return -1; +} +#else /* CONFIG_NO_BROWSER */ +int hs20_web_browser(const char *url); +#endif /* CONFIG_NO_BROWSER */ + +#endif /* BROWSER_H */ diff --git a/src/utils/http-utils.h b/src/utils/http-utils.h new file mode 100644 index 00000000..8d4399a3 --- /dev/null +++ b/src/utils/http-utils.h @@ -0,0 +1,63 @@ +/* + * HTTP wrapper + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef HTTP_UTILS_H +#define HTTP_UTILS_H + +struct http_ctx; + +struct http_othername { + char *oid; + u8 *data; + size_t len; +}; + +#define HTTP_MAX_CERT_LOGO_HASH 32 + +struct http_logo { + char *alg_oid; + u8 *hash; + size_t hash_len; + char *uri; +}; + +struct http_cert { + char **dnsname; + unsigned int num_dnsname; + struct http_othername *othername; + unsigned int num_othername; + struct http_logo *logo; + unsigned int num_logo; +}; + +int soap_init_client(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key); +int soap_reinit_client(struct http_ctx *ctx); +xml_node_t * soap_send_receive(struct http_ctx *ctx, xml_node_t *node); + +struct http_ctx * http_init_ctx(void *upper_ctx, struct xml_node_ctx *xml_ctx); +void http_ocsp_set(struct http_ctx *ctx, int val); +void http_deinit_ctx(struct http_ctx *ctx); + +int http_download_file(struct http_ctx *ctx, const char *url, + const char *fname, const char *ca_fname); +char * http_post(struct http_ctx *ctx, const char *url, const char *data, + const char *content_type, const char *ext_hdr, + const char *ca_fname, + const char *username, const char *password, + const char *client_cert, const char *client_key, + size_t *resp_len); +void http_set_cert_cb(struct http_ctx *ctx, + int (*cb)(void *ctx, struct http_cert *cert), + void *cb_ctx); +const char * http_get_err(struct http_ctx *ctx); +void http_parse_x509_certificate(struct http_ctx *ctx, const char *fname); + +#endif /* HTTP_UTILS_H */ diff --git a/src/utils/http_curl.c b/src/utils/http_curl.c new file mode 100644 index 00000000..668c1a6a --- /dev/null +++ b/src/utils/http_curl.c @@ -0,0 +1,1642 @@ +/* + * HTTP wrapper for libcurl + * Copyright (c) 2012-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 <curl/curl.h> +#ifdef EAP_TLS_OPENSSL +#include <openssl/ssl.h> +#include <openssl/asn1.h> +#include <openssl/asn1t.h> +#include <openssl/x509v3.h> + +#ifdef SSL_set_tlsext_status_type +#ifndef OPENSSL_NO_TLSEXT +#define HAVE_OCSP +#include <openssl/err.h> +#include <openssl/ocsp.h> +#endif /* OPENSSL_NO_TLSEXT */ +#endif /* SSL_set_tlsext_status_type */ +#endif /* EAP_TLS_OPENSSL */ + +#include "common.h" +#include "xml-utils.h" +#include "http-utils.h" + + +struct http_ctx { + void *ctx; + struct xml_node_ctx *xml; + CURL *curl; + struct curl_slist *curl_hdr; + char *svc_address; + char *svc_ca_fname; + char *svc_username; + char *svc_password; + char *svc_client_cert; + char *svc_client_key; + char *curl_buf; + size_t curl_buf_len; + + int (*cert_cb)(void *ctx, struct http_cert *cert); + void *cert_cb_ctx; + + enum { + NO_OCSP, OPTIONAL_OCSP, MANDATORY_OCSP + } ocsp; + X509 *peer_cert; + X509 *peer_issuer; + X509 *peer_issuer_issuer; + + const char *last_err; +}; + + +static void clear_curl(struct http_ctx *ctx) +{ + if (ctx->curl) { + curl_easy_cleanup(ctx->curl); + ctx->curl = NULL; + } + if (ctx->curl_hdr) { + curl_slist_free_all(ctx->curl_hdr); + ctx->curl_hdr = NULL; + } +} + + +static void clone_str(char **dst, const char *src) +{ + os_free(*dst); + if (src) + *dst = os_strdup(src); + else + *dst = NULL; +} + + +static void debug_dump(struct http_ctx *ctx, const char *title, + const char *buf, size_t len) +{ + char *txt = os_malloc(len + 1); + if (txt == NULL) + return; + os_memcpy(txt, buf, len); + txt[len] = '\0'; + while (len > 0) { + len--; + if (txt[len] == '\r' || txt[len] == '\n') + txt[len] = '\0'; + } + wpa_printf(MSG_MSGDUMP, "%s[%s]", title, txt); + os_free(txt); +} + + +static int curl_cb_debug(CURL *curl, curl_infotype info, char *buf, size_t len, + void *userdata) +{ + struct http_ctx *ctx = userdata; + switch (info) { + case CURLINFO_TEXT: + debug_dump(ctx, "CURLINFO_TEXT", buf, len); + break; + case CURLINFO_HEADER_IN: + debug_dump(ctx, "CURLINFO_HEADER_IN", buf, len); + break; + case CURLINFO_HEADER_OUT: + debug_dump(ctx, "CURLINFO_HEADER_OUT", buf, len); + break; + case CURLINFO_DATA_IN: + debug_dump(ctx, "CURLINFO_DATA_IN", buf, len); + break; + case CURLINFO_DATA_OUT: + debug_dump(ctx, "CURLINFO_DATA_OUT", buf, len); + break; + case CURLINFO_SSL_DATA_IN: + wpa_printf(MSG_DEBUG, "debug - CURLINFO_SSL_DATA_IN - %d", + (int) len); + break; + case CURLINFO_SSL_DATA_OUT: + wpa_printf(MSG_DEBUG, "debug - CURLINFO_SSL_DATA_OUT - %d", + (int) len); + break; + case CURLINFO_END: + wpa_printf(MSG_DEBUG, "debug - CURLINFO_END - %d", + (int) len); + break; + } + return 0; +} + + +static size_t curl_cb_header(void *ptr, size_t size, size_t nmemb, + void *userdata) +{ + struct http_ctx *ctx = userdata; + debug_dump(ctx, "curl header", ptr, size * nmemb); + return size * nmemb; +} + + +static size_t curl_cb_write(void *ptr, size_t size, size_t nmemb, + void *userdata) +{ + struct http_ctx *ctx = userdata; + char *n; + debug_dump(ctx, "curl write", ptr, size * nmemb); + n = os_realloc(ctx->curl_buf, ctx->curl_buf_len + size * nmemb + 1); + if (n == NULL) + return 0; + ctx->curl_buf = n; + os_memcpy(n + ctx->curl_buf_len, ptr, size * nmemb); + n[ctx->curl_buf_len + size * nmemb] = '\0'; + ctx->curl_buf_len += size * nmemb; + return size * nmemb; +} + + +#ifdef EAP_TLS_OPENSSL + +static void debug_dump_cert(const char *title, X509 *cert) +{ + BIO *out; + char *txt; + size_t rlen; + + out = BIO_new(BIO_s_mem()); + if (!out) + return; + + X509_print_ex(out, cert, XN_FLAG_COMPAT, X509_FLAG_COMPAT); + rlen = BIO_ctrl_pending(out); + txt = os_malloc(rlen + 1); + if (txt) { + int res = BIO_read(out, txt, rlen); + if (res > 0) { + txt[res] = '\0'; + wpa_printf(MSG_MSGDUMP, "%s:\n%s", title, txt); + } + os_free(txt); + } + BIO_free(out); +} + + +static void add_alt_name_othername(struct http_ctx *ctx, struct http_cert *cert, + OTHERNAME *o) +{ + char txt[100]; + int res; + struct http_othername *on; + ASN1_TYPE *val; + + on = os_realloc_array(cert->othername, cert->num_othername + 1, + sizeof(struct http_othername)); + if (on == NULL) + return; + cert->othername = on; + on = &on[cert->num_othername]; + os_memset(on, 0, sizeof(*on)); + + res = OBJ_obj2txt(txt, sizeof(txt), o->type_id, 1); + if (res < 0 || res >= (int) sizeof(txt)) + return; + + on->oid = os_strdup(txt); + if (on->oid == NULL) + return; + + val = o->value; + on->data = val->value.octet_string->data; + on->len = val->value.octet_string->length; + + cert->num_othername++; +} + + +static void add_alt_name_dns(struct http_ctx *ctx, struct http_cert *cert, + ASN1_STRING *name) +{ + char *buf; + char **n; + + buf = NULL; + if (ASN1_STRING_to_UTF8((unsigned char **) &buf, name) < 0) + return; + + n = os_realloc_array(cert->dnsname, cert->num_dnsname + 1, + sizeof(char *)); + if (n == NULL) + return; + + cert->dnsname = n; + n[cert->num_dnsname] = buf; + cert->num_dnsname++; +} + + +static void add_alt_name(struct http_ctx *ctx, struct http_cert *cert, + const GENERAL_NAME *name) +{ + switch (name->type) { + case GEN_OTHERNAME: + add_alt_name_othername(ctx, cert, name->d.otherName); + break; + case GEN_DNS: + add_alt_name_dns(ctx, cert, name->d.dNSName); + break; + } +} + + +static void add_alt_names(struct http_ctx *ctx, struct http_cert *cert, + GENERAL_NAMES *names) +{ + int num, i; + + num = sk_GENERAL_NAME_num(names); + for (i = 0; i < num; i++) { + const GENERAL_NAME *name; + name = sk_GENERAL_NAME_value(names, i); + add_alt_name(ctx, cert, name); + } +} + + +/* RFC 3709 */ + +typedef struct { + X509_ALGOR *hashAlg; + ASN1_OCTET_STRING *hashValue; +} HashAlgAndValue; + +typedef struct { + STACK_OF(HashAlgAndValue) *refStructHash; + STACK_OF(ASN1_IA5STRING) *refStructURI; +} LogotypeReference; + +typedef struct { + ASN1_IA5STRING *mediaType; + STACK_OF(HashAlgAndValue) *logotypeHash; + STACK_OF(ASN1_IA5STRING) *logotypeURI; +} LogotypeDetails; + +typedef struct { + int type; + union { + ASN1_INTEGER *numBits; + ASN1_INTEGER *tableSize; + } d; +} LogotypeImageResolution; + +typedef struct { + ASN1_INTEGER *type; /* LogotypeImageType ::= INTEGER */ + ASN1_INTEGER *fileSize; + ASN1_INTEGER *xSize; + ASN1_INTEGER *ySize; + LogotypeImageResolution *resolution; + ASN1_IA5STRING *language; +} LogotypeImageInfo; + +typedef struct { + LogotypeDetails *imageDetails; + LogotypeImageInfo *imageInfo; +} LogotypeImage; + +typedef struct { + ASN1_INTEGER *fileSize; + ASN1_INTEGER *playTime; + ASN1_INTEGER *channels; + ASN1_INTEGER *sampleRate; + ASN1_IA5STRING *language; +} LogotypeAudioInfo; + +typedef struct { + LogotypeDetails *audioDetails; + LogotypeAudioInfo *audioInfo; +} LogotypeAudio; + +typedef struct { + STACK_OF(LogotypeImage) *image; + STACK_OF(LogotypeAudio) *audio; +} LogotypeData; + +typedef struct { + int type; + union { + LogotypeData *direct; + LogotypeReference *indirect; + } d; +} LogotypeInfo; + +typedef struct { + ASN1_OBJECT *logotypeType; + LogotypeInfo *info; +} OtherLogotypeInfo; + +typedef struct { + STACK_OF(LogotypeInfo) *communityLogos; + LogotypeInfo *issuerLogo; + LogotypeInfo *subjectLogo; + STACK_OF(OtherLogotypeInfo) *otherLogos; +} LogotypeExtn; + +ASN1_SEQUENCE(HashAlgAndValue) = { + ASN1_SIMPLE(HashAlgAndValue, hashAlg, X509_ALGOR), + ASN1_SIMPLE(HashAlgAndValue, hashValue, ASN1_OCTET_STRING) +} ASN1_SEQUENCE_END(HashAlgAndValue); + +ASN1_SEQUENCE(LogotypeReference) = { + ASN1_SEQUENCE_OF(LogotypeReference, refStructHash, HashAlgAndValue), + ASN1_SEQUENCE_OF(LogotypeReference, refStructURI, ASN1_IA5STRING) +} ASN1_SEQUENCE_END(LogotypeReference); + +ASN1_SEQUENCE(LogotypeDetails) = { + ASN1_SIMPLE(LogotypeDetails, mediaType, ASN1_IA5STRING), + ASN1_SEQUENCE_OF(LogotypeDetails, logotypeHash, HashAlgAndValue), + ASN1_SEQUENCE_OF(LogotypeDetails, logotypeURI, ASN1_IA5STRING) +} ASN1_SEQUENCE_END(LogotypeDetails); + +ASN1_CHOICE(LogotypeImageResolution) = { + ASN1_IMP(LogotypeImageResolution, d.numBits, ASN1_INTEGER, 1), + ASN1_IMP(LogotypeImageResolution, d.tableSize, ASN1_INTEGER, 2) +} ASN1_CHOICE_END(LogotypeImageResolution); + +ASN1_SEQUENCE(LogotypeImageInfo) = { + ASN1_IMP_OPT(LogotypeImageInfo, type, ASN1_INTEGER, 0), + ASN1_SIMPLE(LogotypeImageInfo, fileSize, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeImageInfo, xSize, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeImageInfo, ySize, ASN1_INTEGER), + ASN1_OPT(LogotypeImageInfo, resolution, LogotypeImageResolution), + ASN1_IMP_OPT(LogotypeImageInfo, language, ASN1_IA5STRING, 4), +} ASN1_SEQUENCE_END(LogotypeImageInfo); + +ASN1_SEQUENCE(LogotypeImage) = { + ASN1_SIMPLE(LogotypeImage, imageDetails, LogotypeDetails), + ASN1_OPT(LogotypeImage, imageInfo, LogotypeImageInfo) +} ASN1_SEQUENCE_END(LogotypeImage); + +ASN1_SEQUENCE(LogotypeAudioInfo) = { + ASN1_SIMPLE(LogotypeAudioInfo, fileSize, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeAudioInfo, playTime, ASN1_INTEGER), + ASN1_SIMPLE(LogotypeAudioInfo, channels, ASN1_INTEGER), + ASN1_IMP_OPT(LogotypeAudioInfo, sampleRate, ASN1_INTEGER, 3), + ASN1_IMP_OPT(LogotypeAudioInfo, language, ASN1_IA5STRING, 4) +} ASN1_SEQUENCE_END(LogotypeAudioInfo); + +ASN1_SEQUENCE(LogotypeAudio) = { + ASN1_SIMPLE(LogotypeAudio, audioDetails, LogotypeDetails), + ASN1_OPT(LogotypeAudio, audioInfo, LogotypeAudioInfo) +} ASN1_SEQUENCE_END(LogotypeAudio); + +ASN1_SEQUENCE(LogotypeData) = { + ASN1_SEQUENCE_OF_OPT(LogotypeData, image, LogotypeImage), + ASN1_IMP_SEQUENCE_OF_OPT(LogotypeData, audio, LogotypeAudio, 1) +} ASN1_SEQUENCE_END(LogotypeData); + +ASN1_CHOICE(LogotypeInfo) = { + ASN1_IMP(LogotypeInfo, d.direct, LogotypeData, 0), + ASN1_IMP(LogotypeInfo, d.indirect, LogotypeReference, 1) +} ASN1_CHOICE_END(LogotypeInfo); + +ASN1_SEQUENCE(OtherLogotypeInfo) = { + ASN1_SIMPLE(OtherLogotypeInfo, logotypeType, ASN1_OBJECT), + ASN1_SIMPLE(OtherLogotypeInfo, info, LogotypeInfo) +} ASN1_SEQUENCE_END(OtherLogotypeInfo); + +ASN1_SEQUENCE(LogotypeExtn) = { + ASN1_EXP_SEQUENCE_OF_OPT(LogotypeExtn, communityLogos, LogotypeInfo, 0), + ASN1_EXP_OPT(LogotypeExtn, issuerLogo, LogotypeInfo, 1), + ASN1_EXP_OPT(LogotypeExtn, issuerLogo, LogotypeInfo, 2), + ASN1_EXP_SEQUENCE_OF_OPT(LogotypeExtn, otherLogos, OtherLogotypeInfo, 3) +} ASN1_SEQUENCE_END(LogotypeExtn); + +IMPLEMENT_ASN1_FUNCTIONS(LogotypeExtn); + +#define sk_LogotypeInfo_num(st) SKM_sk_num(LogotypeInfo, (st)) +#define sk_LogotypeInfo_value(st, i) SKM_sk_value(LogotypeInfo, (st), (i)) +#define sk_LogotypeImage_num(st) SKM_sk_num(LogotypeImage, (st)) +#define sk_LogotypeImage_value(st, i) SKM_sk_value(LogotypeImage, (st), (i)) +#define sk_LogotypeAudio_num(st) SKM_sk_num(LogotypeAudio, (st)) +#define sk_LogotypeAudio_value(st, i) SKM_sk_value(LogotypeAudio, (st), (i)) +#define sk_HashAlgAndValue_num(st) SKM_sk_num(HashAlgAndValue, (st)) +#define sk_HashAlgAndValue_value(st, i) SKM_sk_value(HashAlgAndValue, (st), (i)) +#define sk_ASN1_IA5STRING_num(st) SKM_sk_num(ASN1_IA5STRING, (st)) +#define sk_ASN1_IA5STRING_value(st, i) SKM_sk_value(ASN1_IA5STRING, (st), (i)) + + +static void add_logo(struct http_ctx *ctx, struct http_cert *hcert, + HashAlgAndValue *hash, ASN1_IA5STRING *uri) +{ + char txt[100]; + int res, len; + struct http_logo *n; + + if (hash == NULL || uri == NULL) + return; + + res = OBJ_obj2txt(txt, sizeof(txt), hash->hashAlg->algorithm, 1); + if (res < 0 || res >= (int) sizeof(txt)) + return; + + n = os_realloc_array(hcert->logo, hcert->num_logo + 1, + sizeof(struct http_logo)); + if (n == NULL) + return; + hcert->logo = n; + n = &hcert->logo[hcert->num_logo]; + os_memset(n, 0, sizeof(*n)); + + n->alg_oid = os_strdup(txt); + if (n->alg_oid == NULL) + return; + + n->hash_len = ASN1_STRING_length(hash->hashValue); + n->hash = os_malloc(n->hash_len); + if (n->hash == NULL) { + os_free(n->alg_oid); + return; + } + os_memcpy(n->hash, ASN1_STRING_data(hash->hashValue), n->hash_len); + + len = ASN1_STRING_length(uri); + n->uri = os_malloc(len + 1); + if (n->uri == NULL) { + os_free(n->alg_oid); + os_free(n->hash); + return; + } + os_memcpy(n->uri, ASN1_STRING_data(uri), len); + n->uri[len] = '\0'; + + hcert->num_logo++; +} + + +static void add_logo_direct(struct http_ctx *ctx, struct http_cert *hcert, + LogotypeData *data) +{ + int i, num; + + if (data->image == NULL) + return; + + num = sk_LogotypeImage_num(data->image); + for (i = 0; i < num; i++) { + LogotypeImage *image; + LogotypeDetails *details; + int j, hash_num, uri_num; + HashAlgAndValue *found_hash = NULL; + + image = sk_LogotypeImage_value(data->image, i); + if (image == NULL) + continue; + + details = image->imageDetails; + if (details == NULL) + continue; + + hash_num = sk_HashAlgAndValue_num(details->logotypeHash); + for (j = 0; j < hash_num; j++) { + HashAlgAndValue *hash; + char txt[100]; + int res; + hash = sk_HashAlgAndValue_value(details->logotypeHash, + j); + if (hash == NULL) + continue; + res = OBJ_obj2txt(txt, sizeof(txt), + hash->hashAlg->algorithm, 1); + if (res < 0 || res >= (int) sizeof(txt)) + continue; + if (os_strcmp(txt, "2.16.840.1.101.3.4.2.1") == 0) { + found_hash = hash; + break; + } + } + + if (!found_hash) { + wpa_printf(MSG_DEBUG, "OpenSSL: No SHA256 hash found for the logo"); + continue; + } + + uri_num = sk_ASN1_IA5STRING_num(details->logotypeURI); + for (j = 0; j < uri_num; j++) { + ASN1_IA5STRING *uri; + uri = sk_ASN1_IA5STRING_value(details->logotypeURI, j); + add_logo(ctx, hcert, found_hash, uri); + } + } +} + + +static void add_logo_indirect(struct http_ctx *ctx, struct http_cert *hcert, + LogotypeReference *ref) +{ + int j, hash_num, uri_num; + + hash_num = sk_HashAlgAndValue_num(ref->refStructHash); + uri_num = sk_ASN1_IA5STRING_num(ref->refStructURI); + if (hash_num != uri_num) { + wpa_printf(MSG_INFO, "Unexpected LogotypeReference array size difference %d != %d", + hash_num, uri_num); + return; + } + + for (j = 0; j < hash_num; j++) { + HashAlgAndValue *hash; + ASN1_IA5STRING *uri; + hash = sk_HashAlgAndValue_value(ref->refStructHash, j); + uri = sk_ASN1_IA5STRING_value(ref->refStructURI, j); + add_logo(ctx, hcert, hash, uri); + } +} + + +static void i2r_HashAlgAndValue(HashAlgAndValue *hash, BIO *out, int indent) +{ + int i; + const unsigned char *data; + + BIO_printf(out, "%*shashAlg: ", indent, ""); + i2a_ASN1_OBJECT(out, hash->hashAlg->algorithm); + BIO_printf(out, "\n"); + + BIO_printf(out, "%*shashValue: ", indent, ""); + data = hash->hashValue->data; + for (i = 0; i < hash->hashValue->length; i++) + BIO_printf(out, "%s%02x", i > 0 ? ":" : "", data[i]); + BIO_printf(out, "\n"); +} + +static void i2r_LogotypeDetails(LogotypeDetails *details, BIO *out, int indent) +{ + int i, num; + + BIO_printf(out, "%*sLogotypeDetails\n", indent, ""); + if (details->mediaType) { + BIO_printf(out, "%*smediaType: ", indent, ""); + ASN1_STRING_print(out, details->mediaType); + BIO_printf(out, "\n"); + } + + num = details->logotypeHash ? + sk_HashAlgAndValue_num(details->logotypeHash) : 0; + for (i = 0; i < num; i++) { + HashAlgAndValue *hash; + hash = sk_HashAlgAndValue_value(details->logotypeHash, i); + i2r_HashAlgAndValue(hash, out, indent); + } + + num = details->logotypeURI ? + sk_ASN1_IA5STRING_num(details->logotypeURI) : 0; + for (i = 0; i < num; i++) { + ASN1_IA5STRING *uri; + uri = sk_ASN1_IA5STRING_value(details->logotypeURI, i); + BIO_printf(out, "%*slogotypeURI: ", indent, ""); + ASN1_STRING_print(out, uri); + BIO_printf(out, "\n"); + } +} + +static void i2r_LogotypeImageInfo(LogotypeImageInfo *info, BIO *out, int indent) +{ + long val; + + BIO_printf(out, "%*sLogotypeImageInfo\n", indent, ""); + if (info->type) { + val = ASN1_INTEGER_get(info->type); + BIO_printf(out, "%*stype: %ld\n", indent, "", val); + } else { + BIO_printf(out, "%*stype: default (1)\n", indent, ""); + } + val = ASN1_INTEGER_get(info->xSize); + BIO_printf(out, "%*sxSize: %ld\n", indent, "", val); + val = ASN1_INTEGER_get(info->ySize); + BIO_printf(out, "%*sySize: %ld\n", indent, "", val); + if (info->resolution) { + BIO_printf(out, "%*sresolution\n", indent, ""); + /* TODO */ + } + if (info->language) { + BIO_printf(out, "%*slanguage: ", indent, ""); + ASN1_STRING_print(out, info->language); + BIO_printf(out, "\n"); + } +} + +static void i2r_LogotypeImage(LogotypeImage *image, BIO *out, int indent) +{ + BIO_printf(out, "%*sLogotypeImage\n", indent, ""); + if (image->imageDetails) { + i2r_LogotypeDetails(image->imageDetails, out, indent + 4); + } + if (image->imageInfo) { + i2r_LogotypeImageInfo(image->imageInfo, out, indent + 4); + } +} + +static void i2r_LogotypeData(LogotypeData *data, const char *title, BIO *out, + int indent) +{ + int i, num; + + BIO_printf(out, "%*s%s - LogotypeData\n", indent, "", title); + + num = data->image ? sk_LogotypeImage_num(data->image) : 0; + for (i = 0; i < num; i++) { + LogotypeImage *image = sk_LogotypeImage_value(data->image, i); + i2r_LogotypeImage(image, out, indent + 4); + } + + num = data->audio ? sk_LogotypeAudio_num(data->audio) : 0; + for (i = 0; i < num; i++) { + BIO_printf(out, "%*saudio: TODO\n", indent, ""); + } +} + +static void i2r_LogotypeReference(LogotypeReference *ref, const char *title, + BIO *out, int indent) +{ + int i, hash_num, uri_num; + + BIO_printf(out, "%*s%s - LogotypeReference\n", indent, "", title); + + hash_num = ref->refStructHash ? + sk_HashAlgAndValue_num(ref->refStructHash) : 0; + uri_num = ref->refStructURI ? + sk_ASN1_IA5STRING_num(ref->refStructURI) : 0; + if (hash_num != uri_num) { + BIO_printf(out, "%*sUnexpected LogotypeReference array size difference %d != %d\n", + indent, "", hash_num, uri_num); + return; + } + + for (i = 0; i < hash_num; i++) { + HashAlgAndValue *hash; + ASN1_IA5STRING *uri; + + hash = sk_HashAlgAndValue_value(ref->refStructHash, i); + i2r_HashAlgAndValue(hash, out, indent); + + uri = sk_ASN1_IA5STRING_value(ref->refStructURI, i); + BIO_printf(out, "%*srefStructURI: ", indent, ""); + ASN1_STRING_print(out, uri); + BIO_printf(out, "\n"); + } +} + +static void i2r_LogotypeInfo(LogotypeInfo *info, const char *title, BIO *out, + int indent) +{ + switch (info->type) { + case 0: + i2r_LogotypeData(info->d.direct, title, out, indent); + break; + case 1: + i2r_LogotypeReference(info->d.indirect, title, out, indent); + break; + } +} + +static void debug_print_logotypeext(LogotypeExtn *logo) +{ + BIO *out; + int i, num; + int indent = 0; + + out = BIO_new_fp(stdout, BIO_NOCLOSE); + if (out == NULL) + return; + + if (logo->communityLogos) { + num = sk_LogotypeInfo_num(logo->communityLogos); + for (i = 0; i < num; i++) { + LogotypeInfo *info; + info = sk_LogotypeInfo_value(logo->communityLogos, i); + i2r_LogotypeInfo(info, "communityLogo", out, indent); + } + } + + if (logo->issuerLogo) { + i2r_LogotypeInfo(logo->issuerLogo, "issuerLogo", out, indent ); + } + + if (logo->subjectLogo) { + i2r_LogotypeInfo(logo->subjectLogo, "subjectLogo", out, indent); + } + + if (logo->otherLogos) { + BIO_printf(out, "%*sotherLogos - TODO\n", indent, ""); + } + + BIO_free(out); +} + + +static void add_logotype_ext(struct http_ctx *ctx, struct http_cert *hcert, + X509 *cert) +{ + ASN1_OBJECT *obj; + int pos; + X509_EXTENSION *ext; + ASN1_OCTET_STRING *os; + LogotypeExtn *logo; + const unsigned char *data; + int i, num; + + obj = OBJ_txt2obj("1.3.6.1.5.5.7.1.12", 0); + if (obj == NULL) + return; + + pos = X509_get_ext_by_OBJ(cert, obj, -1); + if (pos < 0) { + wpa_printf(MSG_INFO, "No logotype extension included"); + return; + } + + wpa_printf(MSG_INFO, "Parsing logotype extension"); + ext = X509_get_ext(cert, pos); + if (!ext) { + wpa_printf(MSG_INFO, "Could not get logotype extension"); + return; + } + + os = X509_EXTENSION_get_data(ext); + if (os == NULL) { + wpa_printf(MSG_INFO, "Could not get logotype extension data"); + return; + } + + wpa_hexdump(MSG_DEBUG, "logotypeExtn", + ASN1_STRING_data(os), ASN1_STRING_length(os)); + + data = ASN1_STRING_data(os); + logo = d2i_LogotypeExtn(NULL, &data, ASN1_STRING_length(os)); + if (logo == NULL) { + wpa_printf(MSG_INFO, "Failed to parse logotypeExtn"); + return; + } + + if (wpa_debug_level < MSG_INFO) + debug_print_logotypeext(logo); + + if (!logo->communityLogos) { + wpa_printf(MSG_INFO, "No communityLogos included"); + LogotypeExtn_free(logo); + return; + } + + num = sk_LogotypeInfo_num(logo->communityLogos); + for (i = 0; i < num; i++) { + LogotypeInfo *info; + info = sk_LogotypeInfo_value(logo->communityLogos, i); + switch (info->type) { + case 0: + add_logo_direct(ctx, hcert, info->d.direct); + break; + case 1: + add_logo_indirect(ctx, hcert, info->d.indirect); + break; + } + } + + LogotypeExtn_free(logo); +} + + +static void parse_cert(struct http_ctx *ctx, struct http_cert *hcert, + X509 *cert, GENERAL_NAMES **names) +{ + os_memset(hcert, 0, sizeof(*hcert)); + + *names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (*names) + add_alt_names(ctx, hcert, *names); + + add_logotype_ext(ctx, hcert, cert); +} + + +static void parse_cert_free(struct http_cert *hcert, GENERAL_NAMES *names) +{ + unsigned int i; + + for (i = 0; i < hcert->num_dnsname; i++) + OPENSSL_free(hcert->dnsname[i]); + os_free(hcert->dnsname); + + for (i = 0; i < hcert->num_othername; i++) + os_free(hcert->othername[i].oid); + os_free(hcert->othername); + + for (i = 0; i < hcert->num_logo; i++) { + os_free(hcert->logo[i].alg_oid); + os_free(hcert->logo[i].hash); + os_free(hcert->logo[i].uri); + } + os_free(hcert->logo); + + sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); +} + + +static int validate_server_cert(struct http_ctx *ctx, X509 *cert) +{ + GENERAL_NAMES *names; + struct http_cert hcert; + int ret; + + if (ctx->cert_cb == NULL) + return 0; + + if (0) { + BIO *out; + out = BIO_new_fp(stdout, BIO_NOCLOSE); + X509_print_ex(out, cert, XN_FLAG_COMPAT, X509_FLAG_COMPAT); + BIO_free(out); + } + + parse_cert(ctx, &hcert, cert, &names); + ret = ctx->cert_cb(ctx->cert_cb_ctx, &hcert); + parse_cert_free(&hcert, names); + + return ret; +} + + +void http_parse_x509_certificate(struct http_ctx *ctx, const char *fname) +{ + BIO *in, *out; + X509 *cert; + GENERAL_NAMES *names; + struct http_cert hcert; + unsigned int i; + + in = BIO_new_file(fname, "r"); + if (in == NULL) { + wpa_printf(MSG_ERROR, "Could not read '%s'", fname); + return; + } + + cert = d2i_X509_bio(in, NULL); + BIO_free(in); + + if (cert == NULL) { + wpa_printf(MSG_ERROR, "Could not parse certificate"); + return; + } + + out = BIO_new_fp(stdout, BIO_NOCLOSE); + if (out) { + X509_print_ex(out, cert, XN_FLAG_COMPAT, + X509_FLAG_COMPAT); + BIO_free(out); + } + + wpa_printf(MSG_INFO, "Additional parsing information:"); + parse_cert(ctx, &hcert, cert, &names); + for (i = 0; i < hcert.num_othername; i++) { + if (os_strcmp(hcert.othername[i].oid, + "1.3.6.1.4.1.40808.1.1.1") == 0) { + char *name = os_zalloc(hcert.othername[i].len + 1); + if (name) { + os_memcpy(name, hcert.othername[i].data, + hcert.othername[i].len); + wpa_printf(MSG_INFO, + "id-wfa-hotspot-friendlyName: %s", + name); + os_free(name); + } + wpa_hexdump_ascii(MSG_INFO, + "id-wfa-hotspot-friendlyName", + hcert.othername[i].data, + hcert.othername[i].len); + } else { + wpa_printf(MSG_INFO, "subjAltName[othername]: oid=%s", + hcert.othername[i].oid); + wpa_hexdump_ascii(MSG_INFO, "unknown othername", + hcert.othername[i].data, + hcert.othername[i].len); + } + } + parse_cert_free(&hcert, names); + + X509_free(cert); +} + + +static int curl_cb_ssl_verify(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + struct http_ctx *ctx; + X509 *cert; + int err, depth; + char buf[256]; + X509_NAME *name; + const char *err_str; + SSL *ssl; + SSL_CTX *ssl_ctx; + + ssl = X509_STORE_CTX_get_ex_data(x509_ctx, + SSL_get_ex_data_X509_STORE_CTX_idx()); + ssl_ctx = ssl->ctx; + ctx = SSL_CTX_get_app_data(ssl_ctx); + + wpa_printf(MSG_DEBUG, "curl_cb_ssl_verify"); + + err = X509_STORE_CTX_get_error(x509_ctx); + err_str = X509_verify_cert_error_string(err); + depth = X509_STORE_CTX_get_error_depth(x509_ctx); + cert = X509_STORE_CTX_get_current_cert(x509_ctx); + if (!cert) { + wpa_printf(MSG_INFO, "No server certificate available"); + ctx->last_err = "No server certificate available"; + return 0; + } + + if (depth == 0) + ctx->peer_cert = cert; + else if (depth == 1) + ctx->peer_issuer = cert; + else if (depth == 2) + ctx->peer_issuer_issuer = cert; + + name = X509_get_subject_name(cert); + X509_NAME_oneline(name, buf, sizeof(buf)); + wpa_printf(MSG_INFO, "Server certificate chain - depth=%d err=%d (%s) subject=%s", + depth, err, err_str, buf); + debug_dump_cert("Server certificate chain - certificate", cert); + + if (depth == 0 && preverify_ok && validate_server_cert(ctx, cert) < 0) + return 0; + + if (!preverify_ok) + ctx->last_err = "TLS validation failed"; + + return preverify_ok; +} + + +#ifdef HAVE_OCSP + +static void ocsp_debug_print_resp(OCSP_RESPONSE *rsp) +{ + BIO *out; + size_t rlen; + char *txt; + int res; + + out = BIO_new(BIO_s_mem()); + if (!out) + return; + + OCSP_RESPONSE_print(out, rsp, 0); + rlen = BIO_ctrl_pending(out); + txt = os_malloc(rlen + 1); + if (!txt) { + BIO_free(out); + return; + } + + res = BIO_read(out, txt, rlen); + if (res > 0) { + txt[res] = '\0'; + wpa_printf(MSG_MSGDUMP, "OpenSSL: OCSP Response\n%s", txt); + } + os_free(txt); + BIO_free(out); +} + + +static void tls_show_errors(const char *func, const char *txt) +{ + unsigned long err; + + wpa_printf(MSG_DEBUG, "OpenSSL: %s - %s %s", + func, txt, ERR_error_string(ERR_get_error(), NULL)); + + while ((err = ERR_get_error())) { + wpa_printf(MSG_DEBUG, "OpenSSL: pending error: %s", + ERR_error_string(err, NULL)); + } +} + + +static int ocsp_resp_cb(SSL *s, void *arg) +{ + struct http_ctx *ctx = arg; + const unsigned char *p; + int len, status, reason; + OCSP_RESPONSE *rsp; + OCSP_BASICRESP *basic; + OCSP_CERTID *id; + ASN1_GENERALIZEDTIME *produced_at, *this_update, *next_update; + X509_STORE *store; + STACK_OF(X509) *certs = NULL; + + len = SSL_get_tlsext_status_ocsp_resp(s, &p); + if (!p) { + wpa_printf(MSG_DEBUG, "OpenSSL: No OCSP response received"); + if (ctx->ocsp == MANDATORY_OCSP) + ctx->last_err = "No OCSP response received"; + return (ctx->ocsp == MANDATORY_OCSP) ? 0 : 1; + } + + wpa_hexdump(MSG_DEBUG, "OpenSSL: OCSP response", p, len); + + rsp = d2i_OCSP_RESPONSE(NULL, &p, len); + if (!rsp) { + wpa_printf(MSG_INFO, "OpenSSL: Failed to parse OCSP response"); + ctx->last_err = "Failed to parse OCSP response"; + return 0; + } + + ocsp_debug_print_resp(rsp); + + status = OCSP_response_status(rsp); + if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + wpa_printf(MSG_INFO, "OpenSSL: OCSP responder error %d (%s)", + status, OCSP_response_status_str(status)); + ctx->last_err = "OCSP responder error"; + return 0; + } + + basic = OCSP_response_get1_basic(rsp); + if (!basic) { + wpa_printf(MSG_INFO, "OpenSSL: Could not find BasicOCSPResponse"); + ctx->last_err = "Could not find BasicOCSPResponse"; + return 0; + } + + store = SSL_CTX_get_cert_store(s->ctx); + if (ctx->peer_issuer) { + wpa_printf(MSG_DEBUG, "OpenSSL: Add issuer"); + debug_dump_cert("OpenSSL: Issuer certificate", + ctx->peer_issuer); + + if (X509_STORE_add_cert(store, ctx->peer_issuer) != 1) { + tls_show_errors(__func__, + "OpenSSL: Could not add issuer to certificate store\n"); + } + certs = sk_X509_new_null(); + if (certs) { + X509 *cert; + cert = X509_dup(ctx->peer_issuer); + if (cert && !sk_X509_push(certs, cert)) { + tls_show_errors( + __func__, + "OpenSSL: Could not add issuer to OCSP responder trust store\n"); + X509_free(cert); + sk_X509_free(certs); + certs = NULL; + } + if (ctx->peer_issuer_issuer) { + X509 *cert; + cert = X509_dup(ctx->peer_issuer_issuer); + if (cert && !sk_X509_push(certs, cert)) { + tls_show_errors( + __func__, + "OpenSSL: Could not add issuer to OCSP responder trust store\n"); + X509_free(cert); + } + } + } + } + + status = OCSP_basic_verify(basic, certs, store, OCSP_TRUSTOTHER); + sk_X509_pop_free(certs, X509_free); + if (status <= 0) { + tls_show_errors(__func__, + "OpenSSL: OCSP response failed verification"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "OCSP response failed verification"; + return 0; + } + + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP response verification succeeded"); + + if (!ctx->peer_cert) { + wpa_printf(MSG_DEBUG, "OpenSSL: Peer certificate not available for OCSP status check"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "Peer certificate not available for OCSP status check"; + return 0; + } + + if (!ctx->peer_issuer) { + wpa_printf(MSG_DEBUG, "OpenSSL: Peer issuer certificate not available for OCSP status check"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "Peer issuer certificate not available for OCSP status check"; + return 0; + } + + id = OCSP_cert_to_id(NULL, ctx->peer_cert, ctx->peer_issuer); + if (!id) { + wpa_printf(MSG_DEBUG, "OpenSSL: Could not create OCSP certificate identifier"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "Could not create OCSP certificate identifier"; + return 0; + } + + if (!OCSP_resp_find_status(basic, id, &status, &reason, &produced_at, + &this_update, &next_update)) { + wpa_printf(MSG_INFO, "OpenSSL: Could not find current server certificate from OCSP response%s", + (ctx->ocsp == MANDATORY_OCSP) ? "" : + " (OCSP not required)"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + if (ctx->ocsp == MANDATORY_OCSP) + + ctx->last_err = "Could not find current server certificate from OCSP response"; + return (ctx->ocsp == MANDATORY_OCSP) ? 0 : 1; + } + + if (!OCSP_check_validity(this_update, next_update, 5 * 60, -1)) { + tls_show_errors(__func__, "OpenSSL: OCSP status times invalid"); + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + ctx->last_err = "OCSP status times invalid"; + return 0; + } + + OCSP_BASICRESP_free(basic); + OCSP_RESPONSE_free(rsp); + + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status for server certificate: %s", + OCSP_cert_status_str(status)); + + if (status == V_OCSP_CERTSTATUS_GOOD) + return 1; + if (status == V_OCSP_CERTSTATUS_REVOKED) + ctx->last_err = "Server certificate has been revoked"; + return 0; + if (ctx->ocsp == MANDATORY_OCSP) { + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP required"); + ctx->last_err = "OCSP status unknown"; + return 0; + } + wpa_printf(MSG_DEBUG, "OpenSSL: OCSP status unknown, but OCSP was not required, so allow connection to continue"); + return 1; +} + + +static SSL_METHOD patch_ssl_method; +static const SSL_METHOD *real_ssl_method; + +static int curl_patch_ssl_new(SSL *s) +{ + SSL_CTX *ssl = s->ctx; + int ret; + + ssl->method = real_ssl_method; + s->method = real_ssl_method; + + ret = s->method->ssl_new(s); + SSL_set_tlsext_status_type(s, TLSEXT_STATUSTYPE_ocsp); + + return ret; +} + +#endif /* HAVE_OCSP */ + + +static CURLcode curl_cb_ssl(CURL *curl, void *sslctx, void *parm) +{ + struct http_ctx *ctx = parm; + SSL_CTX *ssl = sslctx; + + wpa_printf(MSG_DEBUG, "curl_cb_ssl"); + SSL_CTX_set_app_data(ssl, ctx); + SSL_CTX_set_verify(ssl, SSL_VERIFY_PEER, curl_cb_ssl_verify); + +#ifdef HAVE_OCSP + if (ctx->ocsp != NO_OCSP) { + SSL_CTX_set_tlsext_status_cb(ssl, ocsp_resp_cb); + SSL_CTX_set_tlsext_status_arg(ssl, ctx); + + /* + * Use a temporary SSL_METHOD to get a callback on SSL_new() + * from libcurl since there is no proper callback registration + * available for this. + */ + os_memset(&patch_ssl_method, 0, sizeof(patch_ssl_method)); + patch_ssl_method.ssl_new = curl_patch_ssl_new; + real_ssl_method = ssl->method; + ssl->method = &patch_ssl_method; + } +#endif /* HAVE_OCSP */ + + return CURLE_OK; +} + +#endif /* EAP_TLS_OPENSSL */ + + +static CURL * setup_curl_post(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key) +{ + CURL *curl; + + wpa_printf(MSG_DEBUG, "Start HTTP client: address=%s ca_fname=%s " + "username=%s", address, ca_fname, username); + + curl = curl_easy_init(); + if (curl == NULL) + return NULL; + + curl_easy_setopt(curl, CURLOPT_URL, address); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + if (ca_fname) { + curl_easy_setopt(curl, CURLOPT_CAINFO, ca_fname); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); +#ifdef EAP_TLS_OPENSSL + curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, curl_cb_ssl); + curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, ctx); +#endif /* EAP_TLS_OPENSSL */ + } else { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + } + if (client_cert && client_key) { + curl_easy_setopt(curl, CURLOPT_SSLCERT, client_cert); + curl_easy_setopt(curl, CURLOPT_SSLKEY, client_key); + } + /* TODO: use curl_easy_getinfo() with CURLINFO_CERTINFO to fetch + * information about the server certificate */ + curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_cb_debug); + curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_cb_header); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, ctx); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_cb_write); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + if (username) { + curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE); + curl_easy_setopt(curl, CURLOPT_USERNAME, username); + curl_easy_setopt(curl, CURLOPT_PASSWORD, password); + } + + return curl; +} + + +static int post_init_client(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key) +{ + char *pos; + int count; + + clone_str(&ctx->svc_address, address); + clone_str(&ctx->svc_ca_fname, ca_fname); + clone_str(&ctx->svc_username, username); + clone_str(&ctx->svc_password, password); + clone_str(&ctx->svc_client_cert, client_cert); + clone_str(&ctx->svc_client_key, client_key); + + /* + * Workaround for Apache "Hostname 'FOO' provided via SNI and hostname + * 'foo' provided via HTTP are different. + */ + for (count = 0, pos = ctx->svc_address; count < 3 && pos && *pos; + pos++) { + if (*pos == '/') + count++; + *pos = tolower(*pos); + } + + ctx->curl = setup_curl_post(ctx, ctx->svc_address, ca_fname, username, + password, client_cert, client_key); + if (ctx->curl == NULL) + return -1; + + return 0; +} + + +int soap_init_client(struct http_ctx *ctx, const char *address, + const char *ca_fname, const char *username, + const char *password, const char *client_cert, + const char *client_key) +{ + if (post_init_client(ctx, address, ca_fname, username, password, + client_cert, client_key) < 0) + return -1; + + ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, + "Content-Type: application/soap+xml"); + ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, "SOAPAction: "); + ctx->curl_hdr = curl_slist_append(ctx->curl_hdr, "Expect:"); + curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, ctx->curl_hdr); + + return 0; +} + + +int soap_reinit_client(struct http_ctx *ctx) +{ + char *address = NULL; + char *ca_fname = NULL; + char *username = NULL; + char *password = NULL; + char *client_cert = NULL; + char *client_key = NULL; + int ret; + + clear_curl(ctx); + + clone_str(&address, ctx->svc_address); + clone_str(&ca_fname, ctx->svc_ca_fname); + clone_str(&username, ctx->svc_username); + clone_str(&password, ctx->svc_password); + clone_str(&client_cert, ctx->svc_client_cert); + clone_str(&client_key, ctx->svc_client_key); + + ret = soap_init_client(ctx, address, ca_fname, username, password, + client_cert, client_key); + os_free(address); + os_free(ca_fname); + os_free(username); + os_free(password); + os_free(client_cert); + os_free(client_key); + return ret; +} + + +static void free_curl_buf(struct http_ctx *ctx) +{ + os_free(ctx->curl_buf); + ctx->curl_buf = NULL; + ctx->curl_buf_len = 0; +} + + +xml_node_t * soap_send_receive(struct http_ctx *ctx, xml_node_t *node) +{ + char *str; + xml_node_t *envelope, *ret, *resp, *n; + CURLcode res; + long http = 0; + + ctx->last_err = NULL; + + wpa_printf(MSG_DEBUG, "SOAP: Sending message"); + envelope = soap_build_envelope(ctx->xml, node); + str = xml_node_to_str(ctx->xml, envelope); + xml_node_free(ctx->xml, envelope); + wpa_printf(MSG_MSGDUMP, "SOAP[%s]", str); + + curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDS, str); + free_curl_buf(ctx); + + res = curl_easy_perform(ctx->curl); + if (res != CURLE_OK) { + if (!ctx->last_err) + ctx->last_err = curl_easy_strerror(res); + wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s", + ctx->last_err); + os_free(str); + free_curl_buf(ctx); + return NULL; + } + os_free(str); + + curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &http); + wpa_printf(MSG_DEBUG, "SOAP: Server response code %ld", http); + if (http != 200) { + ctx->last_err = "HTTP download failed"; + wpa_printf(MSG_INFO, "HTTP download failed - code %ld", http); + free_curl_buf(ctx); + return NULL; + } + + if (ctx->curl_buf == NULL) + return NULL; + + wpa_printf(MSG_MSGDUMP, "Server response:\n%s", ctx->curl_buf); + resp = xml_node_from_buf(ctx->xml, ctx->curl_buf); + free_curl_buf(ctx); + if (resp == NULL) { + wpa_printf(MSG_INFO, "Could not parse SOAP response"); + ctx->last_err = "Could not parse SOAP response"; + return NULL; + } + + ret = soap_get_body(ctx->xml, resp); + if (ret == NULL) { + wpa_printf(MSG_INFO, "Could not get SOAP body"); + ctx->last_err = "Could not get SOAP body"; + return NULL; + } + + wpa_printf(MSG_DEBUG, "SOAP body localname: '%s'", + xml_node_get_localname(ctx->xml, ret)); + n = xml_node_copy(ctx->xml, ret); + xml_node_free(ctx->xml, resp); + + return n; +} + + +struct http_ctx * http_init_ctx(void *upper_ctx, struct xml_node_ctx *xml_ctx) +{ + struct http_ctx *ctx; + + ctx = os_zalloc(sizeof(*ctx)); + if (ctx == NULL) + return NULL; + ctx->ctx = upper_ctx; + ctx->xml = xml_ctx; + ctx->ocsp = OPTIONAL_OCSP; + + curl_global_init(CURL_GLOBAL_ALL); + + return ctx; +} + + +void http_ocsp_set(struct http_ctx *ctx, int val) +{ + if (val == 0) + ctx->ocsp = NO_OCSP; + else if (val == 1) + ctx->ocsp = OPTIONAL_OCSP; + if (val == 2) + ctx->ocsp = MANDATORY_OCSP; +} + + +void http_deinit_ctx(struct http_ctx *ctx) +{ + clear_curl(ctx); + os_free(ctx->curl_buf); + curl_global_cleanup(); + + os_free(ctx->svc_address); + os_free(ctx->svc_ca_fname); + os_free(ctx->svc_username); + os_free(ctx->svc_password); + os_free(ctx->svc_client_cert); + os_free(ctx->svc_client_key); + + os_free(ctx); +} + + +int http_download_file(struct http_ctx *ctx, const char *url, + const char *fname, const char *ca_fname) +{ + CURL *curl; + FILE *f; + CURLcode res; + long http = 0; + + ctx->last_err = NULL; + + wpa_printf(MSG_DEBUG, "curl: Download file from %s to %s (ca=%s)", + url, fname, ca_fname); + curl = curl_easy_init(); + if (curl == NULL) + return -1; + + f = fopen(fname, "wb"); + if (f == NULL) { + curl_easy_cleanup(curl); + return -1; + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + if (ca_fname) { + curl_easy_setopt(curl, CURLOPT_CAINFO, ca_fname); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L); + } else { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + } + curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, curl_cb_debug); + curl_easy_setopt(curl, CURLOPT_DEBUGDATA, ctx); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, curl_cb_header); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, ctx); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, f); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + if (!ctx->last_err) + ctx->last_err = curl_easy_strerror(res); + wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s", + ctx->last_err); + curl_easy_cleanup(curl); + fclose(f); + return -1; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http); + wpa_printf(MSG_DEBUG, "curl: Server response code %ld", http); + if (http != 200) { + ctx->last_err = "HTTP download failed"; + wpa_printf(MSG_INFO, "HTTP download failed - code %ld", http); + curl_easy_cleanup(curl); + fclose(f); + return -1; + } + + curl_easy_cleanup(curl); + fclose(f); + + return 0; +} + + +char * http_post(struct http_ctx *ctx, const char *url, const char *data, + const char *content_type, const char *ext_hdr, + const char *ca_fname, + const char *username, const char *password, + const char *client_cert, const char *client_key, + size_t *resp_len) +{ + long http = 0; + CURLcode res; + char *ret; + CURL *curl; + struct curl_slist *curl_hdr = NULL; + + ctx->last_err = NULL; + wpa_printf(MSG_DEBUG, "curl: HTTP POST to %s", url); + curl = setup_curl_post(ctx, url, ca_fname, username, password, + client_cert, client_key); + if (curl == NULL) + return NULL; + + if (content_type) { + char ct[200]; + snprintf(ct, sizeof(ct), "Content-Type: %s", content_type); + curl_hdr = curl_slist_append(curl_hdr, ct); + } + if (ext_hdr) + curl_hdr = curl_slist_append(curl_hdr, ext_hdr); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, curl_hdr); + + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); + free_curl_buf(ctx); + + res = curl_easy_perform(curl); + if (res != CURLE_OK) { + if (!ctx->last_err) + ctx->last_err = curl_easy_strerror(res); + wpa_printf(MSG_ERROR, "curl_easy_perform() failed: %s", + ctx->last_err); + free_curl_buf(ctx); + return NULL; + } + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http); + wpa_printf(MSG_DEBUG, "curl: Server response code %ld", http); + if (http != 200) { + ctx->last_err = "HTTP POST failed"; + wpa_printf(MSG_INFO, "HTTP POST failed - code %ld", http); + free_curl_buf(ctx); + return NULL; + } + + if (ctx->curl_buf == NULL) + return NULL; + + ret = ctx->curl_buf; + if (resp_len) + *resp_len = ctx->curl_buf_len; + ctx->curl_buf = NULL; + ctx->curl_buf_len = 0; + + wpa_printf(MSG_MSGDUMP, "Server response:\n%s", ret); + + return ret; +} + + +void http_set_cert_cb(struct http_ctx *ctx, + int (*cb)(void *ctx, struct http_cert *cert), + void *cb_ctx) +{ + ctx->cert_cb = cb; + ctx->cert_cb_ctx = cb_ctx; +} + + +const char * http_get_err(struct http_ctx *ctx) +{ + return ctx->last_err; +} diff --git a/src/utils/os.h b/src/utils/os.h index d63ac294..f019e26b 100644 --- a/src/utils/os.h +++ b/src/utils/os.h @@ -240,6 +240,13 @@ int os_unsetenv(const char *name); char * os_readfile(const char *name, size_t *len); /** + * os_file_exists - Check whether the specified file exists + * @fname: Path and name of the file + * Returns: 1 if the file exists or 0 if not + */ +int os_file_exists(const char *fname); + +/** * os_zalloc - Allocate and zero memory * @size: Number of bytes to allocate * Returns: Pointer to allocated and zeroed memory or %NULL on failure diff --git a/src/utils/os_unix.c b/src/utils/os_unix.c index fa67fdfb..008ec6b0 100644 --- a/src/utils/os_unix.c +++ b/src/utils/os_unix.c @@ -407,6 +407,16 @@ char * os_readfile(const char *name, size_t *len) } +int os_file_exists(const char *fname) +{ + FILE *f = fopen(fname, "rb"); + if (f == NULL) + return 0; + fclose(f); + return 1; +} + + #ifndef WPA_TRACE void * os_zalloc(size_t size) { diff --git a/src/utils/xml-utils.c b/src/utils/xml-utils.c new file mode 100644 index 00000000..4916d297 --- /dev/null +++ b/src/utils/xml-utils.c @@ -0,0 +1,471 @@ +/* + * Generic XML helper functions + * Copyright (c) 2012-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 "common.h" +#include "xml-utils.h" + + +static xml_node_t * get_node_uri_iter(struct xml_node_ctx *ctx, + xml_node_t *root, char *uri) +{ + char *end; + xml_node_t *node; + const char *name; + + end = strchr(uri, '/'); + if (end) + *end++ = '\0'; + + node = root; + xml_node_for_each_sibling(ctx, node) { + xml_node_for_each_check(ctx, node); + name = xml_node_get_localname(ctx, node); + if (strcasecmp(name, uri) == 0) + break; + } + + if (node == NULL) + return NULL; + + if (end) { + return get_node_uri_iter(ctx, xml_node_first_child(ctx, node), + end); + } + + return node; +} + + +xml_node_t * get_node_uri(struct xml_node_ctx *ctx, xml_node_t *root, + const char *uri) +{ + char *search; + xml_node_t *node; + + search = os_strdup(uri); + if (search == NULL) + return NULL; + + node = get_node_uri_iter(ctx, root, search); + + os_free(search); + return node; +} + + +static xml_node_t * get_node_iter(struct xml_node_ctx *ctx, + xml_node_t *root, const char *path) +{ + char *end; + xml_node_t *node; + const char *name; + + end = os_strchr(path, '/'); + if (end) + *end++ = '\0'; + + xml_node_for_each_child(ctx, node, root) { + xml_node_for_each_check(ctx, node); + name = xml_node_get_localname(ctx, node); + if (os_strcasecmp(name, path) == 0) + break; + } + + if (node == NULL) + return NULL; + if (end) + return get_node_iter(ctx, node, end); + return node; +} + + +xml_node_t * get_node(struct xml_node_ctx *ctx, xml_node_t *root, + const char *path) +{ + char *search; + xml_node_t *node; + + search = os_strdup(path); + if (search == NULL) + return NULL; + + node = get_node_iter(ctx, root, search); + + os_free(search); + return node; +} + + +xml_node_t * get_child_node(struct xml_node_ctx *ctx, xml_node_t *root, + const char *path) +{ + xml_node_t *node; + xml_node_t *match; + + xml_node_for_each_child(ctx, node, root) { + xml_node_for_each_check(ctx, node); + match = get_node(ctx, node, path); + if (match) + return match; + } + + return NULL; +} + + +xml_node_t * node_from_file(struct xml_node_ctx *ctx, const char *name) +{ + xml_node_t *node; + char *buf, *buf2, *start; + size_t len; + + buf = os_readfile(name, &len); + if (buf == NULL) + return NULL; + buf2 = os_realloc(buf, len + 1); + if (buf2 == NULL) { + os_free(buf); + return NULL; + } + buf = buf2; + buf[len] = '\0'; + + start = os_strstr(buf, "<!DOCTYPE "); + if (start) { + char *pos = start + 1; + int count = 1; + while (*pos) { + if (*pos == '<') + count++; + else if (*pos == '>') { + count--; + if (count == 0) { + pos++; + break; + } + } + pos++; + } + if (count == 0) { + /* Remove DOCTYPE to allow the file to be parsed */ + os_memset(start, ' ', pos - start); + } + } + + node = xml_node_from_buf(ctx, buf); + os_free(buf); + + return node; +} + + +int node_to_file(struct xml_node_ctx *ctx, const char *fname, xml_node_t *node) +{ + FILE *f; + char *str; + + str = xml_node_to_str(ctx, node); + if (str == NULL) + return -1; + + f = fopen(fname, "w"); + if (!f) { + os_free(str); + return -1; + } + + fprintf(f, "%s\n", str); + os_free(str); + fclose(f); + + return 0; +} + + +static char * get_val(struct xml_node_ctx *ctx, xml_node_t *node) +{ + char *val, *pos; + + val = xml_node_get_text(ctx, node); + if (val == NULL) + return NULL; + pos = val; + while (*pos) { + if (*pos != ' ' && *pos != '\t' && *pos != '\r' && *pos != '\n') + return val; + pos++; + } + + return NULL; +} + + +static char * add_path(const char *prev, const char *leaf) +{ + size_t len; + char *new_uri; + + if (prev == NULL) + return NULL; + + len = os_strlen(prev) + 1 + os_strlen(leaf) + 1; + new_uri = os_malloc(len); + if (new_uri) + os_snprintf(new_uri, len, "%s/%s", prev, leaf); + + return new_uri; +} + + +static void node_to_tnds(struct xml_node_ctx *ctx, xml_node_t *out, + xml_node_t *in, const char *uri) +{ + xml_node_t *node; + xml_node_t *tnds; + const char *name; + char *val; + char *new_uri; + + xml_node_for_each_child(ctx, node, in) { + xml_node_for_each_check(ctx, node); + name = xml_node_get_localname(ctx, node); + + tnds = xml_node_create(ctx, out, NULL, "Node"); + if (tnds == NULL) + return; + xml_node_create_text(ctx, tnds, NULL, "NodeName", name); + + if (uri) + xml_node_create_text(ctx, tnds, NULL, "Path", uri); + + val = get_val(ctx, node); + if (val) { + xml_node_create_text(ctx, tnds, NULL, "Value", val); + xml_node_get_text_free(ctx, val); + } + + new_uri = add_path(uri, name); + node_to_tnds(ctx, new_uri ? out : tnds, node, new_uri); + os_free(new_uri); + } +} + + +static int add_ddfname(struct xml_node_ctx *ctx, xml_node_t *parent, + const char *urn) +{ + xml_node_t *node; + + node = xml_node_create(ctx, parent, NULL, "RTProperties"); + if (node == NULL) + return -1; + node = xml_node_create(ctx, node, NULL, "Type"); + if (node == NULL) + return -1; + xml_node_create_text(ctx, node, NULL, "DDFName", urn); + return 0; +} + + +xml_node_t * mo_to_tnds(struct xml_node_ctx *ctx, xml_node_t *mo, + int use_path, const char *urn, const char *ns_uri) +{ + xml_node_t *root; + xml_node_t *node; + const char *name; + + root = xml_node_create_root(ctx, ns_uri, NULL, NULL, "MgmtTree"); + if (root == NULL) + return NULL; + + xml_node_create_text(ctx, root, NULL, "VerDTD", "1.2"); + + name = xml_node_get_localname(ctx, mo); + + node = xml_node_create(ctx, root, NULL, "Node"); + if (node == NULL) + goto fail; + xml_node_create_text(ctx, node, NULL, "NodeName", name); + if (urn) + add_ddfname(ctx, node, urn); + + node_to_tnds(ctx, use_path ? root : node, mo, use_path ? name : NULL); + + return root; + +fail: + xml_node_free(ctx, root); + return NULL; +} + + +static xml_node_t * get_first_child_node(struct xml_node_ctx *ctx, + xml_node_t *node, + const char *name) +{ + const char *lname; + xml_node_t *child; + + xml_node_for_each_child(ctx, child, node) { + xml_node_for_each_check(ctx, child); + lname = xml_node_get_localname(ctx, child); + if (os_strcasecmp(lname, name) == 0) + return child; + } + + return NULL; +} + + +static char * get_node_text(struct xml_node_ctx *ctx, xml_node_t *node, + const char *node_name) +{ + node = get_first_child_node(ctx, node, node_name); + if (node == NULL) + return NULL; + return xml_node_get_text(ctx, node); +} + + +static xml_node_t * add_mo_node(struct xml_node_ctx *ctx, xml_node_t *root, + xml_node_t *node, const char *uri) +{ + char *nodename, *value, *path; + xml_node_t *parent; + + nodename = get_node_text(ctx, node, "NodeName"); + if (nodename == NULL) + return NULL; + value = get_node_text(ctx, node, "Value"); + + if (root == NULL) { + root = xml_node_create_root(ctx, NULL, NULL, NULL, + nodename); + if (root && value) + xml_node_set_text(ctx, root, value); + } else { + if (uri == NULL) { + xml_node_get_text_free(ctx, nodename); + xml_node_get_text_free(ctx, value); + return NULL; + } + path = get_node_text(ctx, node, "Path"); + if (path) + uri = path; + parent = get_node_uri(ctx, root, uri); + xml_node_get_text_free(ctx, path); + if (parent == NULL) { + printf("Could not find URI '%s'\n", uri); + xml_node_get_text_free(ctx, nodename); + xml_node_get_text_free(ctx, value); + return NULL; + } + if (value) + xml_node_create_text(ctx, parent, NULL, nodename, + value); + else + xml_node_create(ctx, parent, NULL, nodename); + } + + xml_node_get_text_free(ctx, nodename); + xml_node_get_text_free(ctx, value); + + return root; +} + + +static xml_node_t * tnds_to_mo_iter(struct xml_node_ctx *ctx, xml_node_t *root, + xml_node_t *node, const char *uri) +{ + xml_node_t *child; + const char *name; + char *nodename; + + xml_node_for_each_sibling(ctx, node) { + xml_node_for_each_check(ctx, node); + + nodename = get_node_text(ctx, node, "NodeName"); + if (nodename == NULL) + return NULL; + + name = xml_node_get_localname(ctx, node); + if (strcmp(name, "Node") == 0) { + if (root && !uri) { + printf("Invalid TNDS tree structure - " + "multiple top level nodes\n"); + xml_node_get_text_free(ctx, nodename); + return NULL; + } + root = add_mo_node(ctx, root, node, uri); + } + + child = get_first_child_node(ctx, node, "Node"); + if (child) { + if (uri == NULL) + tnds_to_mo_iter(ctx, root, child, nodename); + else { + char *new_uri; + new_uri = add_path(uri, nodename); + tnds_to_mo_iter(ctx, root, child, new_uri); + os_free(new_uri); + } + } + xml_node_get_text_free(ctx, nodename); + } + + return root; +} + + +xml_node_t * tnds_to_mo(struct xml_node_ctx *ctx, xml_node_t *tnds) +{ + const char *name; + xml_node_t *node; + + name = xml_node_get_localname(ctx, tnds); + if (name == NULL || os_strcmp(name, "MgmtTree") != 0) + return NULL; + + node = get_first_child_node(ctx, tnds, "Node"); + if (!node) + return NULL; + return tnds_to_mo_iter(ctx, NULL, node, NULL); +} + + +xml_node_t * soap_build_envelope(struct xml_node_ctx *ctx, xml_node_t *node) +{ + xml_node_t *envelope, *body; + xml_namespace_t *ns; + + envelope = xml_node_create_root( + ctx, "http://www.w3.org/2003/05/soap-envelope", "soap12", &ns, + "Envelope"); + if (envelope == NULL) + return NULL; + body = xml_node_create(ctx, envelope, ns, "Body"); + xml_node_add_child(ctx, body, node); + return envelope; +} + + +xml_node_t * soap_get_body(struct xml_node_ctx *ctx, xml_node_t *soap) +{ + xml_node_t *body, *child; + + body = get_node_uri(ctx, soap, "Envelope/Body"); + if (body == NULL) + return NULL; + xml_node_for_each_child(ctx, child, body) { + xml_node_for_each_check(ctx, child); + return child; + } + return NULL; +} diff --git a/src/utils/xml-utils.h b/src/utils/xml-utils.h new file mode 100644 index 00000000..0d8e0cb8 --- /dev/null +++ b/src/utils/xml-utils.h @@ -0,0 +1,100 @@ +/* + * Generic XML helper functions + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef XML_UTILS_H +#define XML_UTILS_H + +struct xml_node_ctx; +typedef struct xml_node xml_node_t; +typedef struct xml_namespace_foo xml_namespace_t; + +/* XML library wrappers */ + +int xml_validate(struct xml_node_ctx *ctx, xml_node_t *node, + const char *xml_schema_fname, char **ret_err); +int xml_validate_dtd(struct xml_node_ctx *ctx, xml_node_t *node, + const char *dtd_fname, char **ret_err); +void xml_node_free(struct xml_node_ctx *ctx, xml_node_t *node); +xml_node_t * xml_node_get_parent(struct xml_node_ctx *ctx, xml_node_t *node); +xml_node_t * xml_node_from_buf(struct xml_node_ctx *ctx, const char *buf); +const char * xml_node_get_localname(struct xml_node_ctx *ctx, + xml_node_t *node); +char * xml_node_to_str(struct xml_node_ctx *ctx, xml_node_t *node); +void xml_node_detach(struct xml_node_ctx *ctx, xml_node_t *node); +void xml_node_add_child(struct xml_node_ctx *ctx, xml_node_t *parent, + xml_node_t *child); +xml_node_t * xml_node_create_root(struct xml_node_ctx *ctx, const char *ns_uri, + const char *ns_prefix, + xml_namespace_t **ret_ns, const char *name); +xml_node_t * xml_node_create(struct xml_node_ctx *ctx, xml_node_t *parent, + xml_namespace_t *ns, const char *name); +xml_node_t * xml_node_create_text(struct xml_node_ctx *ctx, + xml_node_t *parent, xml_namespace_t *ns, + const char *name, const char *value); +xml_node_t * xml_node_create_text_ns(struct xml_node_ctx *ctx, + xml_node_t *parent, const char *ns_uri, + const char *name, const char *value); +void xml_node_set_text(struct xml_node_ctx *ctx, xml_node_t *node, + const char *value); +int xml_node_add_attr(struct xml_node_ctx *ctx, xml_node_t *node, + xml_namespace_t *ns, const char *name, const char *value); +char * xml_node_get_attr_value(struct xml_node_ctx *ctx, xml_node_t *node, + char *name); +char * xml_node_get_attr_value_ns(struct xml_node_ctx *ctx, xml_node_t *node, + const char *ns_uri, char *name); +void xml_node_get_attr_value_free(struct xml_node_ctx *ctx, char *val); +xml_node_t * xml_node_first_child(struct xml_node_ctx *ctx, + xml_node_t *parent); +xml_node_t * xml_node_next_sibling(struct xml_node_ctx *ctx, + xml_node_t *node); +int xml_node_is_element(struct xml_node_ctx *ctx, xml_node_t *node); +char * xml_node_get_text(struct xml_node_ctx *ctx, xml_node_t *node); +void xml_node_get_text_free(struct xml_node_ctx *ctx, char *val); +char * xml_node_get_base64_text(struct xml_node_ctx *ctx, xml_node_t *node, + int *ret_len); +xml_node_t * xml_node_copy(struct xml_node_ctx *ctx, xml_node_t *node); + +#define xml_node_for_each_child(ctx, child, parent) \ +for (child = xml_node_first_child(ctx, parent); \ + child; \ + child = xml_node_next_sibling(ctx, child)) + +#define xml_node_for_each_sibling(ctx, node) \ +for (; \ + node; \ + node = xml_node_next_sibling(ctx, node)) + +#define xml_node_for_each_check(ctx, child) \ +if (!xml_node_is_element(ctx, child)) \ + continue + +typedef void (*debug_print_func)(void *ctx, int print, const char *fmt, ...) + __attribute__ ((format (printf, 3, 4))); + + +struct xml_node_ctx * xml_node_init_ctx(void *upper_ctx, + const void *env); +void xml_node_deinit_ctx(struct xml_node_ctx *ctx); + + +xml_node_t * get_node_uri(struct xml_node_ctx *ctx, xml_node_t *root, + const char *uri); +xml_node_t * get_node(struct xml_node_ctx *ctx, xml_node_t *root, + const char *path); +xml_node_t * get_child_node(struct xml_node_ctx *ctx, xml_node_t *root, + const char *path); +xml_node_t * node_from_file(struct xml_node_ctx *ctx, const char *name); +int node_to_file(struct xml_node_ctx *ctx, const char *fname, xml_node_t *node); +xml_node_t * mo_to_tnds(struct xml_node_ctx *ctx, xml_node_t *mo, + int use_path, const char *urn, const char *ns_uri); +xml_node_t * tnds_to_mo(struct xml_node_ctx *ctx, xml_node_t *tnds); + +xml_node_t * soap_build_envelope(struct xml_node_ctx *ctx, xml_node_t *node); +xml_node_t * soap_get_body(struct xml_node_ctx *ctx, xml_node_t *soap); + +#endif /* XML_UTILS_H */ diff --git a/src/utils/xml_libxml2.c b/src/utils/xml_libxml2.c new file mode 100644 index 00000000..c9283946 --- /dev/null +++ b/src/utils/xml_libxml2.c @@ -0,0 +1,457 @@ +/* + * XML wrapper for libxml2 + * Copyright (c) 2012-2013, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "includes.h" +#define LIBXML_VALID_ENABLED +#include <libxml/tree.h> +#include <libxml/xmlschemastypes.h> + +#include "common.h" +#include "base64.h" +#include "xml-utils.h" + + +struct xml_node_ctx { + void *ctx; +}; + + +struct str_buf { + char *buf; + size_t len; +}; + +#define MAX_STR 1000 + +static void add_str(void *ctx_ptr, const char *fmt, ...) +{ + struct str_buf *str = ctx_ptr; + va_list ap; + char *n; + int len; + + n = os_realloc(str->buf, str->len + MAX_STR + 2); + if (n == NULL) + return; + str->buf = n; + + va_start(ap, fmt); + len = vsnprintf(str->buf + str->len, MAX_STR, fmt, ap); + va_end(ap); + if (len >= MAX_STR) + len = MAX_STR - 1; + str->len += len; + str->buf[str->len] = '\0'; +} + + +int xml_validate(struct xml_node_ctx *ctx, xml_node_t *node, + const char *xml_schema_fname, char **ret_err) +{ + xmlDocPtr doc; + xmlNodePtr n; + xmlSchemaParserCtxtPtr pctx; + xmlSchemaValidCtxtPtr vctx; + xmlSchemaPtr schema; + int ret; + struct str_buf errors; + + if (ret_err) + *ret_err = NULL; + + doc = xmlNewDoc((xmlChar *) "1.0"); + if (doc == NULL) + return -1; + n = xmlDocCopyNode((xmlNodePtr) node, doc, 1); + if (n == NULL) { + xmlFreeDoc(doc); + return -1; + } + xmlDocSetRootElement(doc, n); + + os_memset(&errors, 0, sizeof(errors)); + + pctx = xmlSchemaNewParserCtxt(xml_schema_fname); + xmlSchemaSetParserErrors(pctx, (xmlSchemaValidityErrorFunc) add_str, + (xmlSchemaValidityWarningFunc) add_str, + &errors); + schema = xmlSchemaParse(pctx); + xmlSchemaFreeParserCtxt(pctx); + + vctx = xmlSchemaNewValidCtxt(schema); + xmlSchemaSetValidErrors(vctx, (xmlSchemaValidityErrorFunc) add_str, + (xmlSchemaValidityWarningFunc) add_str, + &errors); + + ret = xmlSchemaValidateDoc(vctx, doc); + xmlSchemaFreeValidCtxt(vctx); + xmlFreeDoc(doc); + xmlSchemaFree(schema); + + if (ret == 0) { + os_free(errors.buf); + return 0; + } else if (ret > 0) { + if (ret_err) + *ret_err = errors.buf; + else + os_free(errors.buf); + return -1; + } else { + if (ret_err) + *ret_err = errors.buf; + else + os_free(errors.buf); + return -1; + } +} + + +int xml_validate_dtd(struct xml_node_ctx *ctx, xml_node_t *node, + const char *dtd_fname, char **ret_err) +{ + xmlDocPtr doc; + xmlNodePtr n; + xmlValidCtxt vctx; + xmlDtdPtr dtd; + int ret; + struct str_buf errors; + + if (ret_err) + *ret_err = NULL; + + doc = xmlNewDoc((xmlChar *) "1.0"); + if (doc == NULL) + return -1; + n = xmlDocCopyNode((xmlNodePtr) node, doc, 1); + if (n == NULL) { + xmlFreeDoc(doc); + return -1; + } + xmlDocSetRootElement(doc, n); + + os_memset(&errors, 0, sizeof(errors)); + + dtd = xmlParseDTD(NULL, (const xmlChar *) dtd_fname); + if (dtd == NULL) { + xmlFreeDoc(doc); + return -1; + } + + os_memset(&vctx, 0, sizeof(vctx)); + vctx.userData = &errors; + vctx.error = add_str; + vctx.warning = add_str; + ret = xmlValidateDtd(&vctx, doc, dtd); + xmlFreeDoc(doc); + xmlFreeDtd(dtd); + + if (ret == 1) { + os_free(errors.buf); + return 0; + } else { + if (ret_err) + *ret_err = errors.buf; + else + os_free(errors.buf); + return -1; + } +} + + +void xml_node_free(struct xml_node_ctx *ctx, xml_node_t *node) +{ + xmlFreeNode((xmlNodePtr) node); +} + + +xml_node_t * xml_node_get_parent(struct xml_node_ctx *ctx, xml_node_t *node) +{ + return (xml_node_t *) ((xmlNodePtr) node)->parent; +} + + +xml_node_t * xml_node_from_buf(struct xml_node_ctx *ctx, const char *buf) +{ + xmlDocPtr doc; + xmlNodePtr node; + + doc = xmlParseMemory(buf, strlen(buf)); + if (doc == NULL) + return NULL; + node = xmlDocGetRootElement(doc); + node = xmlCopyNode(node, 1); + xmlFreeDoc(doc); + + return (xml_node_t *) node; +} + + +const char * xml_node_get_localname(struct xml_node_ctx *ctx, + xml_node_t *node) +{ + return (const char *) ((xmlNodePtr) node)->name; +} + + +char * xml_node_to_str(struct xml_node_ctx *ctx, xml_node_t *node) +{ + xmlChar *buf; + int bufsiz; + char *ret, *pos; + xmlNodePtr n = (xmlNodePtr) node; + xmlDocPtr doc; + + doc = xmlNewDoc((xmlChar *) "1.0"); + n = xmlDocCopyNode(n, doc, 1); + xmlDocSetRootElement(doc, n); + xmlDocDumpFormatMemory(doc, &buf, &bufsiz, 0); + xmlFreeDoc(doc); + pos = (char *) buf; + if (strncmp(pos, "<?xml", 5) == 0) { + pos = strchr(pos, '>'); + if (pos) + pos++; + while (pos && (*pos == '\r' || *pos == '\n')) + pos++; + } + if (pos) + ret = os_strdup(pos); + else + ret = NULL; + xmlFree(buf); + + if (ret) { + pos = ret; + if (pos[0]) { + while (pos[1]) + pos++; + } + while (pos >= ret && *pos == '\n') + *pos-- = '\0'; + } + + return ret; +} + + +void xml_node_detach(struct xml_node_ctx *ctx, xml_node_t *node) +{ + xmlUnlinkNode((xmlNodePtr) node); +} + + +void xml_node_add_child(struct xml_node_ctx *ctx, xml_node_t *parent, + xml_node_t *child) +{ + xmlAddChild((xmlNodePtr) parent, (xmlNodePtr) child); +} + + +xml_node_t * xml_node_create_root(struct xml_node_ctx *ctx, const char *ns_uri, + const char *ns_prefix, + xml_namespace_t **ret_ns, const char *name) +{ + xmlNodePtr node; + xmlNsPtr ns = NULL; + + node = xmlNewNode(NULL, (const xmlChar *) name); + if (node == NULL) + return NULL; + if (ns_uri) { + ns = xmlNewNs(node, (const xmlChar *) ns_uri, + (const xmlChar *) ns_prefix); + xmlSetNs(node, ns); + } + + if (ret_ns) + *ret_ns = (xml_namespace_t *) ns; + + return (xml_node_t *) node; +} + + +xml_node_t * xml_node_create(struct xml_node_ctx *ctx, xml_node_t *parent, + xml_namespace_t *ns, const char *name) +{ + xmlNodePtr node; + node = xmlNewChild((xmlNodePtr) parent, (xmlNsPtr) ns, + (const xmlChar *) name, NULL); + return (xml_node_t *) node; +} + + +xml_node_t * xml_node_create_text(struct xml_node_ctx *ctx, + xml_node_t *parent, xml_namespace_t *ns, + const char *name, const char *value) +{ + xmlNodePtr node; + node = xmlNewTextChild((xmlNodePtr) parent, (xmlNsPtr) ns, + (const xmlChar *) name, (const xmlChar *) value); + return (xml_node_t *) node; +} + + +xml_node_t * xml_node_create_text_ns(struct xml_node_ctx *ctx, + xml_node_t *parent, const char *ns_uri, + const char *name, const char *value) +{ + xmlNodePtr node; + xmlNsPtr ns; + + node = xmlNewTextChild((xmlNodePtr) parent, NULL, + (const xmlChar *) name, (const xmlChar *) value); + ns = xmlNewNs(node, (const xmlChar *) ns_uri, NULL); + xmlSetNs(node, ns); + return (xml_node_t *) node; +} + + +void xml_node_set_text(struct xml_node_ctx *ctx, xml_node_t *node, + const char *value) +{ + /* TODO: escape XML special chars in value */ + xmlNodeSetContent((xmlNodePtr) node, (xmlChar *) value); +} + + +int xml_node_add_attr(struct xml_node_ctx *ctx, xml_node_t *node, + xml_namespace_t *ns, const char *name, const char *value) +{ + xmlAttrPtr attr; + + if (ns) { + attr = xmlNewNsProp((xmlNodePtr) node, (xmlNsPtr) ns, + (const xmlChar *) name, + (const xmlChar *) value); + } else { + attr = xmlNewProp((xmlNodePtr) node, (const xmlChar *) name, + (const xmlChar *) value); + } + + return attr ? 0 : -1; +} + + +char * xml_node_get_attr_value(struct xml_node_ctx *ctx, xml_node_t *node, + char *name) +{ + return (char *) xmlGetNoNsProp((xmlNodePtr) node, + (const xmlChar *) name); +} + + +char * xml_node_get_attr_value_ns(struct xml_node_ctx *ctx, xml_node_t *node, + const char *ns_uri, char *name) +{ + return (char *) xmlGetNsProp((xmlNodePtr) node, (const xmlChar *) name, + (const xmlChar *) ns_uri); +} + + +void xml_node_get_attr_value_free(struct xml_node_ctx *ctx, char *val) +{ + if (val) + xmlFree((xmlChar *) val); +} + + +xml_node_t * xml_node_first_child(struct xml_node_ctx *ctx, + xml_node_t *parent) +{ + return (xml_node_t *) ((xmlNodePtr) parent)->children; +} + + +xml_node_t * xml_node_next_sibling(struct xml_node_ctx *ctx, + xml_node_t *node) +{ + return (xml_node_t *) ((xmlNodePtr) node)->next; +} + + +int xml_node_is_element(struct xml_node_ctx *ctx, xml_node_t *node) +{ + return ((xmlNodePtr) node)->type == XML_ELEMENT_NODE; +} + + +char * xml_node_get_text(struct xml_node_ctx *ctx, xml_node_t *node) +{ + if (xmlChildElementCount((xmlNodePtr) node) > 0) + return NULL; + return (char *) xmlNodeGetContent((xmlNodePtr) node); +} + + +void xml_node_get_text_free(struct xml_node_ctx *ctx, char *val) +{ + if (val) + xmlFree((xmlChar *) val); +} + + +char * xml_node_get_base64_text(struct xml_node_ctx *ctx, xml_node_t *node, + int *ret_len) +{ + char *txt; + unsigned char *ret; + size_t len; + + txt = xml_node_get_text(ctx, node); + if (txt == NULL) + return NULL; + + ret = base64_decode((unsigned char *) txt, strlen(txt), &len); + if (ret_len) + *ret_len = len; + xml_node_get_text_free(ctx, txt); + if (ret == NULL) + return NULL; + txt = os_malloc(len + 1); + if (txt == NULL) { + os_free(ret); + return NULL; + } + os_memcpy(txt, ret, len); + txt[len] = '\0'; + return txt; +} + + +xml_node_t * xml_node_copy(struct xml_node_ctx *ctx, xml_node_t *node) +{ + if (node == NULL) + return NULL; + return (xml_node_t *) xmlCopyNode((xmlNodePtr) node, 1); +} + + +struct xml_node_ctx * xml_node_init_ctx(void *upper_ctx, + const void *env) +{ + struct xml_node_ctx *xctx; + + xctx = os_zalloc(sizeof(*xctx)); + if (xctx == NULL) + return NULL; + xctx->ctx = upper_ctx; + + LIBXML_TEST_VERSION + + return xctx; +} + + +void xml_node_deinit_ctx(struct xml_node_ctx *ctx) +{ + xmlSchemaCleanupTypes(); + xmlCleanupParser(); + xmlMemoryDump(); + os_free(ctx); +} |
