Home | History | Annotate | Download | only in wifi
      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