WifiBackupRestore.java revision 3204fb9682242a7b5a749489076c66d448c42577
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.LinkAddress;
21import android.net.NetworkUtils;
22import android.net.ProxyInfo;
23import android.net.RouteInfo;
24import android.net.StaticIpConfiguration;
25import android.net.wifi.WifiConfiguration;
26import android.net.wifi.WifiEnterpriseConfig;
27import android.util.Log;
28import android.util.SparseArray;
29import android.util.Xml;
30
31import com.android.internal.annotations.VisibleForTesting;
32import com.android.internal.util.FastXmlSerializer;
33import com.android.server.net.IpConfigStore;
34import com.android.server.wifi.util.XmlUtil;
35
36import org.xmlpull.v1.XmlPullParser;
37import org.xmlpull.v1.XmlPullParserException;
38import org.xmlpull.v1.XmlSerializer;
39
40import java.io.BufferedReader;
41import java.io.ByteArrayInputStream;
42import java.io.ByteArrayOutputStream;
43import java.io.CharArrayReader;
44import java.io.FileDescriptor;
45import java.io.IOException;
46import java.io.PrintWriter;
47import java.io.UnsupportedEncodingException;
48import java.net.Inet4Address;
49import java.net.InetAddress;
50import java.nio.charset.StandardCharsets;
51import java.util.ArrayList;
52import java.util.BitSet;
53import java.util.List;
54import java.util.Map;
55
56import static android.net.IpConfiguration.IpAssignment;
57import static android.net.IpConfiguration.ProxySettings;
58
59/**
60 * Class used to backup/restore data using the SettingsBackupAgent.
61 * There are 2 symmetric API's exposed here:
62 * 1. retrieveBackupDataFromConfigurations: Retrieve the configuration data to be backed up.
63 * 2. retrieveConfigurationsFromBackupData: Restore the configuration using the provided data.
64 * The byte stream to be backed up is XML encoded and versioned to migrate the data easily across
65 * revisions.
66 */
67public class WifiBackupRestore {
68    private static final String TAG = "WifiBackupRestore";
69
70    /**
71     * Current backup data version. This will be incremented for any additions.
72     */
73    private static final int CURRENT_BACKUP_DATA_VERSION = 1;
74
75    /** This list of older versions will be used to restore data from older backups. */
76    /**
77     * First version of the backup data format.
78     */
79    private static final int INITIAL_BACKUP_DATA_VERSION = 1;
80
81    /**
82     * List of XML tags in the backed up data
83     */
84    private static final String XML_TAG_DOCUMENT_HEADER = "WifiBackupData";
85    private static final String XML_TAG_VERSION = "Version";
86    private static final String XML_TAG_SECTION_HEADER_CONFIGURATION_LIST = "ConfigurationList";
87    private static final String XML_TAG_SECTION_HEADER_CONFIGURATION = "Configuration";
88    private static final String XML_TAG_CONFIGURATION_SSID = "SSID";
89    private static final String XML_TAG_CONFIGURATION_BSSID = "BSSID";
90    private static final String XML_TAG_CONFIGURATION_CONFIG_KEY = "ConfigKey";
91    private static final String XML_TAG_CONFIGURATION_PRE_SHARED_KEY = "PreSharedKey";
92    private static final String XML_TAG_CONFIGURATION_WEP_KEYS = "WEPKeys";
93    private static final String XML_TAG_CONFIGURATION_WEP_TX_KEY_INDEX = "WEPTxKeyIndex";
94    private static final String XML_TAG_CONFIGURATION_HIDDEN_SSID = "HiddenSSID";
95    private static final String XML_TAG_CONFIGURATION_ALLOWED_KEY_MGMT = "AllowedKeyMgmt";
96    private static final String XML_TAG_CONFIGURATION_ALLOWED_PROTOCOLS = "AllowedProtocols";
97    private static final String XML_TAG_CONFIGURATION_ALLOWED_AUTH_ALGOS = "AllowedAuthAlgos";
98    private static final String XML_TAG_CONFIGURATION_SHARED = "Shared";
99    private static final String XML_TAG_CONFIGURATION_CREATOR_UID = "CreatorUid";
100    private static final String XML_TAG_SECTION_HEADER_IP_CONFIGURATION = "IpConfiguration";
101    private static final String XML_TAG_IP_CONFIGURATION_IP_ASSIGNMENT = "IpAssignment";
102    private static final String XML_TAG_IP_CONFIGURATION_LINK_ADDRESS = "LinkAddress";
103    private static final String XML_TAG_IP_CONFIGURATION_LINK_PREFIX_LENGTH = "LinkPrefixLength";
104    private static final String XML_TAG_IP_CONFIGURATION_GATEWAY_ADDRESS = "GatewayAddress";
105    private static final String XML_TAG_IP_CONFIGURATION_DNS_SERVER_ADDRESSES = "DNSServers";
106    private static final String XML_TAG_IP_CONFIGURATION_PROXY_SETTINGS = "ProxySettings";
107    private static final String XML_TAG_IP_CONFIGURATION_PROXY_HOST = "ProxyHost";
108    private static final String XML_TAG_IP_CONFIGURATION_PROXY_PORT = "ProxyPort";
109    private static final String XML_TAG_IP_CONFIGURATION_PROXY_PAC_FILE = "ProxyPac";
110    private static final String XML_TAG_IP_CONFIGURATION_PROXY_EXCLUSION_LIST = "ProxyExclusionList";
111
112    /**
113     * Regex to mask out passwords in backup data dump.
114     */
115    private static final String PASSWORD_MASK_SEARCH_PATTERN =
116            "(<.*" + XML_TAG_CONFIGURATION_PRE_SHARED_KEY + ".*>)(.*)(<.*>)";
117    private static final String PASSWORD_MASK_REPLACE_PATTERN = "$1*$3";
118
119    /**
120     * Verbose logging flag.
121     */
122    private boolean mVerboseLoggingEnabled = false;
123
124    /**
125     * Store the dump of the backup/restore data for debugging. This is only stored when verbose
126     * logging is enabled in developer options.
127     */
128    private byte[] mDebugLastBackupDataRetrieved;
129    private byte[] mDebugLastBackupDataRestored;
130    private byte[] mDebugLastSupplicantBackupDataRestored;
131
132    /**
133     * Retrieve an XML byte stream representing the data that needs to be backed up from the
134     * provided configurations.
135     *
136     * @param configurations list of currently saved networks that needs to be backed up.
137     * @return Raw byte stream of XML that needs to be backed up.
138     */
139    public byte[] retrieveBackupDataFromConfigurations(List<WifiConfiguration> configurations) {
140        if (configurations == null) {
141            Log.e(TAG, "Invalid configuration list received");
142            return null;
143        }
144
145        try {
146            final XmlSerializer out = new FastXmlSerializer();
147            final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
148            out.setOutput(outputStream, StandardCharsets.UTF_8.name());
149
150            // Start writing the XML stream.
151            XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER);
152
153            XmlUtil.writeNextValue(out, XML_TAG_VERSION, CURRENT_BACKUP_DATA_VERSION);
154
155            writeWifiConfigurationsToXml(out, configurations);
156
157            XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER);
158
159            byte[] data = outputStream.toByteArray();
160
161            if (mVerboseLoggingEnabled) {
162                mDebugLastBackupDataRetrieved = data;
163            }
164
165            return data;
166        } catch (XmlPullParserException e) {
167            Log.e(TAG, "Error retrieving the backup data: " + e);
168        } catch (IOException e) {
169            Log.e(TAG, "Error retrieving the backup data: " + e);
170        }
171        return null;
172    }
173
174    /**
175     * Write the list of configurations to the XML stream.
176     */
177    private void writeWifiConfigurationsToXml(
178            XmlSerializer out, List<WifiConfiguration> configurations)
179            throws XmlPullParserException, IOException {
180        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_CONFIGURATION_LIST);
181        for (WifiConfiguration configuration : configurations) {
182            // We don't want to backup/restore enterprise/passpoint configurations.
183            if (configuration.isEnterprise() || configuration.isPasspoint()) {
184                Log.d(TAG, "Skipping enterprise config for backup: " + configuration.configKey());
185                continue;
186            }
187            // Write this configuration data now.
188            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_CONFIGURATION);
189            writeWifiConfigurationToXml(out, configuration);
190            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_CONFIGURATION);
191        }
192        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_CONFIGURATION_LIST);
193    }
194
195    /**
196     * Write WepKeys to the XML stream.
197     * WepKeys array is intialized in WifiConfiguration constructor, but all of the elements
198     * are null. XmlUtils serialization doesn't handle this array of nulls well .
199     * So, write null if the keys are not initialized.
200     */
201    private void writeWepKeysToXml(XmlSerializer out, String[] wepKeys)
202            throws XmlPullParserException, IOException {
203        if (wepKeys[0] != null) {
204            XmlUtil.writeNextValue(out, XML_TAG_CONFIGURATION_WEP_KEYS, wepKeys);
205        } else {
206            XmlUtil.writeNextValue(out, XML_TAG_CONFIGURATION_WEP_KEYS, null);
207        }
208    }
209
210    /**
211     * Write the configuration data elements from the provided Configuration to the XML stream.
212     * Uses XmlUtils to write the values of each element.
213     */
214    private void writeWifiConfigurationToXml(XmlSerializer out, WifiConfiguration configuration)
215            throws XmlPullParserException, IOException {
216        XmlUtil.writeNextValue(out, XML_TAG_CONFIGURATION_CONFIG_KEY, configuration.configKey());
217        XmlUtil.writeNextValue(out, XML_TAG_CONFIGURATION_SSID, configuration.SSID);
218        XmlUtil.writeNextValue(out, XML_TAG_CONFIGURATION_BSSID, configuration.BSSID);
219        XmlUtil.writeNextValue(
220                out, XML_TAG_CONFIGURATION_PRE_SHARED_KEY, configuration.preSharedKey);
221        writeWepKeysToXml(out, configuration.wepKeys);
222        XmlUtil.writeNextValue(
223                out, XML_TAG_CONFIGURATION_WEP_TX_KEY_INDEX, configuration.wepTxKeyIndex);
224        XmlUtil.writeNextValue(out, XML_TAG_CONFIGURATION_HIDDEN_SSID, configuration.hiddenSSID);
225        XmlUtil.writeNextValue(
226                out, XML_TAG_CONFIGURATION_ALLOWED_KEY_MGMT,
227                configuration.allowedKeyManagement.toByteArray());
228        XmlUtil.writeNextValue(
229                out, XML_TAG_CONFIGURATION_ALLOWED_PROTOCOLS,
230                configuration.allowedProtocols.toByteArray());
231        XmlUtil.writeNextValue(
232                out, XML_TAG_CONFIGURATION_ALLOWED_AUTH_ALGOS,
233                configuration.allowedAuthAlgorithms.toByteArray());
234        XmlUtil.writeNextValue(out, XML_TAG_CONFIGURATION_SHARED, configuration.shared);
235        XmlUtil.writeNextValue(out, XML_TAG_CONFIGURATION_CREATOR_UID, configuration.creatorUid);
236
237        if (configuration.getIpConfiguration() != null) {
238            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
239            writeIpConfigurationToXml(out, configuration.getIpConfiguration());
240            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_IP_CONFIGURATION);
241        }
242    }
243
244    /**
245     * Write the static IP configuration data elements to XML stream
246     */
247    private void writeStaticIpConfigurationToXml(XmlSerializer out,
248            StaticIpConfiguration staticIpConfiguration)
249            throws XmlPullParserException, IOException {
250        if (staticIpConfiguration.ipAddress != null) {
251            XmlUtil.writeNextValue(
252                    out, XML_TAG_IP_CONFIGURATION_LINK_ADDRESS,
253                    staticIpConfiguration.ipAddress.getAddress().getHostAddress());
254            XmlUtil.writeNextValue(
255                    out, XML_TAG_IP_CONFIGURATION_LINK_PREFIX_LENGTH,
256                    staticIpConfiguration.ipAddress.getPrefixLength());
257        } else {
258            XmlUtil.writeNextValue(
259                    out, XML_TAG_IP_CONFIGURATION_LINK_ADDRESS, null);
260            XmlUtil.writeNextValue(
261                    out, XML_TAG_IP_CONFIGURATION_LINK_PREFIX_LENGTH, null);
262        }
263        if (staticIpConfiguration.gateway != null) {
264            XmlUtil.writeNextValue(
265                    out, XML_TAG_IP_CONFIGURATION_GATEWAY_ADDRESS,
266                    staticIpConfiguration.gateway.getHostAddress());
267        } else {
268            XmlUtil.writeNextValue(
269                    out, XML_TAG_IP_CONFIGURATION_GATEWAY_ADDRESS, null);
270
271        }
272        if (staticIpConfiguration.dnsServers != null) {
273            // Create a string array of DNS server addresses
274            String[] dnsServers = new String[staticIpConfiguration.dnsServers.size()];
275            int dnsServerIdx = 0;
276            for (InetAddress inetAddr : staticIpConfiguration.dnsServers) {
277                dnsServers[dnsServerIdx++] = inetAddr.getHostAddress();
278            }
279            XmlUtil.writeNextValue(
280                    out, XML_TAG_IP_CONFIGURATION_DNS_SERVER_ADDRESSES, dnsServers);
281        } else {
282            XmlUtil.writeNextValue(
283                    out, XML_TAG_IP_CONFIGURATION_DNS_SERVER_ADDRESSES, null);
284        }
285    }
286
287    /**
288     * Write the IP configuration data elements from the provided Configuration to the XML stream.
289     * Uses XmlUtils to write the values of each element.
290     */
291    private void writeIpConfigurationToXml(XmlSerializer out, IpConfiguration ipConfiguration)
292            throws XmlPullParserException, IOException {
293
294        // Write IP assignment settings
295        XmlUtil.writeNextValue(
296                out, XML_TAG_IP_CONFIGURATION_IP_ASSIGNMENT,
297                ipConfiguration.ipAssignment.toString());
298        switch (ipConfiguration.ipAssignment) {
299            case STATIC:
300                writeStaticIpConfigurationToXml(out, ipConfiguration.getStaticIpConfiguration());
301                break;
302            default:
303                break;
304        }
305
306        // Write proxy settings
307        XmlUtil.writeNextValue(
308                out, XML_TAG_IP_CONFIGURATION_PROXY_SETTINGS,
309                ipConfiguration.proxySettings.toString());
310        switch (ipConfiguration.proxySettings) {
311            case STATIC:
312                XmlUtil.writeNextValue(
313                        out, XML_TAG_IP_CONFIGURATION_PROXY_HOST,
314                        ipConfiguration.httpProxy.getHost());
315                XmlUtil.writeNextValue(
316                        out, XML_TAG_IP_CONFIGURATION_PROXY_PORT,
317                        ipConfiguration.httpProxy.getPort());
318                XmlUtil.writeNextValue(
319                        out, XML_TAG_IP_CONFIGURATION_PROXY_EXCLUSION_LIST,
320                        ipConfiguration.httpProxy.getExclusionListAsString());
321                break;
322            case PAC:
323                XmlUtil.writeNextValue(
324                        out, XML_TAG_IP_CONFIGURATION_PROXY_PAC_FILE,
325                        ipConfiguration.httpProxy.getPacFileUrl().toString());
326                break;
327            default:
328                break;
329        }
330    }
331
332    /**
333     * Parse out the configurations from the back up data.
334     *
335     * @param data raw byte stream representing the XML data.
336     * @return list of networks retrieved from the backed up data.
337     */
338    public List<WifiConfiguration> retrieveConfigurationsFromBackupData(byte[] data) {
339        if (data == null || data.length == 0) {
340            Log.e(TAG, "Invalid backup data received");
341            return null;
342        }
343
344        try {
345            if (mVerboseLoggingEnabled) {
346                mDebugLastBackupDataRestored = data;
347            }
348
349            final XmlPullParser in = Xml.newPullParser();
350            ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
351            in.setInput(inputStream, StandardCharsets.UTF_8.name());
352
353            // Start parsing the XML stream.
354            XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER);
355            int rootTagDepth = in.getDepth();
356
357            int version = (int) XmlUtil.readNextValue(in, XML_TAG_VERSION);
358            if (version < INITIAL_BACKUP_DATA_VERSION || version > CURRENT_BACKUP_DATA_VERSION) {
359                Log.e(TAG, "Invalid version of data: " + version);
360                return null;
361            }
362
363            return parseWifiConfigurationsFromXml(in, rootTagDepth, version);
364        } catch (XmlPullParserException e) {
365            Log.e(TAG, "Error parsing the backup data: " + e);
366        } catch (IOException e) {
367            Log.e(TAG, "Error parsing the backup data: " + e);
368        }
369        return null;
370    }
371
372    /**
373     * Parses the list of configurations from the provided XML stream.
374     *
375     * @param in            XmlPullParser instance pointing to the XML stream.
376     * @param outerTagDepth depth of the outer tag in the XML document.
377     * @param dataVersion   version number parsed from incoming data.
378     * @return List<WifiConfiguration> object if parsing is successful, null otherwise.
379     */
380    private List<WifiConfiguration> parseWifiConfigurationsFromXml(
381            XmlPullParser in, int outerTagDepth, int dataVersion)
382            throws XmlPullParserException, IOException {
383        // Find the configuration list section.
384        if (!XmlUtil.gotoNextSection(
385                in, XML_TAG_SECTION_HEADER_CONFIGURATION_LIST, outerTagDepth)) {
386            Log.e(TAG, "Error parsing the backup data. Did not find configuration list");
387            // Malformed XML input, bail out.
388            return null;
389        }
390        // Find all the configurations within the configuration list section.
391        int confListTagDepth = outerTagDepth + 1;
392        List<WifiConfiguration> configurations = new ArrayList<>();
393        while (XmlUtil.gotoNextSection(
394                in, XML_TAG_SECTION_HEADER_CONFIGURATION, confListTagDepth)) {
395            WifiConfiguration configuration =
396                    parseWifiConfigurationFromXml(in, dataVersion, confListTagDepth);
397            if (configuration != null) {
398                Log.v(TAG, "Parsed Configuration: " + configuration.configKey());
399                configurations.add(configuration);
400            }
401        }
402        return configurations;
403    }
404
405    /**
406     * Parse WepKeys from the XML stream.
407     * Populate wepKeys array only if they were present in the backup data.
408     */
409    private void parseWepKeysFromXml(XmlPullParser in, String[] wepKeys)
410            throws XmlPullParserException, IOException {
411        String[] wepKeysInData =
412                (String[]) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_WEP_KEYS);
413        if (wepKeysInData != null) {
414            for (int i = 0; i < wepKeys.length; i++) {
415                wepKeys[i] = wepKeysInData[i];
416            }
417        }
418    }
419
420    /**
421     * Parses the configuration data elements from the provided XML stream to a Configuration.
422     *
423     * @param in            XmlPullParser instance pointing to the XML stream.
424     * @param outerTagDepth depth of the outer tag in the XML document.
425     * @param dataVersion   version number parsed from incoming data.
426     * @return WifiConfiguration object if parsing is successful, null otherwise.
427     */
428    private WifiConfiguration parseWifiConfigurationFromXml(XmlPullParser in, int dataVersion,
429            int outerTagDepth)
430            throws XmlPullParserException, IOException {
431
432        // Any version migration needs to be handled here in future.
433        if (dataVersion == INITIAL_BACKUP_DATA_VERSION) {
434            WifiConfiguration configuration = new WifiConfiguration();
435            String configKeyInData =
436                    (String) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_CONFIG_KEY);
437            configuration.SSID =
438                    (String) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_SSID);
439            configuration.BSSID =
440                    (String) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_BSSID);
441            configuration.preSharedKey =
442                    (String) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_PRE_SHARED_KEY);
443            parseWepKeysFromXml(in, configuration.wepKeys);
444            configuration.wepTxKeyIndex =
445                    (int) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_WEP_TX_KEY_INDEX);
446            configuration.hiddenSSID =
447                    (boolean) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_HIDDEN_SSID);
448            byte[] allowedKeyMgmt =
449                    (byte[]) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_ALLOWED_KEY_MGMT);
450            configuration.allowedKeyManagement = BitSet.valueOf(allowedKeyMgmt);
451            byte[] allowedProtocols =
452                    (byte[]) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_ALLOWED_PROTOCOLS);
453            configuration.allowedProtocols = BitSet.valueOf(allowedProtocols);
454            byte[] allowedAuthAlgorithms =
455                    (byte[]) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_ALLOWED_AUTH_ALGOS);
456            configuration.allowedAuthAlgorithms = BitSet.valueOf(allowedAuthAlgorithms);
457            configuration.shared =
458                    (boolean) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_SHARED);
459            configuration.creatorUid =
460                    (int) XmlUtil.readNextValue(in, XML_TAG_CONFIGURATION_CREATOR_UID);
461
462            // We should not have all the data to calculate the configKey. Compare it against the
463            // configKey stored in the XML data.
464            String configKeyCalculated = configuration.configKey();
465            if (!configKeyInData.equals(configKeyCalculated)) {
466                Log.e(TAG, "Configuration key does not match. Retrieved: " + configKeyInData
467                        + ", Calculated: " + configKeyCalculated);
468                return null;
469            }
470            // Now retrieve any IP configuration info if present.
471            int confTagDepth = outerTagDepth + 1;
472            if (XmlUtil.gotoNextSection(
473                    in, XML_TAG_SECTION_HEADER_IP_CONFIGURATION, confTagDepth)) {
474                IpConfiguration ipConfiguration = parseIpConfigurationFromXml(in, dataVersion);
475                configuration.setIpConfiguration(ipConfiguration);
476            }
477            return configuration;
478        }
479        return null;
480    }
481
482    /**
483     * Parse out the static IP configuration from the XML stream.
484     */
485    private StaticIpConfiguration parseStaticIpConfigurationFromXml(XmlPullParser in)
486            throws XmlPullParserException, IOException {
487
488        StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
489        String linkAddressString =
490                (String) XmlUtil.readNextValue(in, XML_TAG_IP_CONFIGURATION_LINK_ADDRESS);
491        Integer linkPrefixLength =
492                (Integer) XmlUtil.readNextValue(in, XML_TAG_IP_CONFIGURATION_LINK_PREFIX_LENGTH);
493        if (linkAddressString != null && linkPrefixLength != null) {
494            LinkAddress linkAddress = new LinkAddress(
495                    NetworkUtils.numericToInetAddress(linkAddressString),
496                    linkPrefixLength);
497            if (linkAddress.getAddress() instanceof Inet4Address) {
498                staticIpConfiguration.ipAddress = linkAddress;
499            } else {
500                Log.w(TAG, "Non-IPv4 address: " + linkAddress);
501            }
502        }
503        String gatewayAddressString =
504                (String) XmlUtil.readNextValue(in, XML_TAG_IP_CONFIGURATION_GATEWAY_ADDRESS);
505        if (gatewayAddressString != null) {
506            LinkAddress dest = null;
507            InetAddress gateway =
508                    NetworkUtils.numericToInetAddress(gatewayAddressString);
509            RouteInfo route = new RouteInfo(dest, gateway);
510            if (route.isIPv4Default()) {
511                staticIpConfiguration.gateway = gateway;
512            } else {
513                Log.w(TAG, "Non-IPv4 default route: " + route);
514            }
515        }
516        String[] dnsServerAddressesString =
517                (String[]) XmlUtil.readNextValue(in, XML_TAG_IP_CONFIGURATION_DNS_SERVER_ADDRESSES);
518        if (dnsServerAddressesString != null) {
519            for (String dnsServerAddressString : dnsServerAddressesString) {
520                InetAddress dnsServerAddress =
521                        NetworkUtils.numericToInetAddress(dnsServerAddressString);
522                staticIpConfiguration.dnsServers.add(dnsServerAddress);
523            }
524        }
525        return staticIpConfiguration;
526    }
527
528    /**
529     * Parses the IP configuration data elements from the provided XML stream to a IpConfiguration.
530     *
531     * @param in          XmlPullParser instance pointing to the XML stream.
532     * @param dataVersion version number parsed from incoming data.
533     * @return IpConfiguration object if parsing is successful, null otherwise.
534     */
535    private IpConfiguration parseIpConfigurationFromXml(XmlPullParser in, int dataVersion)
536            throws XmlPullParserException, IOException {
537
538        // Any version migration needs to be handled here in future.
539        if (dataVersion == INITIAL_BACKUP_DATA_VERSION) {
540            IpConfiguration ipConfiguration = new IpConfiguration();
541
542            // Parse out the IP assignment info first.
543            String ipAssignmentString =
544                    (String) XmlUtil.readNextValue(in, XML_TAG_IP_CONFIGURATION_IP_ASSIGNMENT);
545            IpAssignment ipAssignment = IpAssignment.valueOf(ipAssignmentString);
546            ipConfiguration.setIpAssignment(ipAssignment);
547            switch (ipAssignment) {
548                case STATIC:
549                    StaticIpConfiguration staticIpConfiguration =
550                            parseStaticIpConfigurationFromXml(in);
551                    ipConfiguration.setStaticIpConfiguration(staticIpConfiguration);
552                    break;
553                case DHCP:
554                case UNASSIGNED:
555                    break;
556                default:
557                    Log.wtf(TAG, "Unknown ip assignment type: " + ipAssignment);
558                    return null;
559            }
560
561            // Parse out the proxy settings next.
562            String proxySettingsString =
563                    (String) XmlUtil.readNextValue(in, XML_TAG_IP_CONFIGURATION_PROXY_SETTINGS);
564            ProxySettings proxySettings = ProxySettings.valueOf(proxySettingsString);
565            ipConfiguration.setProxySettings(proxySettings);
566            switch (proxySettings) {
567                case STATIC:
568                    String proxyHost =
569                            (String) XmlUtil.readNextValue(in, XML_TAG_IP_CONFIGURATION_PROXY_HOST);
570                    int proxyPort =
571                            (int) XmlUtil.readNextValue(in, XML_TAG_IP_CONFIGURATION_PROXY_PORT);
572                    String proxyExclusionList =
573                            (String) XmlUtil.readNextValue(in,
574                                    XML_TAG_IP_CONFIGURATION_PROXY_EXCLUSION_LIST);
575                    ipConfiguration.setHttpProxy(
576                            new ProxyInfo(proxyHost, proxyPort, proxyExclusionList));
577                    break;
578                case PAC:
579                    String proxyPacFile =
580                            (String) XmlUtil.readNextValue(in,
581                                    XML_TAG_IP_CONFIGURATION_PROXY_PAC_FILE);
582                    ipConfiguration.setHttpProxy(new ProxyInfo(proxyPacFile));
583                    break;
584                case NONE:
585                case UNASSIGNED:
586                    break;
587                default:
588                    Log.wtf(TAG, "Unknown proxy settings type: " + proxySettings);
589                    return null;
590            }
591            return ipConfiguration;
592        }
593        return null;
594    }
595
596    /**
597     * Create log dump of the backup data in XML format with the preShared
598     * key masked.
599     */
600    private String createLogFromBackupData(byte[] data) {
601        String xmlString;
602        try {
603            xmlString = new String(data, StandardCharsets.UTF_8.name());
604            xmlString = xmlString.replaceAll(
605                    PASSWORD_MASK_SEARCH_PATTERN, PASSWORD_MASK_REPLACE_PATTERN);
606        } catch (UnsupportedEncodingException e) {
607            return "";
608        }
609        return xmlString;
610    }
611
612    /**
613     * Restore state from the older supplicant back up data.
614     * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file.
615     *
616     * @param supplicantData Raw byte stream of wpa_supplicant.conf
617     * @param ipConfigData   Raw byte stream of ipconfig.txt
618     * @return list of networks retrieved from the backed up data.
619     */
620    public List<WifiConfiguration> retrieveConfigurationsFromSupplicantBackupData(
621            byte[] supplicantData, byte[] ipConfigData) {
622        if (supplicantData == null || ipConfigData == null
623                || supplicantData.length == 0 || ipConfigData.length == 0) {
624            Log.e(TAG, "Invalid supplicant backup data received");
625            return null;
626        }
627
628        if (mVerboseLoggingEnabled) {
629            mDebugLastSupplicantBackupDataRestored = supplicantData;
630        }
631
632        SupplicantBackupMigration.SupplicantNetworks supplicantNetworks =
633                new SupplicantBackupMigration.SupplicantNetworks();
634        // Incorporate the networks present in the backup data.
635        char[] restoredAsChars = new char[supplicantData.length];
636        for (int i = 0; i < supplicantData.length; i++) {
637            restoredAsChars[i] = (char) supplicantData[i];
638        }
639
640        BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsChars));
641        supplicantNetworks.readNetworksFromStream(in);
642
643        // Retrieve corresponding WifiConfiguration objects.
644        List<WifiConfiguration> configurations = supplicantNetworks.retrieveWifiConfigurations();
645
646        // Now retrieve all the IpConfiguration objects and set in the corresponding
647        // WifiConfiguration objects.
648        SparseArray<IpConfiguration> networks =
649                IpConfigStore.readIpAndProxyConfigurations(new ByteArrayInputStream(ipConfigData));
650        if (networks != null) {
651            for (int i = 0; i < networks.size(); i++) {
652                int id = networks.keyAt(i);
653                for (WifiConfiguration configuration : configurations) {
654                    // This is a dangerous lookup, but that's how it is currently written.
655                    if (configuration.configKey().hashCode() == id) {
656                        configuration.setIpConfiguration(networks.valueAt(i));
657                    }
658                }
659            }
660        } else {
661            Log.e(TAG, "Invalid Ip config data");
662        }
663        return configurations;
664    }
665
666    /**
667     * Enable verbose logging.
668     *
669     * @param verbose verbosity level.
670     */
671    public void enableVerboseLogging(int verbose) {
672        mVerboseLoggingEnabled = (verbose > 0);
673        if (!mVerboseLoggingEnabled) {
674            mDebugLastBackupDataRetrieved = null;
675            mDebugLastBackupDataRestored = null;
676            mDebugLastSupplicantBackupDataRestored = null;
677        }
678    }
679
680    /**
681     * Dump out the last backup/restore data if verbose logging is enabled.
682     *
683     * @param fd   unused
684     * @param pw   PrintWriter for writing dump to
685     * @param args unused
686     */
687    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
688        pw.println("Dump of WifiBackupRestore");
689        if (mDebugLastBackupDataRetrieved != null) {
690            pw.println("Last backup data retrieved: " +
691                    createLogFromBackupData(mDebugLastBackupDataRetrieved));
692        }
693        if (mDebugLastBackupDataRestored != null) {
694            pw.println("Last backup data restored: " +
695                    createLogFromBackupData(mDebugLastBackupDataRestored));
696        }
697        if (mDebugLastSupplicantBackupDataRestored != null) {
698            pw.println("Last old backup data restored: " +
699                    SupplicantBackupMigration.createLogFromBackupData(
700                            mDebugLastSupplicantBackupDataRestored));
701        }
702    }
703
704    /**
705     * These sub classes contain the logic to parse older backups and restore wifi state from it.
706     * Most of the code here has been migrated over from BackupSettingsAgent.
707     * This is kind of ugly text parsing, but it is needed to support the migration of this data.
708     */
709    public static class SupplicantBackupMigration {
710        /**
711         * List of keys to look out for in wpa_supplicant.conf parsing.
712         * These key values are declared in different parts of the wifi codebase today.
713         */
714        @VisibleForTesting
715        public static final String SUPPLICANT_KEY_SSID = WifiConfiguration.ssidVarName;
716        public static final String SUPPLICANT_KEY_HIDDEN = WifiConfiguration.hiddenSSIDVarName;
717        public static final String SUPPLICANT_KEY_KEY_MGMT = WifiConfiguration.KeyMgmt.varName;
718        public static final String SUPPLICANT_KEY_CLIENT_CERT = WifiEnterpriseConfig.CLIENT_CERT_KEY;
719        public static final String SUPPLICANT_KEY_CA_CERT = WifiEnterpriseConfig.CA_CERT_KEY;
720        public static final String SUPPLICANT_KEY_CA_PATH = WifiEnterpriseConfig.CA_PATH_KEY;
721        public static final String SUPPLICANT_KEY_EAP = WifiEnterpriseConfig.EAP_KEY;
722        public static final String SUPPLICANT_KEY_PSK = WifiConfiguration.pskVarName;
723        public static final String SUPPLICANT_KEY_WEP_KEY0 = WifiConfiguration.wepKeyVarNames[0];
724        public static final String SUPPLICANT_KEY_WEP_KEY1 = WifiConfiguration.wepKeyVarNames[1];
725        public static final String SUPPLICANT_KEY_WEP_KEY2 = WifiConfiguration.wepKeyVarNames[2];
726        public static final String SUPPLICANT_KEY_WEP_KEY3 = WifiConfiguration.wepKeyVarNames[3];
727        public static final String SUPPLICANT_KEY_WEP_KEY_IDX = WifiConfiguration.wepTxKeyIdxVarName;
728        public static final String SUPPLICANT_KEY_ID_STR = WifiConfigStore.ID_STRING_VAR_NAME;
729
730        /**
731         * Regex to mask out passwords in backup data dump.
732         */
733        private static final String PASSWORD_MASK_SEARCH_PATTERN =
734                "(.*" + SUPPLICANT_KEY_PSK + ".*=)(.*)";
735        private static final String PASSWORD_MASK_REPLACE_PATTERN = "$1*";
736
737        /**
738         * Class for capturing a network definition from the wifi supplicant config file.
739         */
740        static class SupplicantNetwork {
741            String ssid;
742            String hidden;
743            String key_mgmt;
744            String psk;
745            String[] wepKeys = new String[4];
746            String wepTxKeyIdx;
747            String id_str;
748            boolean certUsed = false;
749            boolean isEap = false;
750
751            /**
752             * Read lines from wpa_supplicant.conf stream for this network.
753             */
754            public static SupplicantNetwork readNetworkFromStream(BufferedReader in) {
755                final SupplicantNetwork n = new SupplicantNetwork();
756                String line;
757                try {
758                    while (in.ready()) {
759                        line = in.readLine();
760                        if (line == null || line.startsWith("}")) {
761                            break;
762                        }
763                        n.parseLine(line);
764                    }
765                } catch (IOException e) {
766                    return null;
767                }
768                return n;
769            }
770
771            /**
772             * Parse a line from wpa_supplicant.conf stream for this network.
773             */
774            void parseLine(String line) {
775                // Can't rely on particular whitespace patterns so strip leading/trailing.
776                line = line.trim();
777                if (line.isEmpty()) return; // only whitespace; drop the line.
778
779                // Now parse the network block within wpa_supplicant.conf and store the important
780                // lines for procesing later.
781                if (line.startsWith(SUPPLICANT_KEY_SSID)) {
782                    ssid = line;
783                } else if (line.startsWith(SUPPLICANT_KEY_HIDDEN)) {
784                    hidden = line;
785                } else if (line.startsWith(SUPPLICANT_KEY_KEY_MGMT)) {
786                    key_mgmt = line;
787                    if (line.contains("EAP")) {
788                        isEap = true;
789                    }
790                } else if (line.startsWith(SUPPLICANT_KEY_CLIENT_CERT)) {
791                    certUsed = true;
792                } else if (line.startsWith(SUPPLICANT_KEY_CA_CERT)) {
793                    certUsed = true;
794                } else if (line.startsWith(SUPPLICANT_KEY_CA_PATH)) {
795                    certUsed = true;
796                } else if (line.startsWith(SUPPLICANT_KEY_EAP)) {
797                    isEap = true;
798                } else if (line.startsWith(SUPPLICANT_KEY_PSK)) {
799                    psk = line;
800                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY0)) {
801                    wepKeys[0] = line;
802                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY1)) {
803                    wepKeys[1] = line;
804                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY2)) {
805                    wepKeys[2] = line;
806                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY3)) {
807                    wepKeys[3] = line;
808                } else if (line.startsWith(SUPPLICANT_KEY_WEP_KEY_IDX)) {
809                    wepTxKeyIdx = line;
810                } else if (line.startsWith(SUPPLICANT_KEY_ID_STR)) {
811                    id_str = line;
812                }
813            }
814
815            /**
816             * Create WifiConfiguration object from the parsed data for this network.
817             */
818            public WifiConfiguration createWifiConfiguration() {
819                if (ssid == null) {
820                    // No SSID => malformed network definition
821                    return null;
822                }
823                WifiConfiguration configuration = new WifiConfiguration();
824                configuration.SSID = ssid.substring(ssid.indexOf('=') + 1);
825
826                if (hidden != null) {
827                    // Can't use Boolean.valueOf() because it works only for true/false.
828                    configuration.hiddenSSID =
829                            Integer.parseInt(hidden.substring(hidden.indexOf('=') + 1)) != 0;
830                }
831                if (key_mgmt == null) {
832                    // no key_mgmt specified; this is defined as equivalent to "WPA-PSK WPA-EAP"
833                    configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
834                    configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
835                } else {
836                    // Need to parse the key_mgmt line
837                    final String bareKeyMgmt = key_mgmt.substring(key_mgmt.indexOf('=') + 1);
838                    String[] typeStrings = bareKeyMgmt.split("\\s+");
839
840                    // Parse out all the key management regimes permitted for this network.
841                    // The literal strings here are the standard values permitted in
842                    // wpa_supplicant.conf.
843                    for (int i = 0; i < typeStrings.length; i++) {
844                        final String ktype = typeStrings[i];
845                        if (ktype.equals("NONE")) {
846                            configuration.allowedKeyManagement.set(
847                                    WifiConfiguration.KeyMgmt.NONE);
848                        } else if (ktype.equals("WPA-PSK")) {
849                            configuration.allowedKeyManagement.set(
850                                    WifiConfiguration.KeyMgmt.WPA_PSK);
851                        } else if (ktype.equals("WPA-EAP")) {
852                            configuration.allowedKeyManagement.set(
853                                    WifiConfiguration.KeyMgmt.WPA_EAP);
854                        } else if (ktype.equals("IEEE8021X")) {
855                            configuration.allowedKeyManagement.set(
856                                    WifiConfiguration.KeyMgmt.IEEE8021X);
857                        }
858                    }
859                }
860                if (psk != null) {
861                    configuration.preSharedKey = psk.substring(psk.indexOf('=') + 1);
862                }
863                if (wepKeys[0] != null) {
864                    configuration.wepKeys[0] = wepKeys[0].substring(wepKeys[0].indexOf('=') + 1);
865                }
866                if (wepKeys[1] != null) {
867                    configuration.wepKeys[1] = wepKeys[1].substring(wepKeys[1].indexOf('=') + 1);
868                }
869                if (wepKeys[2] != null) {
870                    configuration.wepKeys[2] = wepKeys[2].substring(wepKeys[2].indexOf('=') + 1);
871                }
872                if (wepKeys[3] != null) {
873                    configuration.wepKeys[3] = wepKeys[3].substring(wepKeys[3].indexOf('=') + 1);
874                }
875                if (wepTxKeyIdx != null) {
876                    configuration.wepTxKeyIndex =
877                            Integer.valueOf(wepTxKeyIdx.substring(wepTxKeyIdx.indexOf('=') + 1));
878                }
879                if (id_str != null) {
880                    String id_string = id_str.substring(id_str.indexOf('=') + 1);
881                    Map<String, String> extras = WifiNative.parseNetworkExtra(id_string);
882                    configuration.creatorUid =
883                            Integer.valueOf(extras.get(WifiConfigStore.ID_STRING_KEY_CREATOR_UID));
884                    String configKey = extras.get(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY);
885                    if (!configKey.equals(configuration.configKey())) {
886                        Log.e(TAG, "Configuration key does not match. Retrieved: " + configKey
887                                + ", Calculated: " + configuration.configKey());
888                        return null;
889                    }
890                }
891                return configuration;
892            }
893        }
894
895        /**
896         * Ingest multiple wifi config fragments from wpa_supplicant.conf, looking for network={}
897         * blocks and eliminating duplicates
898         */
899        static class SupplicantNetworks {
900            final ArrayList<SupplicantNetwork> mNetworks = new ArrayList<>(8);
901
902            /**
903             * Parse the wpa_supplicant.conf file stream and add networks.
904             */
905            public void readNetworksFromStream(BufferedReader in) {
906                try {
907                    String line;
908                    while (in.ready()) {
909                        line = in.readLine();
910                        if (line != null) {
911                            // Parse out 'network=' decls so we can ignore duplicates
912                            if (line.startsWith("network")) {
913                                SupplicantNetwork net = SupplicantNetwork.readNetworkFromStream(in);
914                                // Don't propagate EAP network definitions
915                                if (net.isEap) {
916                                    Log.d(TAG, "Skipping EAP network " + net.ssid + " / "
917                                            + net.key_mgmt);
918                                    continue;
919                                }
920                                mNetworks.add(net);
921                            }
922                        }
923                    }
924                } catch (IOException e) {
925                    // whatever happened, we're done now
926                }
927            }
928
929            /**
930             * Retrieve a list of WifiConfiguration objects parsed from wpa_supplicant.conf
931             *
932             * @return
933             */
934            public List<WifiConfiguration> retrieveWifiConfigurations() {
935                ArrayList<WifiConfiguration> wifiConfigurations = new ArrayList<>();
936                for (SupplicantNetwork net : mNetworks) {
937                    if (net.certUsed) {
938                        // Networks that use certificates for authentication can't be restored
939                        // because the certificates they need don't get restored (because they
940                        // are stored in keystore, and can't be restored)
941                        continue;
942                    }
943
944                    if (net.isEap) {
945                        // Similarly, omit EAP network definitions to avoid propagating
946                        // controlled enterprise network definitions.
947                        continue;
948                    }
949                    WifiConfiguration wifiConfiguration = net.createWifiConfiguration();
950                    if (wifiConfiguration != null) {
951                        Log.v(TAG, "Parsed Configuration: " + wifiConfiguration.configKey());
952                        wifiConfigurations.add(wifiConfiguration);
953                    }
954                }
955                return wifiConfigurations;
956            }
957        }
958
959        /**
960         * Create log dump of the backup data in wpa_supplicant.conf format with the preShared
961         * key masked.
962         */
963        public static String createLogFromBackupData(byte[] data) {
964            String supplicantConfString;
965            try {
966                supplicantConfString = new String(data, StandardCharsets.UTF_8.name());
967                supplicantConfString = supplicantConfString.replaceAll(
968                        PASSWORD_MASK_SEARCH_PATTERN, PASSWORD_MASK_REPLACE_PATTERN);
969            } catch (UnsupportedEncodingException e) {
970                return "";
971            }
972            return supplicantConfString;
973        }
974    }
975}
976