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