/* * 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.Environment; import android.util.Log; import android.util.SparseArray; import com.android.server.net.IpConfigStore; import com.android.server.wifi.hotspot2.LegacyPasspointConfig; import com.android.server.wifi.hotspot2.LegacyPasspointConfigParser; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * This class provides the API's to load network configurations from legacy store * mechanism (Pre O release). * This class loads network configurations from: * 1. /data/misc/wifi/networkHistory.txt * 2. /data/misc/wifi/wpa_supplicant.conf * 3. /data/misc/wifi/ipconfig.txt * 4. /data/misc/wifi/PerProviderSubscription.conf * * The order of invocation of the public methods during migration is the following: * 1. Check if legacy stores are present using {@link #areStoresPresent()}. * 2. Load all the store data using {@link #read()} * 3. Write the store data to the new store. * 4. Remove all the legacy stores using {@link #removeStores()} * * NOTE: This class should only be used from WifiConfigManager and is not thread-safe! * * TODO(b/31065385): Passpoint config store data migration & deletion. */ public class WifiConfigStoreLegacy { /** * Log tag. */ private static final String TAG = "WifiConfigStoreLegacy"; /** * NetworkHistory config store file path. */ private static final File NETWORK_HISTORY_FILE = new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE); /** * Passpoint config store file path. */ private static final File PPS_FILE = new File(Environment.getDataMiscDirectory(), "wifi/PerProviderSubscription.conf"); /** * IpConfig config store file path. */ private static final File IP_CONFIG_FILE = new File(Environment.getDataMiscDirectory(), "wifi/ipconfig.txt"); /** * List of external dependencies for WifiConfigManager. */ private final WifiNetworkHistory mWifiNetworkHistory; private final WifiNative mWifiNative; private final IpConfigStore mIpconfigStore; private final LegacyPasspointConfigParser mPasspointConfigParser; WifiConfigStoreLegacy(WifiNetworkHistory wifiNetworkHistory, WifiNative wifiNative, IpConfigStore ipConfigStore, LegacyPasspointConfigParser passpointConfigParser) { mWifiNetworkHistory = wifiNetworkHistory; mWifiNative = wifiNative; mIpconfigStore = ipConfigStore; mPasspointConfigParser = passpointConfigParser; } /** * Helper function to lookup the WifiConfiguration object from configKey to WifiConfiguration * object map using the hashcode of the configKey. * * @param configurationMap Map of configKey to WifiConfiguration object. * @param hashCode hash code of the configKey to match. * @return */ private static WifiConfiguration lookupWifiConfigurationUsingConfigKeyHash( Map configurationMap, int hashCode) { for (Map.Entry entry : configurationMap.entrySet()) { if (entry.getKey().hashCode() == hashCode) { return entry.getValue(); } } return null; } /** * Helper function to load {@link IpConfiguration} data from the ip config store file and * populate the provided configuration map. * * @param configurationMap Map of configKey to WifiConfiguration object. */ private void loadFromIpConfigStore(Map configurationMap) { // This is a map of the hash code of the network's configKey to the corresponding // IpConfiguration. SparseArray ipConfigurations = mIpconfigStore.readIpAndProxyConfigurations(IP_CONFIG_FILE.getAbsolutePath()); if (ipConfigurations == null || ipConfigurations.size() == 0) { Log.w(TAG, "No ip configurations found in ipconfig store"); return; } for (int i = 0; i < ipConfigurations.size(); i++) { int id = ipConfigurations.keyAt(i); WifiConfiguration config = lookupWifiConfigurationUsingConfigKeyHash(configurationMap, id); // This is the only place the map is looked up through a (dangerous) hash-value! if (config == null || config.ephemeral) { Log.w(TAG, "configuration found for missing network, nid=" + id + ", ignored, networks.size=" + Integer.toString(ipConfigurations.size())); } else { config.setIpConfiguration(ipConfigurations.valueAt(i)); } } } /** * Helper function to load {@link WifiConfiguration} data from networkHistory file and populate * the provided configuration map and deleted ephemeral ssid list. * * @param configurationMap Map of configKey to WifiConfiguration object. * @param deletedEphemeralSSIDs Map of configKey to WifiConfiguration object. */ private void loadFromNetworkHistory( Map configurationMap, Set deletedEphemeralSSIDs) { // TODO: Need to revisit the scan detail cache persistance. We're not doing it in the new // config store, so ignore it here as well. Map scanDetailCaches = new HashMap<>(); mWifiNetworkHistory.readNetworkHistory( configurationMap, scanDetailCaches, deletedEphemeralSSIDs); } /** * Helper function to load {@link WifiConfiguration} data from wpa_supplicant and populate * the provided configuration map and network extras. * * This method needs to manually parse the wpa_supplicant.conf file to retrieve some of the * password fields like psk, wep_keys. password, etc. * * @param configurationMap Map of configKey to WifiConfiguration object. * @param networkExtras Map of network extras parsed from wpa_supplicant. */ private void loadFromWpaSupplicant( Map configurationMap, SparseArray> networkExtras) { if (!mWifiNative.migrateNetworksFromSupplicant(configurationMap, networkExtras)) { Log.wtf(TAG, "Failed to load wifi configurations from wpa_supplicant"); return; } if (configurationMap.isEmpty()) { Log.w(TAG, "No wifi configurations found in wpa_supplicant"); return; } } /** * Helper function to update {@link WifiConfiguration} that represents a Passpoint * configuration. * * This method will manually parse PerProviderSubscription.conf file to retrieve missing * fields: provider friendly name, roaming consortium OIs, realm, IMSI. * * @param configurationMap Map of configKey to WifiConfiguration object. * @param networkExtras Map of network extras parsed from wpa_supplicant. */ private void loadFromPasspointConfigStore( Map configurationMap, SparseArray> networkExtras) { Map passpointConfigMap = null; try { passpointConfigMap = mPasspointConfigParser.parseConfig(PPS_FILE.getAbsolutePath()); } catch (IOException e) { Log.w(TAG, "Failed to read/parse Passpoint config file: " + e.getMessage()); } List entriesToBeRemoved = new ArrayList<>(); for (Map.Entry entry : configurationMap.entrySet()) { WifiConfiguration wifiConfig = entry.getValue(); // Ignore non-Enterprise network since enterprise configuration is required for // Passpoint. if (wifiConfig.enterpriseConfig == null || wifiConfig.enterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.NONE) { continue; } // Ignore configuration without FQDN. Map extras = networkExtras.get(wifiConfig.networkId); if (extras == null || !extras.containsKey(SupplicantStaNetworkHal.ID_STRING_KEY_FQDN)) { continue; } String fqdn = networkExtras.get(wifiConfig.networkId).get( SupplicantStaNetworkHal.ID_STRING_KEY_FQDN); // Remove the configuration if failed to find the matching configuration in the // Passpoint configuration file. if (passpointConfigMap == null || !passpointConfigMap.containsKey(fqdn)) { entriesToBeRemoved.add(entry.getKey()); continue; } // Update the missing Passpoint configuration fields to this WifiConfiguration. LegacyPasspointConfig passpointConfig = passpointConfigMap.get(fqdn); wifiConfig.isLegacyPasspointConfig = true; wifiConfig.FQDN = fqdn; wifiConfig.providerFriendlyName = passpointConfig.mFriendlyName; if (passpointConfig.mRoamingConsortiumOis != null) { wifiConfig.roamingConsortiumIds = Arrays.copyOf( passpointConfig.mRoamingConsortiumOis, passpointConfig.mRoamingConsortiumOis.length); } if (passpointConfig.mImsi != null) { wifiConfig.enterpriseConfig.setPlmn(passpointConfig.mImsi); } if (passpointConfig.mRealm != null) { wifiConfig.enterpriseConfig.setRealm(passpointConfig.mRealm); } } // Remove any incomplete Passpoint configurations. Should never happen, in case it does // remove them to avoid maintaining any invalid Passpoint configurations. for (String key : entriesToBeRemoved) { Log.w(TAG, "Remove incomplete Passpoint configuration: " + key); configurationMap.remove(key); } } /** * Helper function to load from the different legacy stores: * 1. Read the network configurations from wpa_supplicant using {@link WifiNative}. * 2. Read the network configurations from networkHistory.txt using {@link WifiNetworkHistory}. * 3. Read the Ip configurations from ipconfig.txt using {@link IpConfigStore}. * 4. Read all the passpoint info from PerProviderSubscription.conf using * {@link LegacyPasspointConfigParser}. */ public WifiConfigStoreDataLegacy read() { final Map configurationMap = new HashMap<>(); final SparseArray> networkExtras = new SparseArray<>(); final Set deletedEphemeralSSIDs = new HashSet<>(); loadFromWpaSupplicant(configurationMap, networkExtras); loadFromNetworkHistory(configurationMap, deletedEphemeralSSIDs); loadFromIpConfigStore(configurationMap); loadFromPasspointConfigStore(configurationMap, networkExtras); // Now create config store data instance to be returned. return new WifiConfigStoreDataLegacy( new ArrayList<>(configurationMap.values()), deletedEphemeralSSIDs); } /** * Function to check if the legacy store files are present and hence load from those stores and * then delete them. * * @return true if legacy store files are present, false otherwise. */ public boolean areStoresPresent() { // We may have to keep the wpa_supplicant.conf file around. So, just use networkhistory.txt // as a check to see if we have not yet migrated or not. This should be the last file // that is deleted after migration. File file = new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE); return file.exists(); } /** * Method to remove all the legacy store files. This should only be invoked once all * the data has been migrated to the new store file. * 1. Removes all networks from wpa_supplicant and saves it to wpa_supplicant.conf * 2. Deletes ipconfig.txt * 3. Deletes networkHistory.txt * * @return true if all the store files were deleted successfully, false otherwise. */ public boolean removeStores() { // TODO(b/29352330): Delete wpa_supplicant.conf file instead. // First remove all networks from wpa_supplicant and save configuration. if (!mWifiNative.removeAllNetworks()) { Log.e(TAG, "Removing networks from wpa_supplicant failed"); return false; } // Now remove the ipconfig.txt file. if (!IP_CONFIG_FILE.delete()) { Log.e(TAG, "Removing ipconfig.txt failed"); return false; } // Now finally remove network history.txt if (!NETWORK_HISTORY_FILE.delete()) { Log.e(TAG, "Removing networkHistory.txt failed"); return false; } if (!PPS_FILE.delete()) { Log.e(TAG, "Removing PerProviderSubscription.conf failed"); return false; } Log.i(TAG, "All legacy stores removed!"); return true; } /** * Interface used to set a masked value in the provided configuration. The masked value is * retrieved by parsing the wpa_supplicant.conf file. */ private interface MaskedWpaSupplicantFieldSetter { void setValue(WifiConfiguration config, String value); } /** * Class used to encapsulate all the store data retrieved from the legacy (Pre O) store files. */ public static class WifiConfigStoreDataLegacy { private List mConfigurations; private Set mDeletedEphemeralSSIDs; // private List mHomeSps; WifiConfigStoreDataLegacy(List configurations, Set deletedEphemeralSSIDs) { mConfigurations = configurations; mDeletedEphemeralSSIDs = deletedEphemeralSSIDs; } public List getConfigurations() { return mConfigurations; } public Set getDeletedEphemeralSSIDs() { return mDeletedEphemeralSSIDs; } } }