| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // This is an application of a minimal host process in a Chromoting |
| // system. It serves the purpose of gluing different pieces together |
| // to make a functional host process for testing. |
| // |
| // It peforms the following functionality: |
| // 1. Connect to the GTalk network and register the machine as a host. |
| // 2. Accepts connection through libjingle. |
| // 3. Receive mouse / keyboard events through libjingle. |
| // 4. Sends screen capture through libjingle. |
| |
| #include <iostream> |
| #include <string> |
| |
| #include "build/build_config.h" |
| |
| #include "base/at_exit.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/environment.h" |
| #include "base/file_path.h" |
| #include "base/logging.h" |
| #include "base/message_loop.h" |
| #include "base/path_service.h" |
| #include "base/string_number_conversions.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/threading/thread.h" |
| #include "crypto/nss_util.h" |
| #include "net/base/network_change_notifier.h" |
| #include "remoting/base/constants.h" |
| #include "remoting/host/capturer_fake.h" |
| #include "remoting/host/chromoting_host.h" |
| #include "remoting/host/chromoting_host_context.h" |
| #include "remoting/host/desktop_environment.h" |
| #include "remoting/host/event_executor.h" |
| #include "remoting/host/heartbeat_sender.h" |
| #include "remoting/host/host_key_pair.h" |
| #include "remoting/host/host_secret.h" |
| #include "remoting/host/it2me_host_user_interface.h" |
| #include "remoting/host/json_host_config.h" |
| #include "remoting/host/log_to_server.h" |
| #include "remoting/host/register_support_host_request.h" |
| #include "remoting/host/signaling_connector.h" |
| #include "remoting/jingle_glue/xmpp_signal_strategy.h" |
| #include "remoting/proto/video.pb.h" |
| #include "remoting/protocol/it2me_host_authenticator_factory.h" |
| #include "remoting/protocol/me2me_host_authenticator_factory.h" |
| |
| #if defined(TOOLKIT_USES_GTK) |
| #include "ui/gfx/gtk_util.h" |
| #elif defined(OS_MACOSX) |
| #include "base/mac/scoped_nsautorelease_pool.h" |
| #elif defined(OS_WIN) |
| // TODO(garykac) Make simple host into a proper GUI app on Windows so that we |
| // have an hModule for the dialog resource. |
| HMODULE g_hModule = NULL; |
| #endif |
| |
| using remoting::protocol::CandidateSessionConfig; |
| using remoting::protocol::ChannelConfig; |
| using remoting::protocol::NetworkSettings; |
| |
| namespace { |
| |
| const FilePath::CharType kDefaultConfigPath[] = |
| FILE_PATH_LITERAL(".ChromotingConfig.json"); |
| |
| const char kHomeDrive[] = "HOMEDRIVE"; |
| const char kHomePath[] = "HOMEPATH"; |
| |
| const char kFakeSwitchName[] = "fake"; |
| const char kIT2MeSwitchName[] = "it2me"; |
| const char kConfigSwitchName[] = "config"; |
| const char kVideoSwitchName[] = "video"; |
| const char kDisableNatTraversalSwitchName[] = "disable-nat-traversal"; |
| const char kMinPortSwitchName[] = "min-port"; |
| const char kMaxPortSwitchName[] = "max-port"; |
| |
| const char kVideoSwitchValueVerbatim[] = "verbatim"; |
| const char kVideoSwitchValueZip[] = "zip"; |
| const char kVideoSwitchValueVp8[] = "vp8"; |
| const char kVideoSwitchValueVp8Rtp[] = "vp8rtp"; |
| |
| } // namespace |
| |
| namespace remoting { |
| |
| class SimpleHost { |
| public: |
| SimpleHost() |
| : message_loop_(MessageLoop::TYPE_UI), |
| file_io_thread_("FileIO"), |
| context_(NULL, message_loop_.message_loop_proxy()), |
| fake_(false), |
| is_it2me_(false) { |
| context_.Start(); |
| file_io_thread_.Start(); |
| network_change_notifier_.reset(net::NetworkChangeNotifier::Create()); |
| } |
| |
| int Run() { |
| FilePath config_path = GetConfigPath(); |
| scoped_refptr<JsonHostConfig> config = new JsonHostConfig( |
| config_path, file_io_thread_.message_loop_proxy()); |
| if (!config->Read()) { |
| LOG(ERROR) << "Failed to read configuration file " |
| << config_path.value(); |
| return 1; |
| } |
| |
| if (!config->GetString(kHostIdConfigPath, &host_id_)) { |
| LOG(ERROR) << "host_id is not defined in the config."; |
| return 1; |
| } |
| |
| if (!key_pair_.Load(config)) { |
| return 1; |
| } |
| |
| std::string host_secret_hash_string; |
| if (!config->GetString(kHostSecretHashConfigPath, |
| &host_secret_hash_string)) { |
| host_secret_hash_string = "plain:"; |
| } |
| |
| if (!host_secret_hash_.Parse(host_secret_hash_string)) { |
| LOG(ERROR) << "Invalid host_secret_hash."; |
| return false; |
| } |
| |
| // Use an XMPP connection to the Talk network for session signalling. |
| if (!config->GetString(kXmppLoginConfigPath, &xmpp_login_) || |
| !config->GetString(kXmppAuthTokenConfigPath, &xmpp_auth_token_)) { |
| LOG(ERROR) << "XMPP credentials are not defined in the config."; |
| return 1; |
| } |
| if (!config->GetString(kXmppAuthServiceConfigPath, &xmpp_auth_service_)) { |
| // For the simple host, we assume we always use the ClientLogin token for |
| // chromiumsync because we do not have an HTTP stack with which we can |
| // easily request an OAuth2 access token even if we had a RefreshToken for |
| // the account. |
| xmpp_auth_service_ = kChromotingTokenDefaultServiceName; |
| } |
| |
| context_.network_message_loop()->PostTask(FROM_HERE, base::Bind( |
| &SimpleHost::StartHost, base::Unretained(this))); |
| |
| message_loop_.MessageLoop::Run(); |
| |
| return 0; |
| } |
| |
| void set_config_path(const FilePath& config_path) { |
| config_path_ = config_path; |
| } |
| void set_fake(bool fake) { fake_ = fake; } |
| void set_is_it2me(bool is_it2me) { is_it2me_ = is_it2me; } |
| void set_protocol_config(CandidateSessionConfig* protocol_config) { |
| protocol_config_.reset(protocol_config); |
| } |
| |
| NetworkSettings* network_settings() { return &network_settings_; } |
| |
| private: |
| static void SetIT2MeAccessCode(scoped_refptr<ChromotingHost> host, |
| HostKeyPair* key_pair, |
| bool successful, |
| const std::string& support_id, |
| const base::TimeDelta& lifetime) { |
| if (successful) { |
| std::string host_secret = GenerateSupportHostSecret(); |
| std::string access_code = support_id + host_secret; |
| std::cout << "Support id: " << access_code << std::endl; |
| |
| scoped_ptr<protocol::AuthenticatorFactory> factory( |
| new protocol::It2MeHostAuthenticatorFactory( |
| key_pair->GenerateCertificate(), *key_pair->private_key(), |
| access_code)); |
| host->SetAuthenticatorFactory(factory.Pass()); |
| } else { |
| LOG(ERROR) << "If you haven't done so recently, try running" |
| << " remoting/tools/register_host.py."; |
| } |
| } |
| |
| FilePath GetConfigPath() { |
| if (!config_path_.empty()) |
| return config_path_; |
| |
| scoped_ptr<base::Environment> env(base::Environment::Create()); |
| |
| #if defined(OS_WIN) |
| std::string home_drive; |
| env->GetVar(kHomeDrive, &home_drive); |
| std::string home_path; |
| env->GetVar(kHomePath, &home_path); |
| return FilePath(UTF8ToWide(home_drive)) |
| .Append(UTF8ToWide(home_path)) |
| .Append(kDefaultConfigPath); |
| #else |
| std::string home_path; |
| env->GetVar(base::env_vars::kHome, &home_path); |
| return FilePath(home_path).Append(kDefaultConfigPath); |
| #endif |
| } |
| |
| void StartHost() { |
| signal_strategy_.reset( |
| new XmppSignalStrategy(context_.jingle_thread(), xmpp_login_, |
| xmpp_auth_token_, xmpp_auth_service_)); |
| signaling_connector_.reset(new SignalingConnector(signal_strategy_.get())); |
| |
| if (fake_) { |
| scoped_ptr<Capturer> capturer(new CapturerFake()); |
| scoped_ptr<protocol::HostEventStub> event_executor = |
| EventExecutor::Create( |
| context_.desktop_message_loop(), capturer.get()); |
| desktop_environment_ = DesktopEnvironment::CreateFake( |
| &context_, capturer.Pass(), event_executor.Pass()); |
| } else { |
| desktop_environment_ = DesktopEnvironment::Create(&context_); |
| } |
| |
| host_ = new ChromotingHost(&context_, signal_strategy_.get(), |
| desktop_environment_.get(), network_settings_); |
| |
| ServerLogEntry::Mode mode = |
| is_it2me_ ? ServerLogEntry::IT2ME : ServerLogEntry::ME2ME; |
| log_to_server_.reset(new LogToServer(host_, mode, signal_strategy_.get())); |
| |
| if (is_it2me_) { |
| it2me_host_user_interface_.reset( |
| new It2MeHostUserInterface(host_, &context_)); |
| it2me_host_user_interface_->Init(); |
| } |
| |
| if (protocol_config_.get()) { |
| host_->set_protocol_config(protocol_config_.release()); |
| } |
| |
| if (is_it2me_) { |
| register_request_.reset(new RegisterSupportHostRequest( |
| signal_strategy_.get(), &key_pair_, |
| base::Bind(&SimpleHost::SetIT2MeAccessCode, host_, &key_pair_))); |
| } else { |
| heartbeat_sender_.reset( |
| new HeartbeatSender(host_id_, signal_strategy_.get(), &key_pair_)); |
| } |
| |
| host_->Start(); |
| |
| // Create a Me2Me authenticator factory. |
| if (!is_it2me_) { |
| scoped_ptr<protocol::AuthenticatorFactory> factory( |
| new protocol::Me2MeHostAuthenticatorFactory( |
| xmpp_login_, key_pair_.GenerateCertificate(), |
| *key_pair_.private_key(), host_secret_hash_)); |
| host_->SetAuthenticatorFactory(factory.Pass()); |
| } |
| } |
| |
| MessageLoop message_loop_; |
| base::Thread file_io_thread_; |
| ChromotingHostContext context_; |
| scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_; |
| |
| FilePath config_path_; |
| bool fake_; |
| bool is_it2me_; |
| NetworkSettings network_settings_; |
| scoped_ptr<CandidateSessionConfig> protocol_config_; |
| |
| std::string host_id_; |
| HostKeyPair key_pair_; |
| protocol::SharedSecretHash host_secret_hash_; |
| std::string xmpp_login_; |
| std::string xmpp_auth_token_; |
| std::string xmpp_auth_service_; |
| |
| scoped_ptr<SignalStrategy> signal_strategy_; |
| scoped_ptr<SignalingConnector> signaling_connector_; |
| scoped_ptr<DesktopEnvironment> desktop_environment_; |
| scoped_ptr<LogToServer> log_to_server_; |
| scoped_ptr<It2MeHostUserInterface> it2me_host_user_interface_; |
| scoped_ptr<RegisterSupportHostRequest> register_request_; |
| scoped_ptr<HeartbeatSender> heartbeat_sender_; |
| |
| scoped_refptr<ChromotingHost> host_; |
| }; |
| |
| } // namespace remoting |
| |
| int main(int argc, char** argv) { |
| #if defined(OS_MACOSX) |
| // Needed so we don't leak objects when threads are created. |
| base::mac::ScopedNSAutoreleasePool pool; |
| #endif |
| |
| CommandLine::Init(argc, argv); |
| const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); |
| |
| base::AtExitManager exit_manager; |
| crypto::EnsureNSPRInit(); |
| |
| #if defined(TOOLKIT_USES_GTK) |
| gfx::GtkInitFromCommandLine(*cmd_line); |
| #endif // TOOLKIT_USES_GTK |
| |
| remoting::SimpleHost simple_host; |
| |
| if (cmd_line->HasSwitch(kConfigSwitchName)) { |
| simple_host.set_config_path( |
| cmd_line->GetSwitchValuePath(kConfigSwitchName)); |
| } |
| simple_host.set_fake(cmd_line->HasSwitch(kFakeSwitchName)); |
| simple_host.set_is_it2me(cmd_line->HasSwitch(kIT2MeSwitchName)); |
| |
| if (cmd_line->HasSwitch(kVideoSwitchName)) { |
| std::string video_codec = cmd_line->GetSwitchValueASCII(kVideoSwitchName); |
| scoped_ptr<CandidateSessionConfig> config( |
| CandidateSessionConfig::CreateDefault()); |
| config->mutable_video_configs()->clear(); |
| |
| ChannelConfig::TransportType transport = ChannelConfig::TRANSPORT_STREAM; |
| ChannelConfig::Codec codec; |
| if (video_codec == kVideoSwitchValueVerbatim) { |
| codec = ChannelConfig::CODEC_VERBATIM; |
| } else if (video_codec == kVideoSwitchValueZip) { |
| codec = ChannelConfig::CODEC_ZIP; |
| } else if (video_codec == kVideoSwitchValueVp8) { |
| codec = ChannelConfig::CODEC_VP8; |
| } else if (video_codec == kVideoSwitchValueVp8Rtp) { |
| transport = ChannelConfig::TRANSPORT_SRTP; |
| codec = ChannelConfig::CODEC_VP8; |
| } else { |
| LOG(ERROR) << "Unknown video codec: " << video_codec; |
| return 1; |
| } |
| config->mutable_video_configs()->push_back(ChannelConfig( |
| transport, remoting::protocol::kDefaultStreamVersion, codec)); |
| simple_host.set_protocol_config(config.release()); |
| } |
| |
| simple_host.network_settings()->nat_traversal_mode = |
| cmd_line->HasSwitch(kDisableNatTraversalSwitchName) ? |
| remoting::protocol::TransportConfig::NAT_TRAVERSAL_DISABLED : |
| remoting::protocol::TransportConfig::NAT_TRAVERSAL_ENABLED; |
| |
| if (cmd_line->HasSwitch(kMinPortSwitchName)) { |
| std::string min_port_str = |
| cmd_line->GetSwitchValueASCII(kMinPortSwitchName); |
| int min_port = 0; |
| if (!base::StringToInt(min_port_str, &min_port) || |
| min_port < 0 || min_port > 65535) { |
| LOG(ERROR) << "Invalid min-port value: " << min_port |
| << ". Expected integer in range [0, 65535]."; |
| return 1; |
| } |
| simple_host.network_settings()->min_port = min_port; |
| } |
| |
| if (cmd_line->HasSwitch(kMaxPortSwitchName)) { |
| std::string max_port_str = |
| cmd_line->GetSwitchValueASCII(kMaxPortSwitchName); |
| int max_port = 0; |
| if (!base::StringToInt(max_port_str, &max_port) || |
| max_port < 0 || max_port > 65535) { |
| LOG(ERROR) << "Invalid max-port value: " << max_port |
| << ". Expected integer in range [0, 65535]."; |
| return 1; |
| } |
| simple_host.network_settings()->max_port = max_port; |
| } |
| |
| return simple_host.Run(); |
| } |