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