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/onc/onc_translator.h" 6 7 #include <string> 8 9 #include "base/basictypes.h" 10 #include "base/json/json_reader.h" 11 #include "base/json/json_writer.h" 12 #include "base/logging.h" 13 #include "base/values.h" 14 #include "chromeos/network/network_state.h" 15 #include "chromeos/network/network_util.h" 16 #include "chromeos/network/onc/onc_signature.h" 17 #include "chromeos/network/onc/onc_translation_tables.h" 18 #include "chromeos/network/shill_property_util.h" 19 #include "components/onc/onc_constants.h" 20 #include "third_party/cros_system_api/dbus/service_constants.h" 21 22 namespace chromeos { 23 namespace onc { 24 25 namespace { 26 27 // Converts |str| to a base::Value of the given |type|. If the conversion fails, 28 // returns NULL. 29 scoped_ptr<base::Value> ConvertStringToValue(const std::string& str, 30 base::Value::Type type) { 31 base::Value* value; 32 if (type == base::Value::TYPE_STRING) { 33 value = base::Value::CreateStringValue(str); 34 } else { 35 value = base::JSONReader::Read(str); 36 } 37 38 if (value == NULL || value->GetType() != type) { 39 delete value; 40 value = NULL; 41 } 42 return make_scoped_ptr(value); 43 } 44 45 // This class implements the translation of properties from the given 46 // |shill_dictionary| to a new ONC object of signature |onc_signature|. Using 47 // recursive calls to CreateTranslatedONCObject of new instances, nested objects 48 // are translated. 49 class ShillToONCTranslator { 50 public: 51 ShillToONCTranslator(const base::DictionaryValue& shill_dictionary, 52 const OncValueSignature& onc_signature) 53 : shill_dictionary_(&shill_dictionary), 54 onc_signature_(&onc_signature) { 55 field_translation_table_ = GetFieldTranslationTable(onc_signature); 56 } 57 58 // Translates the associated Shill dictionary and creates an ONC object of the 59 // given signature. 60 scoped_ptr<base::DictionaryValue> CreateTranslatedONCObject(); 61 62 private: 63 void TranslateEthernet(); 64 void TranslateOpenVPN(); 65 void TranslateIPsec(); 66 void TranslateVPN(); 67 void TranslateWiFiWithState(); 68 void TranslateCellularWithState(); 69 void TranslateNetworkWithState(); 70 void TranslateIPConfig(); 71 72 // Creates an ONC object from |dictionary| according to the signature 73 // associated to |onc_field_name| and adds it to |onc_object_| at 74 // |onc_field_name|. 75 void TranslateAndAddNestedObject(const std::string& onc_field_name, 76 const base::DictionaryValue& dictionary); 77 78 // Creates an ONC object from |shill_dictionary_| according to the signature 79 // associated to |onc_field_name| and adds it to |onc_object_| at 80 // |onc_field_name|. 81 void TranslateAndAddNestedObject(const std::string& onc_field_name); 82 83 // Translates a list of nested objects and adds the list to |onc_object_| at 84 // |onc_field_name|. If there are errors while parsing individual objects or 85 // if the resulting list contains no entries, the result will not be added to 86 // |onc_object_|. 87 void TranslateAndAddListOfObjects(const std::string& onc_field_name, 88 const base::ListValue& list); 89 90 // Applies function CopyProperty to each field of |value_signature| and its 91 // base signatures. 92 void CopyPropertiesAccordingToSignature( 93 const OncValueSignature* value_signature); 94 95 // Applies function CopyProperty to each field of |onc_signature_| and its 96 // base signatures. 97 void CopyPropertiesAccordingToSignature(); 98 99 // If |shill_property_name| is defined in |field_signature|, copies this 100 // entry from |shill_dictionary_| to |onc_object_| if it exists. 101 void CopyProperty(const OncFieldSignature* field_signature); 102 103 // If existent, translates the entry at |shill_property_name| in 104 // |shill_dictionary_| using |table|. It is an error if no matching table 105 // entry is found. Writes the result as entry at |onc_field_name| in 106 // |onc_object_|. 107 void TranslateWithTableAndSet(const std::string& shill_property_name, 108 const StringTranslationEntry table[], 109 const std::string& onc_field_name); 110 111 const base::DictionaryValue* shill_dictionary_; 112 const OncValueSignature* onc_signature_; 113 const FieldTranslationEntry* field_translation_table_; 114 scoped_ptr<base::DictionaryValue> onc_object_; 115 116 DISALLOW_COPY_AND_ASSIGN(ShillToONCTranslator); 117 }; 118 119 scoped_ptr<base::DictionaryValue> 120 ShillToONCTranslator::CreateTranslatedONCObject() { 121 onc_object_.reset(new base::DictionaryValue); 122 if (onc_signature_ == &kNetworkWithStateSignature) { 123 TranslateNetworkWithState(); 124 } else if (onc_signature_ == &kEthernetSignature) { 125 TranslateEthernet(); 126 } else if (onc_signature_ == &kVPNSignature) { 127 TranslateVPN(); 128 } else if (onc_signature_ == &kOpenVPNSignature) { 129 TranslateOpenVPN(); 130 } else if (onc_signature_ == &kIPsecSignature) { 131 TranslateIPsec(); 132 } else if (onc_signature_ == &kWiFiWithStateSignature) { 133 TranslateWiFiWithState(); 134 } else if (onc_signature_ == &kCellularWithStateSignature) { 135 TranslateCellularWithState(); 136 } else if (onc_signature_ == &kIPConfigSignature) { 137 TranslateIPConfig(); 138 } else { 139 CopyPropertiesAccordingToSignature(); 140 } 141 return onc_object_.Pass(); 142 } 143 144 void ShillToONCTranslator::TranslateEthernet() { 145 std::string shill_network_type; 146 shill_dictionary_->GetStringWithoutPathExpansion(shill::kTypeProperty, 147 &shill_network_type); 148 const char* onc_auth = ::onc::ethernet::kNone; 149 if (shill_network_type == shill::kTypeEthernetEap) 150 onc_auth = ::onc::ethernet::k8021X; 151 onc_object_->SetStringWithoutPathExpansion(::onc::ethernet::kAuthentication, 152 onc_auth); 153 } 154 155 void ShillToONCTranslator::TranslateOpenVPN() { 156 if (shill_dictionary_->HasKey(shill::kOpenVPNVerifyX509NameProperty)) 157 TranslateAndAddNestedObject(::onc::openvpn::kVerifyX509); 158 159 // Shill supports only one RemoteCertKU but ONC requires a list. If existing, 160 // wraps the value into a list. 161 std::string certKU; 162 if (shill_dictionary_->GetStringWithoutPathExpansion( 163 shill::kOpenVPNRemoteCertKUProperty, &certKU)) { 164 scoped_ptr<base::ListValue> certKUs(new base::ListValue); 165 certKUs->AppendString(certKU); 166 onc_object_->SetWithoutPathExpansion(::onc::openvpn::kRemoteCertKU, 167 certKUs.release()); 168 } 169 170 for (const OncFieldSignature* field_signature = onc_signature_->fields; 171 field_signature->onc_field_name != NULL; ++field_signature) { 172 const std::string& onc_field_name = field_signature->onc_field_name; 173 if (onc_field_name == ::onc::vpn::kSaveCredentials || 174 onc_field_name == ::onc::openvpn::kRemoteCertKU || 175 onc_field_name == ::onc::openvpn::kServerCAPEMs) { 176 CopyProperty(field_signature); 177 continue; 178 } 179 180 std::string shill_property_name; 181 const base::Value* shill_value = NULL; 182 if (!field_translation_table_ || 183 !GetShillPropertyName(field_signature->onc_field_name, 184 field_translation_table_, 185 &shill_property_name) || 186 !shill_dictionary_->GetWithoutPathExpansion(shill_property_name, 187 &shill_value)) { 188 continue; 189 } 190 191 scoped_ptr<base::Value> translated; 192 std::string shill_str; 193 if (shill_value->GetAsString(&shill_str)) { 194 // Shill wants all Provider/VPN fields to be strings. Translates these 195 // strings back to the correct ONC type. 196 translated = ConvertStringToValue( 197 shill_str, 198 field_signature->value_signature->onc_type); 199 200 if (translated.get() == NULL) { 201 LOG(ERROR) << "Shill property '" << shill_property_name 202 << "' with value " << *shill_value 203 << " couldn't be converted to base::Value::Type " 204 << field_signature->value_signature->onc_type; 205 } else { 206 onc_object_->SetWithoutPathExpansion(onc_field_name, 207 translated.release()); 208 } 209 } else { 210 LOG(ERROR) << "Shill property '" << shill_property_name 211 << "' has value " << *shill_value 212 << ", but expected a string"; 213 } 214 } 215 } 216 217 void ShillToONCTranslator::TranslateIPsec() { 218 CopyPropertiesAccordingToSignature(); 219 if (shill_dictionary_->HasKey(shill::kL2tpIpsecXauthUserProperty)) 220 TranslateAndAddNestedObject(::onc::ipsec::kXAUTH); 221 } 222 223 void ShillToONCTranslator::TranslateVPN() { 224 TranslateWithTableAndSet( 225 shill::kProviderTypeProperty, kVPNTypeTable, ::onc::vpn::kType); 226 CopyPropertiesAccordingToSignature(); 227 228 std::string vpn_type; 229 if (onc_object_->GetStringWithoutPathExpansion(::onc::vpn::kType, 230 &vpn_type)) { 231 if (vpn_type == ::onc::vpn::kTypeL2TP_IPsec) { 232 TranslateAndAddNestedObject(::onc::vpn::kIPsec); 233 TranslateAndAddNestedObject(::onc::vpn::kL2TP); 234 } else { 235 TranslateAndAddNestedObject(vpn_type); 236 } 237 } 238 } 239 240 void ShillToONCTranslator::TranslateWiFiWithState() { 241 TranslateWithTableAndSet( 242 shill::kSecurityProperty, kWiFiSecurityTable, ::onc::wifi::kSecurity); 243 std::string ssid = shill_property_util::GetSSIDFromProperties( 244 *shill_dictionary_, NULL /* ignore unknown encoding */); 245 if (!ssid.empty()) 246 onc_object_->SetStringWithoutPathExpansion(::onc::wifi::kSSID, ssid); 247 CopyPropertiesAccordingToSignature(); 248 } 249 250 void ShillToONCTranslator::TranslateCellularWithState() { 251 CopyPropertiesAccordingToSignature(); 252 const base::DictionaryValue* dictionary = NULL; 253 if (shill_dictionary_->GetDictionaryWithoutPathExpansion( 254 shill::kServingOperatorProperty, &dictionary)) { 255 TranslateAndAddNestedObject(::onc::cellular::kServingOperator, *dictionary); 256 } 257 if (shill_dictionary_->GetDictionaryWithoutPathExpansion( 258 shill::kCellularApnProperty, &dictionary)) { 259 TranslateAndAddNestedObject(::onc::cellular::kAPN, *dictionary); 260 } 261 const base::ListValue* shill_apns = NULL; 262 if (shill_dictionary_->GetListWithoutPathExpansion( 263 shill::kCellularApnListProperty, &shill_apns)) { 264 TranslateAndAddListOfObjects(::onc::cellular::kAPNList, *shill_apns); 265 } 266 267 const base::DictionaryValue* device_dictionary = NULL; 268 if (!shill_dictionary_->GetDictionaryWithoutPathExpansion( 269 shill::kDeviceProperty, &device_dictionary)) { 270 return; 271 } 272 273 // Iterate through all fields of the CellularWithState signature and copy 274 // values from the device properties according to the separate 275 // CellularDeviceTable. 276 for (const OncFieldSignature* field_signature = onc_signature_->fields; 277 field_signature->onc_field_name != NULL; ++field_signature) { 278 const std::string& onc_field_name = field_signature->onc_field_name; 279 280 std::string shill_property_name; 281 const base::Value* shill_value = NULL; 282 if (!GetShillPropertyName(field_signature->onc_field_name, 283 kCellularDeviceTable, 284 &shill_property_name) || 285 !device_dictionary->GetWithoutPathExpansion(shill_property_name, 286 &shill_value)) { 287 continue; 288 } 289 onc_object_->SetWithoutPathExpansion(onc_field_name, 290 shill_value->DeepCopy()); 291 } 292 } 293 294 void ShillToONCTranslator::TranslateNetworkWithState() { 295 CopyPropertiesAccordingToSignature(); 296 297 std::string shill_network_type; 298 shill_dictionary_->GetStringWithoutPathExpansion(shill::kTypeProperty, 299 &shill_network_type); 300 std::string onc_network_type = ::onc::network_type::kEthernet; 301 if (shill_network_type != shill::kTypeEthernet && 302 shill_network_type != shill::kTypeEthernetEap) { 303 TranslateStringToONC( 304 kNetworkTypeTable, shill_network_type, &onc_network_type); 305 } 306 // Translate nested Cellular, WiFi, etc. properties. 307 if (!onc_network_type.empty()) { 308 onc_object_->SetStringWithoutPathExpansion(::onc::network_config::kType, 309 onc_network_type); 310 TranslateAndAddNestedObject(onc_network_type); 311 } 312 313 // Since Name is a read only field in Shill unless it's a VPN, it is copied 314 // here, but not when going the other direction (if it's not a VPN). 315 std::string name; 316 shill_dictionary_->GetStringWithoutPathExpansion(shill::kNameProperty, 317 &name); 318 onc_object_->SetStringWithoutPathExpansion(::onc::network_config::kName, 319 name); 320 321 // Limit ONC state to "NotConnected", "Connected", or "Connecting". 322 std::string state; 323 if (shill_dictionary_->GetStringWithoutPathExpansion(shill::kStateProperty, 324 &state)) { 325 std::string onc_state = ::onc::connection_state::kNotConnected; 326 if (NetworkState::StateIsConnected(state)) { 327 onc_state = ::onc::connection_state::kConnected; 328 } else if (NetworkState::StateIsConnecting(state)) { 329 onc_state = ::onc::connection_state::kConnecting; 330 } 331 onc_object_->SetStringWithoutPathExpansion( 332 ::onc::network_config::kConnectionState, onc_state); 333 } 334 335 // Use a human-readable aa:bb format for any hardware MAC address. Note: 336 // this property is provided by the caller but is not part of the Shill 337 // Service properties (it is copied from the Device properties). 338 std::string address; 339 if (shill_dictionary_->GetStringWithoutPathExpansion(shill::kAddressProperty, 340 &address)) { 341 onc_object_->SetStringWithoutPathExpansion( 342 ::onc::network_config::kMacAddress, 343 network_util::FormattedMacAddress(address)); 344 } 345 346 // Shill's Service has an IPConfig property (note the singular), not an 347 // IPConfigs property. However, we require the caller of the translation to 348 // patch the Shill dictionary before passing it to the translator. 349 const base::ListValue* shill_ipconfigs = NULL; 350 if (shill_dictionary_->GetListWithoutPathExpansion(shill::kIPConfigsProperty, 351 &shill_ipconfigs)) { 352 TranslateAndAddListOfObjects(::onc::network_config::kIPConfigs, 353 *shill_ipconfigs); 354 } 355 } 356 357 void ShillToONCTranslator::TranslateIPConfig() { 358 CopyPropertiesAccordingToSignature(); 359 std::string shill_ip_method; 360 shill_dictionary_->GetStringWithoutPathExpansion(shill::kMethodProperty, 361 &shill_ip_method); 362 std::string type; 363 if (shill_ip_method == shill::kTypeIPv4 || 364 shill_ip_method == shill::kTypeDHCP) { 365 type = ::onc::ipconfig::kIPv4; 366 } else if (shill_ip_method == shill::kTypeIPv6 || 367 shill_ip_method == shill::kTypeDHCP6) { 368 type = ::onc::ipconfig::kIPv6; 369 } else { 370 return; // Ignore unhandled IPConfig types, e.g. bootp, zeroconf, ppp 371 } 372 373 onc_object_->SetStringWithoutPathExpansion(::onc::ipconfig::kType, type); 374 } 375 376 void ShillToONCTranslator::TranslateAndAddNestedObject( 377 const std::string& onc_field_name) { 378 TranslateAndAddNestedObject(onc_field_name, *shill_dictionary_); 379 } 380 381 void ShillToONCTranslator::TranslateAndAddNestedObject( 382 const std::string& onc_field_name, 383 const base::DictionaryValue& dictionary) { 384 const OncFieldSignature* field_signature = 385 GetFieldSignature(*onc_signature_, onc_field_name); 386 DCHECK(field_signature) << "Unable to find signature for field " 387 << onc_field_name << "."; 388 ShillToONCTranslator nested_translator(dictionary, 389 *field_signature->value_signature); 390 scoped_ptr<base::DictionaryValue> nested_object = 391 nested_translator.CreateTranslatedONCObject(); 392 if (nested_object->empty()) 393 return; 394 onc_object_->SetWithoutPathExpansion(onc_field_name, nested_object.release()); 395 } 396 397 void ShillToONCTranslator::TranslateAndAddListOfObjects( 398 const std::string& onc_field_name, 399 const base::ListValue& list) { 400 const OncFieldSignature* field_signature = 401 GetFieldSignature(*onc_signature_, onc_field_name); 402 if (field_signature->value_signature->onc_type != base::Value::TYPE_LIST) { 403 LOG(ERROR) << "ONC Field name: '" << onc_field_name << "' has type '" 404 << field_signature->value_signature->onc_type 405 << "', expected: base::Value::TYPE_LIST."; 406 return; 407 } 408 DCHECK(field_signature->value_signature->onc_array_entry_signature); 409 scoped_ptr<base::ListValue> result(new base::ListValue()); 410 for (base::ListValue::const_iterator it = list.begin(); 411 it != list.end(); ++it) { 412 const base::DictionaryValue* shill_value = NULL; 413 if (!(*it)->GetAsDictionary(&shill_value)) 414 continue; 415 ShillToONCTranslator nested_translator( 416 *shill_value, 417 *field_signature->value_signature->onc_array_entry_signature); 418 scoped_ptr<base::DictionaryValue> nested_object = 419 nested_translator.CreateTranslatedONCObject(); 420 // If the nested object couldn't be parsed, simply omit it. 421 if (nested_object->empty()) 422 continue; 423 result->Append(nested_object.release()); 424 } 425 // If there are no entries in the list, there is no need to expose this field. 426 if (result->empty()) 427 return; 428 onc_object_->SetWithoutPathExpansion(onc_field_name, result.release()); 429 } 430 431 void ShillToONCTranslator::CopyPropertiesAccordingToSignature() { 432 CopyPropertiesAccordingToSignature(onc_signature_); 433 } 434 435 void ShillToONCTranslator::CopyPropertiesAccordingToSignature( 436 const OncValueSignature* value_signature) { 437 if (value_signature->base_signature) 438 CopyPropertiesAccordingToSignature(value_signature->base_signature); 439 for (const OncFieldSignature* field_signature = value_signature->fields; 440 field_signature->onc_field_name != NULL; ++field_signature) { 441 CopyProperty(field_signature); 442 } 443 } 444 445 void ShillToONCTranslator::CopyProperty( 446 const OncFieldSignature* field_signature) { 447 std::string shill_property_name; 448 const base::Value* shill_value = NULL; 449 if (!field_translation_table_ || 450 !GetShillPropertyName(field_signature->onc_field_name, 451 field_translation_table_, 452 &shill_property_name) || 453 !shill_dictionary_->GetWithoutPathExpansion(shill_property_name, 454 &shill_value)) { 455 return; 456 } 457 458 if (shill_value->GetType() != field_signature->value_signature->onc_type) { 459 LOG(ERROR) << "Shill property '" << shill_property_name 460 << "' with value " << *shill_value 461 << " has base::Value::Type " << shill_value->GetType() 462 << " but ONC field '" << field_signature->onc_field_name 463 << "' requires type " 464 << field_signature->value_signature->onc_type << "."; 465 return; 466 } 467 468 onc_object_->SetWithoutPathExpansion(field_signature->onc_field_name, 469 shill_value->DeepCopy()); 470 } 471 472 void ShillToONCTranslator::TranslateWithTableAndSet( 473 const std::string& shill_property_name, 474 const StringTranslationEntry table[], 475 const std::string& onc_field_name) { 476 std::string shill_value; 477 if (!shill_dictionary_->GetStringWithoutPathExpansion(shill_property_name, 478 &shill_value)) { 479 return; 480 } 481 std::string onc_value; 482 if (TranslateStringToONC(table, shill_value, &onc_value)) { 483 onc_object_->SetStringWithoutPathExpansion(onc_field_name, onc_value); 484 return; 485 } 486 LOG(ERROR) << "Shill property '" << shill_property_name << "' with value " 487 << shill_value << " couldn't be translated to ONC"; 488 } 489 490 } // namespace 491 492 scoped_ptr<base::DictionaryValue> TranslateShillServiceToONCPart( 493 const base::DictionaryValue& shill_dictionary, 494 const OncValueSignature* onc_signature) { 495 CHECK(onc_signature != NULL); 496 497 ShillToONCTranslator translator(shill_dictionary, *onc_signature); 498 return translator.CreateTranslatedONCObject(); 499 } 500 501 } // namespace onc 502 } // namespace chromeos 503