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