1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chromeos/network/network_state.h" 6 7 #include "base/i18n/icu_encoding_detection.h" 8 #include "base/i18n/icu_string_conversions.h" 9 #include "base/json/json_writer.h" 10 #include "base/strings/string_number_conversions.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/stringprintf.h" 13 #include "base/strings/utf_string_conversion_utils.h" 14 #include "chromeos/network/network_event_log.h" 15 #include "chromeos/network/network_profile_handler.h" 16 #include "chromeos/network/network_util.h" 17 #include "chromeos/network/onc/onc_utils.h" 18 #include "third_party/cros_system_api/dbus/service_constants.h" 19 20 namespace { 21 22 const char kErrorUnknown[] = "Unknown"; 23 24 bool ConvertListValueToStringVector(const base::ListValue& string_list, 25 std::vector<std::string>* result) { 26 for (size_t i = 0; i < string_list.GetSize(); ++i) { 27 std::string str; 28 if (!string_list.GetString(i, &str)) 29 return false; 30 result->push_back(str); 31 } 32 return true; 33 } 34 35 // Replace non UTF8 characters in |str| with a replacement character. 36 std::string ValidateUTF8(const std::string& str) { 37 std::string result; 38 for (int32 index = 0; index < static_cast<int32>(str.size()); ++index) { 39 uint32 code_point_out; 40 bool is_unicode_char = base::ReadUnicodeCharacter(str.c_str(), str.size(), 41 &index, &code_point_out); 42 const uint32 kFirstNonControlChar = 0x20; 43 if (is_unicode_char && (code_point_out >= kFirstNonControlChar)) { 44 base::WriteUnicodeCharacter(code_point_out, &result); 45 } else { 46 const uint32 kReplacementChar = 0xFFFD; 47 // Puts kReplacementChar if character is a control character [0,0x20) 48 // or is not readable UTF8. 49 base::WriteUnicodeCharacter(kReplacementChar, &result); 50 } 51 } 52 return result; 53 } 54 55 bool IsCaCertNssSet(const base::DictionaryValue& properties) { 56 std::string ca_cert_nss; 57 if (properties.GetStringWithoutPathExpansion(flimflam::kEapCaCertNssProperty, 58 &ca_cert_nss) && 59 !ca_cert_nss.empty()) { 60 return true; 61 } 62 63 const base::DictionaryValue* provider = NULL; 64 properties.GetDictionaryWithoutPathExpansion(flimflam::kProviderProperty, 65 &provider); 66 if (!provider) 67 return false; 68 if (provider->GetStringWithoutPathExpansion( 69 flimflam::kL2tpIpsecCaCertNssProperty, &ca_cert_nss) && 70 !ca_cert_nss.empty()) { 71 return true; 72 } 73 if (provider->GetStringWithoutPathExpansion( 74 flimflam::kOpenVPNCaCertNSSProperty, &ca_cert_nss) && 75 !ca_cert_nss.empty()) { 76 return true; 77 } 78 79 return false; 80 } 81 82 } // namespace 83 84 namespace chromeos { 85 86 NetworkState::NetworkState(const std::string& path) 87 : ManagedState(MANAGED_TYPE_NETWORK, path), 88 auto_connect_(false), 89 favorite_(false), 90 priority_(0), 91 prefix_length_(0), 92 signal_strength_(0), 93 connectable_(false), 94 activate_over_non_cellular_networks_(false), 95 cellular_out_of_credits_(false), 96 has_ca_cert_nss_(false) { 97 } 98 99 NetworkState::~NetworkState() { 100 } 101 102 bool NetworkState::PropertyChanged(const std::string& key, 103 const base::Value& value) { 104 // Keep care that these properties are the same as in |GetProperties|. 105 if (ManagedStatePropertyChanged(key, value)) 106 return true; 107 if (key == flimflam::kSignalStrengthProperty) { 108 return GetIntegerValue(key, value, &signal_strength_); 109 } else if (key == flimflam::kStateProperty) { 110 return GetStringValue(key, value, &connection_state_); 111 } else if (key == flimflam::kConnectableProperty) { 112 return GetBooleanValue(key, value, &connectable_); 113 } else if (key == flimflam::kErrorProperty) { 114 if (!GetStringValue(key, value, &error_)) 115 return false; 116 // Shill uses "Unknown" to indicate an unset error state. 117 if (error_ == kErrorUnknown) 118 error_.clear(); 119 return true; 120 } else if (key == shill::kErrorDetailsProperty) { 121 return GetStringValue(key, value, &error_details_); 122 } else if (key == IPConfigProperty(flimflam::kAddressProperty)) { 123 return GetStringValue(key, value, &ip_address_); 124 } else if (key == IPConfigProperty(flimflam::kGatewayProperty)) { 125 return GetStringValue(key, value, &gateway_); 126 } else if (key == IPConfigProperty(flimflam::kNameServersProperty)) { 127 const base::ListValue* dns_servers; 128 if (!value.GetAsList(&dns_servers)) 129 return false; 130 dns_servers_.clear(); 131 ConvertListValueToStringVector(*dns_servers, &dns_servers_); 132 return true; 133 } else if (key == IPConfigProperty(flimflam::kPrefixlenProperty)) { 134 return GetIntegerValue(key, value, &prefix_length_); 135 } else if (key == IPConfigProperty( 136 shill::kWebProxyAutoDiscoveryUrlProperty)) { 137 std::string url_string; 138 if (!GetStringValue(key, value, &url_string)) 139 return false; 140 if (url_string.empty()) { 141 web_proxy_auto_discovery_url_ = GURL(); 142 } else { 143 GURL gurl(url_string); 144 if (!gurl.is_valid()) { 145 web_proxy_auto_discovery_url_ = gurl; 146 } else { 147 NET_LOG_ERROR("Invalid WebProxyAutoDiscoveryUrl: " + url_string, 148 path()); 149 web_proxy_auto_discovery_url_ = GURL(); 150 } 151 } 152 return true; 153 } else if (key == flimflam::kActivationStateProperty) { 154 return GetStringValue(key, value, &activation_state_); 155 } else if (key == flimflam::kRoamingStateProperty) { 156 return GetStringValue(key, value, &roaming_); 157 } else if (key == flimflam::kSecurityProperty) { 158 return GetStringValue(key, value, &security_); 159 } else if (key == flimflam::kAutoConnectProperty) { 160 return GetBooleanValue(key, value, &auto_connect_); 161 } else if (key == flimflam::kFavoriteProperty) { 162 return GetBooleanValue(key, value, &favorite_); 163 } else if (key == flimflam::kPriorityProperty) { 164 return GetIntegerValue(key, value, &priority_); 165 } else if (key == flimflam::kProxyConfigProperty) { 166 std::string proxy_config_str; 167 if (!value.GetAsString(&proxy_config_str)) { 168 NET_LOG_ERROR("Failed to parse " + key, path()); 169 return false; 170 } 171 172 proxy_config_.Clear(); 173 if (proxy_config_str.empty()) 174 return true; 175 176 scoped_ptr<base::DictionaryValue> proxy_config_dict( 177 onc::ReadDictionaryFromJson(proxy_config_str)); 178 if (proxy_config_dict) { 179 // Warning: The DictionaryValue returned from 180 // ReadDictionaryFromJson/JSONParser is an optimized derived class that 181 // doesn't allow releasing ownership of nested values. A Swap in the wrong 182 // order leads to memory access errors. 183 proxy_config_.MergeDictionary(proxy_config_dict.get()); 184 } else { 185 NET_LOG_ERROR("Failed to parse " + key, path()); 186 } 187 return true; 188 } else if (key == flimflam::kUIDataProperty) { 189 if (!GetUIDataFromValue(value, &ui_data_)) { 190 NET_LOG_ERROR("Failed to parse " + key, path()); 191 return false; 192 } 193 return true; 194 } else if (key == flimflam::kNetworkTechnologyProperty) { 195 return GetStringValue(key, value, &network_technology_); 196 } else if (key == flimflam::kDeviceProperty) { 197 return GetStringValue(key, value, &device_path_); 198 } else if (key == flimflam::kGuidProperty) { 199 return GetStringValue(key, value, &guid_); 200 } else if (key == flimflam::kProfileProperty) { 201 return GetStringValue(key, value, &profile_path_); 202 } else if (key == shill::kActivateOverNonCellularNetworkProperty) { 203 return GetBooleanValue(key, value, &activate_over_non_cellular_networks_); 204 } else if (key == shill::kOutOfCreditsProperty) { 205 return GetBooleanValue(key, value, &cellular_out_of_credits_); 206 } else if (key == flimflam::kUsageURLProperty) { 207 return GetStringValue(key, value, &usage_url_); 208 } else if (key == flimflam::kPaymentPortalProperty) { 209 const DictionaryValue* dict; 210 if (!value.GetAsDictionary(&dict)) 211 return false; 212 if (!dict->GetStringWithoutPathExpansion( 213 flimflam::kPaymentPortalURL, &payment_url_) || 214 !dict->GetStringWithoutPathExpansion( 215 flimflam::kPaymentPortalMethod, &post_method_) || 216 !dict->GetStringWithoutPathExpansion( 217 flimflam::kPaymentPortalPostData, &post_data_)) { 218 return false; 219 } 220 return true; 221 } 222 return false; 223 } 224 225 bool NetworkState::InitialPropertiesReceived( 226 const base::DictionaryValue& properties) { 227 NET_LOG_DEBUG("InitialPropertiesReceived", path()); 228 bool changed = UpdateName(properties); 229 bool had_ca_cert_nss = has_ca_cert_nss_; 230 has_ca_cert_nss_ = IsCaCertNssSet(properties); 231 changed |= had_ca_cert_nss != has_ca_cert_nss_; 232 return changed; 233 } 234 235 void NetworkState::GetProperties(base::DictionaryValue* dictionary) const { 236 // Keep care that these properties are the same as in |PropertyChanged|. 237 dictionary->SetStringWithoutPathExpansion(flimflam::kNameProperty, name()); 238 dictionary->SetStringWithoutPathExpansion(flimflam::kTypeProperty, type()); 239 dictionary->SetIntegerWithoutPathExpansion(flimflam::kSignalStrengthProperty, 240 signal_strength_); 241 dictionary->SetStringWithoutPathExpansion(flimflam::kStateProperty, 242 connection_state_); 243 dictionary->SetBooleanWithoutPathExpansion(flimflam::kConnectableProperty, 244 connectable_); 245 246 dictionary->SetStringWithoutPathExpansion(flimflam::kErrorProperty, 247 error_); 248 dictionary->SetStringWithoutPathExpansion(shill::kErrorDetailsProperty, 249 error_details_); 250 251 // IPConfig properties 252 base::DictionaryValue* ipconfig_properties = new base::DictionaryValue; 253 ipconfig_properties->SetStringWithoutPathExpansion(flimflam::kAddressProperty, 254 ip_address_); 255 ipconfig_properties->SetStringWithoutPathExpansion(flimflam::kGatewayProperty, 256 gateway_); 257 base::ListValue* name_servers = new base::ListValue; 258 name_servers->AppendStrings(dns_servers_); 259 ipconfig_properties->SetWithoutPathExpansion(flimflam::kNameServersProperty, 260 name_servers); 261 ipconfig_properties->SetIntegerWithoutPathExpansion( 262 flimflam::kPrefixlenProperty, prefix_length_); 263 ipconfig_properties->SetStringWithoutPathExpansion( 264 shill::kWebProxyAutoDiscoveryUrlProperty, 265 web_proxy_auto_discovery_url_.spec()); 266 dictionary->SetWithoutPathExpansion(shill::kIPConfigProperty, 267 ipconfig_properties); 268 269 dictionary->SetStringWithoutPathExpansion(flimflam::kActivationStateProperty, 270 activation_state_); 271 dictionary->SetStringWithoutPathExpansion(flimflam::kRoamingStateProperty, 272 roaming_); 273 dictionary->SetStringWithoutPathExpansion(flimflam::kSecurityProperty, 274 security_); 275 dictionary->SetBooleanWithoutPathExpansion(flimflam::kAutoConnectProperty, 276 auto_connect_); 277 dictionary->SetBooleanWithoutPathExpansion(flimflam::kFavoriteProperty, 278 favorite_); 279 dictionary->SetIntegerWithoutPathExpansion(flimflam::kPriorityProperty, 280 priority_); 281 // Proxy config and ONC source are intentionally omitted: These properties are 282 // placed in NetworkState to transition ProxyConfigServiceImpl from 283 // NetworkLibrary to the new network stack. The networking extension API 284 // shouldn't depend on this member. Once ManagedNetworkConfigurationHandler 285 // is used instead of NetworkLibrary, we can remove them again. 286 dictionary->SetStringWithoutPathExpansion( 287 flimflam::kNetworkTechnologyProperty, 288 network_technology_); 289 dictionary->SetStringWithoutPathExpansion(flimflam::kDeviceProperty, 290 device_path_); 291 dictionary->SetStringWithoutPathExpansion(flimflam::kGuidProperty, guid_); 292 dictionary->SetStringWithoutPathExpansion(flimflam::kProfileProperty, 293 profile_path_); 294 dictionary->SetBooleanWithoutPathExpansion( 295 shill::kActivateOverNonCellularNetworkProperty, 296 activate_over_non_cellular_networks_); 297 dictionary->SetBooleanWithoutPathExpansion(shill::kOutOfCreditsProperty, 298 cellular_out_of_credits_); 299 base::DictionaryValue* payment_portal_properties = new DictionaryValue; 300 payment_portal_properties->SetStringWithoutPathExpansion( 301 flimflam::kPaymentPortalURL, 302 payment_url_); 303 payment_portal_properties->SetStringWithoutPathExpansion( 304 flimflam::kPaymentPortalMethod, 305 post_method_); 306 payment_portal_properties->SetStringWithoutPathExpansion( 307 flimflam::kPaymentPortalPostData, 308 post_data_); 309 dictionary->SetWithoutPathExpansion(flimflam::kPaymentPortalProperty, 310 payment_portal_properties); 311 } 312 313 bool NetworkState::IsConnectedState() const { 314 return StateIsConnected(connection_state_); 315 } 316 317 bool NetworkState::IsConnectingState() const { 318 return StateIsConnecting(connection_state_); 319 } 320 321 bool NetworkState::IsManaged() const { 322 return ui_data_.onc_source() == onc::ONC_SOURCE_DEVICE_POLICY || 323 ui_data_.onc_source() == onc::ONC_SOURCE_USER_POLICY; 324 } 325 326 bool NetworkState::IsPrivate() const { 327 return !profile_path_.empty() && 328 profile_path_ != NetworkProfileHandler::kSharedProfilePath; 329 } 330 331 std::string NetworkState::GetDnsServersAsString() const { 332 std::string result; 333 for (size_t i = 0; i < dns_servers_.size(); ++i) { 334 if (i != 0) 335 result += ","; 336 result += dns_servers_[i]; 337 } 338 return result; 339 } 340 341 std::string NetworkState::GetNetmask() const { 342 return network_util::PrefixLengthToNetmask(prefix_length_); 343 } 344 345 bool NetworkState::UpdateName(const base::DictionaryValue& properties) { 346 std::string updated_name = GetNameFromProperties(path(), properties); 347 if (updated_name != name()) { 348 set_name(updated_name); 349 return true; 350 } 351 return false; 352 } 353 354 // static 355 std::string NetworkState::GetNameFromProperties( 356 const std::string& service_path, 357 const base::DictionaryValue& properties) { 358 std::string name; 359 properties.GetStringWithoutPathExpansion(flimflam::kNameProperty, &name); 360 361 std::string hex_ssid; 362 properties.GetStringWithoutPathExpansion(flimflam::kWifiHexSsid, &hex_ssid); 363 364 if (hex_ssid.empty()) { 365 // Validate name for UTF8. 366 std::string valid_ssid = ValidateUTF8(name); 367 if (valid_ssid != name) { 368 NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf( 369 "%s: UTF8: %s", service_path.c_str(), valid_ssid.c_str())); 370 } 371 return valid_ssid; 372 } 373 374 std::string ssid; 375 std::vector<uint8> raw_ssid_bytes; 376 if (base::HexStringToBytes(hex_ssid, &raw_ssid_bytes)) { 377 ssid = std::string(raw_ssid_bytes.begin(), raw_ssid_bytes.end()); 378 NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf( 379 "%s: %s, SSID: %s", service_path.c_str(), 380 hex_ssid.c_str(), ssid.c_str())); 381 } else { 382 NET_LOG_ERROR("GetNameFromProperties", 383 base::StringPrintf("%s: Error processing: %s", 384 service_path.c_str(), hex_ssid.c_str())); 385 return name; 386 } 387 388 if (IsStringUTF8(ssid)) { 389 if (ssid != name) { 390 NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf( 391 "%s: UTF8: %s", service_path.c_str(), ssid.c_str())); 392 } 393 return ssid; 394 } 395 396 // Detect encoding and convert to UTF-8. 397 std::string country_code; 398 properties.GetStringWithoutPathExpansion( 399 flimflam::kCountryProperty, &country_code); 400 std::string encoding; 401 if (!base::DetectEncoding(ssid, &encoding)) { 402 // TODO(stevenjb): This is currently experimental. If we find a case where 403 // base::DetectEncoding() fails, we need to figure out whether we can use 404 // country_code with ConvertToUtf8(). crbug.com/233267. 405 encoding = country_code; 406 } 407 if (!encoding.empty()) { 408 std::string utf8_ssid; 409 if (base::ConvertToUtf8AndNormalize(ssid, encoding, &utf8_ssid)) { 410 if (utf8_ssid != name) { 411 NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf( 412 "%s: Encoding=%s: %s", service_path.c_str(), 413 encoding.c_str(), utf8_ssid.c_str())); 414 } 415 return utf8_ssid; 416 } 417 } 418 419 // Unrecognized encoding. Only use raw bytes if name_ is empty. 420 NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf( 421 "%s: Unrecognized Encoding=%s: %s", service_path.c_str(), 422 encoding.c_str(), ssid.c_str())); 423 if (name.empty() && !ssid.empty()) 424 return ssid; 425 return name; 426 } 427 428 // static 429 bool NetworkState::StateIsConnected(const std::string& connection_state) { 430 return (connection_state == flimflam::kStateReady || 431 connection_state == flimflam::kStateOnline || 432 connection_state == flimflam::kStatePortal); 433 } 434 435 // static 436 bool NetworkState::StateIsConnecting(const std::string& connection_state) { 437 return (connection_state == flimflam::kStateAssociation || 438 connection_state == flimflam::kStateConfiguration || 439 connection_state == flimflam::kStateCarrier); 440 } 441 442 // static 443 std::string NetworkState::IPConfigProperty(const char* key) { 444 return base::StringPrintf("%s.%s", shill::kIPConfigProperty, key); 445 } 446 447 // static 448 bool NetworkState::GetUIDataFromValue(const base::Value& ui_data_value, 449 NetworkUIData* out) { 450 std::string ui_data_str; 451 if (!ui_data_value.GetAsString(&ui_data_str)) 452 return false; 453 if (ui_data_str.empty()) { 454 *out = NetworkUIData(); 455 return true; 456 } 457 scoped_ptr<base::DictionaryValue> ui_data_dict( 458 chromeos::onc::ReadDictionaryFromJson(ui_data_str)); 459 if (!ui_data_dict) 460 return false; 461 *out = NetworkUIData(*ui_data_dict); 462 return true; 463 } 464 465 } // namespace chromeos 466