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.
ConvertStringToValue(const std::string & str,base::Value::Type type)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:
ShillToONCTranslator(const base::DictionaryValue & shill_dictionary,const OncValueSignature & onc_signature)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>
CreateTranslatedONCObject()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
TranslateEthernet()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
TranslateOpenVPN()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
TranslateIPsec()217 void ShillToONCTranslator::TranslateIPsec() {
218 CopyPropertiesAccordingToSignature();
219 if (shill_dictionary_->HasKey(shill::kL2tpIpsecXauthUserProperty))
220 TranslateAndAddNestedObject(::onc::ipsec::kXAUTH);
221 }
222
TranslateVPN()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
TranslateWiFiWithState()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
TranslateCellularWithState()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
TranslateNetworkWithState()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
TranslateIPConfig()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
TranslateAndAddNestedObject(const std::string & onc_field_name)376 void ShillToONCTranslator::TranslateAndAddNestedObject(
377 const std::string& onc_field_name) {
378 TranslateAndAddNestedObject(onc_field_name, *shill_dictionary_);
379 }
380
TranslateAndAddNestedObject(const std::string & onc_field_name,const base::DictionaryValue & dictionary)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
TranslateAndAddListOfObjects(const std::string & onc_field_name,const base::ListValue & list)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
CopyPropertiesAccordingToSignature()431 void ShillToONCTranslator::CopyPropertiesAccordingToSignature() {
432 CopyPropertiesAccordingToSignature(onc_signature_);
433 }
434
CopyPropertiesAccordingToSignature(const OncValueSignature * value_signature)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
CopyProperty(const OncFieldSignature * field_signature)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
TranslateWithTableAndSet(const std::string & shill_property_name,const StringTranslationEntry table[],const std::string & onc_field_name)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
TranslateShillServiceToONCPart(const base::DictionaryValue & shill_dictionary,const OncValueSignature * onc_signature)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