// 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. #include #include #include #include #include "base/bind.h" #include "base/callback.h" #include "base/command_line.h" #include "base/debug/alias.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringize_macros.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "ipc/attachment_broker_unprivileged.h" #include "ipc/ipc_channel.h" #include "ipc/ipc_channel_proxy.h" #include "ipc/ipc_listener.h" #include "jingle/glue/thread_wrapper.h" #include "net/base/network_change_notifier.h" #include "net/base/url_util.h" #include "net/socket/client_socket_factory.h" #include "net/socket/ssl_server_socket.h" #include "net/url_request/url_fetcher.h" #include "policy/policy_constants.h" #include "remoting/base/auto_thread_task_runner.h" #include "remoting/base/breakpad.h" #include "remoting/base/chromium_url_request.h" #include "remoting/base/constants.h" #include "remoting/base/logging.h" #include "remoting/base/rsa_key_pair.h" #include "remoting/base/service_urls.h" #include "remoting/base/util.h" #include "remoting/host/branding.h" #include "remoting/host/chromoting_host.h" #include "remoting/host/chromoting_host_context.h" #include "remoting/host/chromoting_messages.h" #include "remoting/host/config_file_watcher.h" #include "remoting/host/config_watcher.h" #include "remoting/host/desktop_environment.h" #include "remoting/host/desktop_session_connector.h" #include "remoting/host/dns_blackhole_checker.h" #include "remoting/host/gcd_rest_client.h" #include "remoting/host/gcd_state_updater.h" #include "remoting/host/heartbeat_sender.h" #include "remoting/host/host_change_notification_listener.h" #include "remoting/host/host_config.h" #include "remoting/host/host_event_logger.h" #include "remoting/host/host_exit_codes.h" #include "remoting/host/host_main.h" #include "remoting/host/host_status_logger.h" #include "remoting/host/input_injector.h" #include "remoting/host/ipc_desktop_environment.h" #include "remoting/host/ipc_host_event_logger.h" #include "remoting/host/logging.h" #include "remoting/host/me2me_desktop_environment.h" #include "remoting/host/oauth_token_getter_impl.h" #include "remoting/host/pairing_registry_delegate.h" #include "remoting/host/policy_watcher.h" #include "remoting/host/security_key/gnubby_auth_handler.h" #include "remoting/host/security_key/gnubby_extension.h" #include "remoting/host/shutdown_watchdog.h" #include "remoting/host/signaling_connector.h" #include "remoting/host/single_window_desktop_environment.h" #include "remoting/host/switches.h" #include "remoting/host/third_party_auth_config.h" #include "remoting/host/token_validator_factory_impl.h" #include "remoting/host/usage_stats_consent.h" #include "remoting/host/username.h" #include "remoting/host/video_frame_recorder_host_extension.h" #include "remoting/protocol/authenticator.h" #include "remoting/protocol/channel_authenticator.h" #include "remoting/protocol/chromium_port_allocator_factory.h" #include "remoting/protocol/jingle_session_manager.h" #include "remoting/protocol/me2me_host_authenticator_factory.h" #include "remoting/protocol/network_settings.h" #include "remoting/protocol/pairing_registry.h" #include "remoting/protocol/port_range.h" #include "remoting/protocol/token_validator.h" #include "remoting/protocol/transport_context.h" #include "remoting/signaling/push_notification_subscriber.h" #include "remoting/signaling/xmpp_signal_strategy.h" #include "third_party/webrtc/base/scoped_ref_ptr.h" #if defined(OS_POSIX) #include #include #include #include "base/file_descriptor_posix.h" #include "remoting/host/pam_authorization_factory_posix.h" #include "remoting/host/posix/signal_handler.h" #endif // defined(OS_POSIX) #if defined(OS_MACOSX) #include "base/mac/scoped_cftyperef.h" #endif // defined(OS_MACOSX) #if defined(OS_LINUX) #include #include #include #include "remoting/host/audio_capturer_linux.h" #endif // defined(OS_LINUX) #if defined(OS_WIN) #include #include "base/win/registry.h" #include "base/win/scoped_handle.h" #include "remoting/host/pairing_registry_delegate_win.h" #include "remoting/host/win/session_desktop_environment.h" #endif // defined(OS_WIN) using remoting::protocol::PairingRegistry; using remoting::protocol::NetworkSettings; #if defined(USE_REMOTING_MACOSX_INTERNAL) #include "remoting/tools/internal/internal_mac-inl.h" #endif namespace { #if !defined(REMOTING_MULTI_PROCESS) // This is used for tagging system event logs. const char kApplicationName[] = "chromoting"; // Value used for --host-config option to indicate that the path must be read // from stdin. const char kStdinConfigPath[] = "-"; #endif // !defined(REMOTING_MULTI_PROCESS) #if defined(OS_LINUX) // The command line switch used to pass name of the pipe to capture audio on // linux. const char kAudioPipeSwitchName[] = "audio-pipe-name"; // The command line switch used to pass name of the unix domain socket used to // listen for gnubby requests. const char kAuthSocknameSwitchName[] = "ssh-auth-sockname"; #endif // defined(OS_LINUX) // The command line switch used by the parent to request the host to signal it // when it is successfully started. const char kSignalParentSwitchName[] = "signal-parent"; // Command line switch used to enable VP9 encoding. const char kEnableVp9SwitchName[] = "enable-vp9"; // Command line switch used to enable and configure the frame-recorder. const char kFrameRecorderBufferKbName[] = "frame-recorder-buffer-kb"; const char kWindowIdSwitchName[] = "window-id"; // Command line switch used to enable WebRTC-based protocol. const char kEnableWebrtcSwitchName[] = "enable-webrtc"; // Command line switch used to enable WebRTC-based protocol. const char kDisableAuthenticationSwitchName[] = "disable-authentication"; // Maximum time to wait for clean shutdown to occur, before forcing termination // of the process. const int kShutdownTimeoutSeconds = 15; // Maximum time to wait for reporting host-offline-reason to the service, // before continuing normal process shutdown. const int kHostOfflineReasonTimeoutSeconds = 10; // Host offline reasons not associated with shutting down the host process // and therefore not expressible through HostExitCodes enum. const char kHostOfflineReasonPolicyReadError[] = "POLICY_READ_ERROR"; const char kHostOfflineReasonPolicyChangeRequiresRestart[] = "POLICY_CHANGE_REQUIRES_RESTART"; } // namespace namespace remoting { #if !defined(NDEBUG) // Authenticator that accepts all connections. Use only for testing. class NoopAuthenticator : public protocol::Authenticator { public: NoopAuthenticator() {} ~NoopAuthenticator() override {} // protocol::Authenticator interface. State state() const override { return done_ ? ACCEPTED : WAITING_MESSAGE; } bool started() const override { return done_; } RejectionReason rejection_reason() const override { NOTREACHED(); return INVALID_CREDENTIALS; } void ProcessMessage(const buzz::XmlElement* message, const base::Closure& resume_callback) override { done_ = true; resume_callback.Run(); } scoped_ptr GetNextMessage() override { NOTREACHED(); return nullptr; } const std::string& GetAuthKey() const override { return auth_key_; } scoped_ptr CreateChannelAuthenticator() const override { NOTREACHED(); return nullptr; }; private: bool done_ = false; std::string auth_key_ = "NOKEY"; }; // Factory for Authenticator instances. class NoopAuthenticatorFactory : public protocol::AuthenticatorFactory { public: NoopAuthenticatorFactory() {} ~NoopAuthenticatorFactory() override {} scoped_ptr CreateAuthenticator( const std::string& local_jid, const std::string& remote_jid, const buzz::XmlElement* first_message) override { return make_scoped_ptr(new NoopAuthenticator()); } }; #endif // !defined(NDEBUG) class HostProcess : public ConfigWatcher::Delegate, public HostChangeNotificationListener::Listener, public IPC::Listener, public base::RefCountedThreadSafe { public: // |shutdown_watchdog| is armed when shutdown is started, and should be kept // alive as long as possible until the process exits (since destroying the // watchdog disarms it). HostProcess(scoped_ptr context, int* exit_code_out, ShutdownWatchdog* shutdown_watchdog); // ConfigWatcher::Delegate interface. void OnConfigUpdated(const std::string& serialized_config) override; void OnConfigWatcherError() override; // IPC::Listener implementation. bool OnMessageReceived(const IPC::Message& message) override; void OnChannelError() override; // HostChangeNotificationListener::Listener overrides. void OnHostDeleted() override; // Handler of the ChromotingDaemonNetworkMsg_InitializePairingRegistry IPC // message. void OnInitializePairingRegistry( IPC::PlatformFileForTransit privileged_key, IPC::PlatformFileForTransit unprivileged_key); private: // See SetState method for a list of allowed state transitions. enum HostState { // Waiting for valid config and policies to be read from the disk. // Either the host process has just been started, or it is trying to start // again after temporarily going offline due to policy change or error. HOST_STARTING, // Host is started and running. HOST_STARTED, // Host is sending offline reason, before trying to restart. HOST_GOING_OFFLINE_TO_RESTART, // Host is sending offline reason, before shutting down. HOST_GOING_OFFLINE_TO_STOP, // Host has been stopped (host process will end soon). HOST_STOPPED, }; enum PolicyState { // Cannot start the host, because a valid policy has not been read yet. POLICY_INITIALIZING, // Policy was loaded successfully. POLICY_LOADED, // Policy error was detected, and we haven't yet sent out a // host-offline-reason (i.e. because we haven't yet read the config). POLICY_ERROR_REPORT_PENDING, // Policy error was detected, and we have sent out a host-offline-reason. POLICY_ERROR_REPORTED, }; friend class base::RefCountedThreadSafe; ~HostProcess() override; void SetState(HostState target_state); void StartOnNetworkThread(); #if defined(OS_POSIX) // Callback passed to RegisterSignalHandler() to handle SIGTERM events. void SigTermHandler(int signal_number); #endif // Called to initialize resources on the UI thread. void StartOnUiThread(); // Initializes IPC control channel and config file path from |cmd_line|. // Called on the UI thread. bool InitWithCommandLine(const base::CommandLine* cmd_line); // Called on the UI thread to start monitoring the configuration file. void StartWatchingConfigChanges(); // Called on the network thread to set the host's Authenticator factory. void CreateAuthenticatorFactory(); // Tear down resources that run on the UI thread. void ShutdownOnUiThread(); // Applies the host config, returning true if successful. bool ApplyConfig(const base::DictionaryValue& config); // Handles policy updates, by calling On*PolicyUpdate methods. void OnPolicyUpdate(scoped_ptr policies); void OnPolicyError(); void ReportPolicyErrorAndRestartHost(); void ApplyHostDomainPolicy(); void ApplyUsernamePolicy(); bool OnClientDomainPolicyUpdate(base::DictionaryValue* policies); bool OnHostDomainPolicyUpdate(base::DictionaryValue* policies); bool OnUsernamePolicyUpdate(base::DictionaryValue* policies); bool OnNatPolicyUpdate(base::DictionaryValue* policies); bool OnRelayPolicyUpdate(base::DictionaryValue* policies); bool OnUdpPortPolicyUpdate(base::DictionaryValue* policies); bool OnCurtainPolicyUpdate(base::DictionaryValue* policies); bool OnHostTalkGadgetPrefixPolicyUpdate(base::DictionaryValue* policies); bool OnHostTokenUrlPolicyUpdate(base::DictionaryValue* policies); bool OnPairingPolicyUpdate(base::DictionaryValue* policies); bool OnGnubbyAuthPolicyUpdate(base::DictionaryValue* policies); void InitializeSignaling(); void StartHostIfReady(); void StartHost(); // Error handler for HeartbeatSender. void OnHeartbeatSuccessful(); void OnUnknownHostIdError(); // Error handler for SignalingConnector. void OnAuthFailed(); void RestartHost(const std::string& host_offline_reason); void ShutdownHost(HostExitCodes exit_code); // Helper methods doing the work needed by RestartHost and ShutdownHost. void GoOffline(const std::string& host_offline_reason); void OnHostOfflineReasonAck(bool success); #if defined(OS_WIN) // Initializes the pairing registry on Windows. This should be invoked on the // network thread. void InitializePairingRegistry( IPC::PlatformFileForTransit privileged_key, IPC::PlatformFileForTransit unprivileged_key); #endif // defined(OS_WIN) // Crashes the process in response to a daemon's request. The daemon passes // the location of the code that detected the fatal error resulted in this // request. void OnCrash(const std::string& function_name, const std::string& file_name, const int& line_number); scoped_ptr context_; // XMPP server/remoting bot configuration (initialized from the command line). XmppSignalStrategy::XmppServerConfig xmpp_server_config_; std::string directory_bot_jid_; // Created on the UI thread but used from the network thread. base::FilePath host_config_path_; std::string host_config_; scoped_ptr desktop_environment_factory_; // Accessed on the network thread. HostState state_; scoped_ptr config_watcher_; std::string host_id_; protocol::SharedSecretHash host_secret_hash_; scoped_refptr key_pair_; std::string oauth_refresh_token_; std::string serialized_config_; std::string host_owner_; std::string host_owner_email_; bool use_service_account_; bool enable_vp9_; int64_t frame_recorder_buffer_size_; scoped_ptr policy_watcher_; PolicyState policy_state_; std::string client_domain_; std::string host_domain_; bool host_username_match_required_; bool allow_nat_traversal_; bool allow_relay_; PortRange udp_port_range_; std::string talkgadget_prefix_; bool allow_pairing_; bool curtain_required_; ThirdPartyAuthConfig third_party_auth_config_; bool gnubby_auth_policy_enabled_; bool gnubby_extension_supported_; // Boolean to change flow, where necessary, if we're // capturing a window instead of the entire desktop. bool enable_window_capture_; // Used to specify which window to stream, if enabled. webrtc::WindowId window_id_; // Must outlive |gcd_state_updater_| and |signaling_connector_|. scoped_ptr oauth_token_getter_; // Must outlive |signaling_connector_|, |gcd_subscriber_|, and // |heartbeat_sender_|. scoped_ptr signal_strategy_; scoped_ptr signaling_connector_; scoped_ptr heartbeat_sender_; #if defined(USE_GCD) scoped_ptr gcd_state_updater_; scoped_ptr gcd_subscriber_; #endif // defined(USE_GCD) scoped_ptr host_change_notification_listener_; scoped_ptr host_status_logger_; scoped_ptr host_event_logger_; scoped_ptr host_; // Used to keep this HostProcess alive until it is shutdown. scoped_refptr self_; #if defined(REMOTING_MULTI_PROCESS) // Accessed on the UI thread. scoped_ptr daemon_channel_; // Owned as |desktop_environment_factory_|. DesktopSessionConnector* desktop_session_connector_ = nullptr; #endif // defined(REMOTING_MULTI_PROCESS) int* exit_code_out_; bool signal_parent_; scoped_refptr pairing_registry_; ShutdownWatchdog* shutdown_watchdog_; DISALLOW_COPY_AND_ASSIGN(HostProcess); }; HostProcess::HostProcess(scoped_ptr context, int* exit_code_out, ShutdownWatchdog* shutdown_watchdog) : context_(std::move(context)), state_(HOST_STARTING), use_service_account_(false), enable_vp9_(false), frame_recorder_buffer_size_(0), policy_state_(POLICY_INITIALIZING), host_username_match_required_(false), allow_nat_traversal_(true), allow_relay_(true), allow_pairing_(true), curtain_required_(false), gnubby_auth_policy_enabled_(false), gnubby_extension_supported_(false), enable_window_capture_(false), window_id_(0), self_(this), exit_code_out_(exit_code_out), signal_parent_(false), shutdown_watchdog_(shutdown_watchdog) { StartOnUiThread(); } HostProcess::~HostProcess() { // Verify that UI components have been torn down. DCHECK(!config_watcher_); DCHECK(!desktop_environment_factory_); // We might be getting deleted on one of the threads the |host_context| owns, // so we need to post it back to the caller thread to safely join & delete the // threads it contains. This will go away when we move to AutoThread. // |context_release()| will null |context_| before the method is invoked, so // we need to pull out the task-runner on which to call DeleteSoon first. scoped_refptr task_runner = context_->ui_task_runner(); task_runner->DeleteSoon(FROM_HERE, context_.release()); } bool HostProcess::InitWithCommandLine(const base::CommandLine* cmd_line) { #if defined(REMOTING_MULTI_PROCESS) // Parse the handle value and convert it to a handle/file descriptor. std::string channel_name = cmd_line->GetSwitchValueASCII(kDaemonPipeSwitchName); int pipe_handle = 0; if (channel_name.empty() || !base::StringToInt(channel_name, &pipe_handle)) { LOG(ERROR) << "Invalid '" << kDaemonPipeSwitchName << "' value: " << channel_name; return false; } #if defined(OS_WIN) base::win::ScopedHandle pipe(reinterpret_cast(pipe_handle)); IPC::ChannelHandle channel_handle(pipe.Get()); #elif defined(OS_POSIX) base::FileDescriptor pipe(pipe_handle, true); IPC::ChannelHandle channel_handle(channel_name, pipe); #endif // defined(OS_POSIX) // Connect to the daemon process. daemon_channel_ = IPC::ChannelProxy::Create(channel_handle, IPC::Channel::MODE_CLIENT, this, context_->network_task_runner()); IPC::AttachmentBrokerUnprivileged::CreateBrokerIfNeeded(); IPC::AttachmentBroker* broker = IPC::AttachmentBroker::GetGlobal(); if (broker && !broker->IsPrivilegedBroker()) broker->RegisterBrokerCommunicationChannel(daemon_channel_.get()); #else // !defined(REMOTING_MULTI_PROCESS) if (cmd_line->HasSwitch(kHostConfigSwitchName)) { host_config_path_ = cmd_line->GetSwitchValuePath(kHostConfigSwitchName); // Read config from stdin if necessary. if (host_config_path_ == base::FilePath(kStdinConfigPath)) { const size_t kBufferSize = 4096; scoped_ptr buf(new char[kBufferSize]); size_t len; while ((len = fread(buf.get(), 1, kBufferSize, stdin)) > 0) { host_config_.append(buf.get(), len); } } } else { base::FilePath default_config_dir = remoting::GetConfigDir(); host_config_path_ = default_config_dir.Append(kDefaultHostConfigFile); } if (host_config_path_ != base::FilePath(kStdinConfigPath) && !base::PathExists(host_config_path_)) { LOG(ERROR) << "Can't find host config at " << host_config_path_.value(); return false; } #endif // !defined(REMOTING_MULTI_PROCESS) // Ignore certificate requests - the host currently has no client certificate // support, so ignoring certificate requests allows connecting to servers that // request, but don't require, a certificate (optional client authentication). net::URLFetcher::SetIgnoreCertificateRequests(true); ServiceUrls* service_urls = ServiceUrls::GetInstance(); const std::string& xmpp_server = service_urls->xmpp_server_address_for_me2me_host(); if (!net::ParseHostAndPort(xmpp_server, &xmpp_server_config_.host, &xmpp_server_config_.port)) { LOG(ERROR) << "Invalid XMPP server: " << xmpp_server; return false; } xmpp_server_config_.use_tls = service_urls->xmpp_server_use_tls(); directory_bot_jid_ = service_urls->directory_bot_jid(); signal_parent_ = cmd_line->HasSwitch(kSignalParentSwitchName); enable_window_capture_ = cmd_line->HasSwitch(kWindowIdSwitchName); if (enable_window_capture_) { #if defined(OS_LINUX) || defined(OS_WIN) LOG(WARNING) << "Window capturing is not fully supported on Linux or " "Windows."; #endif // defined(OS_LINUX) || defined(OS_WIN) // uint32_t is large enough to hold window IDs on all platforms. uint32_t window_id; if (base::StringToUint( cmd_line->GetSwitchValueASCII(kWindowIdSwitchName), &window_id)) { window_id_ = static_cast(window_id); } else { LOG(ERROR) << "Window with window id: " << window_id_ << " not found. Shutting down host."; return false; } } return true; } void HostProcess::OnConfigUpdated( const std::string& serialized_config) { if (!context_->network_task_runner()->BelongsToCurrentThread()) { context_->network_task_runner()->PostTask(FROM_HERE, base::Bind(&HostProcess::OnConfigUpdated, this, serialized_config)); return; } // Filter out duplicates. if (serialized_config_ == serialized_config) return; HOST_LOG << "Processing new host configuration."; serialized_config_ = serialized_config; scoped_ptr config( HostConfigFromJson(serialized_config)); if (!config) { LOG(ERROR) << "Invalid configuration."; ShutdownHost(kInvalidHostConfigurationExitCode); return; } if (!ApplyConfig(*config)) { LOG(ERROR) << "Failed to apply the configuration."; ShutdownHost(kInvalidHostConfigurationExitCode); return; } if (state_ == HOST_STARTING) { StartHostIfReady(); } else if (state_ == HOST_STARTED) { // Reapply policies that could be affected by a new config. DCHECK_EQ(policy_state_, POLICY_LOADED); ApplyHostDomainPolicy(); ApplyUsernamePolicy(); // TODO(sergeyu): Here we assume that PIN is the only part of the config // that may change while the service is running. Change ApplyConfig() to // detect other changes in the config and restart host if necessary here. CreateAuthenticatorFactory(); } } void HostProcess::OnConfigWatcherError() { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); ShutdownHost(kInvalidHostConfigurationExitCode); } // Allowed state transitions (enforced via DCHECKs in SetState method): // STARTING->STARTED (once we have valid config + policy) // STARTING->GOING_OFFLINE_TO_STOP // STARTING->GOING_OFFLINE_TO_RESTART // STARTED->GOING_OFFLINE_TO_STOP // STARTED->GOING_OFFLINE_TO_RESTART // GOING_OFFLINE_TO_RESTART->GOING_OFFLINE_TO_STOP // GOING_OFFLINE_TO_RESTART->STARTING (after OnHostOfflineReasonAck) // GOING_OFFLINE_TO_STOP->STOPPED (after OnHostOfflineReasonAck) // // |host_| must be not-null in STARTED state and nullptr in all other states // (although this invariant can be temporarily violated when doing // synchronous processing on the networking thread). void HostProcess::SetState(HostState target_state) { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); // DCHECKs below enforce state allowed transitions listed in HostState. switch (state_) { case HOST_STARTING: DCHECK((target_state == HOST_STARTED) || (target_state == HOST_GOING_OFFLINE_TO_STOP) || (target_state == HOST_GOING_OFFLINE_TO_RESTART)) << state_ << " -> " << target_state; break; case HOST_STARTED: DCHECK((target_state == HOST_GOING_OFFLINE_TO_STOP) || (target_state == HOST_GOING_OFFLINE_TO_RESTART)) << state_ << " -> " << target_state; break; case HOST_GOING_OFFLINE_TO_RESTART: DCHECK((target_state == HOST_GOING_OFFLINE_TO_STOP) || (target_state == HOST_STARTING)) << state_ << " -> " << target_state; break; case HOST_GOING_OFFLINE_TO_STOP: DCHECK_EQ(target_state, HOST_STOPPED); break; case HOST_STOPPED: // HOST_STOPPED is a terminal state. default: NOTREACHED() << state_ << " -> " << target_state; break; } state_ = target_state; } void HostProcess::StartOnNetworkThread() { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (state_ != HOST_STARTING) { // Host was shutdown before the task had a chance to run. return; } #if !defined(REMOTING_MULTI_PROCESS) if (host_config_path_ == base::FilePath(kStdinConfigPath)) { // Process config we've read from stdin. OnConfigUpdated(host_config_); } else { // Start watching the host configuration file. config_watcher_.reset(new ConfigFileWatcher(context_->network_task_runner(), context_->file_task_runner(), host_config_path_)); config_watcher_->Watch(this); } #endif // !defined(REMOTING_MULTI_PROCESS) #if defined(OS_POSIX) remoting::RegisterSignalHandler( SIGTERM, base::Bind(&HostProcess::SigTermHandler, base::Unretained(this))); #endif // defined(OS_POSIX) } #if defined(OS_POSIX) void HostProcess::SigTermHandler(int signal_number) { DCHECK(signal_number == SIGTERM); DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); HOST_LOG << "Caught SIGTERM: Shutting down..."; ShutdownHost(kSuccessExitCode); } #endif // OS_POSIX void HostProcess::CreateAuthenticatorFactory() { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (state_ != HOST_STARTED) return; if (base::CommandLine::ForCurrentProcess()->HasSwitch( kDisableAuthenticationSwitchName)) { #if defined(NDEBUG) LOG(ERROR) << "Authentication can be disabled only in debug builds."; ShutdownHost(kInitializationFailed); #else // defined(NDEBUG) host_->SetAuthenticatorFactory( make_scoped_ptr(new NoopAuthenticatorFactory())); #endif // !defined(NDEBUG) return; } std::string local_certificate = key_pair_->GenerateCertificate(); if (local_certificate.empty()) { LOG(ERROR) << "Failed to generate host certificate."; ShutdownHost(kInitializationFailed); return; } scoped_ptr factory; if (third_party_auth_config_.is_null()) { scoped_refptr pairing_registry; if (allow_pairing_) { // On Windows |pairing_registry_| is initialized in // InitializePairingRegistry(). #if !defined(OS_WIN) if (!pairing_registry_) { scoped_ptr delegate = CreatePairingRegistryDelegate(); if (delegate) pairing_registry_ = new PairingRegistry(context_->file_task_runner(), std::move(delegate)); } #endif // defined(OS_WIN) pairing_registry = pairing_registry_; } factory = protocol::Me2MeHostAuthenticatorFactory::CreateWithSharedSecret( use_service_account_, host_owner_, local_certificate, key_pair_, client_domain_, host_secret_hash_, pairing_registry); host_->set_pairing_registry(pairing_registry); } else { DCHECK(third_party_auth_config_.token_url.is_valid()); DCHECK(third_party_auth_config_.token_validation_url.is_valid()); scoped_ptr token_validator_factory( new TokenValidatorFactoryImpl( third_party_auth_config_, key_pair_, context_->url_request_context_getter())); factory = protocol::Me2MeHostAuthenticatorFactory::CreateWithThirdPartyAuth( use_service_account_, host_owner_, local_certificate, key_pair_, client_domain_, std::move(token_validator_factory)); } #if defined(OS_POSIX) // On Linux and Mac, perform a PAM authorization step after authentication. factory.reset(new PamAuthorizationFactory(std::move(factory))); #endif host_->SetAuthenticatorFactory(std::move(factory)); } // IPC::Listener implementation. bool HostProcess::OnMessageReceived(const IPC::Message& message) { DCHECK(context_->ui_task_runner()->BelongsToCurrentThread()); #if defined(REMOTING_MULTI_PROCESS) bool handled = true; IPC_BEGIN_MESSAGE_MAP(HostProcess, message) IPC_MESSAGE_HANDLER(ChromotingDaemonMsg_Crash, OnCrash) IPC_MESSAGE_HANDLER(ChromotingDaemonNetworkMsg_Configuration, OnConfigUpdated) IPC_MESSAGE_HANDLER(ChromotingDaemonNetworkMsg_InitializePairingRegistry, OnInitializePairingRegistry) IPC_MESSAGE_FORWARD( ChromotingDaemonNetworkMsg_DesktopAttached, desktop_session_connector_, DesktopSessionConnector::OnDesktopSessionAgentAttached) IPC_MESSAGE_FORWARD(ChromotingDaemonNetworkMsg_TerminalDisconnected, desktop_session_connector_, DesktopSessionConnector::OnTerminalDisconnected) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() CHECK(handled) << "Received unexpected IPC type: " << message.type(); return handled; #else // !defined(REMOTING_MULTI_PROCESS) return false; #endif // !defined(REMOTING_MULTI_PROCESS) } void HostProcess::OnChannelError() { DCHECK(context_->ui_task_runner()->BelongsToCurrentThread()); // Shutdown the host if the daemon process disconnects the IPC channel. context_->network_task_runner()->PostTask( FROM_HERE, base::Bind(&HostProcess::ShutdownHost, this, kSuccessExitCode)); } void HostProcess::StartOnUiThread() { DCHECK(context_->ui_task_runner()->BelongsToCurrentThread()); if (!InitWithCommandLine(base::CommandLine::ForCurrentProcess())) { // Shutdown the host if the command line is invalid. ShutdownOnUiThread(); return; } policy_watcher_ = PolicyWatcher::Create(nullptr, context_->file_task_runner()); policy_watcher_->StartWatching( base::Bind(&HostProcess::OnPolicyUpdate, base::Unretained(this)), base::Bind(&HostProcess::OnPolicyError, base::Unretained(this))); #if defined(OS_LINUX) // If an audio pipe is specific on the command-line then initialize // AudioCapturerLinux to capture from it. base::FilePath audio_pipe_name = base::CommandLine::ForCurrentProcess()-> GetSwitchValuePath(kAudioPipeSwitchName); if (!audio_pipe_name.empty()) { remoting::AudioCapturerLinux::InitializePipeReader( context_->audio_task_runner(), audio_pipe_name); } base::FilePath gnubby_socket_name = base::CommandLine::ForCurrentProcess()-> GetSwitchValuePath(kAuthSocknameSwitchName); if (!gnubby_socket_name.empty()) { remoting::GnubbyAuthHandler::SetGnubbySocketName(gnubby_socket_name); gnubby_extension_supported_ = true; } #endif // defined(OS_LINUX) // Create a desktop environment factory appropriate to the build type & // platform. #if defined(REMOTING_MULTI_PROCESS) IpcDesktopEnvironmentFactory* desktop_environment_factory = new IpcDesktopEnvironmentFactory( context_->audio_task_runner(), context_->network_task_runner(), context_->network_task_runner(), daemon_channel_.get()); desktop_session_connector_ = desktop_environment_factory; #else // !defined(REMOTING_MULTI_PROCESS) BasicDesktopEnvironmentFactory* desktop_environment_factory; if (enable_window_capture_) { desktop_environment_factory = new SingleWindowDesktopEnvironmentFactory( context_->network_task_runner(), context_->video_capture_task_runner(), context_->input_task_runner(), context_->ui_task_runner(), window_id_); } else { desktop_environment_factory = new Me2MeDesktopEnvironmentFactory( context_->network_task_runner(), context_->video_capture_task_runner(), context_->input_task_runner(), context_->ui_task_runner()); } #endif // !defined(REMOTING_MULTI_PROCESS) desktop_environment_factory->set_supports_touch_events( InputInjector::SupportsTouchEvents()); desktop_environment_factory_.reset(desktop_environment_factory); context_->network_task_runner()->PostTask( FROM_HERE, base::Bind(&HostProcess::StartOnNetworkThread, this)); } void HostProcess::ShutdownOnUiThread() { DCHECK(context_->ui_task_runner()->BelongsToCurrentThread()); // Tear down resources that need to be torn down on the UI thread. desktop_environment_factory_.reset(); policy_watcher_.reset(); #if defined(REMOTING_MULTI_PROCESS) IPC::AttachmentBroker* broker = IPC::AttachmentBroker::GetGlobal(); if (broker && !broker->IsPrivilegedBroker()) broker->DeregisterBrokerCommunicationChannel(daemon_channel_.get()); daemon_channel_.reset(); #endif // defined(REMOTING_MULTI_PROCESS) // It is now safe for the HostProcess to be deleted. self_ = nullptr; #if defined(OS_LINUX) // Cause the global AudioPipeReader to be freed, otherwise the audio // thread will remain in-use and prevent the process from exiting. // TODO(wez): DesktopEnvironmentFactory should own the pipe reader. // See crbug.com/161373 and crbug.com/104544. AudioCapturerLinux::InitializePipeReader(nullptr, base::FilePath()); #endif } void HostProcess::OnUnknownHostIdError() { LOG(ERROR) << "Host ID not found."; ShutdownHost(kInvalidHostIdExitCode); } void HostProcess::OnHeartbeatSuccessful() { HOST_LOG << "Host ready to receive connections."; #if defined(OS_POSIX) if (signal_parent_) { kill(getppid(), SIGUSR1); signal_parent_ = false; } #endif } void HostProcess::OnHostDeleted() { LOG(ERROR) << "Host was deleted from the directory."; ShutdownHost(kInvalidHostIdExitCode); } void HostProcess::OnInitializePairingRegistry( IPC::PlatformFileForTransit privileged_key, IPC::PlatformFileForTransit unprivileged_key) { DCHECK(context_->ui_task_runner()->BelongsToCurrentThread()); #if defined(OS_WIN) context_->network_task_runner()->PostTask(FROM_HERE, base::Bind( &HostProcess::InitializePairingRegistry, this, privileged_key, unprivileged_key)); #else // !defined(OS_WIN) NOTREACHED(); #endif // !defined(OS_WIN) } #if defined(OS_WIN) void HostProcess::InitializePairingRegistry( IPC::PlatformFileForTransit privileged_key, IPC::PlatformFileForTransit unprivileged_key) { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); // |privileged_key| can be nullptr but not |unprivileged_key|. DCHECK(unprivileged_key); // |pairing_registry_| should only be initialized once. DCHECK(!pairing_registry_); HKEY privileged_hkey = reinterpret_cast( IPC::PlatformFileForTransitToPlatformFile(privileged_key)); HKEY unprivileged_hkey = reinterpret_cast( IPC::PlatformFileForTransitToPlatformFile(unprivileged_key)); scoped_ptr delegate( new PairingRegistryDelegateWin()); delegate->SetRootKeys(privileged_hkey, unprivileged_hkey); pairing_registry_ = new PairingRegistry(context_->file_task_runner(), std::move(delegate)); // (Re)Create the authenticator factory now that |pairing_registry_| has been // initialized. CreateAuthenticatorFactory(); } #endif // !defined(OS_WIN) // Applies the host config, returning true if successful. bool HostProcess::ApplyConfig(const base::DictionaryValue& config) { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (!config.GetString(kHostIdConfigPath, &host_id_)) { LOG(ERROR) << "Config does not define " << kHostIdConfigPath << "."; return false; } std::string key_base64; if (!config.GetString(kPrivateKeyConfigPath, &key_base64)) { LOG(ERROR) << "Private key couldn't be read from the config file."; return false; } key_pair_ = RsaKeyPair::FromString(key_base64); if (!key_pair_.get()) { LOG(ERROR) << "Invalid private key in the config file."; return false; } 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 signaling. if (!config.GetString(kXmppLoginConfigPath, &xmpp_server_config_.username) || !config.GetString(kOAuthRefreshTokenConfigPath, &oauth_refresh_token_)) { LOG(ERROR) << "XMPP credentials are not defined in the config."; return false; } if (config.GetString(kHostOwnerConfigPath, &host_owner_)) { // Service account configs have a host_owner, different from the xmpp_login. use_service_account_ = true; } else { // User credential configs only have an xmpp_login, which is also the owner. host_owner_ = xmpp_server_config_.username; use_service_account_ = false; } // For non-Gmail Google accounts, the owner base JID differs from the email. // host_owner_ contains the base JID (used for authenticating clients), while // host_owner_email contains the account's email (used for UI and logs). if (!config.GetString(kHostOwnerEmailConfigPath, &host_owner_email_)) { host_owner_email_ = host_owner_; } // Allow offering of VP9 encoding to be overridden by the command-line. if (base::CommandLine::ForCurrentProcess()->HasSwitch(kEnableVp9SwitchName)) { enable_vp9_ = true; } else { config.GetBoolean(kEnableVp9ConfigPath, &enable_vp9_); } // Allow the command-line to override the size of the frame recorder buffer. int frame_recorder_buffer_kb = 0; if (base::CommandLine::ForCurrentProcess()->HasSwitch( kFrameRecorderBufferKbName)) { std::string switch_value = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( kFrameRecorderBufferKbName); base::StringToInt(switch_value, &frame_recorder_buffer_kb); } else { config.GetInteger(kFrameRecorderBufferKbConfigPath, &frame_recorder_buffer_kb); } if (frame_recorder_buffer_kb > 0) { frame_recorder_buffer_size_ = 1024LL * frame_recorder_buffer_kb; } return true; } void HostProcess::OnPolicyUpdate(scoped_ptr policies) { if (!context_->network_task_runner()->BelongsToCurrentThread()) { context_->network_task_runner()->PostTask( FROM_HERE, base::Bind(&HostProcess::OnPolicyUpdate, this, base::Passed(&policies))); return; } bool restart_required = false; restart_required |= OnClientDomainPolicyUpdate(policies.get()); restart_required |= OnHostDomainPolicyUpdate(policies.get()); restart_required |= OnCurtainPolicyUpdate(policies.get()); // Note: UsernamePolicyUpdate must run after OnCurtainPolicyUpdate. restart_required |= OnUsernamePolicyUpdate(policies.get()); restart_required |= OnNatPolicyUpdate(policies.get()); restart_required |= OnRelayPolicyUpdate(policies.get()); restart_required |= OnUdpPortPolicyUpdate(policies.get()); restart_required |= OnHostTalkGadgetPrefixPolicyUpdate(policies.get()); restart_required |= OnHostTokenUrlPolicyUpdate(policies.get()); restart_required |= OnPairingPolicyUpdate(policies.get()); restart_required |= OnGnubbyAuthPolicyUpdate(policies.get()); policy_state_ = POLICY_LOADED; if (state_ == HOST_STARTING) { StartHostIfReady(); } else if (state_ == HOST_STARTED) { if (restart_required) RestartHost(kHostOfflineReasonPolicyChangeRequiresRestart); } } void HostProcess::OnPolicyError() { if (!context_->network_task_runner()->BelongsToCurrentThread()) { context_->network_task_runner()->PostTask( FROM_HERE, base::Bind(&HostProcess::OnPolicyError, this)); return; } if (policy_state_ != POLICY_ERROR_REPORTED) { policy_state_ = POLICY_ERROR_REPORT_PENDING; if ((state_ == HOST_STARTED) || (state_ == HOST_STARTING && !serialized_config_.empty())) { ReportPolicyErrorAndRestartHost(); } } } void HostProcess::ReportPolicyErrorAndRestartHost() { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); DCHECK(!serialized_config_.empty()); DCHECK_EQ(policy_state_, POLICY_ERROR_REPORT_PENDING); policy_state_ = POLICY_ERROR_REPORTED; HOST_LOG << "Restarting the host due to policy errors."; RestartHost(kHostOfflineReasonPolicyReadError); } void HostProcess::ApplyHostDomainPolicy() { if (state_ != HOST_STARTED) return; HOST_LOG << "Policy sets host domain: " << host_domain_; if (!host_domain_.empty()) { // If the user does not have a Google email, their client JID will not be // based on their email. In that case, the username/host domain policies // would be meaningless, since there is no way to check that the JID // trying to connect actually corresponds to the owner email in question. if (host_owner_ != host_owner_email_) { LOG(ERROR) << "The username and host domain policies cannot be enabled " << "for accounts with a non-Google email."; ShutdownHost(kInvalidHostDomainExitCode); } if (!base::EndsWith(host_owner_, std::string("@") + host_domain_, base::CompareCase::INSENSITIVE_ASCII)) { LOG(ERROR) << "The host domain does not match the policy."; ShutdownHost(kInvalidHostDomainExitCode); } } } bool HostProcess::OnHostDomainPolicyUpdate(base::DictionaryValue* policies) { // Returns true if the host has to be restarted after this policy update. DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (!policies->GetString(policy::key::kRemoteAccessHostDomain, &host_domain_)) { return false; } ApplyHostDomainPolicy(); return false; } bool HostProcess::OnClientDomainPolicyUpdate(base::DictionaryValue* policies) { // Returns true if the host has to be restarted after this policy update. DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); return policies->GetString(policy::key::kRemoteAccessHostClientDomain, &client_domain_); } void HostProcess::ApplyUsernamePolicy() { if (state_ != HOST_STARTED) return; if (host_username_match_required_) { HOST_LOG << "Policy requires host username match."; // See comment in ApplyHostDomainPolicy. if (host_owner_ != host_owner_email_) { LOG(ERROR) << "The username and host domain policies cannot be enabled " << "for accounts with a non-Google email."; ShutdownHost(kUsernameMismatchExitCode); } std::string username = GetUsername(); bool shutdown = username.empty() || !base::StartsWith(host_owner_, username + std::string("@"), base::CompareCase::INSENSITIVE_ASCII); #if defined(OS_MACOSX) // On Mac, we run as root at the login screen, so the username won't match. // However, there's no need to enforce the policy at the login screen, as // the client will have to reconnect if a login occurs. if (shutdown && getuid() == 0) { shutdown = false; } #endif // Curtain-mode on Windows presents the standard OS login prompt to the user // for each connection, removing the need for an explicit user-name matching // check. #if defined(OS_WIN) && defined(REMOTING_RDP_SESSION) if (curtain_required_) return; #endif // defined(OS_WIN) && defined(REMOTING_RDP_SESSION) // Shutdown the host if the username does not match. if (shutdown) { LOG(ERROR) << "The host username does not match."; ShutdownHost(kUsernameMismatchExitCode); } } else { HOST_LOG << "Policy does not require host username match."; } } bool HostProcess::OnUsernamePolicyUpdate(base::DictionaryValue* policies) { // Returns false: never restart the host after this policy update. DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (!policies->GetBoolean(policy::key::kRemoteAccessHostMatchUsername, &host_username_match_required_)) { return false; } ApplyUsernamePolicy(); return false; } bool HostProcess::OnNatPolicyUpdate(base::DictionaryValue* policies) { // Returns true if the host has to be restarted after this policy update. DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (!policies->GetBoolean(policy::key::kRemoteAccessHostFirewallTraversal, &allow_nat_traversal_)) { return false; } if (allow_nat_traversal_) { HOST_LOG << "Policy enables NAT traversal."; } else { HOST_LOG << "Policy disables NAT traversal."; } return true; } bool HostProcess::OnRelayPolicyUpdate(base::DictionaryValue* policies) { // Returns true if the host has to be restarted after this policy update. DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (!policies->GetBoolean( policy::key::kRemoteAccessHostAllowRelayedConnection, &allow_relay_)) { return false; } if (allow_relay_) { HOST_LOG << "Policy enables use of relay server."; } else { HOST_LOG << "Policy disables use of relay server."; } return true; } bool HostProcess::OnUdpPortPolicyUpdate(base::DictionaryValue* policies) { // Returns true if the host has to be restarted after this policy update. DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); std::string string_value; if (!policies->GetString(policy::key::kRemoteAccessHostUdpPortRange, &string_value)) { return false; } if (!PortRange::Parse(string_value, &udp_port_range_)) { // PolicyWatcher verifies that the value is formatted correctly. LOG(FATAL) << "Invalid port range: " << string_value; } HOST_LOG << "Policy restricts UDP port range to: " << udp_port_range_; return true; } bool HostProcess::OnCurtainPolicyUpdate(base::DictionaryValue* policies) { // Returns true if the host has to be restarted after this policy update. DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (!policies->GetBoolean(policy::key::kRemoteAccessHostRequireCurtain, &curtain_required_)) { return false; } #if defined(OS_MACOSX) if (curtain_required_) { // When curtain mode is in effect on Mac, the host process runs in the // user's switched-out session, but launchd will also run an instance at // the console login screen. Even if no user is currently logged-on, we // can't support remote-access to the login screen because the current host // process model disconnects the client during login, which would leave // the logged in session un-curtained on the console until they reconnect. // // TODO(jamiewalch): Fix this once we have implemented the multi-process // daemon architecture (crbug.com/134894) if (getuid() == 0) { LOG(ERROR) << "Running the host in the console login session is yet not " "supported."; ShutdownHost(kLoginScreenNotSupportedExitCode); return false; } } #endif if (curtain_required_) { HOST_LOG << "Policy requires curtain-mode."; } else { HOST_LOG << "Policy does not require curtain-mode."; } if (host_) host_->SetEnableCurtaining(curtain_required_); return false; } bool HostProcess::OnHostTalkGadgetPrefixPolicyUpdate( base::DictionaryValue* policies) { // Returns true if the host has to be restarted after this policy update. DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (!policies->GetString(policy::key::kRemoteAccessHostTalkGadgetPrefix, &talkgadget_prefix_)) { return false; } HOST_LOG << "Policy sets talkgadget prefix: " << talkgadget_prefix_; return true; } bool HostProcess::OnHostTokenUrlPolicyUpdate(base::DictionaryValue* policies) { switch (ThirdPartyAuthConfig::Parse(*policies, &third_party_auth_config_)) { case ThirdPartyAuthConfig::NoPolicy: return false; case ThirdPartyAuthConfig::ParsingSuccess: HOST_LOG << "Policy sets third-party token URLs: " << third_party_auth_config_; return true; case ThirdPartyAuthConfig::InvalidPolicy: default: NOTREACHED(); return false; } } bool HostProcess::OnPairingPolicyUpdate(base::DictionaryValue* policies) { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (!policies->GetBoolean(policy::key::kRemoteAccessHostAllowClientPairing, &allow_pairing_)) { return false; } if (allow_pairing_) { HOST_LOG << "Policy enables client pairing."; } else { HOST_LOG << "Policy disables client pairing."; } return true; } bool HostProcess::OnGnubbyAuthPolicyUpdate(base::DictionaryValue* policies) { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); if (!policies->GetBoolean(policy::key::kRemoteAccessHostAllowGnubbyAuth, &gnubby_auth_policy_enabled_)) { return false; } if (gnubby_auth_policy_enabled_) { HOST_LOG << "Policy enables gnubby auth."; } else { HOST_LOG << "Policy disables gnubby auth."; } return true; } void HostProcess::InitializeSignaling() { DCHECK(!host_id_.empty()); // ApplyConfig() should already have been run. DCHECK(!signal_strategy_); DCHECK(!oauth_token_getter_); DCHECK(!signaling_connector_); #if defined(USE_GCD) DCHECK(!gcd_state_updater_); DCHECK(!gcd_subscriber_); #endif // defined(USE_GCD) DCHECK(!heartbeat_sender_); // Create SignalStrategy. XmppSignalStrategy* xmpp_signal_strategy = new XmppSignalStrategy( net::ClientSocketFactory::GetDefaultFactory(), context_->url_request_context_getter(), xmpp_server_config_); signal_strategy_.reset(xmpp_signal_strategy); // Create SignalingConnector. scoped_ptr dns_blackhole_checker(new DnsBlackholeChecker( context_->url_request_context_getter(), talkgadget_prefix_)); scoped_ptr oauth_credentials( new OAuthTokenGetter::OAuthCredentials(xmpp_server_config_.username, oauth_refresh_token_, use_service_account_)); oauth_token_getter_.reset( new OAuthTokenGetterImpl(std::move(oauth_credentials), context_->url_request_context_getter(), false)); signaling_connector_.reset(new SignalingConnector( xmpp_signal_strategy, std::move(dns_blackhole_checker), oauth_token_getter_.get(), base::Bind(&HostProcess::OnAuthFailed, base::Unretained(this)))); #if defined(USE_GCD) // Create objects to manage GCD state. ServiceUrls* service_urls = ServiceUrls::GetInstance(); scoped_ptr gcd_rest_client(new GcdRestClient( service_urls->gcd_base_url(), host_id_, context_->url_request_context_getter(), oauth_token_getter_.get())); gcd_state_updater_.reset(new GcdStateUpdater( base::Bind(&HostProcess::OnHeartbeatSuccessful, base::Unretained(this)), base::Bind(&HostProcess::OnUnknownHostIdError, base::Unretained(this)), signal_strategy_.get(), std::move(gcd_rest_client))); PushNotificationSubscriber::Subscription sub; sub.channel = "cloud_devices"; PushNotificationSubscriber::SubscriptionList subs; subs.push_back(sub); gcd_subscriber_.reset( new PushNotificationSubscriber(signal_strategy_.get(), subs)); #endif // defined(USE_GCD) // Create HeartbeatSender. heartbeat_sender_.reset(new HeartbeatSender( base::Bind(&HostProcess::OnHeartbeatSuccessful, base::Unretained(this)), base::Bind(&HostProcess::OnUnknownHostIdError, base::Unretained(this)), host_id_, signal_strategy_.get(), key_pair_, directory_bot_jid_)); } void HostProcess::StartHostIfReady() { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); DCHECK_EQ(state_, HOST_STARTING); // Start the host if both the config and the policies are loaded. if (!serialized_config_.empty()) { if (policy_state_ == POLICY_LOADED) { StartHost(); } else if (policy_state_ == POLICY_ERROR_REPORT_PENDING) { ReportPolicyErrorAndRestartHost(); } } } void HostProcess::StartHost() { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); DCHECK(!host_); SetState(HOST_STARTED); InitializeSignaling(); uint32_t network_flags = 0; if (allow_nat_traversal_) { network_flags = NetworkSettings::NAT_TRAVERSAL_STUN | NetworkSettings::NAT_TRAVERSAL_OUTGOING; if (allow_relay_) network_flags |= NetworkSettings::NAT_TRAVERSAL_RELAY; } NetworkSettings network_settings(network_flags); if (!udp_port_range_.is_null()) { network_settings.port_range = udp_port_range_; } else if (!allow_nat_traversal_) { // For legacy reasons we have to restrict the port range to a set of default // values when nat traversal is disabled, even if the port range was not // set in policy. network_settings.port_range.min_port = NetworkSettings::kDefaultMinPort; network_settings.port_range.max_port = NetworkSettings::kDefaultMaxPort; } scoped_refptr transport_context = new protocol::TransportContext( signal_strategy_.get(), make_scoped_ptr(new protocol::ChromiumPortAllocatorFactory()), make_scoped_ptr(new ChromiumUrlRequestFactory( context_->url_request_context_getter())), network_settings, protocol::TransportRole::SERVER); scoped_ptr session_manager( new protocol::JingleSessionManager(signal_strategy_.get())); scoped_ptr protocol_config = protocol::CandidateSessionConfig::CreateDefault(); if (!desktop_environment_factory_->SupportsAudioCapture()) protocol_config->DisableAudioChannel(); if (enable_vp9_) protocol_config->set_vp9_experiment_enabled(true); if (base::CommandLine::ForCurrentProcess()->HasSwitch( kEnableWebrtcSwitchName)) { protocol_config->set_webrtc_supported(true); } session_manager->set_protocol_config(std::move(protocol_config)); host_.reset(new ChromotingHost(desktop_environment_factory_.get(), std::move(session_manager), transport_context, context_->audio_task_runner(), context_->video_encode_task_runner())); if (gnubby_auth_policy_enabled_ && gnubby_extension_supported_) { host_->AddExtension(make_scoped_ptr(new GnubbyExtension())); } if (frame_recorder_buffer_size_ > 0) { scoped_ptr frame_recorder_extension( new VideoFrameRecorderHostExtension()); frame_recorder_extension->SetMaxContentBytes(frame_recorder_buffer_size_); host_->AddExtension(std::move(frame_recorder_extension)); } // TODO(simonmorris): Get the maximum session duration from a policy. #if defined(OS_LINUX) host_->SetMaximumSessionDuration(base::TimeDelta::FromHours(20)); #endif host_change_notification_listener_.reset(new HostChangeNotificationListener( this, host_id_, signal_strategy_.get(), directory_bot_jid_)); host_status_logger_.reset(new HostStatusLogger( host_->AsWeakPtr(), ServerLogEntry::ME2ME, signal_strategy_.get(), directory_bot_jid_)); // Set up reporting the host status notifications. #if defined(REMOTING_MULTI_PROCESS) host_event_logger_.reset( new IpcHostEventLogger(host_->AsWeakPtr(), daemon_channel_.get())); #else // !defined(REMOTING_MULTI_PROCESS) host_event_logger_ = HostEventLogger::Create(host_->AsWeakPtr(), kApplicationName); #endif // !defined(REMOTING_MULTI_PROCESS) host_->SetEnableCurtaining(curtain_required_); host_->Start(host_owner_email_); CreateAuthenticatorFactory(); ApplyHostDomainPolicy(); ApplyUsernamePolicy(); } void HostProcess::OnAuthFailed() { ShutdownHost(kInvalidOauthCredentialsExitCode); } void HostProcess::RestartHost(const std::string& host_offline_reason) { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); DCHECK(!host_offline_reason.empty()); SetState(HOST_GOING_OFFLINE_TO_RESTART); GoOffline(host_offline_reason); } void HostProcess::ShutdownHost(HostExitCodes exit_code) { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); *exit_code_out_ = exit_code; switch (state_) { case HOST_STARTING: case HOST_STARTED: SetState(HOST_GOING_OFFLINE_TO_STOP); GoOffline(ExitCodeToString(exit_code)); break; case HOST_GOING_OFFLINE_TO_RESTART: SetState(HOST_GOING_OFFLINE_TO_STOP); break; case HOST_GOING_OFFLINE_TO_STOP: case HOST_STOPPED: // Host is already stopped or being stopped. No action is required. break; } } void HostProcess::GoOffline(const std::string& host_offline_reason) { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); DCHECK(!host_offline_reason.empty()); DCHECK((state_ == HOST_GOING_OFFLINE_TO_STOP) || (state_ == HOST_GOING_OFFLINE_TO_RESTART)); // Shut down everything except the HostSignalingManager. host_.reset(); host_event_logger_.reset(); host_status_logger_.reset(); host_change_notification_listener_.reset(); // Before shutting down HostSignalingManager, send the |host_offline_reason| // if possible (i.e. if we have the config). if (!serialized_config_.empty()) { if (!signal_strategy_) InitializeSignaling(); HOST_LOG << "SendHostOfflineReason: sending " << host_offline_reason << "."; if (heartbeat_sender_) { heartbeat_sender_->SetHostOfflineReason( host_offline_reason, base::TimeDelta::FromSeconds(kHostOfflineReasonTimeoutSeconds), base::Bind(&HostProcess::OnHostOfflineReasonAck, this)); } #if defined(USE_GCD) if (gcd_state_updater_) { gcd_state_updater_->SetHostOfflineReason( host_offline_reason, base::TimeDelta::FromSeconds(kHostOfflineReasonTimeoutSeconds), base::Bind(&HostProcess::OnHostOfflineReasonAck, this)); } #endif // defined(USE_GCD) return; // Shutdown will resume after OnHostOfflineReasonAck. } // Continue the shutdown without sending the host offline reason. HOST_LOG << "Can't send offline reason (" << host_offline_reason << ") " << "without a valid host config."; OnHostOfflineReasonAck(false); } void HostProcess::OnHostOfflineReasonAck(bool success) { DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); DCHECK(!host_); // Assert that the host is really offline at this point. HOST_LOG << "SendHostOfflineReason " << (success ? "succeeded." : "failed."); heartbeat_sender_.reset(); oauth_token_getter_.reset(); signaling_connector_.reset(); signal_strategy_.reset(); #if defined(USE_GCD) gcd_state_updater_.reset(); gcd_subscriber_.reset(); #endif // defined(USE_GCD) if (state_ == HOST_GOING_OFFLINE_TO_RESTART) { SetState(HOST_STARTING); StartHostIfReady(); } else if (state_ == HOST_GOING_OFFLINE_TO_STOP) { SetState(HOST_STOPPED); shutdown_watchdog_->SetExitCode(*exit_code_out_); shutdown_watchdog_->Arm(); config_watcher_.reset(); // Complete the rest of shutdown on the main thread. context_->ui_task_runner()->PostTask( FROM_HERE, base::Bind(&HostProcess::ShutdownOnUiThread, this)); } else { NOTREACHED(); } } void HostProcess::OnCrash(const std::string& function_name, const std::string& file_name, const int& line_number) { char message[1024]; base::snprintf(message, sizeof(message), "Requested by %s at %s, line %d.", function_name.c_str(), file_name.c_str(), line_number); base::debug::Alias(message); // The daemon requested us to crash the process. CHECK(false) << message; } int HostProcessMain() { HOST_LOG << "Starting host process: version " << STRINGIZE(VERSION); #if defined(OS_LINUX) // Required in order for us to run multiple X11 threads. XInitThreads(); // 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). gtk_init(nullptr, nullptr); // Need to prime the host OS version value for linux to prevent IO on the // network thread. base::GetLinuxDistro() caches the result. base::GetLinuxDistro(); #endif // Enable support for SSL server sockets, which must be done while still // single-threaded. net::EnableSSLServerSockets(); // Create the main message loop and start helper threads. base::MessageLoopForUI message_loop; scoped_ptr context = ChromotingHostContext::Create( new AutoThreadTaskRunner(message_loop.task_runner(), base::MessageLoop::QuitWhenIdleClosure())); if (!context) return kInitializationFailed; // NetworkChangeNotifier must be initialized after MessageLoop. scoped_ptr network_change_notifier( net::NetworkChangeNotifier::Create()); // Create & start the HostProcess using these threads. // TODO(wez): The HostProcess holds a reference to itself until Shutdown(). // Remove this hack as part of the multi-process refactoring. int exit_code = kSuccessExitCode; ShutdownWatchdog shutdown_watchdog( base::TimeDelta::FromSeconds(kShutdownTimeoutSeconds)); new HostProcess(std::move(context), &exit_code, &shutdown_watchdog); // Run the main (also UI) message loop until the host no longer needs it. message_loop.Run(); return exit_code; } } // namespace remoting