| /* |
| * Copyright (C) 2016 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.wifi.WifiConfiguration; |
| import android.net.wifi.WifiEnterpriseConfig; |
| import android.os.Process; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.util.Xml; |
| |
| import com.android.internal.util.FastXmlSerializer; |
| import com.android.server.wifi.util.IpConfigStore; |
| import com.android.server.wifi.util.NativeUtil; |
| import com.android.server.wifi.util.WifiPermissionsUtil; |
| 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 org.xmlpull.v1.XmlSerializer; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.CharArrayReader; |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.UnsupportedEncodingException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Class used to backup/restore data using the SettingsBackupAgent. |
| * There are 2 symmetric API's exposed here: |
| * 1. retrieveBackupDataFromConfigurations: Retrieve the configuration data to be backed up. |
| * 2. retrieveConfigurationsFromBackupData: Restore the configuration using the provided data. |
| * The byte stream to be backed up is XML encoded and versioned to migrate the data easily across |
| * revisions. |
| */ |
| public class WifiBackupRestore { |
| private static final String TAG = "WifiBackupRestore"; |
| |
| /** |
| * Current backup data version. |
| * Note: before Android P this used to be an {@code int}, however support for minor versions |
| * has been added in Android P. Currently this field is a {@code float} representing |
| * "majorVersion.minorVersion" of the backed up data. MinorVersion starts with 0 and should |
| * be incremented when necessary. MajorVersion starts with 1 and bumping it up requires |
| * also resetting minorVersion to 0. |
| * |
| * MajorVersion will be incremented for modifications of the XML schema, excluding additive |
| * modifications in <WifiConfiguration> and/or <IpConfiguration> tags. |
| * Should the major version be bumped up, a new {@link WifiBackupDataParser} parser needs to |
| * be added and returned from {@link #getWifiBackupDataParser(int)} ()}. |
| * Note that bumping up the major version will result in inability to restore the backup |
| * set to those lower versions of SDK_INT that don't support the version. |
| * |
| * MinorVersion will only be incremented for addition of <WifiConfiguration> and/or |
| * <IpConfiguration> tags. Any other modifications to the schema should result in bumping up |
| * the major version and resetting the minor version to 0. |
| * Note that bumping up only the minor version will still allow restoring the backup set to |
| * lower versions of SDK_INT. |
| */ |
| private static final int CURRENT_BACKUP_DATA_MAJOR_VERSION = 1; |
| |
| /** This list of older versions will be used to restore data from older backups. */ |
| /** |
| * First version of the backup data format. |
| */ |
| private static final int INITIAL_BACKUP_DATA_VERSION = 1; |
| |
| /** |
| * List of XML section header tags in the backed up data |
| */ |
| private static final String XML_TAG_DOCUMENT_HEADER = "WifiBackupData"; |
| private static final String XML_TAG_VERSION = "Version"; |
| |
| static final String XML_TAG_SECTION_HEADER_NETWORK_LIST = "NetworkList"; |
| static final String XML_TAG_SECTION_HEADER_NETWORK = "Network"; |
| static final String XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION = "WifiConfiguration"; |
| static final String XML_TAG_SECTION_HEADER_IP_CONFIGURATION = "IpConfiguration"; |
| |
| /** |
| * Regex to mask out passwords in backup data dump. |
| */ |
| private static final String PSK_MASK_LINE_MATCH_PATTERN = |
| "<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>.*<.*>"; |
| private static final String PSK_MASK_SEARCH_PATTERN = |
| "(<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>)(.*)(<.*>)"; |
| private static final String PSK_MASK_REPLACE_PATTERN = "$1*$3"; |
| |
| private static final String WEP_KEYS_MASK_LINE_START_MATCH_PATTERN = |
| "<string-array.*" + WifiConfigurationXmlUtil.XML_TAG_WEP_KEYS + ".*num=\"[0-9]\">"; |
| private static final String WEP_KEYS_MASK_LINE_END_MATCH_PATTERN = "</string-array>"; |
| private static final String WEP_KEYS_MASK_SEARCH_PATTERN = "(<.*=)(.*)(/>)"; |
| private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*$3"; |
| |
| private final WifiPermissionsUtil mWifiPermissionsUtil; |
| /** |
| * Verbose logging flag. |
| */ |
| private boolean mVerboseLoggingEnabled = false; |
| |
| /** |
| * Store the dump of the backup/restore data for debugging. This is only stored when verbose |
| * logging is enabled in developer options. |
| */ |
| private byte[] mDebugLastBackupDataRetrieved; |
| private byte[] mDebugLastBackupDataRestored; |
| private byte[] mDebugLastSupplicantBackupDataRestored; |
| private byte[] mDebugLastIpConfigBackupDataRestored; |
| |
| public WifiBackupRestore(WifiPermissionsUtil wifiPermissionsUtil) { |
| mWifiPermissionsUtil = wifiPermissionsUtil; |
| } |
| |
| /** |
| * Retrieve the version for serialization. |
| */ |
| private Float getVersion() { |
| WifiBackupDataParser parser = |
| getWifiBackupDataParser(CURRENT_BACKUP_DATA_MAJOR_VERSION); |
| if (parser == null) { |
| Log.e(TAG, "Major version of backup data is unknown to this Android" |
| + " version; not backing up"); |
| return null; |
| } |
| int minorVersion = parser.getHighestSupportedMinorVersion(); |
| Float version; |
| try { |
| version = Float.valueOf( |
| CURRENT_BACKUP_DATA_MAJOR_VERSION + "." + minorVersion); |
| } catch (NumberFormatException e) { |
| Log.e(TAG, "Failed to generate version", e); |
| return null; |
| } |
| return version; |
| } |
| |
| /** |
| * Retrieve an XML byte stream representing the data that needs to be backed up from the |
| * provided configurations. |
| * |
| * @param configurations list of currently saved networks that needs to be backed up. |
| * @return Raw byte stream of XML that needs to be backed up. |
| */ |
| public byte[] retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations) { |
| if (configurations == null) { |
| Log.e(TAG, "Invalid configuration list received"); |
| return new byte[0]; |
| } |
| |
| try { |
| final XmlSerializer out = new FastXmlSerializer(); |
| final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| out.setOutput(outputStream, StandardCharsets.UTF_8.name()); |
| |
| // Start writing the XML stream. |
| XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER); |
| |
| Float version = getVersion(); |
| if (version == null) return null; |
| XmlUtil.writeNextValue(out, XML_TAG_VERSION, version.floatValue()); |
| |
| writeNetworkConfigurationsToXml(out, configurations); |
| |
| XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER); |
| |
| byte[] data = outputStream.toByteArray(); |
| |
| if (mVerboseLoggingEnabled) { |
| mDebugLastBackupDataRetrieved = data; |
| } |
| |
| return data; |
| } catch (XmlPullParserException e) { |
| Log.e(TAG, "Error retrieving the backup data: " + e); |
| } catch (IOException e) { |
| Log.e(TAG, "Error retrieving the backup data: " + e); |
| } |
| return new byte[0]; |
| } |
| |
| /** |
| * Write the list of configurations to the XML stream. |
| */ |
| private void writeNetworkConfigurationsToXml( |
| XmlSerializer out, List<WifiConfiguration> configurations) |
| throws XmlPullParserException, IOException { |
| XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_LIST); |
| for (WifiConfiguration configuration : configurations) { |
| // We don't want to backup/restore enterprise/passpoint configurations. |
| if (configuration.isEnterprise() || configuration.isPasspoint()) { |
| continue; |
| } |
| if (!mWifiPermissionsUtil.checkConfigOverridePermission(configuration.creatorUid)) { |
| Log.d(TAG, "Ignoring network from an app with no config override permission: " |
| + configuration.getKey()); |
| continue; |
| } |
| // Write this configuration data now. |
| XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK); |
| writeNetworkConfigurationToXml(out, configuration); |
| XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK); |
| } |
| XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_LIST); |
| } |
| |
| /** |
| * Write the configuration data elements from the provided Configuration to the XML stream. |
| * Uses XmlUtils to write the values of each element. |
| */ |
| private void writeNetworkConfigurationToXml(XmlSerializer out, WifiConfiguration configuration) |
| throws XmlPullParserException, IOException { |
| XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); |
| WifiConfigurationXmlUtil.writeToXmlForBackup(out, configuration); |
| XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION); |
| XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION); |
| IpConfigurationXmlUtil.writeToXml(out, configuration.getIpConfiguration()); |
| XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION); |
| } |
| |
| /** |
| * Parse out the configurations from the back up data. |
| * |
| * @param data raw byte stream representing the XML data. |
| * @return list of networks retrieved from the backed up data. |
| */ |
| public List<WifiConfiguration> retrieveConfigurationsFromBackupData(byte[] data) { |
| if (data == null || data.length == 0) { |
| Log.e(TAG, "Invalid backup data received"); |
| return null; |
| } |
| try { |
| if (mVerboseLoggingEnabled) { |
| mDebugLastBackupDataRestored = data; |
| } |
| |
| final XmlPullParser in = Xml.newPullParser(); |
| ByteArrayInputStream inputStream = new ByteArrayInputStream(data); |
| in.setInput(inputStream, StandardCharsets.UTF_8.name()); |
| |
| // Start parsing the XML stream. |
| XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER); |
| int rootTagDepth = in.getDepth(); |
| |
| int majorVersion = -1; |
| int minorVersion = -1; |
| try { |
| float version = (float) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION); |
| |
| // parse out major and minor versions |
| String versionStr = new Float(version).toString(); |
| int separatorPos = versionStr.indexOf('.'); |
| if (separatorPos == -1) { |
| majorVersion = Integer.parseInt(versionStr); |
| minorVersion = 0; |
| } else { |
| majorVersion = Integer.parseInt(versionStr.substring(0, separatorPos)); |
| minorVersion = Integer.parseInt(versionStr.substring(separatorPos + 1)); |
| } |
| } catch (ClassCastException cce) { |
| // Integer cannot be cast to Float for data coming from before Android P |
| majorVersion = 1; |
| minorVersion = 0; |
| } |
| Log.d(TAG, "Version of backup data - major: " + majorVersion |
| + "; minor: " + minorVersion); |
| |
| WifiBackupDataParser parser = getWifiBackupDataParser(majorVersion); |
| if (parser == null) { |
| Log.w(TAG, "Major version of backup data is unknown to this Android" |
| + " version; not restoring"); |
| return null; |
| } else { |
| return parser.parseNetworkConfigurationsFromXml(in, rootTagDepth, minorVersion); |
| } |
| } catch (XmlPullParserException | IOException | ClassCastException |
| | IllegalArgumentException e) { |
| Log.e(TAG, "Error parsing the backup data: " + e); |
| } |
| return null; |
| } |
| |
| private WifiBackupDataParser getWifiBackupDataParser(int majorVersion) { |
| switch (majorVersion) { |
| case INITIAL_BACKUP_DATA_VERSION: |
| return new WifiBackupDataV1Parser(); |
| default: |
| Log.e(TAG, "Unrecognized majorVersion of backup data: " + majorVersion); |
| return null; |
| } |
| } |
| |
| /** |
| * Create log dump of the backup data in XML format with the preShared & WEP key masked. |
| * |
| * PSK keys are written in the following format in XML: |
| * <string name="PreSharedKey">WifiBackupRestorePsk</string> |
| * |
| * WEP Keys are written in following format in XML: |
| * <string-array name="WEPKeys" num="4"> |
| * <item value="WifiBackupRestoreWep1" /> |
| * <item value="WifiBackupRestoreWep2" /> |
| * <item value="WifiBackupRestoreWep3" /> |
| * <item value="WifiBackupRestoreWep3" /> |
| * </string-array> |
| */ |
| private String createLogFromBackupData(byte[] data) { |
| StringBuilder sb = new StringBuilder(); |
| try { |
| String xmlString = new String(data, StandardCharsets.UTF_8.name()); |
| boolean wepKeysLine = false; |
| for (String line : xmlString.split("\n")) { |
| if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) { |
| line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN); |
| } |
| if (line.matches(WEP_KEYS_MASK_LINE_START_MATCH_PATTERN)) { |
| wepKeysLine = true; |
| } else if (line.matches(WEP_KEYS_MASK_LINE_END_MATCH_PATTERN)) { |
| wepKeysLine = false; |
| } else if (wepKeysLine) { |
| line = line.replaceAll( |
| WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN); |
| } |
| sb.append(line).append("\n"); |
| } |
| } catch (UnsupportedEncodingException e) { |
| return ""; |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Restore state from the older supplicant back up data. |
| * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file. |
| * |
| * @param supplicantData Raw byte stream of wpa_supplicant.conf |
| * @param ipConfigData Raw byte stream of ipconfig.txt |
| * @return list of networks retrieved from the backed up data. |
| */ |
| public List<WifiConfiguration> retrieveConfigurationsFromSupplicantBackupData( |
| byte[] supplicantData, byte[] ipConfigData) { |
| if (supplicantData == null || supplicantData.length == 0) { |
| Log.e(TAG, "Invalid supplicant backup data received"); |
| return null; |
| } |
| |
| if (mVerboseLoggingEnabled) { |
| mDebugLastSupplicantBackupDataRestored = supplicantData; |
| mDebugLastIpConfigBackupDataRestored = ipConfigData; |
| } |
| |
| SupplicantBackupMigration.SupplicantNetworks supplicantNetworks = |
| new SupplicantBackupMigration.SupplicantNetworks(); |
| // Incorporate the networks present in the backup data. |
| char[] restoredAsChars = new char[supplicantData.length]; |
| for (int i = 0; i < supplicantData.length; i++) { |
| restoredAsChars[i] = (char) supplicantData[i]; |
| } |
| |
| BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsChars)); |
| supplicantNetworks.readNetworksFromStream(in); |
| |
| // Retrieve corresponding WifiConfiguration objects. |
| List<WifiConfiguration> configurations = supplicantNetworks.retrieveWifiConfigurations(); |
| |
| // Now retrieve all the IpConfiguration objects and set in the corresponding |
| // WifiConfiguration objects if ipconfig data is present. |
| if (ipConfigData != null && ipConfigData.length != 0) { |
| SparseArray<IpConfiguration> networks = |
| IpConfigStore.readIpAndProxyConfigurations( |
| new ByteArrayInputStream(ipConfigData)); |
| if (networks != null) { |
| for (int i = 0; i < networks.size(); i++) { |
| int id = networks.keyAt(i); |
| for (WifiConfiguration configuration : configurations) { |
| // This is a dangerous lookup, but that's how it is currently written. |
| if (configuration.getKey().hashCode() == id) { |
| configuration.setIpConfiguration(networks.valueAt(i)); |
| } |
| } |
| } |
| } else { |
| Log.e(TAG, "Failed to parse ipconfig data"); |
| } |
| } else { |
| Log.e(TAG, "Invalid ipconfig backup data received"); |
| } |
| return configurations; |
| } |
| |
| /** |
| * Enable verbose logging. |
| * |
| * @param verbose verbosity level. |
| */ |
| public void enableVerboseLogging(int verbose) { |
| mVerboseLoggingEnabled = (verbose > 0); |
| if (!mVerboseLoggingEnabled) { |
| mDebugLastBackupDataRetrieved = null; |
| mDebugLastBackupDataRestored = null; |
| mDebugLastSupplicantBackupDataRestored = null; |
| mDebugLastIpConfigBackupDataRestored = null; |
| } |
| } |
| |
| /** |
| * Dump out the last backup/restore data if verbose logging is enabled. |
| * |
| * @param fd unused |
| * @param pw PrintWriter for writing dump to |
| * @param args unused |
| */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("Dump of WifiBackupRestore"); |
| if (mDebugLastBackupDataRetrieved != null) { |
| pw.println("Last backup data retrieved: " |
| + createLogFromBackupData(mDebugLastBackupDataRetrieved)); |
| } |
| if (mDebugLastBackupDataRestored != null) { |
| pw.println("Last backup data restored: " |
| + createLogFromBackupData(mDebugLastBackupDataRestored)); |
| } |
| if (mDebugLastSupplicantBackupDataRestored != null) { |
| pw.println("Last old supplicant backup data restored: " |
| + SupplicantBackupMigration.createLogFromBackupData( |
| mDebugLastSupplicantBackupDataRestored)); |
| } |
| if (mDebugLastIpConfigBackupDataRestored != null) { |
| pw.println("Last old ipconfig backup data restored: " |
| + mDebugLastIpConfigBackupDataRestored); |
| } |
| } |
| |
| /** |
| * These sub classes contain the logic to parse older backups and restore wifi state from it. |
| * Most of the code here has been migrated over from BackupSettingsAgent. |
| * This is kind of ugly text parsing, but it is needed to support the migration of this data. |
| */ |
| public static class SupplicantBackupMigration { |
| /** |
| * List of keys to look out for in wpa_supplicant.conf parsing. |
| * These key values are declared in different parts of the wifi codebase today. |
| */ |
| public static final String SUPPLICANT_KEY_SSID = WifiConfiguration.ssidVarName; |
| public static final String SUPPLICANT_KEY_HIDDEN = WifiConfiguration.hiddenSSIDVarName; |
| public static final String SUPPLICANT_KEY_KEY_MGMT = WifiConfiguration.KeyMgmt.varName; |
| public static final String SUPPLICANT_KEY_AUTH_ALG = |
| WifiConfiguration.AuthAlgorithm.varName; |
| public static final String SUPPLICANT_KEY_CLIENT_CERT = |
| WifiEnterpriseConfig.CLIENT_CERT_KEY; |
| public static final String SUPPLICANT_KEY_CA_CERT = WifiEnterpriseConfig.CA_CERT_KEY; |
| public static final String SUPPLICANT_KEY_CA_PATH = WifiEnterpriseConfig.CA_PATH_KEY; |
| public static final String SUPPLICANT_KEY_EAP = WifiEnterpriseConfig.EAP_KEY; |
| public static final String SUPPLICANT_KEY_PSK = WifiConfiguration.pskVarName; |
| public static final String SUPPLICANT_KEY_WEP_KEY0 = WifiConfiguration.wepKeyVarNames[0]; |
| public static final String SUPPLICANT_KEY_WEP_KEY1 = WifiConfiguration.wepKeyVarNames[1]; |
| public static final String SUPPLICANT_KEY_WEP_KEY2 = WifiConfiguration.wepKeyVarNames[2]; |
| public static final String SUPPLICANT_KEY_WEP_KEY3 = WifiConfiguration.wepKeyVarNames[3]; |
| public static final String SUPPLICANT_KEY_WEP_KEY_IDX = |
| WifiConfiguration.wepTxKeyIdxVarName; |
| public static final String SUPPLICANT_KEY_ID_STR = "id_str"; |
| |
| /** |
| * Regex to mask out passwords in backup data dump. |
| */ |
| private static final String PSK_MASK_LINE_MATCH_PATTERN = |
| ".*" + SUPPLICANT_KEY_PSK + ".*=.*"; |
| private static final String PSK_MASK_SEARCH_PATTERN = |
| "(.*" + SUPPLICANT_KEY_PSK + ".*=)(.*)"; |
| private static final String PSK_MASK_REPLACE_PATTERN = "$1*"; |
| |
| private static final String WEP_KEYS_MASK_LINE_MATCH_PATTERN = |
| ".*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=.*"; |
| private static final String WEP_KEYS_MASK_SEARCH_PATTERN = |
| "(.*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=)(.*)"; |
| private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*"; |
| |
| /** |
| * Create log dump of the backup data in wpa_supplicant.conf format with the preShared & |
| * WEP key masked. |
| * |
| * PSK keys are written in the following format in wpa_supplicant.conf: |
| * psk=WifiBackupRestorePsk |
| * |
| * WEP Keys are written in following format in wpa_supplicant.conf: |
| * wep_keys0=WifiBackupRestoreWep0 |
| * wep_keys1=WifiBackupRestoreWep1 |
| * wep_keys2=WifiBackupRestoreWep2 |
| * wep_keys3=WifiBackupRestoreWep3 |
| */ |
| public static String createLogFromBackupData(byte[] data) { |
| StringBuilder sb = new StringBuilder(); |
| try { |
| String supplicantConfString = new String(data, StandardCharsets.UTF_8.name()); |
| for (String line : supplicantConfString.split("\n")) { |
| if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) { |
| line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN); |
| } |
| if (line.matches(WEP_KEYS_MASK_LINE_MATCH_PATTERN)) { |
| line = line.replaceAll( |
| WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN); |
| } |
| sb.append(line).append("\n"); |
| } |
| } catch (UnsupportedEncodingException e) { |
| return ""; |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Class for capturing a network definition from the wifi supplicant config file. |
| */ |
| static class SupplicantNetwork { |
| private String mParsedSSIDLine; |
| private String mParsedHiddenLine; |
| private String mParsedKeyMgmtLine; |
| private String mParsedAuthAlgLine; |
| private String mParsedPskLine; |
| private String[] mParsedWepKeyLines = new String[4]; |
| private String mParsedWepTxKeyIdxLine; |
| private String mParsedIdStrLine; |
| public boolean certUsed = false; |
| public boolean isEap = false; |
| |
| /** |
| * Read lines from wpa_supplicant.conf stream for this network. |
| */ |
| public static SupplicantNetwork readNetworkFromStream(BufferedReader in) { |
| final SupplicantNetwork n = new SupplicantNetwork(); |
| String line; |
| try { |
| while (in.ready()) { |
| line = in.readLine(); |
| if (line == null || line.startsWith("}")) { |
| break; |
| } |
| n.parseLine(line); |
| } |
| } catch (IOException e) { |
| return null; |
| } |
| return n; |
| } |
| |
| /** |
| * Parse a line from wpa_supplicant.conf stream for this network. |
| */ |
| void parseLine(String line) { |
| // Can't rely on particular whitespace patterns so strip leading/trailing. |
| line = line.trim(); |
| if (line.isEmpty()) return; // only whitespace; drop the line. |
| |
| // Now parse the network block within wpa_supplicant.conf and store the important |
| // lines for processing later. |
| if (line.startsWith(SUPPLICANT_KEY_SSID + "=")) { |
| mParsedSSIDLine = line; |
| } else if (line.startsWith(SUPPLICANT_KEY_HIDDEN + "=")) { |
| mParsedHiddenLine = line; |
| } else if (line.startsWith(SUPPLICANT_KEY_KEY_MGMT + "=")) { |
| mParsedKeyMgmtLine = line; |
| if (line.contains("EAP")) { |
| isEap = true; |
| } |
| } else if (line.startsWith(SUPPLICANT_KEY_AUTH_ALG + "=")) { |
| mParsedAuthAlgLine = line; |
| } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT + "=")) { |
| certUsed = true; |
| } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT + "=")) { |
| certUsed = true; |
| } else if (line.startsWith(SUPPLICANT_KEY_CA_PATH + "=")) { |
| certUsed = true; |
| } else if (line.startsWith(SUPPLICANT_KEY_EAP + "=")) { |
| isEap = true; |
| } else if (line.startsWith(SUPPLICANT_KEY_PSK + "=")) { |
| mParsedPskLine = line; |
| } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY0 + "=")) { |
| mParsedWepKeyLines[0] = line; |
| } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY1 + "=")) { |
| mParsedWepKeyLines[1] = line; |
| } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY2 + "=")) { |
| mParsedWepKeyLines[2] = line; |
| } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY3 + "=")) { |
| mParsedWepKeyLines[3] = line; |
| } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY_IDX + "=")) { |
| mParsedWepTxKeyIdxLine = line; |
| } else if (line.startsWith(SUPPLICANT_KEY_ID_STR + "=")) { |
| mParsedIdStrLine = line; |
| } |
| } |
| |
| /** |
| * Create WifiConfiguration object from the parsed data for this network. |
| */ |
| public WifiConfiguration createWifiConfiguration() { |
| if (mParsedSSIDLine == null) { |
| // No SSID => malformed network definition |
| return null; |
| } |
| WifiConfiguration configuration = new WifiConfiguration(); |
| configuration.SSID = mParsedSSIDLine.substring(mParsedSSIDLine.indexOf('=') + 1); |
| |
| if (mParsedHiddenLine != null) { |
| // Can't use Boolean.valueOf() because it works only for true/false. |
| configuration.hiddenSSID = |
| Integer.parseInt(mParsedHiddenLine.substring( |
| mParsedHiddenLine.indexOf('=') + 1)) != 0; |
| } |
| if (mParsedKeyMgmtLine == null) { |
| // no key_mgmt line specified; this is defined as equivalent to |
| // "WPA-PSK WPA-EAP". |
| configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); |
| configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP); |
| } else { |
| // Need to parse the mParsedKeyMgmtLine line |
| final String bareKeyMgmt = |
| mParsedKeyMgmtLine.substring(mParsedKeyMgmtLine.indexOf('=') + 1); |
| String[] typeStrings = bareKeyMgmt.split("\\s+"); |
| |
| // Parse out all the key management regimes permitted for this network. |
| // The literal strings here are the standard values permitted in |
| // wpa_supplicant.conf. |
| for (int i = 0; i < typeStrings.length; i++) { |
| final String ktype = typeStrings[i]; |
| if (ktype.equals("NONE")) { |
| configuration.allowedKeyManagement.set( |
| WifiConfiguration.KeyMgmt.NONE); |
| } else if (ktype.equals("WPA-PSK")) { |
| configuration.allowedKeyManagement.set( |
| WifiConfiguration.KeyMgmt.WPA_PSK); |
| } else if (ktype.equals("WPA-EAP")) { |
| configuration.allowedKeyManagement.set( |
| WifiConfiguration.KeyMgmt.WPA_EAP); |
| } else if (ktype.equals("IEEE8021X")) { |
| configuration.allowedKeyManagement.set( |
| WifiConfiguration.KeyMgmt.IEEE8021X); |
| } else if (ktype.equals("WAPI-PSK")) { |
| configuration.allowedKeyManagement.set( |
| WifiConfiguration.KeyMgmt.WAPI_PSK); |
| } else if (ktype.equals("WAPI-CERT")) { |
| configuration.allowedKeyManagement.set( |
| WifiConfiguration.KeyMgmt.WAPI_CERT); |
| } |
| } |
| } |
| if (mParsedAuthAlgLine != null) { |
| if (mParsedAuthAlgLine.contains("OPEN")) { |
| configuration.allowedAuthAlgorithms.set( |
| WifiConfiguration.AuthAlgorithm.OPEN); |
| } |
| if (mParsedAuthAlgLine.contains("SHARED")) { |
| configuration.allowedAuthAlgorithms.set( |
| WifiConfiguration.AuthAlgorithm.SHARED); |
| } |
| } |
| if (mParsedPskLine != null) { |
| configuration.preSharedKey = |
| mParsedPskLine.substring(mParsedPskLine.indexOf('=') + 1); |
| } |
| if (mParsedWepKeyLines[0] != null) { |
| configuration.wepKeys[0] = |
| mParsedWepKeyLines[0].substring(mParsedWepKeyLines[0].indexOf('=') + 1); |
| } |
| if (mParsedWepKeyLines[1] != null) { |
| configuration.wepKeys[1] = |
| mParsedWepKeyLines[1].substring(mParsedWepKeyLines[1].indexOf('=') + 1); |
| } |
| if (mParsedWepKeyLines[2] != null) { |
| configuration.wepKeys[2] = |
| mParsedWepKeyLines[2].substring(mParsedWepKeyLines[2].indexOf('=') + 1); |
| } |
| if (mParsedWepKeyLines[3] != null) { |
| configuration.wepKeys[3] = |
| mParsedWepKeyLines[3].substring(mParsedWepKeyLines[3].indexOf('=') + 1); |
| } |
| if (mParsedWepTxKeyIdxLine != null) { |
| configuration.wepTxKeyIndex = |
| Integer.valueOf(mParsedWepTxKeyIdxLine.substring( |
| mParsedWepTxKeyIdxLine.indexOf('=') + 1)); |
| } |
| if (mParsedIdStrLine != null) { |
| String idString = |
| mParsedIdStrLine.substring(mParsedIdStrLine.indexOf('=') + 1); |
| if (idString != null) { |
| Map<String, String> extras = |
| SupplicantStaNetworkHal.parseNetworkExtra( |
| NativeUtil.removeEnclosingQuotes(idString)); |
| if (extras == null) { |
| Log.e(TAG, "Error parsing network extras, ignoring network."); |
| return null; |
| } |
| String configKey = extras.get( |
| SupplicantStaNetworkHal.ID_STRING_KEY_CONFIG_KEY); |
| // No ConfigKey was passed but we need it for validating the parsed |
| // network so we stop the restore. |
| if (configKey == null) { |
| Log.e(TAG, "Configuration key was not passed, ignoring network."); |
| return null; |
| } |
| if (!configKey.equals(configuration.getKey())) { |
| // ConfigKey mismatches are expected for private networks because the |
| // UID is not preserved across backup/restore. |
| Log.w(TAG, "Configuration key does not match. Retrieved: " + configKey |
| + ", Calculated: " + configuration.getKey()); |
| } |
| // For wpa_supplicant backup data, parse out the creatorUid to ensure that |
| // these networks were created by system apps. |
| int creatorUid = |
| Integer.parseInt(extras.get( |
| SupplicantStaNetworkHal.ID_STRING_KEY_CREATOR_UID)); |
| if (creatorUid >= Process.FIRST_APPLICATION_UID) { |
| Log.d(TAG, "Ignoring network from non-system app: " |
| + configuration.getKey()); |
| return null; |
| } |
| } |
| } |
| return configuration; |
| } |
| } |
| |
| /** |
| * Ingest multiple wifi config fragments from wpa_supplicant.conf, looking for network={} |
| * blocks and eliminating duplicates |
| */ |
| static class SupplicantNetworks { |
| final ArrayList<SupplicantNetwork> mNetworks = new ArrayList<>(8); |
| |
| /** |
| * Parse the wpa_supplicant.conf file stream and add networks. |
| */ |
| public void readNetworksFromStream(BufferedReader in) { |
| try { |
| String line; |
| while (in.ready()) { |
| line = in.readLine(); |
| if (line != null) { |
| if (line.startsWith("network")) { |
| SupplicantNetwork net = SupplicantNetwork.readNetworkFromStream(in); |
| |
| // An IOException occurred while trying to read the network. |
| if (net == null) { |
| Log.e(TAG, "Error while parsing the network."); |
| continue; |
| } |
| |
| // Networks that use certificates for authentication can't be |
| // restored because the certificates they need don't get restored |
| // (because they are stored in keystore, and can't be restored). |
| // Similarly, omit EAP network definitions to avoid propagating |
| // controlled enterprise network definitions. |
| if (net.isEap || net.certUsed) { |
| Log.d(TAG, "Skipping enterprise network for restore: " |
| + net.mParsedSSIDLine + " / " + net.mParsedKeyMgmtLine); |
| continue; |
| } |
| mNetworks.add(net); |
| } |
| } |
| } |
| } catch (IOException e) { |
| // whatever happened, we're done now |
| } |
| } |
| |
| /** |
| * Retrieve a list of WifiConfiguration objects parsed from wpa_supplicant.conf |
| */ |
| public List<WifiConfiguration> retrieveWifiConfigurations() { |
| ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>(); |
| for (SupplicantNetwork net : mNetworks) { |
| try { |
| WifiConfiguration wifiConfiguration = net.createWifiConfiguration(); |
| if (wifiConfiguration != null) { |
| Log.v(TAG, "Parsed Configuration: " + wifiConfiguration.getKey()); |
| wifiConfigurations.add(wifiConfiguration); |
| } |
| } catch (NumberFormatException e) { |
| // Occurs if we are unable to parse the hidden SSID, WEP Key index or |
| // creator UID. |
| Log.e(TAG, "Error parsing wifi configuration: " + e); |
| return null; |
| } |
| } |
| return wifiConfigurations; |
| } |
| } |
| } |
| } |