| // 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 file implements a standalone host process for Me2Me, which is currently |
| // used for the Linux-only Virtual Me2Me build. |
| |
| #if defined(OS_WIN) |
| #include <windows.h> |
| #endif |
| |
| #include <string> |
| |
| #include "base/at_exit.h" |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/file_path.h" |
| #include "base/file_util.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/message_loop.h" |
| #include "base/threading/thread.h" |
| #include "build/build_config.h" |
| #include "crypto/nss_util.h" |
| #include "net/base/network_change_notifier.h" |
| #include "remoting/base/constants.h" |
| #include "remoting/host/branding.h" |
| #include "remoting/host/capturer.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_config.h" |
| #include "remoting/host/host_event_logger.h" |
| #include "remoting/host/json_host_config.h" |
| #include "remoting/host/log_to_server.h" |
| #include "remoting/host/oauth_client.h" |
| #include "remoting/host/policy_hack/nat_policy.h" |
| #include "remoting/host/signaling_connector.h" |
| #include "remoting/jingle_glue/xmpp_signal_strategy.h" |
| #include "remoting/protocol/me2me_host_authenticator_factory.h" |
| |
| #if defined(TOOLKIT_USES_GTK) |
| #include "ui/gfx/gtk_util.h" |
| #endif |
| |
| namespace { |
| |
| // This is used for tagging system event logs. |
| const char kApplicationName[] = "chromoting"; |
| |
| // These are used for parsing the config-file locations from the command line, |
| // and for defining the default locations if the switches are not present. |
| const char kAuthConfigSwitchName[] = "auth-config"; |
| const char kHostConfigSwitchName[] = "host-config"; |
| |
| const FilePath::CharType kDefaultAuthConfigFile[] = |
| FILE_PATH_LITERAL("auth.json"); |
| const FilePath::CharType kDefaultHostConfigFile[] = |
| FILE_PATH_LITERAL("host.json"); |
| |
| const int kMinPortNumber = 12400; |
| const int kMaxPortNumber = 12409; |
| |
| } // namespace |
| |
| namespace remoting { |
| |
| class HostProcess : public OAuthClient::Delegate { |
| public: |
| HostProcess() |
| : message_loop_(MessageLoop::TYPE_UI), |
| file_io_thread_("FileIO"), |
| allow_nat_traversal_(true), |
| restarting_(false) { |
| file_io_thread_.StartWithOptions( |
| base::Thread::Options(MessageLoop::TYPE_IO, 0)); |
| |
| context_.reset(new ChromotingHostContext( |
| file_io_thread_.message_loop_proxy(), |
| message_loop_.message_loop_proxy())); |
| context_->Start(); |
| network_change_notifier_.reset(net::NetworkChangeNotifier::Create()); |
| } |
| |
| void InitWithCommandLine(const CommandLine* cmd_line) { |
| FilePath default_config_dir = remoting::GetConfigDir(); |
| if (cmd_line->HasSwitch(kAuthConfigSwitchName)) { |
| auth_config_path_ = cmd_line->GetSwitchValuePath(kAuthConfigSwitchName); |
| } else { |
| auth_config_path_ = default_config_dir.Append(kDefaultAuthConfigFile); |
| } |
| |
| if (cmd_line->HasSwitch(kHostConfigSwitchName)) { |
| host_config_path_ = cmd_line->GetSwitchValuePath(kHostConfigSwitchName); |
| } else { |
| host_config_path_ = default_config_dir.Append(kDefaultHostConfigFile); |
| } |
| |
| #if defined(OS_LINUX) |
| Capturer::EnableXDamage(true); |
| #endif |
| } |
| |
| int Run() { |
| bool tokens_pending = false; |
| if (!LoadConfig(file_io_thread_.message_loop_proxy(), &tokens_pending)) { |
| return 1; |
| } |
| if (tokens_pending) { |
| // If we have an OAuth refresh token, then XmppSignalStrategy can't |
| // handle it directly, so refresh it asynchronously. A task will be |
| // posted on the message loop to start watching the NAT policy when |
| // the access token is available. |
| oauth_client_.Start(oauth_refresh_token_, this, |
| message_loop_.message_loop_proxy()); |
| } else { |
| StartWatchingNatPolicy(); |
| } |
| |
| message_loop_.Run(); |
| |
| return 0; |
| } |
| |
| // Overridden from OAuthClient::Delegate |
| virtual void OnRefreshTokenResponse(const std::string& access_token, |
| int expires) OVERRIDE { |
| xmpp_auth_token_ = access_token; |
| // If there's already a signal strategy object, update it ready for the |
| // next time it calls Connect. If not, then this is the initial token |
| // exchange, so proceed to the next stage of connection. |
| if (signal_strategy_.get()) { |
| signal_strategy_->SetAuthInfo(xmpp_login_, xmpp_auth_token_, |
| xmpp_auth_service_); |
| } else { |
| StartWatchingNatPolicy(); |
| } |
| } |
| |
| virtual void OnOAuthError() OVERRIDE { |
| LOG(ERROR) << "OAuth: invalid credentials."; |
| } |
| |
| private: |
| void StartWatchingNatPolicy() { |
| nat_policy_.reset( |
| policy_hack::NatPolicy::Create(file_io_thread_.message_loop_proxy())); |
| nat_policy_->StartWatching( |
| base::Bind(&HostProcess::OnNatPolicyUpdate, base::Unretained(this))); |
| } |
| |
| // Read Host config from disk, returning true if successful. |
| bool LoadConfig(base::MessageLoopProxy* io_message_loop, |
| bool* tokens_pending) { |
| scoped_refptr<JsonHostConfig> host_config = |
| new JsonHostConfig(host_config_path_, io_message_loop); |
| scoped_refptr<JsonHostConfig> auth_config = |
| new JsonHostConfig(auth_config_path_, io_message_loop); |
| |
| FilePath failed_path; |
| if (!host_config->Read()) { |
| failed_path = host_config_path_; |
| } else if (!auth_config->Read()) { |
| failed_path = auth_config_path_; |
| } |
| if (!failed_path.empty()) { |
| LOG(ERROR) << "Failed to read configuration file " << failed_path.value(); |
| return false; |
| } |
| |
| if (!host_config->GetString(kHostIdConfigPath, &host_id_)) { |
| LOG(ERROR) << "host_id is not defined in the config."; |
| return false; |
| } |
| |
| if (!key_pair_.Load(host_config)) { |
| return false; |
| } |
| |
| std::string host_secret_hash_string; |
| if (!host_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 (!auth_config->GetString(kXmppLoginConfigPath, &xmpp_login_) || |
| !(auth_config->GetString(kXmppAuthTokenConfigPath, &xmpp_auth_token_) || |
| auth_config->GetString(kOAuthRefreshTokenConfigPath, |
| &oauth_refresh_token_))) { |
| LOG(ERROR) << "XMPP credentials are not defined in the config."; |
| return false; |
| } |
| |
| *tokens_pending = oauth_refresh_token_ != ""; |
| if (*tokens_pending) { |
| xmpp_auth_token_ = ""; // This will be set to the access token later. |
| xmpp_auth_service_ = "oauth2"; |
| } else if (!auth_config->GetString(kXmppAuthServiceConfigPath, |
| &xmpp_auth_service_)) { |
| // For the me2me host, we default to ClientLogin token for chromiumsync |
| // because earlier versions of the host had no HTTP stack with which to |
| // request an OAuth2 access token. |
| xmpp_auth_service_ = kChromotingTokenDefaultServiceName; |
| } |
| return true; |
| } |
| |
| void OnNatPolicyUpdate(bool nat_traversal_enabled) { |
| if (!context_->network_message_loop()->BelongsToCurrentThread()) { |
| context_->network_message_loop()->PostTask(FROM_HERE, base::Bind( |
| &HostProcess::OnNatPolicyUpdate, base::Unretained(this), |
| nat_traversal_enabled)); |
| return; |
| } |
| |
| bool policy_changed = allow_nat_traversal_ != nat_traversal_enabled; |
| allow_nat_traversal_ = nat_traversal_enabled; |
| |
| if (host_) { |
| // Restart the host if the policy has changed while the host was |
| // online. |
| if (policy_changed) |
| RestartHost(); |
| } else { |
| // Just start the host otherwise. |
| StartHost(); |
| } |
| } |
| |
| void StartHost() { |
| DCHECK(context_->network_message_loop()->BelongsToCurrentThread()); |
| DCHECK(!host_); |
| |
| if (!signal_strategy_.get()) { |
| 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 (!desktop_environment_.get()) { |
| desktop_environment_ = |
| DesktopEnvironment::CreateForService(context_.get()); |
| } |
| |
| protocol::NetworkSettings network_settings(allow_nat_traversal_); |
| if (!allow_nat_traversal_) { |
| network_settings.min_port = kMinPortNumber; |
| network_settings.max_port = kMaxPortNumber; |
| } |
| |
| host_ = new ChromotingHost( |
| context_.get(), signal_strategy_.get(), desktop_environment_.get(), |
| network_settings); |
| |
| heartbeat_sender_.reset( |
| new HeartbeatSender(host_id_, signal_strategy_.get(), &key_pair_)); |
| |
| log_to_server_.reset( |
| new LogToServer(host_, ServerLogEntry::ME2ME, signal_strategy_.get())); |
| host_event_logger_ = HostEventLogger::Create(host_, kApplicationName); |
| |
| host_->Start(); |
| |
| // Create authenticator factory. |
| scoped_ptr<protocol::AuthenticatorFactory> factory( |
| new protocol::Me2MeHostAuthenticatorFactory( |
| xmpp_login_, key_pair_.GenerateCertificate(), |
| *key_pair_.private_key(), host_secret_hash_)); |
| host_->SetAuthenticatorFactory(factory.Pass()); |
| } |
| |
| void RestartHost() { |
| DCHECK(context_->network_message_loop()->BelongsToCurrentThread()); |
| |
| if (restarting_) |
| return; |
| |
| restarting_ = true; |
| host_->Shutdown(base::Bind( |
| &HostProcess::RestartOnHostShutdown, base::Unretained(this))); |
| } |
| |
| void RestartOnHostShutdown() { |
| DCHECK(context_->network_message_loop()->BelongsToCurrentThread()); |
| |
| restarting_ = false; |
| |
| host_ = NULL; |
| log_to_server_.reset(); |
| host_event_logger_.reset(); |
| heartbeat_sender_.reset(); |
| |
| StartHost(); |
| } |
| |
| MessageLoop message_loop_; |
| base::Thread file_io_thread_; |
| scoped_ptr<ChromotingHostContext> context_; |
| scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_; |
| |
| FilePath auth_config_path_; |
| FilePath host_config_path_; |
| |
| 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_; |
| |
| std::string oauth_refresh_token_; |
| OAuthClient oauth_client_; |
| |
| scoped_ptr<policy_hack::NatPolicy> nat_policy_; |
| bool allow_nat_traversal_; |
| |
| bool restarting_; |
| |
| scoped_ptr<XmppSignalStrategy> signal_strategy_; |
| scoped_ptr<SignalingConnector> signaling_connector_; |
| scoped_ptr<DesktopEnvironment> desktop_environment_; |
| scoped_ptr<HeartbeatSender> heartbeat_sender_; |
| scoped_ptr<LogToServer> log_to_server_; |
| scoped_ptr<HostEventLogger> host_event_logger_; |
| scoped_refptr<ChromotingHost> host_; |
| }; |
| |
| } // namespace remoting |
| |
| int main(int argc, char** argv) { |
| CommandLine::Init(argc, argv); |
| |
| // This object instance is required by Chrome code (for example, |
| // LazyInstance, MessageLoop). |
| base::AtExitManager exit_manager; |
| |
| #if defined(OS_WIN) |
| // Write logs to the application profile directory. |
| FilePath debug_log = remoting::GetConfigDir(). |
| Append(FILE_PATH_LITERAL("debug.log")); |
| InitLogging(debug_log.value().c_str(), |
| logging::LOG_ONLY_TO_FILE, |
| logging::DONT_LOCK_LOG_FILE, |
| logging::APPEND_TO_OLD_LOG_FILE, |
| logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); |
| #endif |
| |
| const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); |
| |
| #if defined(TOOLKIT_USES_GTK) |
| // Required for any calls into GTK functions, such as the Disconnect and |
| // Continue windows, though these should not be used for the Me2Me case |
| // (crbug.com/104377). |
| gfx::GtkInitFromCommandLine(*cmd_line); |
| #endif // TOOLKIT_USES_GTK |
| |
| remoting::HostProcess me2me_host; |
| me2me_host.InitWithCommandLine(cmd_line); |
| |
| return me2me_host.Run(); |
| } |
| |
| #if defined(OS_WIN) |
| |
| int CALLBACK WinMain(HINSTANCE instance, |
| HINSTANCE previous_instance, |
| LPSTR command_line, |
| int show_command) { |
| // CommandLine::Init() ignores the passed |argc| and |argv| on Windows getting |
| // the command line from GetCommandLineW(), so we can safely pass NULL here. |
| return main(0, NULL); |
| } |
| |
| #endif |