WifiBackupRestore.java revision 2fafcc56fda54b1adf8b6743beaac59dbb84dfec
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.util.Log;
23import android.util.Pair;
24import android.util.SparseArray;
25import android.util.Xml;
26
27import com.android.internal.util.FastXmlSerializer;
28import com.android.server.net.IpConfigStore;
29import com.android.server.wifi.util.XmlUtil;
30import com.android.server.wifi.util.XmlUtil.IpConfigurationXmlUtil;
31import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;
32
33import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
35import org.xmlpull.v1.XmlSerializer;
36
37import java.io.BufferedReader;
38import java.io.ByteArrayInputStream;
39import java.io.ByteArrayOutputStream;
40import java.io.CharArrayReader;
41import java.io.FileDescriptor;
42import java.io.IOException;
43import java.io.PrintWriter;
44import java.io.UnsupportedEncodingException;
45import java.nio.charset.StandardCharsets;
46import java.util.ArrayList;
47import java.util.List;
48import java.util.Map;
49
50/**
51 * Class used to backup/restore data using the SettingsBackupAgent.
52 * There are 2 symmetric API's exposed here:
53 * 1. retrieveBackupDataFromConfigurations: Retrieve the configuration data to be backed up.
54 * 2. retrieveConfigurationsFromBackupData: Restore the configuration using the provided data.
55 * The byte stream to be backed up is XML encoded and versioned to migrate the data easily across
56 * revisions.
57 */
58public class WifiBackupRestore {
59    private static final String TAG = "WifiBackupRestore";
60
61    /**
62     * Current backup data version. This will be incremented for any additions.
63     */
64    private static final int CURRENT_BACKUP_DATA_VERSION = 1;
65
66    /** This list of older versions will be used to restore data from older backups. */
67    /**
68     * First version of the backup data format.
69     */
70    private static final int INITIAL_BACKUP_DATA_VERSION = 1;
71
72    /**
73     * List of XML section header tags in the backed up data
74     */
75    private static final String XML_TAG_DOCUMENT_HEADER = "WifiBackupData";
76    private static final String XML_TAG_VERSION = "Version";
77    private static final String XML_TAG_SECTION_HEADER_NETWORK_LIST = "NetworkList";
78    private static final String XML_TAG_SECTION_HEADER_NETWORK = "Network";
79    private static final String XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION = "WifiConfiguration";
80    private static final String XML_TAG_SECTION_HEADER_IP_CONFIGURATION = "IpConfiguration";
81
82    /**
83     * Regex to mask out passwords in backup data dump.
84     */
85    private static final String PSK_MASK_LINE_MATCH_PATTERN =
86            "<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>.*<.*>";
87    private static final String PSK_MASK_SEARCH_PATTERN =
88            "(<.*" + WifiConfigurationXmlUtil.XML_TAG_PRE_SHARED_KEY + ".*>)(.*)(<.*>)";
89    private static final String PSK_MASK_REPLACE_PATTERN = "$1*$3";
90
91    private static final String WEP_KEYS_MASK_LINE_START_MATCH_PATTERN =
92            "<string-array.*" + WifiConfigurationXmlUtil.XML_TAG_WEP_KEYS + ".*num=\"[0-9]\">";
93    private static final String WEP_KEYS_MASK_LINE_END_MATCH_PATTERN = "</string-array>";
94    private static final String WEP_KEYS_MASK_SEARCH_PATTERN = "(<.*=)(.*)(/>)";
95    private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*$3";
96
97    /**
98     * Verbose logging flag.
99     */
100    private boolean mVerboseLoggingEnabled = false;
101
102    /**
103     * Store the dump of the backup/restore data for debugging. This is only stored when verbose
104     * logging is enabled in developer options.
105     */
106    private byte[] mDebugLastBackupDataRetrieved;
107    private byte[] mDebugLastBackupDataRestored;
108    private byte[] mDebugLastSupplicantBackupDataRestored;
109
110    /**
111     * Retrieve an XML byte stream representing the data that needs to be backed up from the
112     * provided configurations.
113     *
114     * @param configurations list of currently saved networks that needs to be backed up.
115     * @return Raw byte stream of XML that needs to be backed up.
116     */
117    public byte[] retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations) {
118        if (configurations == null) {
119            Log.e(TAG, "Invalid configuration list received");
120            return null;
121        }
122
123        try {
124            final XmlSerializer out = new FastXmlSerializer();
125            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
126            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
127
128            // Start writing the XML stream.
129            XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
130
131            XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_BACKUP_DATA_VERSION);
132
133            writeNetworkConfigurationsToXml(out, configurations);
134
135            XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
136
137            byte[] data = outputStream.toByteArray();
138
139            if (mVerboseLoggingEnabled) {
140                mDebugLastBackupDataRetrieved = data;
141            }
142
143            return data;
144        } catch (XmlPullParserException e) {
145            Log.e(TAG, "Error retrieving the backup data: " + e);
146        } catch (IOException e) {
147            Log.e(TAG, "Error retrieving the backup data: " + e);
148        }
149        return null;
150    }
151
152    /**
153     * Write the list of configurations to the XML stream.
154     */
155    private void writeNetworkConfigurationsToXml(
156            XmlSerializer out, List<WifiConfiguration> configurations)
157            throws XmlPullParserException, IOException {
158        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
159        for (WifiConfiguration configuration : configurations) {
160            // We don't want to backup/restore enterprise/passpoint configurations.
161            if (configuration.isEnterprise() || configuration.isPasspoint()) {
162                Log.d(TAG, "Skipping enterprise network for backup: " + configuration.configKey());
163                continue;
164            }
165            // Write this configuration data now.
166            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK);
167            writeNetworkConfigurationToXml(out, configuration);
168            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK);
169        }
170        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_LIST);
171    }
172
173    /**
174     * Write the configuration data elements from the provided Configuration to the XML stream.
175     * Uses XmlUtils to write the values of each element.
176     */
177    private void writeNetworkConfigurationToXml(XmlSerializer out, WifiConfiguration configuration)
178            throws XmlPullParserException, IOException {
179        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
180        WifiConfigurationXmlUtil.writeWifiConfigurationToXmlForBackup(out, configuration);
181        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
182        if (configuration.getIpConfiguration() != null) {
183            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
184            IpConfigurationXmlUtil.writeIpConfigurationToXml(
185                    out, configuration.getIpConfiguration());
186            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
187        }
188    }
189
190    /**
191     * Parse out the configurations from the back up data.
192     *
193     * @param data raw byte stream representing the XML data.
194     * @return list of networks retrieved from the backed up data.
195     */
196    public List<WifiConfiguration> retrieveConfigurationsFromBackupData(byte[] data) {
197        if (data == null || data.length == 0) {
198            Log.e(TAG, "Invalid backup data received");
199            return null;
200        }
201
202        try {
203            if (mVerboseLoggingEnabled) {
204                mDebugLastBackupDataRestored = data;
205            }
206
207            final XmlPullParser in = Xml.newPullParser();
208            ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
209            in.setInput(inputStream, StandardCharsets.UTF_8.name());
210
211            // Start parsing the XML stream.
212            XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
213            int rootTagDepth = in.getDepth();
214
215            int version = (int) XmlUtil.readNextValueWithName(in, XML_TAG_VERSION);
216            if (version < INITIAL_BACKUP_DATA_VERSION || version > CURRENT_BACKUP_DATA_VERSION) {
217                Log.e(TAG, "Invalid version of data: " + version);
218                return null;
219            }
220
221            return parseNetworkConfigurationsFromXml(in, rootTagDepth, version);
222        } catch (XmlPullParserException e) {
223            Log.e(TAG, "Error parsing the backup data: " + e);
224        } catch (IOException e) {
225            Log.e(TAG, "Error parsing the backup data: " + e);
226        }
227        return null;
228    }
229
230    /**
231     * Parses the list of configurations from the provided XML stream.
232     *
233     * @param in            XmlPullParser instance pointing to the XML stream.
234     * @param outerTagDepth depth of the outer tag in the XML document.
235     * @param dataVersion   version number parsed from incoming data.
236     * @return List<WifiConfiguration> object if parsing is successful, null otherwise.
237     */
238    private List<WifiConfiguration> parseNetworkConfigurationsFromXml(
239            XmlPullParser in, int outerTagDepth, int dataVersion)
240            throws XmlPullParserException, IOException {
241        // Find the configuration list section.
242        if (!XmlUtil.gotoNextSection(
243                in, XML_TAG_SECTION_HEADER_NETWORK_LIST, outerTagDepth)) {
244            Log.e(TAG, "Error parsing the backup data. Did not find network list");
245            // Malformed XML input, bail out.
246            return null;
247        }
248        // Find all the configurations within the configuration list section.
249        int networkListTagDepth = outerTagDepth + 1;
250        List<WifiConfiguration> configurations = new ArrayList<>();
251        while (XmlUtil.gotoNextSection(in, XML_TAG_SECTION_HEADER_NETWORK, networkListTagDepth)) {
252            WifiConfiguration configuration =
253                    parseNetworkConfigurationFromXml(in, dataVersion, networkListTagDepth);
254            if (configuration != null) {
255                Log.v(TAG, "Parsed Configuration: " + configuration.configKey());
256                configurations.add(configuration);
257            }
258        }
259        return configurations;
260    }
261
262    /**
263     * Helper method to parse the WifiConfiguration object and validate the configKey parsed.
264     */
265    private WifiConfiguration parseWifiConfigurationFromXmlAndValidateConfigKey(
266            XmlPullParser in, int outerTagDepth)
267            throws XmlPullParserException, IOException {
268        Pair<String, WifiConfiguration> parsedConfig =
269                WifiConfigurationXmlUtil.parseWifiConfigurationFromXml(in, outerTagDepth);
270        if (parsedConfig == null || parsedConfig.first == null || parsedConfig.second == null) {
271            return null;
272        }
273        String configKeyParsed = parsedConfig.first;
274        WifiConfiguration configuration = parsedConfig.second;
275        String configKeyCalculated = configuration.configKey();
276        if (!configKeyParsed.equals(configKeyCalculated)) {
277            String configKeyMismatchLog =
278                    "Configuration key does not match. Retrieved: " + configKeyParsed
279                            + ", Calculated: " + configKeyCalculated;
280            if (configuration.shared) {
281                Log.e(TAG, configKeyMismatchLog);
282                return null;
283            } else {
284                // ConfigKey mismatches are expected for private networks because the
285                // UID is not preserved across backup/restore.
286                Log.w(TAG, configKeyMismatchLog);
287            }
288        }
289       return configuration;
290    }
291
292    /**
293     * Parses the configuration data elements from the provided XML stream to a Configuration.
294     *
295     * @param in            XmlPullParser instance pointing to the XML stream.
296     * @param outerTagDepth depth of the outer tag in the XML document.
297     * @param dataVersion   version number parsed from incoming data.
298     * @return WifiConfiguration object if parsing is successful, null otherwise.
299     */
300    private WifiConfiguration parseNetworkConfigurationFromXml(XmlPullParser in, int dataVersion,
301            int outerTagDepth)
302            throws XmlPullParserException, IOException {
303        // Any version migration needs to be handled here in future.
304        if (dataVersion == INITIAL_BACKUP_DATA_VERSION) {
305            WifiConfiguration configuration = null;
306            int networkTagDepth = outerTagDepth + 1;
307            // Retrieve WifiConfiguration object first.
308            if (XmlUtil.gotoNextSection(
309                    in, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION, networkTagDepth)) {
310                int configTagDepth = networkTagDepth + 1;
311                configuration =
312                        parseWifiConfigurationFromXmlAndValidateConfigKey(in, configTagDepth);
313                if (configuration == null) {
314                    return null;
315                }
316            }
317            // Now retrieve any IP configuration info if present.
318            if (XmlUtil.gotoNextSection(
319                    in, XML_TAG_SECTION_HEADER_IP_CONFIGURATION, networkTagDepth)) {
320                int configTagDepth = networkTagDepth + 1;
321                IpConfiguration ipConfiguration =
322                        IpConfigurationXmlUtil.parseIpConfigurationFromXml(in, configTagDepth);
323                configuration.setIpConfiguration(ipConfiguration);
324            }
325            return configuration;
326        }
327        return null;
328    }
329
330    /**
331     * Create log dump of the backup data in XML format with the preShared & WEP key masked.
332     *
333     * PSK keys are written in the following format in XML:
334     * <string name="PreSharedKey">WifiBackupRestorePsk</string>
335     *
336     * WEP Keys are written in following format in XML:
337     * <string-array name="WEPKeys" num="4">
338     *  <item value="WifiBackupRestoreWep1" />
339     *  <item value="WifiBackupRestoreWep2" />
340     *  <item value="WifiBackupRestoreWep3" />
341     *  <item value="WifiBackupRestoreWep3" />
342     * </string-array>
343     */
344    private String createLogFromBackupData(byte[] data) {
345        StringBuilder sb = new StringBuilder();
346        try {
347            String xmlString = new String(data, StandardCharsets.UTF_8.name());
348            boolean wepKeysLine = false;
349            for (String line : xmlString.split("\n")) {
350                if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) {
351                    line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN);
352                }
353                if (line.matches(WEP_KEYS_MASK_LINE_START_MATCH_PATTERN)) {
354                    wepKeysLine = true;
355                } else if (line.matches(WEP_KEYS_MASK_LINE_END_MATCH_PATTERN)) {
356                    wepKeysLine = false;
357                } else if (wepKeysLine) {
358                    line = line.replaceAll(
359                            WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN);
360                }
361                sb.append(line).append("\n");
362            }
363        } catch (UnsupportedEncodingException e) {
364            return "";
365        }
366        return sb.toString();
367    }
368
369    /**
370     * Restore state from the older supplicant back up data.
371     * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file.
372     *
373     * @param supplicantData Raw byte stream of wpa_supplicant.conf
374     * @param ipConfigData   Raw byte stream of ipconfig.txt
375     * @return list of networks retrieved from the backed up data.
376     */
377    public List<WifiConfiguration> retrieveConfigurationsFromSupplicantBackupData(
378            byte[] supplicantData, byte[] ipConfigData) {
379        if (supplicantData == null || supplicantData.length == 0) {
380            Log.e(TAG, "Invalid supplicant backup data received");
381            return null;
382        }
383
384        if (mVerboseLoggingEnabled) {
385            mDebugLastSupplicantBackupDataRestored = supplicantData;
386        }
387
388        SupplicantBackupMigration.SupplicantNetworks supplicantNetworks =
389                new SupplicantBackupMigration.SupplicantNetworks();
390        // Incorporate the networks present in the backup data.
391        char[] restoredAsChars = new char[supplicantData.length];
392        for (int i = 0; i < supplicantData.length; i++) {
393            restoredAsChars[i] = (char) supplicantData[i];
394        }
395
396        BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsChars));
397        supplicantNetworks.readNetworksFromStream(in);
398
399        // Retrieve corresponding WifiConfiguration objects.
400        List<WifiConfiguration> configurations = supplicantNetworks.retrieveWifiConfigurations();
401
402        // Now retrieve all the IpConfiguration objects and set in the corresponding
403        // WifiConfiguration objects if ipconfig data is present.
404        if (ipConfigData != null && ipConfigData.length != 0) {
405            SparseArray<IpConfiguration> networks =
406                    IpConfigStore.readIpAndProxyConfigurations(
407                            new ByteArrayInputStream(ipConfigData));
408            if (networks != null) {
409                for (int i = 0; i < networks.size(); i++) {
410                    int id = networks.keyAt(i);
411                    for (WifiConfiguration configuration : configurations) {
412                        // This is a dangerous lookup, but that's how it is currently written.
413                        if (configuration.configKey().hashCode() == id) {
414                            configuration.setIpConfiguration(networks.valueAt(i));
415                        }
416                    }
417                }
418            } else {
419                Log.e(TAG, "Failed to parse ipconfig data");
420            }
421        } else {
422            Log.e(TAG, "Invalid ipconfig backup data received");
423        }
424        return configurations;
425    }
426
427    /**
428     * Enable verbose logging.
429     *
430     * @param verbose verbosity level.
431     */
432    public void enableVerboseLogging(int verbose) {
433        mVerboseLoggingEnabled = (verbose > 0);
434        if (!mVerboseLoggingEnabled) {
435            mDebugLastBackupDataRetrieved = null;
436            mDebugLastBackupDataRestored = null;
437            mDebugLastSupplicantBackupDataRestored = null;
438        }
439    }
440
441    /**
442     * Dump out the last backup/restore data if verbose logging is enabled.
443     *
444     * @param fd   unused
445     * @param pw   PrintWriter for writing dump to
446     * @param args unused
447     */
448    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
449        pw.println("Dump of WifiBackupRestore");
450        if (mDebugLastBackupDataRetrieved != null) {
451            pw.println("Last backup data retrieved: "
452                    + createLogFromBackupData(mDebugLastBackupDataRetrieved));
453        }
454        if (mDebugLastBackupDataRestored != null) {
455            pw.println("Last backup data restored: "
456                    + createLogFromBackupData(mDebugLastBackupDataRestored));
457        }
458        if (mDebugLastSupplicantBackupDataRestored != null) {
459            pw.println("Last old backup data restored: "
460                    + SupplicantBackupMigration.createLogFromBackupData(
461                            mDebugLastSupplicantBackupDataRestored));
462        }
463    }
464
465    /**
466     * These sub classes contain the logic to parse older backups and restore wifi state from it.
467     * Most of the code here has been migrated over from BackupSettingsAgent.
468     * This is kind of ugly text parsing, but it is needed to support the migration of this data.
469     */
470    public static class SupplicantBackupMigration {
471        /**
472         * List of keys to look out for in wpa_supplicant.conf parsing.
473         * These key values are declared in different parts of the wifi codebase today.
474         */
475        public static final String SUPPLICANT_KEY_SSID = WifiConfiguration.ssidVarName;
476        public static final String SUPPLICANT_KEY_HIDDEN = WifiConfiguration.hiddenSSIDVarName;
477        public static final String SUPPLICANT_KEY_KEY_MGMT = WifiConfiguration.KeyMgmt.varName;
478        public static final String SUPPLICANT_KEY_CLIENT_CERT =
479                WifiEnterpriseConfig.CLIENT_CERT_KEY;
480        public static final String SUPPLICANT_KEY_CA_CERT = WifiEnterpriseConfig.CA_CERT_KEY;
481        public static final String SUPPLICANT_KEY_CA_PATH = WifiEnterpriseConfig.CA_PATH_KEY;
482        public static final String SUPPLICANT_KEY_EAP = WifiEnterpriseConfig.EAP_KEY;
483        public static final String SUPPLICANT_KEY_PSK = WifiConfiguration.pskVarName;
484        public static final String SUPPLICANT_KEY_WEP_KEY0 = WifiConfiguration.wepKeyVarNames[0];
485        public static final String SUPPLICANT_KEY_WEP_KEY1 = WifiConfiguration.wepKeyVarNames[1];
486        public static final String SUPPLICANT_KEY_WEP_KEY2 = WifiConfiguration.wepKeyVarNames[2];
487        public static final String SUPPLICANT_KEY_WEP_KEY3 = WifiConfiguration.wepKeyVarNames[3];
488        public static final String SUPPLICANT_KEY_WEP_KEY_IDX =
489                WifiConfiguration.wepTxKeyIdxVarName;
490        public static final String SUPPLICANT_KEY_ID_STR = WifiConfigStore.ID_STRING_VAR_NAME;
491
492        /**
493         * Regex to mask out passwords in backup data dump.
494         */
495        private static final String PSK_MASK_LINE_MATCH_PATTERN =
496                ".*" + SUPPLICANT_KEY_PSK + ".*=.*";
497        private static final String PSK_MASK_SEARCH_PATTERN =
498                "(.*" + SUPPLICANT_KEY_PSK + ".*=)(.*)";
499        private static final String PSK_MASK_REPLACE_PATTERN = "$1*";
500
501        private static final String WEP_KEYS_MASK_LINE_MATCH_PATTERN =
502                ".*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=.*";
503        private static final String WEP_KEYS_MASK_SEARCH_PATTERN =
504                "(.*" + SUPPLICANT_KEY_WEP_KEY0.replace("0", "") + ".*=)(.*)";
505        private static final String WEP_KEYS_MASK_REPLACE_PATTERN = "$1*";
506
507        /**
508         * Create log dump of the backup data in wpa_supplicant.conf format with the preShared &
509         * WEP key masked.
510         *
511         * PSK keys are written in the following format in wpa_supplicant.conf:
512         *  psk=WifiBackupRestorePsk
513         *
514         * WEP Keys are written in following format in wpa_supplicant.conf:
515         *  wep_keys0=WifiBackupRestoreWep0
516         *  wep_keys1=WifiBackupRestoreWep1
517         *  wep_keys2=WifiBackupRestoreWep2
518         *  wep_keys3=WifiBackupRestoreWep3
519         */
520        public static String createLogFromBackupData(byte[] data) {
521            StringBuilder sb = new StringBuilder();
522            try {
523                String supplicantConfString = new String(data, StandardCharsets.UTF_8.name());
524                for (String line : supplicantConfString.split("\n")) {
525                    if (line.matches(PSK_MASK_LINE_MATCH_PATTERN)) {
526                        line = line.replaceAll(PSK_MASK_SEARCH_PATTERN, PSK_MASK_REPLACE_PATTERN);
527                    }
528                    if (line.matches(WEP_KEYS_MASK_LINE_MATCH_PATTERN)) {
529                        line = line.replaceAll(
530                                WEP_KEYS_MASK_SEARCH_PATTERN, WEP_KEYS_MASK_REPLACE_PATTERN);
531                    }
532                    sb.append(line).append("\n");
533                }
534            } catch (UnsupportedEncodingException e) {
535                return "";
536            }
537            return sb.toString();
538        }
539
540        /**
541         * Class for capturing a network definition from the wifi supplicant config file.
542         */
543        static class SupplicantNetwork {
544            private String mParsedSSIDLine;
545            private String mParsedHiddenLine;
546            private String mParsedKeyMgmtLine;
547            private String mParsedPskLine;
548            private String[] mParsedWepKeyLines = new String[4];
549            private String mParsedWepTxKeyIdxLine;
550            private String mParsedIdStrLine;
551            public boolean certUsed = false;
552            public boolean isEap = false;
553
554            /**
555             * Read lines from wpa_supplicant.conf stream for this network.
556             */
557            public static SupplicantNetwork readNetworkFromStream(BufferedReader in) {
558                final SupplicantNetwork n = new SupplicantNetwork();
559                String line;
560                try {
561                    while (in.ready()) {
562                        line = in.readLine();
563                        if (line == null || line.startsWith("}")) {
564                            break;
565                        }
566                        n.parseLine(line);
567                    }
568                } catch (IOException e) {
569                    return null;
570                }
571                return n;
572            }
573
574            /**
575             * Parse a line from wpa_supplicant.conf stream for this network.
576             */
577            void parseLine(String line) {
578                // Can't rely on particular whitespace patterns so strip leading/trailing.
579                line = line.trim();
580                if (line.isEmpty()) return; // only whitespace; drop the line.
581
582                // Now parse the network block within wpa_supplicant.conf and store the important
583                // lines for procesing later.
584                if (line.startsWith(SUPPLICANT_KEY_SSID)) {
585                    mParsedSSIDLine = line;
586                } else if (line.startsWith(SUPPLICANT_KEY_HIDDEN)) {
587                    mParsedHiddenLine = line;
588                } else if (line.startsWith(SUPPLICANT_KEY_KEY_MGMT)) {
589                    mParsedKeyMgmtLine = line;
590                    if (line.contains("EAP")) {
591                        isEap = true;
592                    }
593                } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT)) {
594                    certUsed = true;
595                } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT)) {
596                    certUsed = true;
597                } else if (line.startsWith(SUPPLICANT_KEY_CA_PATH)) {
598                    certUsed = true;
599                } else if (line.startsWith(SUPPLICANT_KEY_EAP)) {
600                    isEap = true;
601                } else if (line.startsWith(SUPPLICANT_KEY_PSK)) {
602                    mParsedPskLine = line;
603                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY0)) {
604                    mParsedWepKeyLines[0] = line;
605                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY1)) {
606                    mParsedWepKeyLines[1] = line;
607                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY2)) {
608                    mParsedWepKeyLines[2] = line;
609                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY3)) {
610                    mParsedWepKeyLines[3] = line;
611                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY_IDX)) {
612                    mParsedWepTxKeyIdxLine = line;
613                } else if (line.startsWith(SUPPLICANT_KEY_ID_STR)) {
614                    mParsedIdStrLine = line;
615                }
616            }
617
618            /**
619             * Create WifiConfiguration object from the parsed data for this network.
620             */
621            public WifiConfiguration createWifiConfiguration() {
622                if (mParsedSSIDLine == null) {
623                    // No SSID => malformed network definition
624                    return null;
625                }
626                WifiConfiguration configuration = new WifiConfiguration();
627                configuration.SSID = mParsedSSIDLine.substring(mParsedSSIDLine.indexOf('=') + 1);
628
629                if (mParsedHiddenLine != null) {
630                    // Can't use Boolean.valueOf() because it works only for true/false.
631                    configuration.hiddenSSID =
632                            Integer.parseInt(mParsedHiddenLine.substring(
633                                    mParsedHiddenLine.indexOf('=') + 1)) != 0;
634                }
635                if (mParsedKeyMgmtLine == null) {
636                    // no key_mgmt line specified; this is defined as equivalent to
637                    // "WPA-PSK WPA-EAP".
638                    configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
639                    configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
640                } else {
641                    // Need to parse the mParsedKeyMgmtLine line
642                    final String bareKeyMgmt =
643                            mParsedKeyMgmtLine.substring(mParsedKeyMgmtLine.indexOf('=') + 1);
644                    String[] typeStrings = bareKeyMgmt.split("\\s+");
645
646                    // Parse out all the key management regimes permitted for this network.
647                    // The literal strings here are the standard values permitted in
648                    // wpa_supplicant.conf.
649                    for (int i = 0; i < typeStrings.length; i++) {
650                        final String ktype = typeStrings[i];
651                        if (ktype.equals("NONE")) {
652                            configuration.allowedKeyManagement.set(
653                                    WifiConfiguration.KeyMgmt.NONE);
654                        } else if (ktype.equals("WPA-PSK")) {
655                            configuration.allowedKeyManagement.set(
656                                    WifiConfiguration.KeyMgmt.WPA_PSK);
657                        } else if (ktype.equals("WPA-EAP")) {
658                            configuration.allowedKeyManagement.set(
659                                    WifiConfiguration.KeyMgmt.WPA_EAP);
660                        } else if (ktype.equals("IEEE8021X")) {
661                            configuration.allowedKeyManagement.set(
662                                    WifiConfiguration.KeyMgmt.IEEE8021X);
663                        }
664                    }
665                }
666                if (mParsedPskLine != null) {
667                    configuration.preSharedKey =
668                            mParsedPskLine.substring(mParsedPskLine.indexOf('=') + 1);
669                }
670                if (mParsedWepKeyLines[0] != null) {
671                    configuration.wepKeys[0] =
672                            mParsedWepKeyLines[0].substring(mParsedWepKeyLines[0].indexOf('=') + 1);
673                }
674                if (mParsedWepKeyLines[1] != null) {
675                    configuration.wepKeys[1] =
676                            mParsedWepKeyLines[1].substring(mParsedWepKeyLines[1].indexOf('=') + 1);
677                }
678                if (mParsedWepKeyLines[2] != null) {
679                    configuration.wepKeys[2] =
680                            mParsedWepKeyLines[2].substring(mParsedWepKeyLines[2].indexOf('=') + 1);
681                }
682                if (mParsedWepKeyLines[3] != null) {
683                    configuration.wepKeys[3] =
684                            mParsedWepKeyLines[3].substring(mParsedWepKeyLines[3].indexOf('=') + 1);
685                }
686                if (mParsedWepTxKeyIdxLine != null) {
687                    configuration.wepTxKeyIndex =
688                            Integer.valueOf(mParsedWepTxKeyIdxLine.substring(
689                                    mParsedWepTxKeyIdxLine.indexOf('=') + 1));
690                }
691                if (mParsedIdStrLine != null) {
692                    String idString =
693                            mParsedIdStrLine.substring(mParsedIdStrLine.indexOf('=') + 1);
694                    Map<String, String> extras = WifiNative.parseNetworkExtra(idString);
695                    String configKey = extras.get(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY);
696                    if (!configKey.equals(configuration.configKey())) {
697                        // ConfigKey mismatches are expected for private networks because the
698                        // UID is not preserved across backup/restore.
699                        Log.w(TAG, "Configuration key does not match. Retrieved: " + configKey
700                                + ", Calculated: " + configuration.configKey());
701                    }
702                }
703                return configuration;
704            }
705        }
706
707        /**
708         * Ingest multiple wifi config fragments from wpa_supplicant.conf, looking for network={}
709         * blocks and eliminating duplicates
710         */
711        static class SupplicantNetworks {
712            final ArrayList<SupplicantNetwork> mNetworks = new ArrayList<>(8);
713
714            /**
715             * Parse the wpa_supplicant.conf file stream and add networks.
716             */
717            public void readNetworksFromStream(BufferedReader in) {
718                try {
719                    String line;
720                    while (in.ready()) {
721                        line = in.readLine();
722                        if (line != null) {
723                            if (line.startsWith("network")) {
724                                SupplicantNetwork net = SupplicantNetwork.readNetworkFromStream(in);
725                                // Networks that use certificates for authentication can't be
726                                // restored because the certificates they need don't get restored
727                                // (because they are stored in keystore, and can't be restored).
728                                // Similarly, omit EAP network definitions to avoid propagating
729                                // controlled enterprise network definitions.
730                                if (net.isEap || net.certUsed) {
731                                    Log.d(TAG, "Skipping enterprise network for restore: "
732                                            + net.mParsedSSIDLine + " / " + net.mParsedKeyMgmtLine);
733                                    continue;
734                                }
735                                mNetworks.add(net);
736                            }
737                        }
738                    }
739                } catch (IOException e) {
740                    // whatever happened, we're done now
741                }
742            }
743
744            /**
745             * Retrieve a list of WifiConfiguration objects parsed from wpa_supplicant.conf
746             */
747            public List<WifiConfiguration> retrieveWifiConfigurations() {
748                ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>();
749                for (SupplicantNetwork net : mNetworks) {
750                    WifiConfiguration wifiConfiguration = net.createWifiConfiguration();
751                    if (wifiConfiguration != null) {
752                        Log.v(TAG, "Parsed Configuration: " + wifiConfiguration.configKey());
753                        wifiConfigurations.add(wifiConfiguration);
754                    }
755                }
756                return wifiConfigurations;
757            }
758        }
759    }
760}
761