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