| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 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 "remoting/host/it2me/it2me_impl.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/strings/string_util.h" | |
| 9 #include "base/synchronization/waitable_event.h" | |
| 10 #include "base/threading/platform_thread.h" | |
| 11 #include "net/socket/client_socket_factory.h" | |
| 12 #include "remoting/base/auto_thread.h" | |
| 13 #include "remoting/base/rsa_key_pair.h" | |
| 14 #include "remoting/host/chromoting_host.h" | |
| 15 #include "remoting/host/chromoting_host_context.h" | |
| 16 #include "remoting/host/host_event_logger.h" | |
| 17 #include "remoting/host/host_secret.h" | |
| 18 #include "remoting/host/it2me_desktop_environment.h" | |
| 19 #include "remoting/host/policy_hack/policy_watcher.h" | |
| 20 #include "remoting/host/register_support_host_request.h" | |
| 21 #include "remoting/host/session_manager_factory.h" | |
| 22 #include "remoting/jingle_glue/network_settings.h" | |
| 23 #include "remoting/protocol/it2me_host_authenticator_factory.h" | |
| 24 | |
| 25 namespace remoting { | |
| 26 | |
| 27 namespace { | |
| 28 | |
| 29 // This is used for tagging system event logs. | |
| 30 const char kApplicationName[] = "chromoting"; | |
| 31 const int kMaxLoginAttempts = 5; | |
| 32 | |
| 33 } // namespace | |
| 34 | |
| 35 It2MeImpl::It2MeImpl( | |
| 36 scoped_ptr<ChromotingHostContext> host_context, | |
| 37 scoped_refptr<base::SingleThreadTaskRunner> plugin_task_runner, | |
| 38 base::WeakPtr<It2MeImpl::Observer> observer, | |
| 39 const XmppSignalStrategy::XmppServerConfig& xmpp_server_config, | |
| 40 const std::string& directory_bot_jid) | |
| 41 : host_context_(host_context.Pass()), | |
| 42 plugin_task_runner_(plugin_task_runner), | |
| 43 observer_(observer), | |
| 44 xmpp_server_config_(xmpp_server_config), | |
| 45 directory_bot_jid_(directory_bot_jid), | |
| 46 state_(kDisconnected), | |
| 47 failed_login_attempts_(0), | |
| 48 nat_traversal_enabled_(false), | |
| 49 policy_received_(false) { | |
| 50 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); | |
| 51 } | |
| 52 | |
| 53 void It2MeImpl::Connect() { | |
| 54 if (!host_context_->ui_task_runner()->BelongsToCurrentThread()) { | |
| 55 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); | |
| 56 host_context_->ui_task_runner()->PostTask( | |
| 57 FROM_HERE, base::Bind(&It2MeImpl::Connect, this)); | |
| 58 return; | |
| 59 } | |
| 60 | |
| 61 desktop_environment_factory_.reset(new It2MeDesktopEnvironmentFactory( | |
| 62 host_context_->network_task_runner(), | |
| 63 host_context_->input_task_runner(), | |
| 64 host_context_->ui_task_runner())); | |
| 65 | |
| 66 // Start monitoring configured policies. | |
| 67 policy_watcher_.reset( | |
| 68 policy_hack::PolicyWatcher::Create(host_context_->network_task_runner())); | |
| 69 policy_watcher_->StartWatching( | |
| 70 base::Bind(&It2MeImpl::OnPolicyUpdate, this)); | |
| 71 | |
| 72 // Switch to the network thread to start the actual connection. | |
| 73 host_context_->network_task_runner()->PostTask( | |
| 74 FROM_HERE, base::Bind(&It2MeImpl::ReadPolicyAndConnect, this)); | |
| 75 } | |
| 76 | |
| 77 void It2MeImpl::Disconnect() { | |
| 78 if (!host_context_->network_task_runner()->BelongsToCurrentThread()) { | |
| 79 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); | |
| 80 host_context_->network_task_runner()->PostTask( | |
| 81 FROM_HERE, base::Bind(&It2MeImpl::Disconnect, this)); | |
| 82 return; | |
| 83 } | |
| 84 | |
| 85 switch (state_) { | |
| 86 case kDisconnected: | |
| 87 ShutdownOnNetworkThread(); | |
| 88 return; | |
| 89 | |
| 90 case kStarting: | |
| 91 SetState(kDisconnecting); | |
| 92 SetState(kDisconnected); | |
| 93 ShutdownOnNetworkThread(); | |
| 94 return; | |
| 95 | |
| 96 case kDisconnecting: | |
| 97 return; | |
| 98 | |
| 99 default: | |
| 100 SetState(kDisconnecting); | |
| 101 | |
| 102 if (!host_) { | |
| 103 SetState(kDisconnected); | |
| 104 ShutdownOnNetworkThread(); | |
| 105 return; | |
| 106 } | |
| 107 | |
| 108 // Deleting the host destroys SignalStrategy synchronously, but | |
| 109 // SignalStrategy::Listener handlers are not allowed to destroy | |
| 110 // SignalStrategy, so post task to destroy the host later. | |
| 111 host_context_->network_task_runner()->PostTask( | |
| 112 FROM_HERE, base::Bind(&It2MeImpl::ShutdownOnNetworkThread, this)); | |
| 113 return; | |
| 114 } | |
| 115 } | |
| 116 | |
| 117 void It2MeImpl::RequestNatPolicy() { | |
| 118 if (!host_context_->network_task_runner()->BelongsToCurrentThread()) { | |
| 119 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); | |
| 120 host_context_->network_task_runner()->PostTask( | |
| 121 FROM_HERE, base::Bind(&It2MeImpl::RequestNatPolicy, this)); | |
| 122 return; | |
| 123 } | |
| 124 | |
| 125 if (policy_received_) | |
| 126 UpdateNatPolicy(nat_traversal_enabled_); | |
| 127 } | |
| 128 | |
| 129 void It2MeImpl::ReadPolicyAndConnect() { | |
| 130 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 131 | |
| 132 SetState(kStarting); | |
| 133 | |
| 134 // Only proceed to FinishConnect() if at least one policy update has been | |
| 135 // received. | |
| 136 if (policy_received_) { | |
| 137 FinishConnect(); | |
| 138 } else { | |
| 139 // Otherwise, create the policy watcher, and thunk the connect. | |
| 140 pending_connect_ = | |
| 141 base::Bind(&It2MeImpl::FinishConnect, this); | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 void It2MeImpl::FinishConnect() { | |
| 146 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 147 | |
| 148 if (state_ != kStarting) { | |
| 149 // Host has been stopped while we were fetching policy. | |
| 150 return; | |
| 151 } | |
| 152 | |
| 153 // Check the host domain policy. | |
| 154 if (!required_host_domain_.empty() && | |
| 155 !EndsWith(xmpp_server_config_.username, | |
| 156 std::string("@") + required_host_domain_, false)) { | |
| 157 SetState(kInvalidDomainError); | |
| 158 return; | |
| 159 } | |
| 160 | |
| 161 // Generate a key pair for the Host to use. | |
| 162 // TODO(wez): Move this to the worker thread. | |
| 163 host_key_pair_ = RsaKeyPair::Generate(); | |
| 164 | |
| 165 // Create XMPP connection. | |
| 166 scoped_ptr<SignalStrategy> signal_strategy( | |
| 167 new XmppSignalStrategy(net::ClientSocketFactory::GetDefaultFactory(), | |
| 168 host_context_->url_request_context_getter(), | |
| 169 xmpp_server_config_)); | |
| 170 | |
| 171 // Request registration of the host for support. | |
| 172 scoped_ptr<RegisterSupportHostRequest> register_request( | |
| 173 new RegisterSupportHostRequest( | |
| 174 signal_strategy.get(), host_key_pair_, directory_bot_jid_, | |
| 175 base::Bind(&It2MeImpl::OnReceivedSupportID, | |
| 176 base::Unretained(this)))); | |
| 177 | |
| 178 // Beyond this point nothing can fail, so save the config and request. | |
| 179 signal_strategy_ = signal_strategy.Pass(); | |
| 180 register_request_ = register_request.Pass(); | |
| 181 | |
| 182 // If NAT traversal is off then limit port range to allow firewall pin-holing. | |
| 183 LOG(INFO) << "NAT state: " << nat_traversal_enabled_; | |
| 184 NetworkSettings network_settings( | |
| 185 nat_traversal_enabled_ ? | |
| 186 NetworkSettings::NAT_TRAVERSAL_ENABLED : | |
| 187 NetworkSettings::NAT_TRAVERSAL_DISABLED); | |
| 188 if (!nat_traversal_enabled_) { | |
| 189 network_settings.min_port = NetworkSettings::kDefaultMinPort; | |
| 190 network_settings.max_port = NetworkSettings::kDefaultMaxPort; | |
| 191 } | |
| 192 | |
| 193 // Create the host. | |
| 194 host_.reset(new ChromotingHost( | |
| 195 signal_strategy_.get(), | |
| 196 desktop_environment_factory_.get(), | |
| 197 CreateHostSessionManager(network_settings, | |
| 198 host_context_->url_request_context_getter()), | |
| 199 host_context_->audio_task_runner(), | |
| 200 host_context_->input_task_runner(), | |
| 201 host_context_->video_capture_task_runner(), | |
| 202 host_context_->video_encode_task_runner(), | |
| 203 host_context_->network_task_runner(), | |
| 204 host_context_->ui_task_runner())); | |
| 205 host_->AddStatusObserver(this); | |
| 206 log_to_server_.reset( | |
| 207 new LogToServer(host_->AsWeakPtr(), ServerLogEntry::IT2ME, | |
| 208 signal_strategy_.get(), directory_bot_jid_)); | |
| 209 | |
| 210 // Disable audio by default. | |
| 211 // TODO(sergeyu): Add UI to enable it. | |
| 212 scoped_ptr<protocol::CandidateSessionConfig> protocol_config = | |
| 213 protocol::CandidateSessionConfig::CreateDefault(); | |
| 214 protocol::CandidateSessionConfig::DisableAudioChannel(protocol_config.get()); | |
| 215 | |
| 216 // VP9 encode is not yet supported. | |
| 217 protocol::CandidateSessionConfig::DisableVideoCodec( | |
| 218 protocol_config.get(), protocol::ChannelConfig::CODEC_VP9); | |
| 219 | |
| 220 host_->set_protocol_config(protocol_config.Pass()); | |
| 221 | |
| 222 // Create event logger. | |
| 223 host_event_logger_ = | |
| 224 HostEventLogger::Create(host_->AsWeakPtr(), kApplicationName); | |
| 225 | |
| 226 // Connect signaling and start the host. | |
| 227 signal_strategy_->Connect(); | |
| 228 host_->Start(xmpp_server_config_.username); | |
| 229 | |
| 230 SetState(kRequestedAccessCode); | |
| 231 return; | |
| 232 } | |
| 233 | |
| 234 void It2MeImpl::ShutdownOnNetworkThread() { | |
| 235 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 236 DCHECK(state_ == kDisconnecting || state_ == kDisconnected); | |
| 237 | |
| 238 if (state_ == kDisconnecting) { | |
| 239 host_event_logger_.reset(); | |
| 240 host_->RemoveStatusObserver(this); | |
| 241 host_.reset(); | |
| 242 | |
| 243 register_request_.reset(); | |
| 244 log_to_server_.reset(); | |
| 245 signal_strategy_.reset(); | |
| 246 SetState(kDisconnected); | |
| 247 } | |
| 248 | |
| 249 host_context_->ui_task_runner()->PostTask( | |
| 250 FROM_HERE, base::Bind(&It2MeImpl::ShutdownOnUiThread, this)); | |
| 251 } | |
| 252 | |
| 253 void It2MeImpl::ShutdownOnUiThread() { | |
| 254 DCHECK(host_context_->ui_task_runner()->BelongsToCurrentThread()); | |
| 255 | |
| 256 // Destroy the DesktopEnvironmentFactory, to free thread references. | |
| 257 desktop_environment_factory_.reset(); | |
| 258 | |
| 259 // Stop listening for policy updates. | |
| 260 if (policy_watcher_.get()) { | |
| 261 base::WaitableEvent policy_watcher_stopped_(true, false); | |
| 262 policy_watcher_->StopWatching(&policy_watcher_stopped_); | |
| 263 policy_watcher_stopped_.Wait(); | |
| 264 policy_watcher_.reset(); | |
| 265 } | |
| 266 } | |
| 267 | |
| 268 void It2MeImpl::OnAccessDenied(const std::string& jid) { | |
| 269 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 270 | |
| 271 ++failed_login_attempts_; | |
| 272 if (failed_login_attempts_ == kMaxLoginAttempts) { | |
| 273 Disconnect(); | |
| 274 } | |
| 275 } | |
| 276 | |
| 277 void It2MeImpl::OnClientAuthenticated( | |
| 278 const std::string& jid) { | |
| 279 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 280 | |
| 281 if (state_ == kDisconnecting) { | |
| 282 // Ignore the new connection if we are disconnecting. | |
| 283 return; | |
| 284 } | |
| 285 if (state_ == kConnected) { | |
| 286 // If we already connected another client then one of the connections may be | |
| 287 // an attacker, so both are suspect and we have to reject the second | |
| 288 // connection and shutdown the host. | |
| 289 host_->RejectAuthenticatingClient(); | |
| 290 Disconnect(); | |
| 291 return; | |
| 292 } | |
| 293 | |
| 294 std::string client_username = jid; | |
| 295 size_t pos = client_username.find('/'); | |
| 296 if (pos != std::string::npos) | |
| 297 client_username.replace(pos, std::string::npos, ""); | |
| 298 | |
| 299 LOG(INFO) << "Client " << client_username << " connected."; | |
| 300 | |
| 301 // Pass the client user name to the script object before changing state. | |
| 302 plugin_task_runner_->PostTask( | |
| 303 FROM_HERE, base::Bind(&It2MeImpl::Observer::OnClientAuthenticated, | |
| 304 observer_, client_username)); | |
| 305 | |
| 306 SetState(kConnected); | |
| 307 } | |
| 308 | |
| 309 void It2MeImpl::OnClientDisconnected( | |
| 310 const std::string& jid) { | |
| 311 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 312 | |
| 313 Disconnect(); | |
| 314 } | |
| 315 | |
| 316 void It2MeImpl::OnPolicyUpdate( | |
| 317 scoped_ptr<base::DictionaryValue> policies) { | |
| 318 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 319 | |
| 320 bool nat_policy; | |
| 321 if (policies->GetBoolean(policy_hack::PolicyWatcher::kNatPolicyName, | |
| 322 &nat_policy)) { | |
| 323 UpdateNatPolicy(nat_policy); | |
| 324 } | |
| 325 std::string host_domain; | |
| 326 if (policies->GetString(policy_hack::PolicyWatcher::kHostDomainPolicyName, | |
| 327 &host_domain)) { | |
| 328 UpdateHostDomainPolicy(host_domain); | |
| 329 } | |
| 330 | |
| 331 policy_received_ = true; | |
| 332 | |
| 333 if (!pending_connect_.is_null()) { | |
| 334 pending_connect_.Run(); | |
| 335 pending_connect_.Reset(); | |
| 336 } | |
| 337 } | |
| 338 | |
| 339 void It2MeImpl::UpdateNatPolicy( | |
| 340 bool nat_traversal_enabled) { | |
| 341 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 342 | |
| 343 VLOG(2) << "UpdateNatPolicy: " << nat_traversal_enabled; | |
| 344 | |
| 345 // When transitioning from enabled to disabled, force disconnect any | |
| 346 // existing session. | |
| 347 if (nat_traversal_enabled_ && !nat_traversal_enabled && IsConnected()) { | |
| 348 Disconnect(); | |
| 349 } | |
| 350 | |
| 351 nat_traversal_enabled_ = nat_traversal_enabled; | |
| 352 | |
| 353 // Notify the web-app of the policy setting. | |
| 354 plugin_task_runner_->PostTask( | |
| 355 FROM_HERE, base::Bind(&It2MeImpl::Observer::OnNatPolicyChanged, | |
| 356 observer_, nat_traversal_enabled_)); | |
| 357 } | |
| 358 | |
| 359 void It2MeImpl::UpdateHostDomainPolicy( | |
| 360 const std::string& host_domain) { | |
| 361 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 362 | |
| 363 VLOG(2) << "UpdateHostDomainPolicy: " << host_domain; | |
| 364 | |
| 365 // When setting a host domain policy, force disconnect any existing session. | |
| 366 if (!host_domain.empty() && IsConnected()) { | |
| 367 Disconnect(); | |
| 368 } | |
| 369 | |
| 370 required_host_domain_ = host_domain; | |
| 371 } | |
| 372 | |
| 373 It2MeImpl::~It2MeImpl() { | |
| 374 // Check that resources that need to be torn down on the UI thread are gone. | |
| 375 DCHECK(!desktop_environment_factory_.get()); | |
| 376 DCHECK(!policy_watcher_.get()); | |
| 377 } | |
| 378 | |
| 379 void It2MeImpl::SetState(It2MeHostState state) { | |
| 380 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 381 | |
| 382 switch (state_) { | |
| 383 case kDisconnected: | |
| 384 DCHECK(state == kStarting || | |
| 385 state == kError) << state; | |
| 386 break; | |
| 387 case kStarting: | |
| 388 DCHECK(state == kRequestedAccessCode || | |
| 389 state == kDisconnecting || | |
| 390 state == kError || | |
| 391 state == kInvalidDomainError) << state; | |
| 392 break; | |
| 393 case kRequestedAccessCode: | |
| 394 DCHECK(state == kReceivedAccessCode || | |
| 395 state == kDisconnecting || | |
| 396 state == kError) << state; | |
| 397 break; | |
| 398 case kReceivedAccessCode: | |
| 399 DCHECK(state == kConnected || | |
| 400 state == kDisconnecting || | |
| 401 state == kError) << state; | |
| 402 break; | |
| 403 case kConnected: | |
| 404 DCHECK(state == kDisconnecting || | |
| 405 state == kDisconnected || | |
| 406 state == kError) << state; | |
| 407 break; | |
| 408 case kDisconnecting: | |
| 409 DCHECK(state == kDisconnected) << state; | |
| 410 break; | |
| 411 case kError: | |
| 412 DCHECK(state == kDisconnecting) << state; | |
| 413 break; | |
| 414 case kInvalidDomainError: | |
| 415 DCHECK(state == kDisconnecting) << state; | |
| 416 break; | |
| 417 }; | |
| 418 | |
| 419 state_ = state; | |
| 420 | |
| 421 // Post a state-change notification to the web-app. | |
| 422 plugin_task_runner_->PostTask( | |
| 423 FROM_HERE, base::Bind(&It2MeImpl::Observer::OnStateChanged, | |
| 424 observer_, state)); | |
| 425 } | |
| 426 | |
| 427 bool It2MeImpl::IsConnected() const { | |
| 428 return state_ == kRequestedAccessCode || state_ == kReceivedAccessCode || | |
| 429 state_ == kConnected; | |
| 430 } | |
| 431 | |
| 432 void It2MeImpl::OnReceivedSupportID( | |
| 433 bool success, | |
| 434 const std::string& support_id, | |
| 435 const base::TimeDelta& lifetime) { | |
| 436 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread()); | |
| 437 | |
| 438 if (!success) { | |
| 439 SetState(kError); | |
| 440 Disconnect(); | |
| 441 return; | |
| 442 } | |
| 443 | |
| 444 std::string host_secret = GenerateSupportHostSecret(); | |
| 445 std::string access_code = support_id + host_secret; | |
| 446 | |
| 447 std::string local_certificate = host_key_pair_->GenerateCertificate(); | |
| 448 if (local_certificate.empty()) { | |
| 449 LOG(ERROR) << "Failed to generate host certificate."; | |
| 450 SetState(kError); | |
| 451 Disconnect(); | |
| 452 return; | |
| 453 } | |
| 454 | |
| 455 scoped_ptr<protocol::AuthenticatorFactory> factory( | |
| 456 new protocol::It2MeHostAuthenticatorFactory( | |
| 457 local_certificate, host_key_pair_, access_code)); | |
| 458 host_->SetAuthenticatorFactory(factory.Pass()); | |
| 459 | |
| 460 // Pass the Access Code to the script object before changing state. | |
| 461 plugin_task_runner_->PostTask( | |
| 462 FROM_HERE, base::Bind(&It2MeImpl::Observer::OnStoreAccessCode, | |
| 463 observer_, access_code, lifetime)); | |
| 464 | |
| 465 SetState(kReceivedAccessCode); | |
| 466 } | |
| 467 | |
| 468 } // namespace remoting | |
| OLD | NEW |