summaryrefslogtreecommitdiff
path: root/com/android/server/wifi/WifiConfigStoreLegacy.java
blob: 184ee2f677cf5edaf6a125156969e57efe170dd6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.wifi;

import android.net.IpConfiguration;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiEnterpriseConfig;
import android.os.Environment;
import android.util.Log;
import android.util.SparseArray;

import com.android.server.net.IpConfigStore;
import com.android.server.wifi.hotspot2.LegacyPasspointConfig;
import com.android.server.wifi.hotspot2.LegacyPasspointConfigParser;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * This class provides the API's to load network configurations from legacy store
 * mechanism (Pre O release).
 * This class loads network configurations from:
 * 1. /data/misc/wifi/networkHistory.txt
 * 2. /data/misc/wifi/wpa_supplicant.conf
 * 3. /data/misc/wifi/ipconfig.txt
 * 4. /data/misc/wifi/PerProviderSubscription.conf
 *
 * The order of invocation of the public methods during migration is the following:
 * 1. Check if legacy stores are present using {@link #areStoresPresent()}.
 * 2. Load all the store data using {@link #read()}
 * 3. Write the store data to the new store.
 * 4. Remove all the legacy stores using {@link #removeStores()}
 *
 * NOTE: This class should only be used from WifiConfigManager and is not thread-safe!
 *
 * TODO(b/31065385): Passpoint config store data migration & deletion.
 */
public class WifiConfigStoreLegacy {
    /**
     * Log tag.
     */
    private static final String TAG = "WifiConfigStoreLegacy";
    /**
     * NetworkHistory config store file path.
     */
    private static final File NETWORK_HISTORY_FILE =
            new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE);
    /**
     * Passpoint config store file path.
     */
    private static final File PPS_FILE =
            new File(Environment.getDataMiscDirectory(), "wifi/PerProviderSubscription.conf");
    /**
     * IpConfig config store file path.
     */
    private static final File IP_CONFIG_FILE =
            new File(Environment.getDataMiscDirectory(), "wifi/ipconfig.txt");
    /**
     * List of external dependencies for WifiConfigManager.
     */
    private final WifiNetworkHistory mWifiNetworkHistory;
    private final WifiNative mWifiNative;
    private final IpConfigStore mIpconfigStore;

    private final LegacyPasspointConfigParser mPasspointConfigParser;

    WifiConfigStoreLegacy(WifiNetworkHistory wifiNetworkHistory,
            WifiNative wifiNative, IpConfigStore ipConfigStore,
            LegacyPasspointConfigParser passpointConfigParser) {
        mWifiNetworkHistory = wifiNetworkHistory;
        mWifiNative = wifiNative;
        mIpconfigStore = ipConfigStore;
        mPasspointConfigParser = passpointConfigParser;
    }

    /**
     * Helper function to lookup the WifiConfiguration object from configKey to WifiConfiguration
     * object map using the hashcode of the configKey.
     *
     * @param configurationMap Map of configKey to WifiConfiguration object.
     * @param hashCode         hash code of the configKey to match.
     * @return
     */
    private static WifiConfiguration lookupWifiConfigurationUsingConfigKeyHash(
            Map<String, WifiConfiguration> configurationMap, int hashCode) {
        for (Map.Entry<String, WifiConfiguration> entry : configurationMap.entrySet()) {
            if (entry.getKey().hashCode() == hashCode) {
                return entry.getValue();
            }
        }
        return null;
    }

    /**
     * Helper function to load {@link IpConfiguration} data from the ip config store file and
     * populate the provided configuration map.
     *
     * @param configurationMap Map of configKey to WifiConfiguration object.
     */
    private void loadFromIpConfigStore(Map<String, WifiConfiguration> configurationMap) {
        // This is a map of the hash code of the network's configKey to the corresponding
        // IpConfiguration.
        SparseArray<IpConfiguration> ipConfigurations =
                mIpconfigStore.readIpAndProxyConfigurations(IP_CONFIG_FILE.getAbsolutePath());
        if (ipConfigurations == null || ipConfigurations.size() == 0) {
            Log.w(TAG, "No ip configurations found in ipconfig store");
            return;
        }
        for (int i = 0; i < ipConfigurations.size(); i++) {
            int id = ipConfigurations.keyAt(i);
            WifiConfiguration config =
                    lookupWifiConfigurationUsingConfigKeyHash(configurationMap, id);
            // This is the only place the map is looked up through a (dangerous) hash-value!
            if (config == null || config.ephemeral) {
                Log.w(TAG, "configuration found for missing network, nid=" + id
                        + ", ignored, networks.size=" + Integer.toString(ipConfigurations.size()));
            } else {
                config.setIpConfiguration(ipConfigurations.valueAt(i));
            }
        }
    }

    /**
     * Helper function to load {@link WifiConfiguration} data from networkHistory file and populate
     * the provided configuration map and deleted ephemeral ssid list.
     *
     * @param configurationMap      Map of configKey to WifiConfiguration object.
     * @param deletedEphemeralSSIDs Map of configKey to WifiConfiguration object.
     */
    private void loadFromNetworkHistory(
            Map<String, WifiConfiguration> configurationMap, Set<String> deletedEphemeralSSIDs) {
        // TODO: Need  to revisit the scan detail cache persistance. We're not doing it in the new
        // config store, so ignore it here as well.
        Map<Integer, ScanDetailCache> scanDetailCaches = new HashMap<>();
        mWifiNetworkHistory.readNetworkHistory(
                configurationMap, scanDetailCaches, deletedEphemeralSSIDs);
    }

    /**
     * Helper function to load {@link WifiConfiguration} data from wpa_supplicant and populate
     * the provided configuration map and network extras.
     *
     * This method needs to manually parse the wpa_supplicant.conf file to retrieve some of the
     * password fields like psk, wep_keys. password, etc.
     *
     * @param configurationMap Map of configKey to WifiConfiguration object.
     * @param networkExtras    Map of network extras parsed from wpa_supplicant.
     */
    private void loadFromWpaSupplicant(
            Map<String, WifiConfiguration> configurationMap,
            SparseArray<Map<String, String>> networkExtras) {
        if (!mWifiNative.migrateNetworksFromSupplicant(mWifiNative.getClientInterfaceName(),
                configurationMap, networkExtras)) {
            Log.wtf(TAG, "Failed to load wifi configurations from wpa_supplicant");
            return;
        }
        if (configurationMap.isEmpty()) {
            Log.w(TAG, "No wifi configurations found in wpa_supplicant");
            return;
        }
    }

    /**
     * Helper function to update {@link WifiConfiguration} that represents a Passpoint
     * configuration.
     *
     * This method will manually parse PerProviderSubscription.conf file to retrieve missing
     * fields: provider friendly name, roaming consortium OIs, realm, IMSI.
     *
     * @param configurationMap Map of configKey to WifiConfiguration object.
     * @param networkExtras    Map of network extras parsed from wpa_supplicant.
     */
    private void loadFromPasspointConfigStore(
            Map<String, WifiConfiguration> configurationMap,
            SparseArray<Map<String, String>> networkExtras) {
        Map<String, LegacyPasspointConfig> passpointConfigMap = null;
        try {
            passpointConfigMap = mPasspointConfigParser.parseConfig(PPS_FILE.getAbsolutePath());
        } catch (IOException e) {
            Log.w(TAG, "Failed to read/parse Passpoint config file: " + e.getMessage());
        }

        List<String> entriesToBeRemoved = new ArrayList<>();
        for (Map.Entry<String, WifiConfiguration> entry : configurationMap.entrySet()) {
            WifiConfiguration wifiConfig = entry.getValue();
            // Ignore non-Enterprise network since enterprise configuration is required for
            // Passpoint.
            if (wifiConfig.enterpriseConfig == null || wifiConfig.enterpriseConfig.getEapMethod()
                    == WifiEnterpriseConfig.Eap.NONE) {
                continue;
            }
            // Ignore configuration without FQDN.
            Map<String, String> extras = networkExtras.get(wifiConfig.networkId);
            if (extras == null || !extras.containsKey(SupplicantStaNetworkHal.ID_STRING_KEY_FQDN)) {
                continue;
            }
            String fqdn = networkExtras.get(wifiConfig.networkId).get(
                    SupplicantStaNetworkHal.ID_STRING_KEY_FQDN);

            // Remove the configuration if failed to find the matching configuration in the
            // Passpoint configuration file.
            if (passpointConfigMap == null || !passpointConfigMap.containsKey(fqdn)) {
                entriesToBeRemoved.add(entry.getKey());
                continue;
            }

            // Update the missing Passpoint configuration fields to this WifiConfiguration.
            LegacyPasspointConfig passpointConfig = passpointConfigMap.get(fqdn);
            wifiConfig.isLegacyPasspointConfig = true;
            wifiConfig.FQDN = fqdn;
            wifiConfig.providerFriendlyName = passpointConfig.mFriendlyName;
            if (passpointConfig.mRoamingConsortiumOis != null) {
                wifiConfig.roamingConsortiumIds = Arrays.copyOf(
                        passpointConfig.mRoamingConsortiumOis,
                        passpointConfig.mRoamingConsortiumOis.length);
            }
            if (passpointConfig.mImsi != null) {
                wifiConfig.enterpriseConfig.setPlmn(passpointConfig.mImsi);
            }
            if (passpointConfig.mRealm != null) {
                wifiConfig.enterpriseConfig.setRealm(passpointConfig.mRealm);
            }
        }

        // Remove any incomplete Passpoint configurations. Should never happen, in case it does
        // remove them to avoid maintaining any invalid Passpoint configurations.
        for (String key : entriesToBeRemoved) {
            Log.w(TAG, "Remove incomplete Passpoint configuration: " + key);
            configurationMap.remove(key);
        }
    }

    /**
     * Helper function to load from the different legacy stores:
     * 1. Read the network configurations from wpa_supplicant using {@link WifiNative}.
     * 2. Read the network configurations from networkHistory.txt using {@link WifiNetworkHistory}.
     * 3. Read the Ip configurations from ipconfig.txt using {@link IpConfigStore}.
     * 4. Read all the passpoint info from PerProviderSubscription.conf using
     * {@link LegacyPasspointConfigParser}.
     */
    public WifiConfigStoreDataLegacy read() {
        final Map<String, WifiConfiguration> configurationMap = new HashMap<>();
        final SparseArray<Map<String, String>> networkExtras = new SparseArray<>();
        final Set<String> deletedEphemeralSSIDs = new HashSet<>();

        loadFromWpaSupplicant(configurationMap, networkExtras);
        loadFromNetworkHistory(configurationMap, deletedEphemeralSSIDs);
        loadFromIpConfigStore(configurationMap);
        loadFromPasspointConfigStore(configurationMap, networkExtras);

        // Now create config store data instance to be returned.
        return new WifiConfigStoreDataLegacy(
                new ArrayList<>(configurationMap.values()), deletedEphemeralSSIDs);
    }

    /**
     * Function to check if the legacy store files are present and hence load from those stores and
     * then delete them.
     *
     * @return true if legacy store files are present, false otherwise.
     */
    public boolean areStoresPresent() {
        // We may have to keep the wpa_supplicant.conf file around. So, just use networkhistory.txt
        // as a check to see if we have not yet migrated or not. This should be the last file
        // that is deleted after migration.
        File file = new File(WifiNetworkHistory.NETWORK_HISTORY_CONFIG_FILE);
        return file.exists();
    }

    /**
     * Method to remove all the legacy store files. This should only be invoked once all
     * the data has been migrated to the new store file.
     * 1. Removes all networks from wpa_supplicant and saves it to wpa_supplicant.conf
     * 2. Deletes ipconfig.txt
     * 3. Deletes networkHistory.txt
     *
     * @return true if all the store files were deleted successfully, false otherwise.
     */
    public boolean removeStores() {
        // TODO(b/29352330): Delete wpa_supplicant.conf file instead.
        // First remove all networks from wpa_supplicant and save configuration.
        if (!mWifiNative.removeAllNetworks(mWifiNative.getClientInterfaceName())) {
            Log.e(TAG, "Removing networks from wpa_supplicant failed");
        }

        // Now remove the ipconfig.txt file.
        if (!IP_CONFIG_FILE.delete()) {
            Log.e(TAG, "Removing ipconfig.txt failed");
        }

        // Now finally remove network history.txt
        if (!NETWORK_HISTORY_FILE.delete()) {
            Log.e(TAG, "Removing networkHistory.txt failed");
        }

        if (!PPS_FILE.delete()) {
            Log.e(TAG, "Removing PerProviderSubscription.conf failed");
        }

        Log.i(TAG, "All legacy stores removed!");
        return true;
    }

    /**
     * Interface used to set a masked value in the provided configuration. The masked value is
     * retrieved by parsing the wpa_supplicant.conf file.
     */
    private interface MaskedWpaSupplicantFieldSetter {
        void setValue(WifiConfiguration config, String value);
    }

    /**
     * Class used to encapsulate all the store data retrieved from the legacy (Pre O) store files.
     */
    public static class WifiConfigStoreDataLegacy {
        private List<WifiConfiguration> mConfigurations;
        private Set<String> mDeletedEphemeralSSIDs;
        // private List<HomeSP> mHomeSps;

        WifiConfigStoreDataLegacy(List<WifiConfiguration> configurations,
                Set<String> deletedEphemeralSSIDs) {
            mConfigurations = configurations;
            mDeletedEphemeralSSIDs = deletedEphemeralSSIDs;
        }

        public List<WifiConfiguration> getConfigurations() {
            return mConfigurations;
        }

        public Set<String> getDeletedEphemeralSSIDs() {
            return mDeletedEphemeralSSIDs;
        }
    }
}