/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi; import android.net.IpConfiguration; import android.net.IpConfiguration.IpAssignment; import android.net.IpConfiguration.ProxySettings; import android.net.LinkAddress; import android.net.NetworkUtils; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.StaticIpConfiguration; import android.net.wifi.WifiConfiguration; import android.util.Log; import android.util.Pair; import com.android.server.wifi.util.XmlUtil; import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil; import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Parser for major version 1 of WiFi backup data. * Contains whitelists of tags for WifiConfiguration and IpConfiguration sections for each of * the minor versions. * * Overall structure of the major version 1 XML schema: * * * * * * * value * value * value * value * * * * * * * ... (other supported tag names in minor version 1: "WEPTxKeyIndex", "HiddenSSID", * "RequirePMF", "AllowedKeyMgmt", "AllowedProtocols", "AllowedAuthAlgos", * "AllowedGroupCiphers", "AllowedPairwiseCiphers", "Shared") * * * value * value * ... (other supported tag names in minor version 1: "LinkAddress", "LinkPrefixLength", * "GatewayAddress", "DNSServers", "ProxyHost", "ProxyPort", "ProxyPac", * "ProxyExclusionList") * * * * ... (format as above) * * * */ class WifiBackupDataV1Parser implements WifiBackupDataParser { private static final String TAG = "WifiBackupDataV1Parser"; private static final int HIGHEST_SUPPORTED_MINOR_VERSION = 1; // List of tags supported for section in minor version 0 private static final Set WIFI_CONFIGURATION_MINOR_V0_SUPPORTED_TAGS = new HashSet(Arrays.asList(new String[] { WifiConfigurationXmlUtil.XML_TAG_CONFIG_KEY, WifiConfigurationXmlUtil.XML_TAG_SSID, WifiConfigurationXmlUtil.XML_TAG_BSSID, WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY, WifiConfigurationXmlUtil.XML_TAG_WEP_KEYS, WifiConfigurationXmlUtil.XML_TAG_WEP_TX_KEY_INDEX, WifiConfigurationXmlUtil.XML_TAG_HIDDEN_SSID, WifiConfigurationXmlUtil.XML_TAG_REQUIRE_PMF, WifiConfigurationXmlUtil.XML_TAG_ALLOWED_KEY_MGMT, WifiConfigurationXmlUtil.XML_TAG_ALLOWED_PROTOCOLS, WifiConfigurationXmlUtil.XML_TAG_ALLOWED_AUTH_ALGOS, WifiConfigurationXmlUtil.XML_TAG_ALLOWED_GROUP_CIPHERS, WifiConfigurationXmlUtil.XML_TAG_ALLOWED_PAIRWISE_CIPHERS, WifiConfigurationXmlUtil.XML_TAG_SHARED, })); // List of tags supported for section in minor version 1 private static final Set WIFI_CONFIGURATION_MINOR_V1_SUPPORTED_TAGS = new HashSet() {{ addAll(WIFI_CONFIGURATION_MINOR_V0_SUPPORTED_TAGS); add(WifiConfigurationXmlUtil.XML_TAG_METERED_OVERRIDE); }}; // List of tags supported for section in minor version 0 & 1 private static final Set IP_CONFIGURATION_MINOR_V0_V1_SUPPORTED_TAGS = new HashSet(Arrays.asList(new String[] { IpConfigurationXmlUtil.XML_TAG_IP_ASSIGNMENT, IpConfigurationXmlUtil.XML_TAG_LINK_ADDRESS, IpConfigurationXmlUtil.XML_TAG_LINK_PREFIX_LENGTH, IpConfigurationXmlUtil.XML_TAG_GATEWAY_ADDRESS, IpConfigurationXmlUtil.XML_TAG_DNS_SERVER_ADDRESSES, IpConfigurationXmlUtil.XML_TAG_PROXY_SETTINGS, IpConfigurationXmlUtil.XML_TAG_PROXY_HOST, IpConfigurationXmlUtil.XML_TAG_PROXY_PORT, IpConfigurationXmlUtil.XML_TAG_PROXY_EXCLUSION_LIST, IpConfigurationXmlUtil.XML_TAG_PROXY_PAC_FILE, })); public List parseNetworkConfigurationsFromXml(XmlPullParser in, int outerTagDepth, int minorVersion) throws XmlPullParserException, IOException { // clamp down the minorVersion to the highest one that this parser version supports if (minorVersion > HIGHEST_SUPPORTED_MINOR_VERSION) { minorVersion = HIGHEST_SUPPORTED_MINOR_VERSION; } // Find the configuration list section. XmlUtil.gotoNextSectionWithName(in, WifiBackupRestore.XML_TAG_SECTION_HEADER_NETWORK_LIST, outerTagDepth); // Find all the configurations within the configuration list section. int networkListTagDepth = outerTagDepth + 1; List configurations = new ArrayList<>(); while (XmlUtil.gotoNextSectionWithNameOrEnd( in, WifiBackupRestore.XML_TAG_SECTION_HEADER_NETWORK, networkListTagDepth)) { WifiConfiguration configuration = parseNetworkConfigurationFromXml(in, minorVersion, networkListTagDepth); if (configuration != null) { Log.v(TAG, "Parsed Configuration: " + configuration.configKey()); configurations.add(configuration); } } return configurations; } /** * Parses the configuration data elements from the provided XML stream to a Configuration. * * @param in XmlPullParser instance pointing to the XML stream. * @param minorVersion minor version number parsed from incoming data. * @param outerTagDepth depth of the outer tag in the XML document. * @return WifiConfiguration object if parsing is successful, null otherwise. */ private WifiConfiguration parseNetworkConfigurationFromXml(XmlPullParser in, int minorVersion, int outerTagDepth) throws XmlPullParserException, IOException { WifiConfiguration configuration = null; int networkTagDepth = outerTagDepth + 1; // Retrieve WifiConfiguration object first. XmlUtil.gotoNextSectionWithName( in, WifiBackupRestore.XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION, networkTagDepth); int configTagDepth = networkTagDepth + 1; configuration = parseWifiConfigurationFromXml(in, configTagDepth, minorVersion); if (configuration == null) { return null; } // Now retrieve any IP configuration info. XmlUtil.gotoNextSectionWithName( in, WifiBackupRestore.XML_TAG_SECTION_HEADER_IP_CONFIGURATION, networkTagDepth); IpConfiguration ipConfiguration = parseIpConfigurationFromXml(in, configTagDepth, minorVersion); configuration.setIpConfiguration(ipConfiguration); return configuration; } /** * Helper method to parse the WifiConfiguration object. */ private WifiConfiguration parseWifiConfigurationFromXml(XmlPullParser in, int outerTagDepth, int minorVersion) throws XmlPullParserException, IOException { Pair parsedConfig = parseWifiConfigurationFromXmlInternal(in, outerTagDepth, minorVersion); if (parsedConfig == null || parsedConfig.first == null || parsedConfig.second == null) { return null; } String configKeyParsed = parsedConfig.first; WifiConfiguration configuration = parsedConfig.second; String configKeyCalculated = configuration.configKey(); if (!configKeyParsed.equals(configKeyCalculated)) { // configKey is not part of the SDK. So, we can't expect this to be the same // across OEM's. Just log a warning & continue. Log.w(TAG, "Configuration key does not match. Retrieved: " + configKeyParsed + ", Calculated: " + configKeyCalculated); } return configuration; } /** * Helper method to mask out any invalid data in parsed WifiConfiguration. * * This is a compatibility layer added to the parsing logic to try and weed out any known * issues in the backup data format from other OEM's. */ private static void clearAnyKnownIssuesInParsedConfiguration(WifiConfiguration config) { /** * Fix for b/73987207. Clear any invalid bits in the bitsets. */ // |allowedKeyManagement| if (config.allowedKeyManagement.length() > WifiConfiguration.KeyMgmt.strings.length) { config.allowedKeyManagement.clear( WifiConfiguration.KeyMgmt.strings.length, config.allowedKeyManagement.length()); } // |allowedProtocols| if (config.allowedProtocols.length() > WifiConfiguration.Protocol.strings.length) { config.allowedProtocols.clear( WifiConfiguration.Protocol.strings.length, config.allowedProtocols.length()); } // |allowedAuthAlgorithms| if (config.allowedAuthAlgorithms.length() > WifiConfiguration.AuthAlgorithm.strings.length) { config.allowedAuthAlgorithms.clear( WifiConfiguration.AuthAlgorithm.strings.length, config.allowedAuthAlgorithms.length()); } // |allowedGroupCiphers| if (config.allowedGroupCiphers.length() > WifiConfiguration.GroupCipher.strings.length) { config.allowedGroupCiphers.clear( WifiConfiguration.GroupCipher.strings.length, config.allowedGroupCiphers.length()); } // |allowedPairwiseCiphers| if (config.allowedPairwiseCiphers.length() > WifiConfiguration.PairwiseCipher.strings.length) { config.allowedPairwiseCiphers.clear( WifiConfiguration.PairwiseCipher.strings.length, config.allowedPairwiseCiphers.length()); } // Add any other fixable issues discovered from other OEM's here. } /** * Parses the configuration data elements from the provided XML stream to a * WifiConfiguration object. * Looping through the tags makes it easy to add elements in the future minor versions if * needed. Unsupported elements will be ignored. * * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. * @param minorVersion minor version number parsed from incoming data. * @return Pair if parsing is successful, null otherwise. */ private static Pair parseWifiConfigurationFromXmlInternal( XmlPullParser in, int outerTagDepth, int minorVersion) throws XmlPullParserException, IOException { WifiConfiguration configuration = new WifiConfiguration(); String configKeyInData = null; Set supportedTags = getSupportedWifiConfigurationTags(minorVersion); // Loop through and parse out all the elements from the stream within this section. while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); String tagName = valueName[0]; if (tagName == null) { throw new XmlPullParserException("Missing value name"); } // ignore the tags that are not supported up until the current minor version if (!supportedTags.contains(tagName)) { Log.w(TAG, "Unsupported tag + \"" + tagName + "\" found in " + " section, ignoring."); continue; } // note: the below switch case list should contain all tags supported up until the // highest minor version supported by this parser switch (tagName) { case WifiConfigurationXmlUtil.XML_TAG_CONFIG_KEY: configKeyInData = (String) value; break; case WifiConfigurationXmlUtil.XML_TAG_SSID: configuration.SSID = (String) value; break; case WifiConfigurationXmlUtil.XML_TAG_BSSID: configuration.BSSID = (String) value; break; case WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY: configuration.preSharedKey = (String) value; break; case WifiConfigurationXmlUtil.XML_TAG_WEP_KEYS: populateWepKeysFromXmlValue(value, configuration.wepKeys); break; case WifiConfigurationXmlUtil.XML_TAG_WEP_TX_KEY_INDEX: configuration.wepTxKeyIndex = (int) value; break; case WifiConfigurationXmlUtil.XML_TAG_HIDDEN_SSID: configuration.hiddenSSID = (boolean) value; break; case WifiConfigurationXmlUtil.XML_TAG_REQUIRE_PMF: configuration.requirePMF = (boolean) value; break; case WifiConfigurationXmlUtil.XML_TAG_ALLOWED_KEY_MGMT: byte[] allowedKeyMgmt = (byte[]) value; configuration.allowedKeyManagement = BitSet.valueOf(allowedKeyMgmt); break; case WifiConfigurationXmlUtil.XML_TAG_ALLOWED_PROTOCOLS: byte[] allowedProtocols = (byte[]) value; configuration.allowedProtocols = BitSet.valueOf(allowedProtocols); break; case WifiConfigurationXmlUtil.XML_TAG_ALLOWED_AUTH_ALGOS: byte[] allowedAuthAlgorithms = (byte[]) value; configuration.allowedAuthAlgorithms = BitSet.valueOf(allowedAuthAlgorithms); break; case WifiConfigurationXmlUtil.XML_TAG_ALLOWED_GROUP_CIPHERS: byte[] allowedGroupCiphers = (byte[]) value; configuration.allowedGroupCiphers = BitSet.valueOf(allowedGroupCiphers); break; case WifiConfigurationXmlUtil.XML_TAG_ALLOWED_PAIRWISE_CIPHERS: byte[] allowedPairwiseCiphers = (byte[]) value; configuration.allowedPairwiseCiphers = BitSet.valueOf(allowedPairwiseCiphers); break; case WifiConfigurationXmlUtil.XML_TAG_SHARED: configuration.shared = (boolean) value; break; case WifiConfigurationXmlUtil.XML_TAG_METERED_OVERRIDE: configuration.meteredOverride = (int) value; break; default: // should never happen, since other tags are filtered out earlier throw new XmlPullParserException( "Unknown value name found: " + valueName[0]); } } clearAnyKnownIssuesInParsedConfiguration(configuration); return Pair.create(configKeyInData, configuration); } /** * Returns a set of supported tags of element for all minor versions of * this major version up to and including the specified minorVersion (only adding tags is * supported in minor versions, removal or changing the meaning of tags requires bumping * the major version and reseting the minor to 0). * * @param minorVersion minor version number parsed from incoming data. */ private static Set getSupportedWifiConfigurationTags(int minorVersion) { switch (minorVersion) { case 0: return WIFI_CONFIGURATION_MINOR_V0_SUPPORTED_TAGS; case 1: return WIFI_CONFIGURATION_MINOR_V1_SUPPORTED_TAGS; default: Log.e(TAG, "Invalid minorVersion: " + minorVersion); return Collections.emptySet(); } } /** * Populate wepKeys array elements only if they were non-empty in the backup data. * * @throws XmlPullParserException if parsing errors occur. */ private static void populateWepKeysFromXmlValue(Object value, String[] wepKeys) throws XmlPullParserException, IOException { String[] wepKeysInData = (String[]) value; if (wepKeysInData == null) { return; } if (wepKeysInData.length != wepKeys.length) { throw new XmlPullParserException( "Invalid Wep Keys length: " + wepKeysInData.length); } for (int i = 0; i < wepKeys.length; i++) { if (wepKeysInData[i].isEmpty()) { wepKeys[i] = null; } else { wepKeys[i] = wepKeysInData[i]; } } } /** * Parses the IP configuration data elements from the provided XML stream to an * IpConfiguration object. * * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. * @param minorVersion minor version number parsed from incoming data. * @return IpConfiguration object if parsing is successful, null otherwise. */ private static IpConfiguration parseIpConfigurationFromXml(XmlPullParser in, int outerTagDepth, int minorVersion) throws XmlPullParserException, IOException { // First parse *all* of the tags in section Set supportedTags = getSupportedIpConfigurationTags(minorVersion); String ipAssignmentString = null; String linkAddressString = null; Integer linkPrefixLength = null; String gatewayAddressString = null; String[] dnsServerAddressesString = null; String proxySettingsString = null; String proxyHost = null; int proxyPort = -1; String proxyExclusionList = null; String proxyPacFile = null; // Loop through and parse out all the elements from the stream within this section. while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); String tagName = valueName[0]; if (tagName == null) { throw new XmlPullParserException("Missing value name"); } // ignore the tags that are not supported up until the current minor version if (!supportedTags.contains(tagName)) { Log.w(TAG, "Unsupported tag + \"" + tagName + "\" found in " + " section, ignoring."); continue; } // note: the below switch case list should contain all tags supported up until the // highest minor version supported by this parser // should any tags be added in next minor versions, conditional processing of them // also needs to be added in the below code (processing into IpConfiguration object) switch (tagName) { case IpConfigurationXmlUtil.XML_TAG_IP_ASSIGNMENT: ipAssignmentString = (String) value; break; case IpConfigurationXmlUtil.XML_TAG_LINK_ADDRESS: linkAddressString = (String) value; break; case IpConfigurationXmlUtil.XML_TAG_LINK_PREFIX_LENGTH: linkPrefixLength = (Integer) value; break; case IpConfigurationXmlUtil.XML_TAG_GATEWAY_ADDRESS: gatewayAddressString = (String) value; break; case IpConfigurationXmlUtil.XML_TAG_DNS_SERVER_ADDRESSES: dnsServerAddressesString = (String[]) value; break; case IpConfigurationXmlUtil.XML_TAG_PROXY_SETTINGS: proxySettingsString = (String) value; break; case IpConfigurationXmlUtil.XML_TAG_PROXY_HOST: proxyHost = (String) value; break; case IpConfigurationXmlUtil.XML_TAG_PROXY_PORT: proxyPort = (int) value; break; case IpConfigurationXmlUtil.XML_TAG_PROXY_EXCLUSION_LIST: proxyExclusionList = (String) value; break; case IpConfigurationXmlUtil.XML_TAG_PROXY_PAC_FILE: proxyPacFile = (String) value; break; default: // should never happen, since other tags are filtered out earlier throw new XmlPullParserException( "Unknown value name found: " + valueName[0]); } } // Now process the values into IpConfiguration object IpConfiguration ipConfiguration = new IpConfiguration(); if (ipAssignmentString == null) { throw new XmlPullParserException("IpAssignment was missing in IpConfiguration section"); } IpAssignment ipAssignment = IpAssignment.valueOf(ipAssignmentString); ipConfiguration.setIpAssignment(ipAssignment); switch (ipAssignment) { case STATIC: StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration(); if (linkAddressString != null && linkPrefixLength != null) { LinkAddress linkAddress = new LinkAddress( NetworkUtils.numericToInetAddress(linkAddressString), linkPrefixLength); if (linkAddress.getAddress() instanceof Inet4Address) { staticIpConfiguration.ipAddress = linkAddress; } else { Log.w(TAG, "Non-IPv4 address: " + linkAddress); } } if (gatewayAddressString != null) { LinkAddress dest = null; InetAddress gateway = NetworkUtils.numericToInetAddress(gatewayAddressString); RouteInfo route = new RouteInfo(dest, gateway); if (route.isIPv4Default()) { staticIpConfiguration.gateway = gateway; } else { Log.w(TAG, "Non-IPv4 default route: " + route); } } if (dnsServerAddressesString != null) { for (String dnsServerAddressString : dnsServerAddressesString) { InetAddress dnsServerAddress = NetworkUtils.numericToInetAddress(dnsServerAddressString); staticIpConfiguration.dnsServers.add(dnsServerAddress); } } ipConfiguration.setStaticIpConfiguration(staticIpConfiguration); break; case DHCP: case UNASSIGNED: break; default: throw new XmlPullParserException("Unknown ip assignment type: " + ipAssignment); } // Process the proxy settings next if (proxySettingsString == null) { throw new XmlPullParserException("ProxySettings was missing in" + " IpConfiguration section"); } ProxySettings proxySettings = ProxySettings.valueOf(proxySettingsString); ipConfiguration.setProxySettings(proxySettings); switch (proxySettings) { case STATIC: if (proxyHost == null) { throw new XmlPullParserException("ProxyHost was missing in" + " IpConfiguration section"); } if (proxyPort == -1) { throw new XmlPullParserException("ProxyPort was missing in" + " IpConfiguration section"); } if (proxyExclusionList == null) { throw new XmlPullParserException("ProxyExclusionList was missing in" + " IpConfiguration section"); } ipConfiguration.setHttpProxy( new ProxyInfo(proxyHost, proxyPort, proxyExclusionList)); break; case PAC: if (proxyPacFile == null) { throw new XmlPullParserException("ProxyPac was missing in" + " IpConfiguration section"); } ipConfiguration.setHttpProxy(new ProxyInfo(proxyPacFile)); break; case NONE: case UNASSIGNED: break; default: throw new XmlPullParserException( "Unknown proxy settings type: " + proxySettings); } return ipConfiguration; } /** * Returns a set of supported tags of element for all minor versions of * this major version up to and including the specified minorVersion (only adding tags is * supported in minor versions, removal or changing the meaning of tags requires bumping * the major version and reseting the minor to 0). * * @param minorVersion minor version number parsed from incoming data. */ private static Set getSupportedIpConfigurationTags(int minorVersion) { switch (minorVersion) { case 0: case 1: return IP_CONFIGURATION_MINOR_V0_V1_SUPPORTED_TAGS; default: Log.e(TAG, "Invalid minorVersion: " + minorVersion); return Collections.emptySet(); } } }