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