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