| // Copyright 2012 The Chromium Authors |
| // 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 <stddef.h> |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/notreached.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringize_macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/single_thread_task_executor.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool/thread_pool_instance.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "components/policy/policy_constants.h" |
| #include "components/webrtc/thread_wrapper.h" |
| #include "ipc/ipc_channel.h" |
| #include "ipc/ipc_channel_proxy.h" |
| #include "ipc/ipc_listener.h" |
| #include "mojo/core/embedder/scoped_ipc_support.h" |
| #include "mojo/public/cpp/bindings/associated_receiver.h" |
| #include "mojo/public/cpp/bindings/associated_remote.h" |
| #include "mojo/public/cpp/bindings/pending_associated_receiver.h" |
| #include "mojo/public/cpp/platform/platform_channel.h" |
| #include "mojo/public/cpp/system/invitation.h" |
| #include "mojo/public/cpp/system/message_pipe.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/base/url_util.h" |
| #include "net/socket/client_socket_factory.h" |
| #include "remoting/base/auto_thread_task_runner.h" |
| #include "remoting/base/constants.h" |
| #include "remoting/base/cpu_utils.h" |
| #include "remoting/base/host_settings.h" |
| #include "remoting/base/logging.h" |
| #include "remoting/base/oauth_token_getter_impl.h" |
| #include "remoting/base/oauth_token_getter_proxy.h" |
| #include "remoting/base/rsa_key_pair.h" |
| #include "remoting/base/service_urls.h" |
| #include "remoting/base/util.h" |
| #include "remoting/host/base/desktop_environment_options.h" |
| #include "remoting/host/base/host_exit_codes.h" |
| #include "remoting/host/base/switches.h" |
| #include "remoting/host/base/username.h" |
| #include "remoting/host/branding.h" |
| #include "remoting/host/chromoting_host.h" |
| #include "remoting/host/chromoting_host_context.h" |
| #include "remoting/host/config_file_watcher.h" |
| #include "remoting/host/config_watcher.h" |
| #include "remoting/host/crash_process.h" |
| #include "remoting/host/desktop_environment.h" |
| #include "remoting/host/desktop_session_connector.h" |
| #include "remoting/host/ftl_echo_message_listener.h" |
| #include "remoting/host/ftl_host_change_notification_listener.h" |
| #include "remoting/host/ftl_signaling_connector.h" |
| #include "remoting/host/heartbeat_sender.h" |
| #include "remoting/host/host_config.h" |
| #include "remoting/host/host_event_logger.h" |
| #include "remoting/host/host_power_save_blocker.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/me2me_desktop_environment.h" |
| #include "remoting/host/mojom/desktop_session.mojom.h" |
| #include "remoting/host/mojom/remoting_host.mojom.h" |
| #include "remoting/host/pairing_registry_delegate.h" |
| #include "remoting/host/pin_hash.h" |
| #include "remoting/host/policy_watcher.h" |
| #include "remoting/host/security_key/security_key_auth_handler.h" |
| #include "remoting/host/security_key/security_key_extension.h" |
| #include "remoting/host/shutdown_watchdog.h" |
| #include "remoting/host/test_echo_extension.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/zombie_host_detector.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/ftl_host_device_id_provider.h" |
| #include "remoting/signaling/ftl_signal_strategy.h" |
| #include "remoting/signaling/remoting_log_to_server.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/webrtc/rtc_base/event_tracer.h" |
| |
| #if BUILDFLAG(IS_POSIX) |
| #include <signal.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include "base/file_descriptor_posix.h" |
| #include "remoting/host/pam_authorization_factory_posix.h" |
| #include "remoting/host/posix/signal_handler.h" |
| #endif // BUILDFLAG(IS_POSIX) |
| |
| #if BUILDFLAG(IS_APPLE) |
| #include "base/mac/mac_util.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #include "remoting/host/audio_capturer_mac.h" |
| #include "remoting/host/desktop_capturer_checker.h" |
| #include "remoting/host/mac/permission_utils.h" |
| #endif // BUILDFLAG(IS_APPLE) |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| #if defined(REMOTING_USE_X11) |
| #include <gtk/gtk.h> |
| #endif // defined(REMOTING_USE_X11) |
| |
| #if defined(REMOTING_USE_X11) |
| #include "ui/events/platform/x11/x11_event_source.h" |
| #include "ui/gfx/x/xlib_support.h" |
| #endif // defined(REMOTING_USE_X11) |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| #include "base/linux_util.h" |
| #include "remoting/host/audio_capturer_linux.h" |
| #include "remoting/host/linux/certificate_watcher.h" |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_LINUX) |
| #include "remoting/host/host_utmp_logger.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| #include <commctrl.h> |
| #include "base/win/registry.h" |
| #include "base/win/scoped_handle.h" |
| #include "base/win/windows_version.h" |
| #include "remoting/host/pairing_registry_delegate_win.h" |
| #include "remoting/host/win/session_desktop_environment.h" |
| #endif // BUILDFLAG(IS_WIN) |
| |
| #if BUILDFLAG(IS_LINUX) |
| #include "remoting/host/linux/wayland_manager.h" |
| #include "remoting/host/linux/wayland_utils.h" |
| #endif // BUILDFLAG(IS_LINUX) |
| |
| using remoting::protocol::NetworkSettings; |
| using remoting::protocol::PairingRegistry; |
| |
| #if BUILDFLAG(IS_APPLE) |
| |
| // The following creates a section that tells Mac OS X that it is OK to let us |
| // inject input in the login screen. Just the name of the section is important, |
| // not its contents. |
| __attribute__((used)) |
| __attribute__((section("__CGPreLoginApp,__cgpreloginapp"))) |
| static const char magic_section[] = ""; |
| |
| #endif // BUILDFLAG(IS_APPLE) |
| |
| 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 BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| // The command line switch used to pass name of the pipe to capture audio on |
| // linux. |
| const char kAudioPipeSwitchName[] = "audio-pipe-name"; |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_POSIX) |
| // The command line switch used to pass name of the unix domain socket used to |
| // listen for security key requests. |
| const char kAuthSocknameSwitchName[] = "ssh-auth-sockname"; |
| #endif // BUILDFLAG(IS_POSIX) |
| |
| // 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 send a custom offline reason and exit. |
| const char kReportOfflineReasonSwitchName[] = "report-offline-reason"; |
| |
| // 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"; |
| const char kHostOfflineReasonZombieStateDetected[] = "ZOMBIE_STATE_DETECTED"; |
| |
| // The default email domain for Googlers. Used to determine whether the host's |
| // email address is Google-internal or not. |
| constexpr char kGooglerEmailDomain[] = "@google.com"; |
| |
| // File to write webrtc trace events to. If not specified, webrtc trace events |
| // will not be enabled. |
| const char kWebRtcTraceEventFile[] = "webrtc-trace-event-file"; |
| |
| } // namespace |
| |
| namespace remoting { |
| |
| class HostProcess : public ConfigWatcher::Delegate, |
| public FtlHostChangeNotificationListener::Listener, |
| public HeartbeatSender::Delegate, |
| public IPC::Listener, |
| public base::RefCountedThreadSafe<HostProcess>, |
| public mojom::RemotingHostControl, |
| public mojom::WorkerProcessControl { |
| 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(std::unique_ptr<ChromotingHostContext> context, |
| int* exit_code_out, |
| ShutdownWatchdog* shutdown_watchdog); |
| |
| HostProcess(const HostProcess&) = delete; |
| HostProcess& operator=(const HostProcess&) = delete; |
| |
| // 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; |
| void OnAssociatedInterfaceRequest( |
| const std::string& interface_name, |
| mojo::ScopedInterfaceEndpointHandle handle) override; |
| |
| // FtlHostChangeNotificationListener::Listener overrides. |
| void OnHostDeleted() override; |
| |
| 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>; |
| ~HostProcess() override; |
| |
| void SetState(HostState target_state); |
| |
| void StartOnNetworkThread(); |
| |
| void ShutdownOnNetworkThread(); |
| |
| #if BUILDFLAG(IS_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(); |
| |
| // Determines whether a new config should be applied and handles starting or |
| // restarting the host process as necessary. |
| void OnConfigParsed(base::Value::Dict config); |
| |
| // Applies the host config, returning true if successful. |
| bool ApplyConfig(const base::Value::Dict& config); |
| |
| // Handles policy updates, by calling On*PolicyUpdate methods. |
| void OnPolicyUpdate(base::Value::Dict policies); |
| void OnPolicyError(); |
| void ReportPolicyErrorAndRestartHost(); |
| void ApplyHostDomainListPolicy(); |
| void ApplyUsernamePolicy(); |
| void ApplyAllowRemoteAccessConnections(); |
| bool OnClientDomainListPolicyUpdate(const base::Value::Dict& policies); |
| bool OnHostDomainListPolicyUpdate(const base::Value::Dict& policies); |
| bool OnUsernamePolicyUpdate(const base::Value::Dict& policies); |
| bool OnNatPolicyUpdate(const base::Value::Dict& policies); |
| bool OnRelayPolicyUpdate(const base::Value::Dict& policies); |
| bool OnUdpPortPolicyUpdate(const base::Value::Dict& policies); |
| bool OnCurtainPolicyUpdate(const base::Value::Dict& policies); |
| bool OnHostTokenUrlPolicyUpdate(const base::Value::Dict& policies); |
| bool OnPairingPolicyUpdate(const base::Value::Dict& policies); |
| bool OnGnubbyAuthPolicyUpdate(const base::Value::Dict& policies); |
| bool OnFileTransferPolicyUpdate(const base::Value::Dict& policies); |
| bool OnEnableUserInterfacePolicyUpdate(const base::Value::Dict& policies); |
| bool OnAllowRemoteAccessConnections(const base::Value::Dict& policies); |
| bool OnMaxSessionDurationPolicyUpdate(const base::Value::Dict& policies); |
| bool OnMaxClipboardSizePolicyUpdate(const base::Value::Dict& policies); |
| |
| void InitializeSignaling(); |
| |
| void StartHostIfReady(); |
| void StartHost(); |
| |
| // HeartbeatSender::Delegate implementation. |
| void OnFirstHeartbeatSuccessful() override; |
| void OnHostNotFound() override; |
| void OnAuthFailed() override; |
| |
| void OnZombieStateDetected(); |
| |
| 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); |
| |
| // mojom::WorkerProcessControl implementation. |
| void CrashProcess(const std::string& function_name, |
| const std::string& file_name, |
| int line_number) override; |
| |
| #if BUILDFLAG(IS_WIN) |
| // mojom::RemotingHostControl implementation. |
| void ApplyHostConfig(base::Value::Dict serialized_config) override; |
| void InitializePairingRegistry( |
| ::mojo::PlatformHandle privileged_handle, |
| ::mojo::PlatformHandle unprivileged_handle) override; |
| #endif |
| |
| std::unique_ptr<ChromotingHostContext> context_; |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| // Watch for certificate changes and kill the host when changes occur |
| std::unique_ptr<CertificateWatcher> cert_watcher_; |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| |
| // Created on the UI thread but used from the network thread. |
| base::FilePath host_config_path_; |
| std::string host_config_; |
| std::unique_ptr<DesktopEnvironmentFactory> desktop_environment_factory_; |
| |
| // Accessed on the network thread. |
| HostState state_ = HOST_STARTING; |
| |
| std::unique_ptr<ConfigWatcher> config_watcher_; |
| |
| std::string host_id_; |
| std::string pin_hash_; |
| scoped_refptr<RsaKeyPair> key_pair_; |
| std::string oauth_refresh_token_; |
| std::string robot_account_username_; |
| base::Value::Dict config_; |
| std::string host_owner_; |
| bool is_googler_ = false; |
| absl::optional<size_t> max_clipboard_size_; |
| |
| std::unique_ptr<PolicyWatcher> policy_watcher_; |
| PolicyState policy_state_ = POLICY_INITIALIZING; |
| std::vector<std::string> client_domain_list_; |
| std::vector<std::string> host_domain_list_; |
| bool host_username_match_required_ = false; |
| bool allow_nat_traversal_ = true; |
| bool allow_relay_ = true; |
| PortRange udp_port_range_; |
| bool allow_pairing_ = true; |
| bool enable_user_interface_ = true; |
| bool allow_remote_access_connections_ = true; |
| |
| DesktopEnvironmentOptions desktop_environment_options_; |
| ThirdPartyAuthConfig third_party_auth_config_; |
| bool security_key_auth_policy_enabled_ = false; |
| bool security_key_extension_supported_ = true; |
| absl::optional<int> max_session_duration_minutes_; |
| |
| // Allows us to override field trials which are causing issues for chromoting. |
| std::unique_ptr<base::FieldTrialList> field_trial_list_; |
| |
| // Used to specify which window to stream, if enabled. |
| webrtc::WindowId window_id_ = 0; |
| |
| // Must outlive |signal_strategy_| and |ftl_signaling_connector_|. |
| std::unique_ptr<OAuthTokenGetterImpl> oauth_token_getter_; |
| |
| // Must outlive |host_status_logger_|. |
| std::unique_ptr<LogToServer> log_to_server_; |
| |
| // Must outlive |signal_strategy_| and |heartbeat_sender_|. |
| std::unique_ptr<ZombieHostDetector> zombie_host_detector_; |
| |
| // Signal strategies must outlive |ftl_signaling_connector_|. |
| std::unique_ptr<SignalStrategy> signal_strategy_; |
| |
| std::unique_ptr<FtlSignalingConnector> ftl_signaling_connector_; |
| std::unique_ptr<HeartbeatSender> heartbeat_sender_; |
| std::unique_ptr<FtlHostChangeNotificationListener> |
| ftl_host_change_notification_listener_; |
| std::unique_ptr<FtlEchoMessageListener> ftl_echo_message_listener_; |
| |
| std::unique_ptr<HostStatusLogger> host_status_logger_; |
| std::unique_ptr<HostEventLogger> host_event_logger_; |
| #if BUILDFLAG(IS_LINUX) |
| std::unique_ptr<HostUTMPLogger> host_utmp_logger_; |
| #endif |
| std::unique_ptr<HostPowerSaveBlocker> power_save_blocker_; |
| |
| std::unique_ptr<ChromotingHost> host_; |
| |
| // Used to keep this HostProcess alive until it is shutdown. |
| scoped_refptr<HostProcess> self_; |
| |
| std::unique_ptr<mojo::core::ScopedIPCSupport> ipc_support_; |
| |
| #if defined(REMOTING_MULTI_PROCESS) |
| |
| // Accessed on the UI thread. |
| std::unique_ptr<IPC::ChannelProxy> daemon_channel_; |
| |
| // Raw interface pointer which refers to the object owned by |
| // |desktop_environment_factory_|. |
| raw_ptr<DesktopSessionConnector> desktop_session_connector_ = nullptr; |
| #endif // defined(REMOTING_MULTI_PROCESS) |
| |
| raw_ptr<int> exit_code_out_; |
| bool signal_parent_ = false; |
| std::string report_offline_reason_; |
| |
| scoped_refptr<PairingRegistry> pairing_registry_; |
| |
| raw_ptr<ShutdownWatchdog> shutdown_watchdog_; |
| |
| mojo::AssociatedReceiver<mojom::RemotingHostControl> remoting_host_control_{ |
| this}; |
| mojo::AssociatedReceiver<mojom::WorkerProcessControl> worker_process_control_{ |
| this}; |
| |
| #if BUILDFLAG(IS_APPLE) |
| // When using the command line option to check the Accessibility or Screen |
| // Recording permission, these track the permission state and indicate that |
| // the host should exit immediately with the result. |
| bool checking_permission_state_ = false; |
| bool permission_granted_ = false; |
| #endif // BUILDFLAG(IS_APPLE) |
| }; |
| |
| HostProcess::HostProcess(std::unique_ptr<ChromotingHostContext> context, |
| int* exit_code_out, |
| ShutdownWatchdog* shutdown_watchdog) |
| : context_(std::move(context)), |
| desktop_environment_options_(DesktopEnvironmentOptions::CreateDefault()), |
| self_(this), |
| exit_code_out_(exit_code_out), |
| shutdown_watchdog_(shutdown_watchdog) { |
| // TODO(zijiehe): |
| // desktop_environment_options_.desktop_capture_options() |
| // ->set_use_update_notifications(true); |
| // And remove the same line from me2me_desktop_environment.cc. |
| |
| StartOnUiThread(); |
| |
| #if BUILDFLAG(IS_APPLE) |
| if (checking_permission_state_) { |
| *exit_code_out = (permission_granted_ ? EXIT_SUCCESS : EXIT_FAILURE); |
| } |
| #endif |
| } |
| |
| 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<base::SingleThreadTaskRunner> task_runner = |
| context_->ui_task_runner(); |
| task_runner->DeleteSoon(FROM_HERE, context_.release()); |
| } |
| |
| bool HostProcess::InitWithCommandLine(const base::CommandLine* cmd_line) { |
| #if BUILDFLAG(IS_APPLE) |
| if (cmd_line->HasSwitch(kCheckAccessibilityPermissionSwitchName)) { |
| checking_permission_state_ = true; |
| permission_granted_ = mac::CanInjectInput(); |
| return false; |
| } |
| if (cmd_line->HasSwitch(kCheckScreenRecordingPermissionSwitchName)) { |
| // Trigger screen-capture, even if CanRecordScreen() returns true. It uses a |
| // heuristic that might not be 100% reliable, but it is critically |
| // important to add the host bundle to the list of apps under |
| // Security & Privacy -> Screen Recording. |
| if (base::mac::IsAtLeastOS10_15()) { |
| DesktopCapturerChecker().TriggerSingleCapture(); |
| } |
| checking_permission_state_ = true; |
| permission_granted_ = mac::CanRecordScreen(); |
| return false; |
| } |
| if (cmd_line->HasSwitch(kListAudioDevicesSwitchName)) { |
| std::vector<AudioCapturerMac::AudioDeviceInfo> audio_devices = |
| AudioCapturerMac::GetAudioDevices(); |
| printf("Audio devices:\n"); |
| for (const auto& audio_device : audio_devices) { |
| printf("\n"); |
| printf(" Device name: %s\n", audio_device.device_name.c_str()); |
| printf(" Device UID: %s\n", audio_device.device_uid.c_str()); |
| } |
| return false; |
| } |
| #endif // BUILDFLAG(IS_APPLE) |
| |
| // Mojo keeps the task runner passed to it alive forever, so an |
| // AutoThreadTaskRunner should not be passed to it. Otherwise, the process may |
| // never shut down cleanly. |
| ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>( |
| context_->network_task_runner()->task_runner(), |
| mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST); |
| |
| #if defined(REMOTING_MULTI_PROCESS) |
| auto endpoint = |
| mojo::PlatformChannel::RecoverPassedEndpointFromCommandLine(*cmd_line); |
| if (!endpoint.is_valid()) { |
| LOG(ERROR) << "IPC channel endpoint provided via command line param was " |
| "missing or invalid"; |
| return false; |
| } |
| auto invitation = mojo::IncomingInvitation::Accept(std::move(endpoint)); |
| |
| // Connect to the daemon process. |
| daemon_channel_ = IPC::ChannelProxy::Create( |
| invitation |
| .ExtractMessagePipe(cmd_line->GetSwitchValueASCII(kMojoPipeToken)) |
| .release(), |
| IPC::Channel::MODE_CLIENT, this, context_->network_task_runner(), |
| base::SingleThreadTaskRunner::GetCurrentDefault()); |
| |
| #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; |
| std::unique_ptr<char[]> 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) |
| |
| signal_parent_ = cmd_line->HasSwitch(kSignalParentSwitchName); |
| |
| if (cmd_line->HasSwitch(kReportOfflineReasonSwitchName)) { |
| report_offline_reason_ = |
| cmd_line->GetSwitchValueASCII(kReportOfflineReasonSwitchName); |
| if (report_offline_reason_.empty()) { |
| LOG(ERROR) << "--" << kReportOfflineReasonSwitchName |
| << " requires an argument."; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void HostProcess::OnConfigUpdated(const std::string& serialized_config) { |
| HOST_LOG << "Parsing new host configuration."; |
| |
| absl::optional<base::Value::Dict> config( |
| HostConfigFromJson(serialized_config)); |
| if (!config.has_value()) { |
| LOG(ERROR) << "Invalid configuration."; |
| ShutdownHost(kInvalidHostConfigurationExitCode); |
| return; |
| } |
| |
| OnConfigParsed(std::move(config.value())); |
| } |
| |
| void HostProcess::OnConfigParsed(base::Value::Dict config) { |
| if (!context_->network_task_runner()->BelongsToCurrentThread()) { |
| context_->network_task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&HostProcess::OnConfigParsed, this, std::move(config))); |
| return; |
| } |
| |
| // Filter out duplicates. |
| if (config_ == config) { |
| return; |
| } |
| |
| HOST_LOG << "Applying new host configuration."; |
| |
| config_ = std::move(config); |
| 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); |
| ApplyHostDomainListPolicy(); |
| ApplyUsernamePolicy(); |
| ApplyAllowRemoteAccessConnections(); |
| |
| // 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_ = std::make_unique<ConfigFileWatcher>( |
| context_->network_task_runner(), context_->file_task_runner(), |
| host_config_path_); |
| config_watcher_->Watch(this); |
| } |
| #endif // !defined(REMOTING_MULTI_PROCESS) |
| |
| #if BUILDFLAG(IS_POSIX) |
| remoting::RegisterSignalHandler( |
| SIGTERM, base::BindRepeating(&HostProcess::SigTermHandler, |
| base::Unretained(this))); |
| #endif // BUILDFLAG(IS_POSIX) |
| } |
| |
| void HostProcess::ShutdownOnNetworkThread() { |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| config_watcher_.reset(); |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| cert_watcher_.reset(); |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| #if BUILDFLAG(IS_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 // BUILDFLAG(IS_POSIX) |
| |
| void HostProcess::CreateAuthenticatorFactory() { |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| if (state_ != HOST_STARTED) { |
| return; |
| } |
| |
| std::string local_certificate = key_pair_->GenerateCertificate(); |
| if (local_certificate.empty()) { |
| LOG(ERROR) << "Failed to generate host certificate."; |
| ShutdownHost(kInitializationFailed); |
| return; |
| } |
| |
| std::unique_ptr<protocol::AuthenticatorFactory> factory; |
| |
| if (third_party_auth_config_.is_null()) { |
| scoped_refptr<PairingRegistry> pairing_registry; |
| if (allow_pairing_) { |
| // On Windows |pairing_registry_| is initialized in |
| // InitializePairingRegistry(). |
| #if !BUILDFLAG(IS_WIN) |
| if (!pairing_registry_) { |
| std::unique_ptr<PairingRegistry::Delegate> delegate = |
| CreatePairingRegistryDelegate(); |
| |
| if (delegate) { |
| pairing_registry_ = new PairingRegistry(context_->file_task_runner(), |
| std::move(delegate)); |
| } |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| pairing_registry = pairing_registry_; |
| } |
| |
| factory = protocol::Me2MeHostAuthenticatorFactory::CreateWithPin( |
| host_owner_, local_certificate, key_pair_, client_domain_list_, |
| pin_hash_, pairing_registry); |
| |
| host_->set_pairing_registry(pairing_registry); |
| } else { |
| // ThirdPartyAuthConfig::Parse() leaves the config in a valid state, so |
| // these URLs are both valid. |
| DCHECK(third_party_auth_config_.token_url.is_valid()); |
| DCHECK(third_party_auth_config_.token_validation_url.is_valid()); |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| if (!cert_watcher_) { |
| cert_watcher_ = std::make_unique<CertificateWatcher>( |
| base::BindRepeating(&HostProcess::ShutdownHost, |
| base::Unretained(this), kSuccessExitCode), |
| context_->file_task_runner()); |
| cert_watcher_->Start(); |
| } |
| cert_watcher_->SetMonitor(host_->status_monitor()); |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| |
| scoped_refptr<protocol::TokenValidatorFactory> token_validator_factory = |
| new TokenValidatorFactoryImpl(third_party_auth_config_, key_pair_, |
| context_->url_request_context_getter()); |
| factory = protocol::Me2MeHostAuthenticatorFactory::CreateWithThirdPartyAuth( |
| host_owner_, local_certificate, key_pair_, client_domain_list_, |
| token_validator_factory); |
| } |
| |
| #if BUILDFLAG(IS_POSIX) |
| // On Linux and Mac, perform a PAM authorization step after authentication. |
| factory = std::make_unique<PamAuthorizationFactory>(std::move(factory)); |
| #endif // BUILDFLAG(IS_POSIX) |
| host_->SetAuthenticatorFactory(std::move(factory)); |
| } |
| |
| // IPC::Listener implementation. |
| bool HostProcess::OnMessageReceived(const IPC::Message& message) { |
| NOTREACHED() << "Received unexpected IPC type: " << message.type(); |
| return false; |
| } |
| |
| 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::BindOnce(&HostProcess::ShutdownHost, this, kSuccessExitCode)); |
| } |
| |
| void HostProcess::OnAssociatedInterfaceRequest( |
| const std::string& interface_name, |
| mojo::ScopedInterfaceEndpointHandle handle) { |
| DCHECK(context_->ui_task_runner()->BelongsToCurrentThread()); |
| |
| #if defined(REMOTING_MULTI_PROCESS) |
| if (interface_name == mojom::RemotingHostControl::Name_) { |
| if (remoting_host_control_.is_bound()) { |
| LOG(ERROR) << "Receiver already bound for associated interface: " |
| << mojom::RemotingHostControl::Name_; |
| CrashProcess(__FUNCTION__, __FILE__, __LINE__); |
| } |
| |
| mojo::PendingAssociatedReceiver<mojom::RemotingHostControl> |
| pending_receiver(std::move(handle)); |
| remoting_host_control_.Bind(std::move(pending_receiver)); |
| } else if (interface_name == mojom::WorkerProcessControl::Name_) { |
| if (worker_process_control_.is_bound()) { |
| LOG(ERROR) << "Receiver already bound for associated interface: " |
| << mojom::WorkerProcessControl::Name_; |
| CrashProcess(__FUNCTION__, __FILE__, __LINE__); |
| } |
| |
| mojo::PendingAssociatedReceiver<mojom::WorkerProcessControl> |
| pending_receiver(std::move(handle)); |
| worker_process_control_.Bind(std::move(pending_receiver)); |
| } else if (interface_name == mojom::DesktopSessionConnectionEvents::Name_) { |
| if (!desktop_session_connector_->BindConnectionEventsReceiver( |
| std::move(handle))) { |
| LOG(ERROR) << "Failed to bind Receiver for associated interface: " |
| << mojom::DesktopSessionConnectionEvents::Name_; |
| CrashProcess(__FUNCTION__, __FILE__, __LINE__); |
| } |
| } else { |
| LOG(ERROR) << "Unknown associated interface requested: " << interface_name |
| << ", crashing the network process"; |
| CrashProcess(__FUNCTION__, __FILE__, __LINE__); |
| } |
| #else // !defined(REMOTING_MULTI_PROCESS) |
| LOG(ERROR) << "Unexpected call requesting an associated interface: " |
| << interface_name << ", crashing the network process"; |
| CrashProcess(__FUNCTION__, __FILE__, __LINE__); |
| #endif // !defined(REMOTING_MULTI_PROCESS) |
| } |
| |
| 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; |
| } |
| |
| // Determine if the CPU this host is running on meets a set of minimum |
| // requirements. Note that this isn't a perfect solution as it is possible |
| // that the host will have crashed prior to reaching this point in the code, |
| // however this is the earliest time we can log an offline reason to the |
| // directory if it is unsupported. |
| if (!IsCpuSupported()) { |
| report_offline_reason_ = ExitCodeToString(kCpuNotSupported); |
| } |
| |
| if (!report_offline_reason_.empty()) { |
| // Don't need to do any UI initialization. |
| context_->network_task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&HostProcess::StartOnNetworkThread, this)); |
| return; |
| } |
| |
| HostSettings::Initialize(); |
| |
| policy_watcher_ = PolicyWatcher::CreateWithTaskRunner( |
| context_->file_task_runner(), context_->management_service()); |
| policy_watcher_->StartWatching( |
| base::BindRepeating(&HostProcess::OnPolicyUpdate, base::Unretained(this)), |
| base::BindRepeating(&HostProcess::OnPolicyError, base::Unretained(this))); |
| |
| #if BUILDFLAG(IS_LINUX) |
| if (IsRunningWayland()) { |
| WaylandManager::Get()->Init(context_->ui_task_runner()); |
| } |
| #endif // BUILDFLAG(IS_LINUX) |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| // 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); |
| } |
| #endif // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| |
| #if BUILDFLAG(IS_POSIX) |
| base::FilePath security_key_socket_name = |
| base::CommandLine::ForCurrentProcess()->GetSwitchValuePath( |
| kAuthSocknameSwitchName); |
| if (!security_key_socket_name.empty()) { |
| remoting::SecurityKeyAuthHandler::SetSecurityKeySocketName( |
| security_key_socket_name); |
| } else { |
| security_key_extension_supported_ = false; |
| } |
| #endif // BUILDFLAG(IS_POSIX) |
| |
| // Create a desktop environment factory appropriate to the build type & |
| // platform. |
| #if defined(REMOTING_MULTI_PROCESS) |
| // Set up the AssociatedRemote used to send requests to the Daemon process. |
| // We need to do a little dance here using a pending associated receiver so |
| // that the remote is associated with the proper task_runner since it will be |
| // invoked on the network thread. |
| mojo::AssociatedRemote<mojom::DesktopSessionManager> remote; |
| mojo::GenericPendingAssociatedReceiver pending_receiver = |
| remote.BindNewEndpointAndPassReceiver(context_->network_task_runner()); |
| daemon_channel_->GetRemoteAssociatedInterface(std::move(pending_receiver)); |
| |
| IpcDesktopEnvironmentFactory* desktop_environment_factory = |
| new IpcDesktopEnvironmentFactory( |
| context_->audio_task_runner(), context_->network_task_runner(), |
| context_->network_task_runner(), std::move(remote)); |
| desktop_session_connector_ = desktop_environment_factory; |
| #else // !defined(REMOTING_MULTI_PROCESS) |
| BasicDesktopEnvironmentFactory* desktop_environment_factory; |
| 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_.reset(desktop_environment_factory); |
| |
| context_->network_task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&HostProcess::StartOnNetworkThread, this)); |
| } |
| |
| void HostProcess::ShutdownOnUiThread() { |
| DCHECK(context_->ui_task_runner()->BelongsToCurrentThread()); |
| |
| context_->network_task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&HostProcess::ShutdownOnNetworkThread, this)); |
| |
| // 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) |
| daemon_channel_.reset(); |
| desktop_session_connector_ = nullptr; |
| #endif // defined(REMOTING_MULTI_PROCESS) |
| |
| // It is now safe for the HostProcess to be deleted. |
| self_ = nullptr; |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| // 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 // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| |
| #if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && defined(REMOTING_USE_X11) |
| context_->input_task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce([]() { delete ui::X11EventSource::GetInstance(); })); |
| #endif // (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && |
| // defined(REMOTING_USE_X11) |
| } |
| |
| void HostProcess::OnHostNotFound() { |
| LOG(ERROR) << "Host ID not found."; |
| ShutdownHost(kInvalidHostIdExitCode); |
| } |
| |
| void HostProcess::OnFirstHeartbeatSuccessful() { |
| if (state_ != HOST_STARTED) { |
| return; |
| } |
| HOST_LOG << "Host ready to receive connections."; |
| #if BUILDFLAG(IS_POSIX) |
| if (signal_parent_) { |
| kill(getppid(), SIGUSR1); |
| signal_parent_ = false; |
| } |
| #endif |
| } |
| |
| void HostProcess::OnHostDeleted() { |
| LOG(ERROR) << "Host was deleted from the directory."; |
| ShutdownHost(kHostDeletedExitCode); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| void HostProcess::ApplyHostConfig(base::Value::Dict config) { |
| DCHECK(context_->ui_task_runner()->BelongsToCurrentThread()); |
| OnConfigParsed(std::move(config)); |
| } |
| |
| void HostProcess::InitializePairingRegistry( |
| ::mojo::PlatformHandle privileged_handle, |
| ::mojo::PlatformHandle unprivileged_handle) { |
| // This IPC is handled on the UI thread and bounced over to the network thread |
| // so being called on any other thread is unexpected. |
| DCHECK(context_->ui_task_runner()->BelongsToCurrentThread() || |
| context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| if (context_->ui_task_runner()->BelongsToCurrentThread()) { |
| context_->network_task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&HostProcess::InitializePairingRegistry, this, |
| std::move(privileged_handle), |
| std::move(unprivileged_handle))); |
| return; |
| } |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| // |pairing_registry_| must only be initialized once. |
| DCHECK(!pairing_registry_) << "Received multiple calls to initialize the " |
| << "pairing registry"; |
| |
| std::unique_ptr<PairingRegistryDelegateWin> delegate( |
| new PairingRegistryDelegateWin()); |
| delegate->SetRootKeys(static_cast<HKEY>(privileged_handle.ReleaseHandle()), |
| static_cast<HKEY>(unprivileged_handle.ReleaseHandle())); |
| |
| 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 // BUILDFLAG(IS_WIN) |
| |
| // Applies the host config, returning true if successful. |
| bool HostProcess::ApplyConfig(const base::Value::Dict& config) { |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| const std::string* host_id = config.FindString(kHostIdConfigPath); |
| if (!host_id) { |
| LOG(ERROR) << "Config does not define " << kHostIdConfigPath << "."; |
| return false; |
| } |
| host_id_ = *host_id; |
| |
| const std::string* key_base64 = config.FindString(kPrivateKeyConfigPath); |
| if (!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; |
| } |
| |
| const std::string* host_secret_hash = |
| config.FindString(kHostSecretHashConfigPath); |
| if (!host_secret_hash) { |
| LOG(ERROR) << "Missing host_secret_hash value in configuration file."; |
| return false; |
| } |
| |
| if (!ParsePinHashFromConfig(*host_secret_hash, host_id_, &pin_hash_)) { |
| LOG(ERROR) << "Cannot parse host_secret_hash configuration value."; |
| return false; |
| } |
| |
| // Retrieve robot account to use for signaling and backend communication. |
| const std::string* robot_account_username = |
| config.FindString(kXmppLoginConfigPath); |
| if (!robot_account_username) { |
| LOG(ERROR) << "Robot account username is not defined in the config."; |
| return false; |
| } |
| robot_account_username_ = *robot_account_username; |
| |
| // Retrieve robot account credentials for session signaling. |
| const std::string* oauth_refresh_token = |
| config.FindString(kOAuthRefreshTokenConfigPath); |
| if (!oauth_refresh_token) { |
| LOG(ERROR) << "Robot account credentials are not defined in the config."; |
| return false; |
| } |
| oauth_refresh_token_ = *oauth_refresh_token; |
| |
| // Some old host configs have a host_owner field that's set to a JID ending |
| // with @id.talk.google.com, and a host_owner_email field that's set to the |
| // owner's actual email address. Other host configs only have a host_owner |
| // field. We are not generating separate addresses nor using JID any more but |
| // we still read host_owner_email first for compatibility reason. |
| const std::string* host_owner_ptr = |
| config.FindString(kHostOwnerEmailConfigPath); |
| if (!host_owner_ptr) { |
| host_owner_ptr = config.FindString(kHostOwnerConfigPath); |
| } |
| if (!host_owner_ptr) { |
| LOG(ERROR) << "Host config has no host_owner or host_owner_email fields."; |
| return false; |
| } |
| host_owner_ = *host_owner_ptr; |
| |
| // Check if the host owner's email is Google-internal. |
| is_googler_ = base::EndsWith(host_owner_, kGooglerEmailDomain, |
| base::CompareCase::INSENSITIVE_ASCII); |
| |
| return true; |
| } |
| |
| void HostProcess::OnPolicyUpdate(base::Value::Dict policies) { |
| if (!context_->network_task_runner()->BelongsToCurrentThread()) { |
| context_->network_task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&HostProcess::OnPolicyUpdate, this, |
| std::move(policies))); |
| return; |
| } |
| |
| bool restart_required = false; |
| restart_required |= OnClientDomainListPolicyUpdate(policies); |
| restart_required |= OnHostDomainListPolicyUpdate(policies); |
| restart_required |= OnCurtainPolicyUpdate(policies); |
| // Note: UsernamePolicyUpdate must run after OnCurtainPolicyUpdate. |
| restart_required |= OnUsernamePolicyUpdate(policies); |
| restart_required |= OnNatPolicyUpdate(policies); |
| restart_required |= OnRelayPolicyUpdate(policies); |
| restart_required |= OnUdpPortPolicyUpdate(policies); |
| restart_required |= OnHostTokenUrlPolicyUpdate(policies); |
| restart_required |= OnPairingPolicyUpdate(policies); |
| restart_required |= OnGnubbyAuthPolicyUpdate(policies); |
| restart_required |= OnFileTransferPolicyUpdate(policies); |
| restart_required |= OnEnableUserInterfacePolicyUpdate(policies); |
| restart_required |= OnAllowRemoteAccessConnections(policies); |
| restart_required |= OnMaxSessionDurationPolicyUpdate(policies); |
| restart_required |= OnMaxClipboardSizePolicyUpdate(policies); |
| |
| 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::BindOnce(&HostProcess::OnPolicyError, this)); |
| return; |
| } |
| |
| if (policy_state_ != POLICY_ERROR_REPORTED) { |
| policy_state_ = POLICY_ERROR_REPORT_PENDING; |
| if ((state_ == HOST_STARTED) || |
| (state_ == HOST_STARTING && !config_.empty())) { |
| ReportPolicyErrorAndRestartHost(); |
| } |
| } |
| } |
| |
| void HostProcess::ReportPolicyErrorAndRestartHost() { |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| DCHECK(!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::ApplyHostDomainListPolicy() { |
| if (state_ != HOST_STARTED) { |
| return; |
| } |
| |
| HOST_LOG << "Policy sets host domains: " |
| << base::JoinString(host_domain_list_, ", "); |
| |
| if (!host_domain_list_.empty()) { |
| bool matched = false; |
| for (const std::string& domain : host_domain_list_) { |
| if (base::EndsWith(host_owner_, std::string("@") + domain, |
| base::CompareCase::INSENSITIVE_ASCII)) { |
| matched = true; |
| } |
| } |
| if (!matched) { |
| LOG(ERROR) << "The host domain does not match the policy."; |
| ShutdownHost(kInvalidHostDomainExitCode); |
| } |
| } |
| } |
| |
| void HostProcess::ApplyAllowRemoteAccessConnections() { |
| if (state_ != HOST_STARTED) { |
| return; |
| } |
| |
| HOST_LOG << "Policy allows remote access connections: " |
| << allow_remote_access_connections_; |
| |
| if (!allow_remote_access_connections_) { |
| ShutdownHost(kRemoteAccessDisallowedExitCode); |
| } |
| } |
| |
| bool HostProcess::OnHostDomainListPolicyUpdate( |
| const base::Value::Dict& policies) { |
| // Returns false: never restart the host after this policy update. |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| const base::Value::List* list = |
| policies.FindList(policy::key::kRemoteAccessHostDomainList); |
| if (!list) { |
| return false; |
| } |
| |
| host_domain_list_.clear(); |
| for (const auto& value : *list) { |
| host_domain_list_.push_back(value.GetString()); |
| } |
| |
| ApplyHostDomainListPolicy(); |
| return false; |
| } |
| |
| bool HostProcess::OnClientDomainListPolicyUpdate( |
| const base::Value::Dict& policies) { |
| // Returns true if the host has to be restarted after this policy update. |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| const base::Value::List* list = |
| policies.FindList(policy::key::kRemoteAccessHostClientDomainList); |
| if (!list) { |
| return false; |
| } |
| |
| client_domain_list_.clear(); |
| for (const auto& value : *list) { |
| client_domain_list_.push_back(value.GetString()); |
| } |
| |
| return true; |
| } |
| |
| void HostProcess::ApplyUsernamePolicy() { |
| if (state_ != HOST_STARTED) { |
| return; |
| } |
| |
| if (host_username_match_required_) { |
| HOST_LOG << "Policy requires host username match."; |
| |
| std::string username = GetUsername(); |
| bool shutdown = username.empty() || |
| !base::StartsWith(host_owner_, username + std::string("@"), |
| base::CompareCase::INSENSITIVE_ASCII); |
| |
| #if BUILDFLAG(IS_APPLE) |
| // 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 BUILDFLAG(IS_WIN) && defined(REMOTING_RDP_SESSION) |
| if (desktop_environment_options_.enable_curtaining()) { |
| return; |
| } |
| #endif // BUILDFLAG(IS_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(const base::Value::Dict& policies) { |
| // Returns false: never restart the host after this policy update. |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) |
| absl::optional<bool> host_username_match_required = |
| policies.FindBool(policy::key::kRemoteAccessHostMatchUsername); |
| if (!host_username_match_required.has_value()) { |
| return false; |
| } |
| |
| host_username_match_required_ = host_username_match_required.value(); |
| ApplyUsernamePolicy(); |
| #endif |
| return false; |
| } |
| |
| bool HostProcess::OnNatPolicyUpdate(const base::Value::Dict& policies) { |
| // Returns true if the host has to be restarted after this policy update. |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| absl::optional<bool> allow_nat_traversal = |
| policies.FindBool(policy::key::kRemoteAccessHostFirewallTraversal); |
| if (!allow_nat_traversal.has_value()) { |
| return false; |
| } |
| |
| allow_nat_traversal_ = allow_nat_traversal.value(); |
| if (allow_nat_traversal_) { |
| HOST_LOG << "Policy enables NAT traversal."; |
| } else { |
| HOST_LOG << "Policy disables NAT traversal."; |
| } |
| return true; |
| } |
| |
| bool HostProcess::OnRelayPolicyUpdate(const base::Value::Dict& policies) { |
| // Returns true if the host has to be restarted after this policy update. |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| absl::optional<bool> allow_relay = |
| policies.FindBool(policy::key::kRemoteAccessHostAllowRelayedConnection); |
| if (!allow_relay.has_value()) { |
| return false; |
| } |
| |
| allow_relay_ = allow_relay.value(); |
| 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(const base::Value::Dict& policies) { |
| // Returns true if the host has to be restarted after this policy update. |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| const std::string* string_value = |
| policies.FindString(policy::key::kRemoteAccessHostUdpPortRange); |
| if (!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(const base::Value::Dict& policies) { |
| // Returns true if the host has to be restarted after this policy update. |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| absl::optional<bool> curtain_required = |
| policies.FindBool(policy::key::kRemoteAccessHostRequireCurtain); |
| if (!curtain_required.has_value()) { |
| return false; |
| } |
| |
| desktop_environment_options_.set_enable_curtaining(curtain_required.value()); |
| |
| #if BUILDFLAG(IS_APPLE) |
| if (curtain_required.value()) { |
| // 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 not yet " |
| "supported."; |
| ShutdownHost(kLoginScreenNotSupportedExitCode); |
| return false; |
| } |
| } |
| #endif |
| |
| if (curtain_required.value()) { |
| HOST_LOG << "Policy requires curtain-mode."; |
| } else { |
| HOST_LOG << "Policy does not require curtain-mode."; |
| } |
| |
| return true; |
| } |
| |
| bool HostProcess::OnHostTokenUrlPolicyUpdate( |
| const base::Value::Dict& 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: |
| // Unreachable, because PolicyWatcher::OnPolicyUpdated() enforces that |
| // the policy is well-formed (including checks specific to |
| // ThirdPartyAuthConfig), before notifying of policy updates. |
| NOTREACHED(); |
| return false; |
| } |
| } |
| |
| bool HostProcess::OnPairingPolicyUpdate(const base::Value::Dict& policies) { |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| absl::optional<bool> allow_pairing = |
| policies.FindBool(policy::key::kRemoteAccessHostAllowClientPairing); |
| if (!allow_pairing.has_value()) { |
| return false; |
| } |
| |
| allow_pairing_ = allow_pairing.value(); |
| if (allow_pairing_) { |
| HOST_LOG << "Policy enables client pairing."; |
| } else { |
| HOST_LOG << "Policy disables client pairing."; |
| } |
| return true; |
| } |
| |
| bool HostProcess::OnGnubbyAuthPolicyUpdate(const base::Value::Dict& policies) { |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| absl::optional<bool> security_key_auth_policy_enabled = |
| policies.FindBool(policy::key::kRemoteAccessHostAllowGnubbyAuth); |
| if (!security_key_auth_policy_enabled.has_value()) { |
| return false; |
| } |
| |
| security_key_auth_policy_enabled_ = security_key_auth_policy_enabled.value(); |
| if (security_key_auth_policy_enabled_) { |
| HOST_LOG << "Policy enables security key auth."; |
| } else { |
| HOST_LOG << "Policy disables security key auth."; |
| } |
| |
| return true; |
| } |
| |
| bool HostProcess::OnFileTransferPolicyUpdate( |
| const base::Value::Dict& policies) { |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| absl::optional<bool> file_transfer_enabled = |
| policies.FindBool(policy::key::kRemoteAccessHostAllowFileTransfer); |
| if (!file_transfer_enabled.has_value()) { |
| return false; |
| } |
| |
| desktop_environment_options_.set_enable_file_transfer( |
| file_transfer_enabled.value()); |
| |
| if (file_transfer_enabled.value()) { |
| HOST_LOG << "Policy enables file transfer."; |
| } else { |
| HOST_LOG << "Policy disables file transfer."; |
| } |
| |
| // Restart required. |
| return true; |
| } |
| |
| bool HostProcess::OnEnableUserInterfacePolicyUpdate( |
| const base::Value::Dict& policies) { |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| absl::optional<bool> enable_user_interface = |
| policies.FindBool(policy::key::kRemoteAccessHostEnableUserInterface); |
| if (!enable_user_interface) { |
| return false; |
| } |
| |
| // Save the value until we have parsed the host config since we only want the |
| // policy to be applied to machines owned by a Googler. |
| enable_user_interface_ = enable_user_interface.value(); |
| if (enable_user_interface_) { |
| HOST_LOG << "Policy enables user interface for non-curtained sessions."; |
| } else { |
| HOST_LOG << "Policy disables user interface for non-curtained sessions."; |
| } |
| |
| // Restart required. |
| return true; |
| } |
| |
| bool HostProcess::OnMaxSessionDurationPolicyUpdate( |
| const base::Value::Dict& policies) { |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| absl::optional<int> value = policies.FindInt( |
| policy::key::kRemoteAccessHostMaximumSessionDurationMinutes); |
| if (!value) { |
| return false; |
| } |
| |
| max_session_duration_minutes_ = *value; |
| |
| if (max_session_duration_minutes_ > 0) { |
| HOST_LOG << "Policy sets maximum session duration to " |
| << max_session_duration_minutes_.value() << " minutes."; |
| } else { |
| HOST_LOG << "Policy does not set a maximum session duration."; |
| } |
| |
| // Restart required. |
| return true; |
| } |
| |
| bool HostProcess::OnMaxClipboardSizePolicyUpdate( |
| const base::Value::Dict& policies) { |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| absl::optional<int> max_clipboard_size = |
| policies.FindInt(policy::key::kRemoteAccessHostClipboardSizeBytes); |
| if (!max_clipboard_size) { |
| return false; |
| } |
| |
| if (*max_clipboard_size >= 0) { |
| max_clipboard_size_ = *max_clipboard_size; |
| HOST_LOG << "Policy sets maximum clipboard size to " |
| << max_clipboard_size_.value() << " bytes."; |
| } else { |
| max_clipboard_size_.reset(); |
| HOST_LOG << "Policy does not set a maximum clipboard size."; |
| } |
| |
| // Restart required. |
| return true; |
| } |
| |
| bool HostProcess::OnAllowRemoteAccessConnections( |
| const base::Value::Dict& policies) { |
| // Returns false: never restart the host after this policy update. |
| DCHECK(context_->network_task_runner()->BelongsToCurrentThread()); |
| |
| absl::optional<bool> allow_remote_access_connections = policies.FindBool( |
| policy::key::kRemoteAccessHostAllowRemoteAccessConnections); |
| if (!allow_remote_access_connections.has_value()) { |
| return false; |
| } |
| |
| // Update the value if the policy was set and retrieval was successful. |
| allow_remote_access_connections_ = allow_remote_access_connections.value(); |
| ApplyAllowRemoteAccessConnections(); |
| return false; |
| } |
| |
| void HostProcess::InitializeSignaling() { |
| DCHECK(!host_id_.empty()); // ApplyConfig() should already have been run. |
| |
| DCHECK(!signal_strategy_); |
| DCHECK(!oauth_token_getter_); |
| DCHECK(!ftl_signaling_connector_); |
| DCHECK(!heartbeat_sender_); |
| |
| auto oauth_credentials = |
| std::make_unique<OAuthTokenGetter::OAuthAuthorizationCredentials>( |
| robot_account_username_, oauth_refresh_token_, |
| /* is_service_account */ true); |
| // Unretained is sound because we own the OAuthTokenGetterImpl, and the |
| // callback will never be invoked once it is destroyed. |
| oauth_token_getter_ = std::make_unique<OAuthTokenGetterImpl>( |
| std::move(oauth_credentials), context_->url_loader_factory(), false); |
| |
| log_to_server_ = std::make_unique<RemotingLogToServer>( |
| ServerLogEntry::ME2ME, |
| std::make_unique<OAuthTokenGetterProxy>( |
| oauth_token_getter_->GetWeakPtr()), |
| context_->url_loader_factory()); |
| zombie_host_detector_ = std::make_unique<ZombieHostDetector>(base::BindOnce( |
| &HostProcess::OnZombieStateDetected, base::Unretained(this))); |
| |
| auto ftl_signal_strategy = std::make_unique<FtlSignalStrategy>( |
| std::make_unique<OAuthTokenGetterProxy>( |
| oauth_token_getter_->GetWeakPtr()), |
| context_->url_loader_factory(), |
| std::make_unique<FtlHostDeviceIdProvider>(host_id_), |
| zombie_host_detector_.get()); |
| ftl_signaling_connector_ = std::make_unique<FtlSignalingConnector>( |
| ftl_signal_strategy.get(), |
| base::BindOnce(&HostProcess::OnAuthFailed, base::Unretained(this))); |
| ftl_signaling_connector_->Start(); |
| |
| heartbeat_sender_ = std::make_unique<HeartbeatSender>( |
| this, host_id_, ftl_signal_strategy.get(), oauth_token_getter_.get(), |
| zombie_host_detector_.get(), context_->url_loader_factory(), is_googler_); |
| signal_strategy_ = std::move(ftl_signal_strategy); |
| |
| zombie_host_detector_->Start(); |
| } |
| |
| 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 (!config_.empty()) { |
| if (!report_offline_reason_.empty()) { |
| SetState(HOST_GOING_OFFLINE_TO_STOP); |
| GoOffline(report_offline_reason_); |
| } else 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_); |
| |
| // This thread is used as a network thread in WebRTC. |
| webrtc::ThreadWrapper::EnsureForCurrentMessageLoop(); |
| |
| // Initialize global field trials. |
| field_trial_list_ = std::make_unique<base::FieldTrialList>(); |
| |
| // Override LossBasedBweV2 trial. |
| // TODO(b/266103942): Remove this override once we figure out why the BWE is |
| // crashing for some users and have a fix available. |
| base::FieldTrialList::CreateTrialsFromString( |
| "WebRTC-Bwe-LossBasedBweV2/Enabled:false/"); |
| |
| 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<protocol::TransportContext> transport_context = |
| new protocol::TransportContext( |
| std::make_unique<protocol::ChromiumPortAllocatorFactory>(), |
| webrtc::ThreadWrapper::current()->SocketServer(), |
| context_->url_loader_factory(), oauth_token_getter_.get(), |
| network_settings, protocol::TransportRole::SERVER); |
| std::unique_ptr<protocol::SessionManager> session_manager( |
| new protocol::JingleSessionManager(signal_strategy_.get())); |
| |
| std::unique_ptr<protocol::CandidateSessionConfig> protocol_config = |
| protocol::CandidateSessionConfig::CreateDefault(); |
| if (!desktop_environment_factory_->SupportsAudioCapture()) { |
| protocol_config->DisableAudioChannel(); |
| } |
| protocol_config->set_webrtc_supported(true); |
| session_manager->set_protocol_config(std::move(protocol_config)); |
| |
| if (is_googler_) { |
| // Enabling this policy means that a local user sitting at a host would not |
| // see any UI or indication that a remote user was connected. We do have a |
| // few use cases for this internally where we know for a fact that there |
| // will not be a local user. Since that isn't something we can control |
| // externally, we don't want to apply this policy for non-Googlers. |
| desktop_environment_options_.set_enable_user_interface( |
| enable_user_interface_); |
| } |
| |
| // Always enable remote open URL when the platform supports it. There is an |
| // additional IsRemoteOpenUrlSupported() check that makes sure the capability |
| // won't be advertised if it's missing a registry key or something. |
| desktop_environment_options_.set_enable_remote_open_url(true); |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) |
| desktop_environment_options_.set_enable_remote_webauthn(is_googler_); |
| #endif |
| |
| if (max_clipboard_size_.has_value()) { |
| desktop_environment_options_.set_clipboard_size( |
| max_clipboard_size_.value()); |
| } else if (desktop_environment_options_.clipboard_size().has_value()) { |
| // If we've transitioned from having a policy value to no value then make |
| // sure the value stored in desktop_environment_options has been cleared. |
| desktop_environment_options_.set_clipboard_size(absl::optional<size_t>()); |
| } |
| |
| host_ = std::make_unique<ChromotingHost>( |
| desktop_environment_factory_.get(), std::move(session_manager), |
| transport_context, context_->audio_task_runner(), |
| context_->video_encode_task_runner(), desktop_environment_options_); |
| |
| if (security_key_auth_policy_enabled_ && security_key_extension_supported_) { |
| host_->AddExtension( |
| std::make_unique<SecurityKeyExtension>(context_->file_task_runner())); |
| } |
| |
| host_->AddExtension(std::make_unique<TestEchoExtension>()); |
| |
| if (max_session_duration_minutes_ && max_session_duration_minutes_ > 0) { |
| host_->SetMaximumSessionDuration( |
| base::Minutes(max_session_duration_minutes_.value())); |
| } |
| |
| host_status_logger_ = std::make_unique<HostStatusLogger>( |
| host_->status_monitor(), log_to_server_.get()); |
| |
| #if BUILDFLAG(IS_LINUX) |
| const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); |
| if (cmd_line->HasSwitch(kEnableUtempter)) { |
| host_utmp_logger_ = |
| std::make_unique<HostUTMPLogger>(host_->status_monitor()); |
| } |
| #endif |
| |
| power_save_blocker_ = std::make_unique<HostPowerSaveBlocker>( |
| host_->status_monitor(), context_->ui_task_runner(), |
| context_->file_task_runner()); |
| |
| ftl_host_change_notification_listener_ = |
| std::make_unique<FtlHostChangeNotificationListener>( |
| this, signal_strategy_.get()); |
| |
| ftl_echo_message_listener_ = std::make_unique<FtlEchoMessageListener>( |
| host_owner_, signal_strategy_.get()); |
| |
| // Set up reporting the host status notifications. |
| #if defined(REMOTING_MULTI_PROCESS) |
| mojo::AssociatedRemote<mojom::HostStatusObserver> remote; |
| daemon_channel_->GetRemoteAssociatedInterface(&remote); |
| host_event_logger_ = std::make_unique<IpcHostEventLogger>( |
| host_->status_monitor(), std::move(remote)); |
| #else // !defined(REMOTING_MULTI_PROCESS) |
| host_event_logger_ = |
| HostEventLogger::Create(host_->status_monitor(), kApplicationName); |
| #endif // !defined(REMOTING_MULTI_PROCESS) |
| |
| #if BUILDFLAG(IS_APPLE) |
| // Don't run the permission-checks as root (i.e. at the login screen), as they |
| // are not actionable there. |
| // Also, the permission-checks are not needed on MacOS 10.15+, as they are |
| // always handled by the new permission-wizard (the old shell script is |
| // never used on 10.15+). |
| if (getuid() != 0U && base::mac::IsAtMostOS10_14()) { |
| mac::PromptUserToChangeTrustStateIfNeeded(context_->ui_task_runner()); |
| } |
| #endif // BUILDFLAG(IS_APPLE) |
| |
| host_->Start(host_owner_); |
| host_->StartChromotingHostServices(); |
| |
| CreateAuthenticatorFactory(); |
| |
| ApplyHostDomainListPolicy(); |
| ApplyUsernamePolicy(); |
| ApplyAllowRemoteAccessConnections(); |
| } |
| |
| void HostProcess::OnAuthFailed() { |
| ShutdownHost(kInvalidOauthCredentialsExitCode); |
| } |
| |
| void HostProcess::OnZombieStateDetected() { |
| RestartHost(kHostOfflineReasonZombieStateDetected); |
| } |
| |
| 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(); |
| power_save_blocker_.reset(); |
| ftl_host_change_notification_listener_.reset(); |
| |
| // Before shutting down HostSignalingManager, send the |host_offline_reason| |
| // if possible (i.e. if we have the config). |
| if (host_offline_reason == ExitCodeToString(kHostDeletedExitCode)) { |
| // Host is deleted. There is no need to report the host offline reason back |
| // to directory. |
| OnHostOfflineReasonAck(true); |
| return; |
| } else if (!config_.empty()) { |
| if (!signal_strategy_) { |
| InitializeSignaling(); |
| } |
| |
| HOST_LOG << "SendHostOfflineReason: sending " << host_offline_reason << "."; |
| heartbeat_sender_->SetHostOfflineReason( |
| host_offline_reason, base::Seconds(kHostOfflineReasonTimeoutSeconds), |
| base::BindOnce(&HostProcess::OnHostOfflineReasonAck, this)); |
| 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."); |
| log_to_server_.reset(); |
| heartbeat_sender_.reset(); |
| oauth_token_getter_.reset(); |
| ftl_signaling_connector_.reset(); |
| ftl_echo_message_listener_.reset(); |
| signal_strategy_.reset(); |
| zombie_host_detector_.reset(); |
| |
| 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::BindOnce(&HostProcess::ShutdownOnUiThread, this)); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void HostProcess::CrashProcess(const std::string& function_name, |
| const std::string& file_name, |
| int line_number) { |
| // The daemon requested us to crash the process. |
| ::remoting::CrashProcess(function_name, file_name, line_number); |
| } |
| |
| int HostProcessMain() { |
| HOST_LOG << "Starting host process: version " << STRINGIZE(VERSION); |
| const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); |
| |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| #if defined(REMOTING_USE_X11) |
| // Initialize Xlib for multi-threaded use, allowing non-Chromium code to |
| // use X11 safely (such as the WebRTC capturer, GTK ...) |
| x11::InitXlib(); |
| #endif // defined(REMOTING_USE_X11) |
| |
| #if defined(REMOTING_USE_X11) |
| if (!cmd_line->HasSwitch(kReportOfflineReasonSwitchName)) { |
| // 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). |
| #if GTK_CHECK_VERSION(3, 90, 0) |
| gtk_init(); |
| #else |
| gtk_init(nullptr, nullptr); |
| #endif |
| } |
| #endif // defined(REMOTING_USE_X11) |
| |
| // 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 // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| |
| if (cmd_line->HasSwitch(kWebRtcTraceEventFile)) { |
| rtc::tracing::SetupInternalTracer(); |
| rtc::tracing::StartInternalCapture( |
| cmd_line->GetSwitchValuePath(kWebRtcTraceEventFile) |
| .AsUTF8Unsafe() |
| .c_str()); |
| } |
| |
| base::ThreadPoolInstance::CreateAndStartWithDefaultParams("Me2Me"); |
| |
| // Create the main task executor and start helper threads. |
| base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI); |
| base::RunLoop run_loop; |
| std::unique_ptr<ChromotingHostContext> context = |
| ChromotingHostContext::Create(base::MakeRefCounted<AutoThreadTaskRunner>( |
| main_task_executor.task_runner(), run_loop.QuitClosure())); |
| if (!context) { |
| return kInitializationFailed; |
| } |
| |
| // NetworkChangeNotifier must be initialized after SingleThreadTaskExecutor. |
| std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier( |
| net::NetworkChangeNotifier::CreateIfNeeded()); |
| |
| #if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && defined(REMOTING_USE_X11) |
| // Create an X11EventSource on all UI threads, so the global X11 connection |
| // (x11::Connection::Get()) can dispatch X events. |
| auto event_source = |
| std::make_unique<ui::X11EventSource>(x11::Connection::Get()); |
| context->input_task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce([]() { new ui::X11EventSource(x11::Connection::Get()); })); |
| #endif // (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && |
| // defined(REMOTING_USE_X11) |
| |
| // 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::Seconds(kShutdownTimeoutSeconds)); |
| new HostProcess(std::move(context), &exit_code, &shutdown_watchdog); |
| |
| // Run the main (also UI) task executor until the host no longer needs it. |
| run_loop.Run(); |
| |
| // Block until tasks blocking shutdown have completed their execution. |
| base::ThreadPoolInstance::Get()->Shutdown(); |
| |
| if (cmd_line->HasSwitch(kWebRtcTraceEventFile)) { |
| rtc::tracing::ShutdownInternalTracer(); |
| } |
| |
| return exit_code; |
| } |
| |
| } // namespace remoting |