Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(603)

Side by Side Diff: remoting/host/remoting_me2me_host.cc

Issue 2682473003: Add support for multiple allowed domains (Closed)
Patch Set: Rework to follow a deprecation approach Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 // 4 //
5 // This file implements a standalone host process for Me2Me. 5 // This file implements a standalone host process for Me2Me.
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 8
9 #include <cstdint> 9 #include <cstdint>
10 #include <memory> 10 #include <memory>
11 #include <string> 11 #include <string>
12 #include <utility> 12 #include <utility>
13 #include <vector>
13 14
14 #include "base/bind.h" 15 #include "base/bind.h"
15 #include "base/callback.h" 16 #include "base/callback.h"
16 #include "base/command_line.h" 17 #include "base/command_line.h"
17 #include "base/debug/alias.h" 18 #include "base/debug/alias.h"
18 #include "base/files/file_path.h" 19 #include "base/files/file_path.h"
19 #include "base/files/file_util.h" 20 #include "base/files/file_util.h"
20 #include "base/macros.h" 21 #include "base/macros.h"
21 #include "base/memory/ptr_util.h" 22 #include "base/memory/ptr_util.h"
22 #include "base/message_loop/message_loop.h" 23 #include "base/message_loop/message_loop.h"
(...skipping 265 matching lines...) Expand 10 before | Expand all | Expand 10 after
288 // Tear down resources that run on the UI thread. 289 // Tear down resources that run on the UI thread.
289 void ShutdownOnUiThread(); 290 void ShutdownOnUiThread();
290 291
291 // Applies the host config, returning true if successful. 292 // Applies the host config, returning true if successful.
292 bool ApplyConfig(const base::DictionaryValue& config); 293 bool ApplyConfig(const base::DictionaryValue& config);
293 294
294 // Handles policy updates, by calling On*PolicyUpdate methods. 295 // Handles policy updates, by calling On*PolicyUpdate methods.
295 void OnPolicyUpdate(std::unique_ptr<base::DictionaryValue> policies); 296 void OnPolicyUpdate(std::unique_ptr<base::DictionaryValue> policies);
296 void OnPolicyError(); 297 void OnPolicyError();
297 void ReportPolicyErrorAndRestartHost(); 298 void ReportPolicyErrorAndRestartHost();
298 void ApplyHostDomainPolicy(); 299 void ApplyHostDomainListPolicy();
299 void ApplyUsernamePolicy(); 300 void ApplyUsernamePolicy();
300 bool OnClientDomainPolicyUpdate(base::DictionaryValue* policies); 301 bool OnClientDomainListPolicyUpdate(base::DictionaryValue* policies);
301 bool OnHostDomainPolicyUpdate(base::DictionaryValue* policies); 302 bool OnHostDomainListPolicyUpdate(base::DictionaryValue* policies);
302 bool OnUsernamePolicyUpdate(base::DictionaryValue* policies); 303 bool OnUsernamePolicyUpdate(base::DictionaryValue* policies);
303 bool OnNatPolicyUpdate(base::DictionaryValue* policies); 304 bool OnNatPolicyUpdate(base::DictionaryValue* policies);
304 bool OnRelayPolicyUpdate(base::DictionaryValue* policies); 305 bool OnRelayPolicyUpdate(base::DictionaryValue* policies);
305 bool OnUdpPortPolicyUpdate(base::DictionaryValue* policies); 306 bool OnUdpPortPolicyUpdate(base::DictionaryValue* policies);
306 bool OnCurtainPolicyUpdate(base::DictionaryValue* policies); 307 bool OnCurtainPolicyUpdate(base::DictionaryValue* policies);
307 bool OnHostTalkGadgetPrefixPolicyUpdate(base::DictionaryValue* policies); 308 bool OnHostTalkGadgetPrefixPolicyUpdate(base::DictionaryValue* policies);
308 bool OnHostTokenUrlPolicyUpdate(base::DictionaryValue* policies); 309 bool OnHostTokenUrlPolicyUpdate(base::DictionaryValue* policies);
309 bool OnPairingPolicyUpdate(base::DictionaryValue* policies); 310 bool OnPairingPolicyUpdate(base::DictionaryValue* policies);
310 bool OnGnubbyAuthPolicyUpdate(base::DictionaryValue* policies); 311 bool OnGnubbyAuthPolicyUpdate(base::DictionaryValue* policies);
311 312
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after
369 scoped_refptr<RsaKeyPair> key_pair_; 370 scoped_refptr<RsaKeyPair> key_pair_;
370 std::string oauth_refresh_token_; 371 std::string oauth_refresh_token_;
371 std::string serialized_config_; 372 std::string serialized_config_;
372 std::string host_owner_; 373 std::string host_owner_;
373 std::string host_owner_email_; 374 std::string host_owner_email_;
374 bool use_service_account_ = false; 375 bool use_service_account_ = false;
375 bool enable_vp9_ = false; 376 bool enable_vp9_ = false;
376 377
377 std::unique_ptr<PolicyWatcher> policy_watcher_; 378 std::unique_ptr<PolicyWatcher> policy_watcher_;
378 PolicyState policy_state_ = POLICY_INITIALIZING; 379 PolicyState policy_state_ = POLICY_INITIALIZING;
379 std::string client_domain_; 380 std::vector<std::string> client_domain_list_;
380 std::string host_domain_; 381 std::vector<std::string> host_domain_list_;
381 bool host_username_match_required_ = false; 382 bool host_username_match_required_ = false;
382 bool allow_nat_traversal_ = true; 383 bool allow_nat_traversal_ = true;
383 bool allow_relay_ = true; 384 bool allow_relay_ = true;
384 PortRange udp_port_range_; 385 PortRange udp_port_range_;
385 std::string talkgadget_prefix_; 386 std::string talkgadget_prefix_;
386 bool allow_pairing_ = true; 387 bool allow_pairing_ = true;
387 388
388 DesktopEnvironmentOptions desktop_environment_options_; 389 DesktopEnvironmentOptions desktop_environment_options_;
389 ThirdPartyAuthConfig third_party_auth_config_; 390 ThirdPartyAuthConfig third_party_auth_config_;
390 bool security_key_auth_policy_enabled_ = false; 391 bool security_key_auth_policy_enabled_ = false;
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after
585 LOG(ERROR) << "Failed to apply the configuration."; 586 LOG(ERROR) << "Failed to apply the configuration.";
586 ShutdownHost(kInvalidHostConfigurationExitCode); 587 ShutdownHost(kInvalidHostConfigurationExitCode);
587 return; 588 return;
588 } 589 }
589 590
590 if (state_ == HOST_STARTING) { 591 if (state_ == HOST_STARTING) {
591 StartHostIfReady(); 592 StartHostIfReady();
592 } else if (state_ == HOST_STARTED) { 593 } else if (state_ == HOST_STARTED) {
593 // Reapply policies that could be affected by a new config. 594 // Reapply policies that could be affected by a new config.
594 DCHECK_EQ(policy_state_, POLICY_LOADED); 595 DCHECK_EQ(policy_state_, POLICY_LOADED);
595 ApplyHostDomainPolicy(); 596 ApplyHostDomainListPolicy();
596 ApplyUsernamePolicy(); 597 ApplyUsernamePolicy();
597 598
598 // TODO(sergeyu): Here we assume that PIN is the only part of the config 599 // TODO(sergeyu): Here we assume that PIN is the only part of the config
599 // that may change while the service is running. Change ApplyConfig() to 600 // that may change while the service is running. Change ApplyConfig() to
600 // detect other changes in the config and restart host if necessary here. 601 // detect other changes in the config and restart host if necessary here.
601 CreateAuthenticatorFactory(); 602 CreateAuthenticatorFactory();
602 } 603 }
603 } 604 }
604 605
605 void HostProcess::OnConfigWatcherError() { 606 void HostProcess::OnConfigWatcherError() {
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
718 pairing_registry_ = new PairingRegistry(context_->file_task_runner(), 719 pairing_registry_ = new PairingRegistry(context_->file_task_runner(),
719 std::move(delegate)); 720 std::move(delegate));
720 } 721 }
721 #endif // defined(OS_WIN) 722 #endif // defined(OS_WIN)
722 723
723 pairing_registry = pairing_registry_; 724 pairing_registry = pairing_registry_;
724 } 725 }
725 726
726 factory = protocol::Me2MeHostAuthenticatorFactory::CreateWithPin( 727 factory = protocol::Me2MeHostAuthenticatorFactory::CreateWithPin(
727 use_service_account_, host_owner_, local_certificate, key_pair_, 728 use_service_account_, host_owner_, local_certificate, key_pair_,
728 client_domain_, pin_hash_, pairing_registry); 729 client_domain_list_, pin_hash_, pairing_registry);
729 730
730 host_->set_pairing_registry(pairing_registry); 731 host_->set_pairing_registry(pairing_registry);
731 } else { 732 } else {
732 // ThirdPartyAuthConfig::Parse() leaves the config in a valid state, so 733 // ThirdPartyAuthConfig::Parse() leaves the config in a valid state, so
733 // these URLs are both valid. 734 // these URLs are both valid.
734 DCHECK(third_party_auth_config_.token_url.is_valid()); 735 DCHECK(third_party_auth_config_.token_url.is_valid());
735 DCHECK(third_party_auth_config_.token_validation_url.is_valid()); 736 DCHECK(third_party_auth_config_.token_validation_url.is_valid());
736 737
737 #if defined(OS_LINUX) 738 #if defined(OS_LINUX)
738 if (!cert_watcher_) { 739 if (!cert_watcher_) {
739 cert_watcher_.reset(new CertificateWatcher( 740 cert_watcher_.reset(new CertificateWatcher(
740 base::Bind(&HostProcess::ShutdownHost, this, kSuccessExitCode), 741 base::Bind(&HostProcess::ShutdownHost, this, kSuccessExitCode),
741 context_->file_task_runner())); 742 context_->file_task_runner()));
742 cert_watcher_->Start(); 743 cert_watcher_->Start();
743 } 744 }
744 cert_watcher_->SetMonitor(host_->AsWeakPtr()); 745 cert_watcher_->SetMonitor(host_->AsWeakPtr());
745 #endif 746 #endif
746 747
747 scoped_refptr<protocol::TokenValidatorFactory> token_validator_factory = 748 scoped_refptr<protocol::TokenValidatorFactory> token_validator_factory =
748 new TokenValidatorFactoryImpl(third_party_auth_config_, key_pair_, 749 new TokenValidatorFactoryImpl(third_party_auth_config_, key_pair_,
749 context_->url_request_context_getter()); 750 context_->url_request_context_getter());
750 factory = protocol::Me2MeHostAuthenticatorFactory::CreateWithThirdPartyAuth( 751 factory = protocol::Me2MeHostAuthenticatorFactory::CreateWithThirdPartyAuth(
751 use_service_account_, host_owner_, local_certificate, key_pair_, 752 use_service_account_, host_owner_, local_certificate, key_pair_,
752 client_domain_, token_validator_factory); 753 client_domain_list_, token_validator_factory);
753 } 754 }
754 755
755 #if defined(OS_POSIX) 756 #if defined(OS_POSIX)
756 // On Linux and Mac, perform a PAM authorization step after authentication. 757 // On Linux and Mac, perform a PAM authorization step after authentication.
757 factory.reset(new PamAuthorizationFactory(std::move(factory))); 758 factory.reset(new PamAuthorizationFactory(std::move(factory)));
758 #endif 759 #endif
759 host_->SetAuthenticatorFactory(std::move(factory)); 760 host_->SetAuthenticatorFactory(std::move(factory));
760 } 761 }
761 762
762 // IPC::Listener implementation. 763 // IPC::Listener implementation.
(...skipping 250 matching lines...) Expand 10 before | Expand all | Expand 10 after
1013 void HostProcess::OnPolicyUpdate( 1014 void HostProcess::OnPolicyUpdate(
1014 std::unique_ptr<base::DictionaryValue> policies) { 1015 std::unique_ptr<base::DictionaryValue> policies) {
1015 if (!context_->network_task_runner()->BelongsToCurrentThread()) { 1016 if (!context_->network_task_runner()->BelongsToCurrentThread()) {
1016 context_->network_task_runner()->PostTask( 1017 context_->network_task_runner()->PostTask(
1017 FROM_HERE, base::Bind(&HostProcess::OnPolicyUpdate, this, 1018 FROM_HERE, base::Bind(&HostProcess::OnPolicyUpdate, this,
1018 base::Passed(&policies))); 1019 base::Passed(&policies)));
1019 return; 1020 return;
1020 } 1021 }
1021 1022
1022 bool restart_required = false; 1023 bool restart_required = false;
1023 restart_required |= OnClientDomainPolicyUpdate(policies.get()); 1024 restart_required |= OnClientDomainListPolicyUpdate(policies.get());
1024 restart_required |= OnHostDomainPolicyUpdate(policies.get()); 1025 restart_required |= OnHostDomainListPolicyUpdate(policies.get());
1025 restart_required |= OnCurtainPolicyUpdate(policies.get()); 1026 restart_required |= OnCurtainPolicyUpdate(policies.get());
1026 // Note: UsernamePolicyUpdate must run after OnCurtainPolicyUpdate. 1027 // Note: UsernamePolicyUpdate must run after OnCurtainPolicyUpdate.
1027 restart_required |= OnUsernamePolicyUpdate(policies.get()); 1028 restart_required |= OnUsernamePolicyUpdate(policies.get());
1028 restart_required |= OnNatPolicyUpdate(policies.get()); 1029 restart_required |= OnNatPolicyUpdate(policies.get());
1029 restart_required |= OnRelayPolicyUpdate(policies.get()); 1030 restart_required |= OnRelayPolicyUpdate(policies.get());
1030 restart_required |= OnUdpPortPolicyUpdate(policies.get()); 1031 restart_required |= OnUdpPortPolicyUpdate(policies.get());
1031 restart_required |= OnHostTalkGadgetPrefixPolicyUpdate(policies.get()); 1032 restart_required |= OnHostTalkGadgetPrefixPolicyUpdate(policies.get());
1032 restart_required |= OnHostTokenUrlPolicyUpdate(policies.get()); 1033 restart_required |= OnHostTokenUrlPolicyUpdate(policies.get());
1033 restart_required |= OnPairingPolicyUpdate(policies.get()); 1034 restart_required |= OnPairingPolicyUpdate(policies.get());
1034 restart_required |= OnGnubbyAuthPolicyUpdate(policies.get()); 1035 restart_required |= OnGnubbyAuthPolicyUpdate(policies.get());
(...skipping 28 matching lines...) Expand all
1063 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); 1064 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
1064 DCHECK(!serialized_config_.empty()); 1065 DCHECK(!serialized_config_.empty());
1065 1066
1066 DCHECK_EQ(policy_state_, POLICY_ERROR_REPORT_PENDING); 1067 DCHECK_EQ(policy_state_, POLICY_ERROR_REPORT_PENDING);
1067 policy_state_ = POLICY_ERROR_REPORTED; 1068 policy_state_ = POLICY_ERROR_REPORTED;
1068 1069
1069 HOST_LOG << "Restarting the host due to policy errors."; 1070 HOST_LOG << "Restarting the host due to policy errors.";
1070 RestartHost(kHostOfflineReasonPolicyReadError); 1071 RestartHost(kHostOfflineReasonPolicyReadError);
1071 } 1072 }
1072 1073
1073 void HostProcess::ApplyHostDomainPolicy() { 1074 void HostProcess::ApplyHostDomainListPolicy() {
1074 if (state_ != HOST_STARTED) 1075 if (state_ != HOST_STARTED)
1075 return; 1076 return;
1076 1077
1077 HOST_LOG << "Policy sets host domain: " << host_domain_; 1078 HOST_LOG << "Policy sets host domains: "
1079 << base::JoinString(host_domain_list_, ", ");
1078 1080
1079 if (!host_domain_.empty()) { 1081 if (!host_domain_list_.empty()) {
1080 // If the user does not have a Google email, their client JID will not be 1082 // If the user does not have a Google email, their client JID will not be
1081 // based on their email. In that case, the username/host domain policies 1083 // based on their email. In that case, the username/host domain policies
1082 // would be meaningless, since there is no way to check that the JID 1084 // would be meaningless, since there is no way to check that the JID
1083 // trying to connect actually corresponds to the owner email in question. 1085 // trying to connect actually corresponds to the owner email in question.
1084 if (host_owner_ != host_owner_email_) { 1086 if (host_owner_ != host_owner_email_) {
1085 LOG(ERROR) << "The username and host domain policies cannot be enabled " 1087 LOG(ERROR) << "The username and host domain policies cannot be enabled "
1086 << "for accounts with a non-Google email."; 1088 << "for accounts with a non-Google email.";
1087 ShutdownHost(kInvalidHostDomainExitCode); 1089 ShutdownHost(kInvalidHostDomainExitCode);
1088 } 1090 }
1089 1091
1090 if (!base::EndsWith(host_owner_, std::string("@") + host_domain_, 1092 bool matched = false;
1091 base::CompareCase::INSENSITIVE_ASCII)) { 1093 for (const std::string& domain : host_domain_list_) {
1094 if (base::EndsWith(host_owner_, std::string("@") + domain,
1095 base::CompareCase::INSENSITIVE_ASCII)) {
1096 matched = true;
1097 }
1098 }
1099 if (!matched) {
1092 LOG(ERROR) << "The host domain does not match the policy."; 1100 LOG(ERROR) << "The host domain does not match the policy.";
1093 ShutdownHost(kInvalidHostDomainExitCode); 1101 ShutdownHost(kInvalidHostDomainExitCode);
1094 } 1102 }
1095 } 1103 }
1096 } 1104 }
1097 1105
1098 bool HostProcess::OnHostDomainPolicyUpdate(base::DictionaryValue* policies) { 1106 bool HostProcess::OnHostDomainListPolicyUpdate(
1107 base::DictionaryValue* policies) {
1099 // Returns true if the host has to be restarted after this policy update. 1108 // Returns true if the host has to be restarted after this policy update.
1100 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); 1109 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
1101 1110
1102 if (!policies->GetString(policy::key::kRemoteAccessHostDomain, 1111 const base::ListValue* list;
1103 &host_domain_)) { 1112 if (!policies->GetList(policy::key::kRemoteAccessHostDomainList, &list)) {
1104 return false; 1113 return false;
1105 } 1114 }
1106 1115
1107 ApplyHostDomainPolicy(); 1116 host_domain_list_.clear();
1117 for (const auto& value : *list) {
1118 host_domain_list_.push_back(value.GetString());
1119 }
1120
1121 ApplyHostDomainListPolicy();
1108 return false; 1122 return false;
1109 } 1123 }
1110 1124
1111 bool HostProcess::OnClientDomainPolicyUpdate(base::DictionaryValue* policies) { 1125 bool HostProcess::OnClientDomainListPolicyUpdate(
1126 base::DictionaryValue* policies) {
1112 // Returns true if the host has to be restarted after this policy update. 1127 // Returns true if the host has to be restarted after this policy update.
1113 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); 1128 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
1114 return policies->GetString(policy::key::kRemoteAccessHostClientDomain, 1129 const base::ListValue* list;
1115 &client_domain_); 1130 if (!policies->GetList(policy::key::kRemoteAccessHostClientDomainList,
Sergey Ulanov 2017/04/20 00:42:54 This will return false if the policy is there, but
rkjnsn 2017/04/20 00:58:26 My understanding is that Schema::Normalize (which
1131 &list)) {
1132 return false;
1133 }
1134
1135 client_domain_list_.clear();
1136 for (const auto& value : *list) {
1137 client_domain_list_.push_back(value.GetString());
1138 }
1139
1140 return true;
1116 } 1141 }
1117 1142
1118 void HostProcess::ApplyUsernamePolicy() { 1143 void HostProcess::ApplyUsernamePolicy() {
1119 if (state_ != HOST_STARTED) 1144 if (state_ != HOST_STARTED)
1120 return; 1145 return;
1121 1146
1122 if (host_username_match_required_) { 1147 if (host_username_match_required_) {
1123 HOST_LOG << "Policy requires host username match."; 1148 HOST_LOG << "Policy requires host username match.";
1124 1149
1125 // See comment in ApplyHostDomainPolicy. 1150 // See comment in ApplyHostDomainListPolicy.
1126 if (host_owner_ != host_owner_email_) { 1151 if (host_owner_ != host_owner_email_) {
1127 LOG(ERROR) << "The username and host domain policies cannot be enabled " 1152 LOG(ERROR) << "The username and host domain policies cannot be enabled "
1128 << "for accounts with a non-Google email."; 1153 << "for accounts with a non-Google email.";
1129 ShutdownHost(kUsernameMismatchExitCode); 1154 ShutdownHost(kUsernameMismatchExitCode);
1130 } 1155 }
1131 1156
1132 std::string username = GetUsername(); 1157 std::string username = GetUsername();
1133 bool shutdown = 1158 bool shutdown =
1134 username.empty() || 1159 username.empty() ||
1135 !base::StartsWith(host_owner_, username + std::string("@"), 1160 !base::StartsWith(host_owner_, username + std::string("@"),
(...skipping 354 matching lines...) Expand 10 before | Expand all | Expand 10 after
1490 new IpcHostEventLogger(host_->AsWeakPtr(), daemon_channel_.get())); 1515 new IpcHostEventLogger(host_->AsWeakPtr(), daemon_channel_.get()));
1491 #else // !defined(REMOTING_MULTI_PROCESS) 1516 #else // !defined(REMOTING_MULTI_PROCESS)
1492 host_event_logger_ = 1517 host_event_logger_ =
1493 HostEventLogger::Create(host_->AsWeakPtr(), kApplicationName); 1518 HostEventLogger::Create(host_->AsWeakPtr(), kApplicationName);
1494 #endif // !defined(REMOTING_MULTI_PROCESS) 1519 #endif // !defined(REMOTING_MULTI_PROCESS)
1495 1520
1496 host_->Start(host_owner_email_); 1521 host_->Start(host_owner_email_);
1497 1522
1498 CreateAuthenticatorFactory(); 1523 CreateAuthenticatorFactory();
1499 1524
1500 ApplyHostDomainPolicy(); 1525 ApplyHostDomainListPolicy();
1501 ApplyUsernamePolicy(); 1526 ApplyUsernamePolicy();
1502 } 1527 }
1503 1528
1504 void HostProcess::OnAuthFailed() { 1529 void HostProcess::OnAuthFailed() {
1505 ShutdownHost(kInvalidOauthCredentialsExitCode); 1530 ShutdownHost(kInvalidOauthCredentialsExitCode);
1506 } 1531 }
1507 1532
1508 void HostProcess::RestartHost(const std::string& host_offline_reason) { 1533 void HostProcess::RestartHost(const std::string& host_offline_reason) {
1509 DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); 1534 DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
1510 DCHECK(!host_offline_reason.empty()); 1535 DCHECK(!host_offline_reason.empty());
(...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after
1668 // Run the main (also UI) message loop until the host no longer needs it. 1693 // Run the main (also UI) message loop until the host no longer needs it.
1669 base::RunLoop().Run(); 1694 base::RunLoop().Run();
1670 1695
1671 // Block until tasks blocking shutdown have completed their execution. 1696 // Block until tasks blocking shutdown have completed their execution.
1672 base::TaskScheduler::GetInstance()->Shutdown(); 1697 base::TaskScheduler::GetInstance()->Shutdown();
1673 1698
1674 return exit_code; 1699 return exit_code;
1675 } 1700 }
1676 1701
1677 } // namespace remoting 1702 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698