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.
5 #include "remoting/host/it2me/it2me_host.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"
29 // This is used for tagging system event logs.
30 const char kApplicationName[] = "chromoting";
31 const int kMaxLoginAttempts = 5;
36 scoped_ptr<ChromotingHostContext> host_context,
37 scoped_refptr<base::SingleThreadTaskRunner> plugin_task_runner,
38 base::WeakPtr<It2MeHost::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),
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());
53 void It2MeHost::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(&It2MeHost::Connect, this));
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()));
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(&It2MeHost::OnPolicyUpdate, this));
72 // Switch to the network thread to start the actual connection.
73 host_context_->network_task_runner()->PostTask(
74 FROM_HERE, base::Bind(&It2MeHost::ReadPolicyAndConnect, this));
77 void It2MeHost::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(&It2MeHost::Disconnect, this));
87 ShutdownOnNetworkThread();
91 SetState(kDisconnecting);
92 SetState(kDisconnected);
93 ShutdownOnNetworkThread();
100 SetState(kDisconnecting);
103 SetState(kDisconnected);
104 ShutdownOnNetworkThread();
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(&It2MeHost::ShutdownOnNetworkThread, this));
117 void It2MeHost::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(&It2MeHost::RequestNatPolicy, this));
125 if (policy_received_)
126 UpdateNatPolicy(nat_traversal_enabled_);
129 void It2MeHost::ReadPolicyAndConnect() {
130 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
134 // Only proceed to FinishConnect() if at least one policy update has been
136 if (policy_received_) {
139 // Otherwise, create the policy watcher, and thunk the connect.
141 base::Bind(&It2MeHost::FinishConnect, this);
145 void It2MeHost::FinishConnect() {
146 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
148 if (state_ != kStarting) {
149 // Host has been stopped while we were fetching policy.
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);
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();
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_));
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(&It2MeHost::OnReceivedSupportID,
176 base::Unretained(this))));
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();
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;
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_));
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());
216 // VP9 encode is not yet supported.
217 protocol::CandidateSessionConfig::DisableVideoCodec(
218 protocol_config.get(), protocol::ChannelConfig::CODEC_VP9);
220 host_->set_protocol_config(protocol_config.Pass());
222 // Create event logger.
224 HostEventLogger::Create(host_->AsWeakPtr(), kApplicationName);
226 // Connect signaling and start the host.
227 signal_strategy_->Connect();
228 host_->Start(xmpp_server_config_.username);
230 SetState(kRequestedAccessCode);
234 void It2MeHost::ShutdownOnNetworkThread() {
235 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
236 DCHECK(state_ == kDisconnecting || state_ == kDisconnected);
238 if (state_ == kDisconnecting) {
239 host_event_logger_.reset();
240 host_->RemoveStatusObserver(this);
243 register_request_.reset();
244 log_to_server_.reset();
245 signal_strategy_.reset();
246 SetState(kDisconnected);
249 host_context_->ui_task_runner()->PostTask(
250 FROM_HERE, base::Bind(&It2MeHost::ShutdownOnUiThread, this));
253 void It2MeHost::ShutdownOnUiThread() {
254 DCHECK(host_context_->ui_task_runner()->BelongsToCurrentThread());
256 // Destroy the DesktopEnvironmentFactory, to free thread references.
257 desktop_environment_factory_.reset();
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();
268 void It2MeHost::OnAccessDenied(const std::string& jid) {
269 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
271 ++failed_login_attempts_;
272 if (failed_login_attempts_ == kMaxLoginAttempts) {
277 void It2MeHost::OnClientAuthenticated(const std::string& jid) {
278 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
280 if (state_ == kDisconnecting) {
281 // Ignore the new connection if we are disconnecting.
284 if (state_ == kConnected) {
285 // If we already connected another client then one of the connections may be
286 // an attacker, so both are suspect and we have to reject the second
287 // connection and shutdown the host.
288 host_->RejectAuthenticatingClient();
293 std::string client_username = jid;
294 size_t pos = client_username.find('/');
295 if (pos != std::string::npos)
296 client_username.replace(pos, std::string::npos, "");
298 LOG(INFO) << "Client " << client_username << " connected.";
300 // Pass the client user name to the script object before changing state.
301 plugin_task_runner_->PostTask(
302 FROM_HERE, base::Bind(&It2MeHost::Observer::OnClientAuthenticated,
303 observer_, client_username));
305 SetState(kConnected);
308 void It2MeHost::OnClientDisconnected(const std::string& jid) {
309 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
314 void It2MeHost::OnPolicyUpdate(scoped_ptr<base::DictionaryValue> policies) {
315 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
318 if (policies->GetBoolean(policy_hack::PolicyWatcher::kNatPolicyName,
320 UpdateNatPolicy(nat_policy);
322 std::string host_domain;
323 if (policies->GetString(policy_hack::PolicyWatcher::kHostDomainPolicyName,
325 UpdateHostDomainPolicy(host_domain);
328 policy_received_ = true;
330 if (!pending_connect_.is_null()) {
331 pending_connect_.Run();
332 pending_connect_.Reset();
336 void It2MeHost::UpdateNatPolicy(bool nat_traversal_enabled) {
337 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
339 VLOG(2) << "UpdateNatPolicy: " << nat_traversal_enabled;
341 // When transitioning from enabled to disabled, force disconnect any
343 if (nat_traversal_enabled_ && !nat_traversal_enabled && IsConnected()) {
347 nat_traversal_enabled_ = nat_traversal_enabled;
349 // Notify the web-app of the policy setting.
350 plugin_task_runner_->PostTask(
351 FROM_HERE, base::Bind(&It2MeHost::Observer::OnNatPolicyChanged,
352 observer_, nat_traversal_enabled_));
355 void It2MeHost::UpdateHostDomainPolicy(const std::string& host_domain) {
356 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
358 VLOG(2) << "UpdateHostDomainPolicy: " << host_domain;
360 // When setting a host domain policy, force disconnect any existing session.
361 if (!host_domain.empty() && IsConnected()) {
365 required_host_domain_ = host_domain;
368 It2MeHost::~It2MeHost() {
369 // Check that resources that need to be torn down on the UI thread are gone.
370 DCHECK(!desktop_environment_factory_.get());
371 DCHECK(!policy_watcher_.get());
374 void It2MeHost::SetState(It2MeHostState state) {
375 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
379 DCHECK(state == kStarting ||
380 state == kError) << state;
383 DCHECK(state == kRequestedAccessCode ||
384 state == kDisconnecting ||
386 state == kInvalidDomainError) << state;
388 case kRequestedAccessCode:
389 DCHECK(state == kReceivedAccessCode ||
390 state == kDisconnecting ||
391 state == kError) << state;
393 case kReceivedAccessCode:
394 DCHECK(state == kConnected ||
395 state == kDisconnecting ||
396 state == kError) << state;
399 DCHECK(state == kDisconnecting ||
400 state == kDisconnected ||
401 state == kError) << state;
404 DCHECK(state == kDisconnected) << state;
407 DCHECK(state == kDisconnecting) << state;
409 case kInvalidDomainError:
410 DCHECK(state == kDisconnecting) << state;
416 // Post a state-change notification to the web-app.
417 plugin_task_runner_->PostTask(
418 FROM_HERE, base::Bind(&It2MeHost::Observer::OnStateChanged,
422 bool It2MeHost::IsConnected() const {
423 return state_ == kRequestedAccessCode || state_ == kReceivedAccessCode ||
424 state_ == kConnected;
427 void It2MeHost::OnReceivedSupportID(
429 const std::string& support_id,
430 const base::TimeDelta& lifetime) {
431 DCHECK(host_context_->network_task_runner()->BelongsToCurrentThread());
439 std::string host_secret = GenerateSupportHostSecret();
440 std::string access_code = support_id + host_secret;
442 std::string local_certificate = host_key_pair_->GenerateCertificate();
443 if (local_certificate.empty()) {
444 LOG(ERROR) << "Failed to generate host certificate.";
450 scoped_ptr<protocol::AuthenticatorFactory> factory(
451 new protocol::It2MeHostAuthenticatorFactory(
452 local_certificate, host_key_pair_, access_code));
453 host_->SetAuthenticatorFactory(factory.Pass());
455 // Pass the Access Code to the script object before changing state.
456 plugin_task_runner_->PostTask(
457 FROM_HERE, base::Bind(&It2MeHost::Observer::OnStoreAccessCode,
458 observer_, access_code, lifetime));
460 SetState(kReceivedAccessCode);
463 } // namespace remoting