1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.wifi; 18 19import android.net.IpConfiguration; 20import android.net.wifi.WifiConfiguration; 21import android.net.wifi.WifiEnterpriseConfig; 22import android.os.Environment; 23import android.util.Log; 24import android.util.SparseArray; 25 26import com.android.server.net.IpConfigStore; 27import com.android.server.wifi.hotspot2.LegacyPasspointConfig; 28import com.android.server.wifi.hotspot2.LegacyPasspointConfigParser; 29 30import java.io.File; 31import java.io.IOException; 32import java.util.ArrayList; 33import java.util.Arrays; 34import java.util.HashMap; 35import java.util.HashSet; 36import java.util.List; 37import java.util.Map; 38import java.util.Set; 39 40/** 41 * This class provides the API's to load network configurations from legacy store 42 * mechanism (Pre O release). 43 * This class loads network configurations from: 44 * 1. /data/misc/wifi/networkHistory.txt 45 * 2. /data/misc/wifi/wpa_supplicant.conf 46 * 3. /data/misc/wifi/ipconfig.txt 47 * 4. /data/misc/wifi/PerProviderSubscription.conf 48 * 49 * The order of invocation of the public methods during migration is the following: 50 * 1. Check if legacy stores are present using {@link #areStoresPresent()}. 51 * 2. Load all the store data using {@link #read()} 52 * 3. Write the store data to the new store. 53 * 4. Remove all the legacy stores using {@link #removeStores()} 54 * 55 * NOTE: This class should only be used from WifiConfigManager and is not thread-safe! 56 * 57 * TODO(b/31065385): Passpoint config store data migration & deletion. 58 */ 59public class WifiConfigStoreLegacy { 60 /** 61 * Log tag. 62 */ 63 private static final String TAG = "WifiConfigStoreLegacy"; 64 /** 65 * NetworkHistory config store file path. 66 */ 67 private static final File NETWORK_HISTORY_FILE = 68 new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE); 69 /** 70 * Passpoint config store file path. 71 */ 72 private static final File PPS_FILE = 73 new File(Environment.getDataMiscDirectory(), "wifi/PerProviderSubscription.conf"); 74 /** 75 * IpConfig config store file path. 76 */ 77 private static final File IP_CONFIG_FILE = 78 new File(Environment.getDataMiscDirectory(), "wifi/ipconfig.txt"); 79 /** 80 * List of external dependencies for WifiConfigManager. 81 */ 82 private final WifiNetworkHistory mWifiNetworkHistory; 83 private final WifiNative mWifiNative; 84 private final IpConfigStoreWrapper mIpconfigStoreWrapper; 85 86 private final LegacyPasspointConfigParser mPasspointConfigParser; 87 88 /** 89 * Used to help mocking the static methods of IpconfigStore. 90 */ 91 public static class IpConfigStoreWrapper { 92 /** 93 * Read IP configurations from Ip config store. 94 */ 95 public SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) { 96 return IpConfigStore.readIpAndProxyConfigurations(filePath); 97 } 98 } 99 100 WifiConfigStoreLegacy(WifiNetworkHistory wifiNetworkHistory, 101 WifiNative wifiNative, IpConfigStoreWrapper ipConfigStore, 102 LegacyPasspointConfigParser passpointConfigParser) { 103 mWifiNetworkHistory = wifiNetworkHistory; 104 mWifiNative = wifiNative; 105 mIpconfigStoreWrapper = ipConfigStore; 106 mPasspointConfigParser = passpointConfigParser; 107 } 108 109 /** 110 * Helper function to lookup the WifiConfiguration object from configKey to WifiConfiguration 111 * object map using the hashcode of the configKey. 112 * 113 * @param configurationMap Map of configKey to WifiConfiguration object. 114 * @param hashCode hash code of the configKey to match. 115 * @return 116 */ 117 private static WifiConfiguration lookupWifiConfigurationUsingConfigKeyHash( 118 Map<String, WifiConfiguration> configurationMap, int hashCode) { 119 for (Map.Entry<String, WifiConfiguration> entry : configurationMap.entrySet()) { 120 if (entry.getKey() != null && entry.getKey().hashCode() == hashCode) { 121 return entry.getValue(); 122 } 123 } 124 return null; 125 } 126 127 /** 128 * Helper function to load {@link IpConfiguration} data from the ip config store file and 129 * populate the provided configuration map. 130 * 131 * @param configurationMap Map of configKey to WifiConfiguration object. 132 */ 133 private void loadFromIpConfigStore(Map<String, WifiConfiguration> configurationMap) { 134 // This is a map of the hash code of the network's configKey to the corresponding 135 // IpConfiguration. 136 SparseArray<IpConfiguration> ipConfigurations = 137 mIpconfigStoreWrapper.readIpAndProxyConfigurations( 138 IP_CONFIG_FILE.getAbsolutePath()); 139 if (ipConfigurations == null || ipConfigurations.size() == 0) { 140 Log.w(TAG, "No ip configurations found in ipconfig store"); 141 return; 142 } 143 for (int i = 0; i < ipConfigurations.size(); i++) { 144 int id = ipConfigurations.keyAt(i); 145 WifiConfiguration config = 146 lookupWifiConfigurationUsingConfigKeyHash(configurationMap, id); 147 // This is the only place the map is looked up through a (dangerous) hash-value! 148 if (config == null || config.ephemeral) { 149 Log.w(TAG, "configuration found for missing network, nid=" + id 150 + ", ignored, networks.size=" + Integer.toString(ipConfigurations.size())); 151 } else { 152 config.setIpConfiguration(ipConfigurations.valueAt(i)); 153 } 154 } 155 } 156 157 /** 158 * Helper function to load {@link WifiConfiguration} data from networkHistory file and populate 159 * the provided configuration map and deleted ephemeral ssid list. 160 * 161 * @param configurationMap Map of configKey to WifiConfiguration object. 162 * @param deletedEphemeralSSIDs Map of configKey to WifiConfiguration object. 163 */ 164 private void loadFromNetworkHistory( 165 Map<String, WifiConfiguration> configurationMap, Set<String> deletedEphemeralSSIDs) { 166 // TODO: Need to revisit the scan detail cache persistance. We're not doing it in the new 167 // config store, so ignore it here as well. 168 Map<Integer, ScanDetailCache> scanDetailCaches = new HashMap<>(); 169 mWifiNetworkHistory.readNetworkHistory( 170 configurationMap, scanDetailCaches, deletedEphemeralSSIDs); 171 } 172 173 /** 174 * Helper function to load {@link WifiConfiguration} data from wpa_supplicant and populate 175 * the provided configuration map and network extras. 176 * 177 * This method needs to manually parse the wpa_supplicant.conf file to retrieve some of the 178 * password fields like psk, wep_keys. password, etc. 179 * 180 * @param configurationMap Map of configKey to WifiConfiguration object. 181 * @param networkExtras Map of network extras parsed from wpa_supplicant. 182 */ 183 private void loadFromWpaSupplicant( 184 Map<String, WifiConfiguration> configurationMap, 185 SparseArray<Map<String, String>> networkExtras) { 186 if (!mWifiNative.migrateNetworksFromSupplicant(mWifiNative.getClientInterfaceName(), 187 configurationMap, networkExtras)) { 188 Log.wtf(TAG, "Failed to load wifi configurations from wpa_supplicant"); 189 return; 190 } 191 if (configurationMap.isEmpty()) { 192 Log.w(TAG, "No wifi configurations found in wpa_supplicant"); 193 return; 194 } 195 } 196 197 /** 198 * Helper function to update {@link WifiConfiguration} that represents a Passpoint 199 * configuration. 200 * 201 * This method will manually parse PerProviderSubscription.conf file to retrieve missing 202 * fields: provider friendly name, roaming consortium OIs, realm, IMSI. 203 * 204 * @param configurationMap Map of configKey to WifiConfiguration object. 205 * @param networkExtras Map of network extras parsed from wpa_supplicant. 206 */ 207 private void loadFromPasspointConfigStore( 208 Map<String, WifiConfiguration> configurationMap, 209 SparseArray<Map<String, String>> networkExtras) { 210 Map<String, LegacyPasspointConfig> passpointConfigMap = null; 211 try { 212 passpointConfigMap = mPasspointConfigParser.parseConfig(PPS_FILE.getAbsolutePath()); 213 } catch (IOException e) { 214 Log.w(TAG, "Failed to read/parse Passpoint config file: " + e.getMessage()); 215 } 216 217 List<String> entriesToBeRemoved = new ArrayList<>(); 218 for (Map.Entry<String, WifiConfiguration> entry : configurationMap.entrySet()) { 219 WifiConfiguration wifiConfig = entry.getValue(); 220 // Ignore non-Enterprise network since enterprise configuration is required for 221 // Passpoint. 222 if (wifiConfig.enterpriseConfig == null || wifiConfig.enterpriseConfig.getEapMethod() 223 == WifiEnterpriseConfig.Eap.NONE) { 224 continue; 225 } 226 // Ignore configuration without FQDN. 227 Map<String, String> extras = networkExtras.get(wifiConfig.networkId); 228 if (extras == null || !extras.containsKey(SupplicantStaNetworkHal.ID_STRING_KEY_FQDN)) { 229 continue; 230 } 231 String fqdn = networkExtras.get(wifiConfig.networkId).get( 232 SupplicantStaNetworkHal.ID_STRING_KEY_FQDN); 233 234 // Remove the configuration if failed to find the matching configuration in the 235 // Passpoint configuration file. 236 if (passpointConfigMap == null || !passpointConfigMap.containsKey(fqdn)) { 237 entriesToBeRemoved.add(entry.getKey()); 238 continue; 239 } 240 241 // Update the missing Passpoint configuration fields to this WifiConfiguration. 242 LegacyPasspointConfig passpointConfig = passpointConfigMap.get(fqdn); 243 wifiConfig.isLegacyPasspointConfig = true; 244 wifiConfig.FQDN = fqdn; 245 wifiConfig.providerFriendlyName = passpointConfig.mFriendlyName; 246 if (passpointConfig.mRoamingConsortiumOis != null) { 247 wifiConfig.roamingConsortiumIds = Arrays.copyOf( 248 passpointConfig.mRoamingConsortiumOis, 249 passpointConfig.mRoamingConsortiumOis.length); 250 } 251 if (passpointConfig.mImsi != null) { 252 wifiConfig.enterpriseConfig.setPlmn(passpointConfig.mImsi); 253 } 254 if (passpointConfig.mRealm != null) { 255 wifiConfig.enterpriseConfig.setRealm(passpointConfig.mRealm); 256 } 257 } 258 259 // Remove any incomplete Passpoint configurations. Should never happen, in case it does 260 // remove them to avoid maintaining any invalid Passpoint configurations. 261 for (String key : entriesToBeRemoved) { 262 Log.w(TAG, "Remove incomplete Passpoint configuration: " + key); 263 configurationMap.remove(key); 264 } 265 } 266 267 /** 268 * Helper function to load from the different legacy stores: 269 * 1. Read the network configurations from wpa_supplicant using {@link WifiNative}. 270 * 2. Read the network configurations from networkHistory.txt using {@link WifiNetworkHistory}. 271 * 3. Read the Ip configurations from ipconfig.txt using {@link IpConfigStore}. 272 * 4. Read all the passpoint info from PerProviderSubscription.conf using 273 * {@link LegacyPasspointConfigParser}. 274 */ 275 public WifiConfigStoreDataLegacy read() { 276 final Map<String, WifiConfiguration> configurationMap = new HashMap<>(); 277 final SparseArray<Map<String, String>> networkExtras = new SparseArray<>(); 278 final Set<String> deletedEphemeralSSIDs = new HashSet<>(); 279 280 loadFromWpaSupplicant(configurationMap, networkExtras); 281 loadFromNetworkHistory(configurationMap, deletedEphemeralSSIDs); 282 loadFromIpConfigStore(configurationMap); 283 loadFromPasspointConfigStore(configurationMap, networkExtras); 284 285 // Now create config store data instance to be returned. 286 return new WifiConfigStoreDataLegacy( 287 new ArrayList<>(configurationMap.values()), deletedEphemeralSSIDs); 288 } 289 290 /** 291 * Function to check if the legacy store files are present and hence load from those stores and 292 * then delete them. 293 * 294 * @return true if legacy store files are present, false otherwise. 295 */ 296 public boolean areStoresPresent() { 297 // We may have to keep the wpa_supplicant.conf file around. So, just use networkhistory.txt 298 // as a check to see if we have not yet migrated or not. This should be the last file 299 // that is deleted after migration. 300 File file = new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE); 301 return file.exists(); 302 } 303 304 /** 305 * Method to remove all the legacy store files. This should only be invoked once all 306 * the data has been migrated to the new store file. 307 * 1. Removes all networks from wpa_supplicant and saves it to wpa_supplicant.conf 308 * 2. Deletes ipconfig.txt 309 * 3. Deletes networkHistory.txt 310 * 311 * @return true if all the store files were deleted successfully, false otherwise. 312 */ 313 public boolean removeStores() { 314 // TODO(b/29352330): Delete wpa_supplicant.conf file instead. 315 // First remove all networks from wpa_supplicant and save configuration. 316 if (!mWifiNative.removeAllNetworks(mWifiNative.getClientInterfaceName())) { 317 Log.e(TAG, "Removing networks from wpa_supplicant failed"); 318 } 319 320 // Now remove the ipconfig.txt file. 321 if (!IP_CONFIG_FILE.delete()) { 322 Log.e(TAG, "Removing ipconfig.txt failed"); 323 } 324 325 // Now finally remove network history.txt 326 if (!NETWORK_HISTORY_FILE.delete()) { 327 Log.e(TAG, "Removing networkHistory.txt failed"); 328 } 329 330 if (!PPS_FILE.delete()) { 331 Log.e(TAG, "Removing PerProviderSubscription.conf failed"); 332 } 333 334 Log.i(TAG, "All legacy stores removed!"); 335 return true; 336 } 337 338 /** 339 * Interface used to set a masked value in the provided configuration. The masked value is 340 * retrieved by parsing the wpa_supplicant.conf file. 341 */ 342 private interface MaskedWpaSupplicantFieldSetter { 343 void setValue(WifiConfiguration config, String value); 344 } 345 346 /** 347 * Class used to encapsulate all the store data retrieved from the legacy (Pre O) store files. 348 */ 349 public static class WifiConfigStoreDataLegacy { 350 private List<WifiConfiguration> mConfigurations; 351 private Set<String> mDeletedEphemeralSSIDs; 352 // private List<HomeSP> mHomeSps; 353 354 WifiConfigStoreDataLegacy(List<WifiConfiguration> configurations, 355 Set<String> deletedEphemeralSSIDs) { 356 mConfigurations = configurations; 357 mDeletedEphemeralSSIDs = deletedEphemeralSSIDs; 358 } 359 360 public List<WifiConfiguration> getConfigurations() { 361 return mConfigurations; 362 } 363 364 public Set<String> getDeletedEphemeralSSIDs() { 365 return mDeletedEphemeralSSIDs; 366 } 367 } 368} 369