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 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