| // 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. |
| |
| #include "remoting/host/desktop_session_win.h" |
| |
| #include <limits> |
| #include <sddl.h> |
| |
| #include "base/base_switches.h" |
| #include "base/command_line.h" |
| #include "base/files/file_path.h" |
| #include "base/guid.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/path_service.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/timer/timer.h" |
| #include "base/win/scoped_bstr.h" |
| #include "base/win/scoped_comptr.h" |
| #include "base/win/scoped_handle.h" |
| #include "base/win/windows_version.h" |
| #include "ipc/ipc_message_macros.h" |
| #include "ipc/ipc_platform_file.h" |
| #include "remoting/base/auto_thread_task_runner.h" |
| // MIDL-generated declarations and definitions. |
| #include "remoting/host/chromoting_lib.h" |
| #include "remoting/host/chromoting_messages.h" |
| #include "remoting/host/daemon_process.h" |
| #include "remoting/host/desktop_session.h" |
| #include "remoting/host/host_main.h" |
| #include "remoting/host/ipc_constants.h" |
| #include "remoting/host/sas_injector.h" |
| #include "remoting/host/screen_resolution.h" |
| #include "remoting/host/win/host_service.h" |
| #include "remoting/host/win/worker_process_launcher.h" |
| #include "remoting/host/win/wts_session_process_delegate.h" |
| #include "remoting/host/win/wts_terminal_monitor.h" |
| #include "remoting/host/win/wts_terminal_observer.h" |
| #include "remoting/host/worker_process_ipc_delegate.h" |
| |
| using base::win::ScopedHandle; |
| |
| namespace remoting { |
| |
| namespace { |
| |
| // The security descriptor of the daemon IPC endpoint. It gives full access |
| // to SYSTEM and denies access by anyone else. |
| const wchar_t kDaemonIpcSecurityDescriptor[] = |
| SDDL_OWNER L":" SDDL_LOCAL_SYSTEM |
| SDDL_GROUP L":" SDDL_LOCAL_SYSTEM |
| SDDL_DACL L":(" |
| SDDL_ACCESS_ALLOWED L";;" SDDL_GENERIC_ALL L";;;" SDDL_LOCAL_SYSTEM |
| L")"; |
| |
| // The command line parameters that should be copied from the service's command |
| // line to the desktop process. |
| const char* kCopiedSwitchNames[] = { switches::kV, switches::kVModule }; |
| |
| // The default screen dimensions for an RDP session. |
| const int kDefaultRdpScreenWidth = 1280; |
| const int kDefaultRdpScreenHeight = 768; |
| |
| // RDC 6.1 (W2K8) supports dimensions of up to 4096x2048. |
| const int kMaxRdpScreenWidth = 4096; |
| const int kMaxRdpScreenHeight = 2048; |
| |
| // The minimum effective screen dimensions supported by Windows are 800x600. |
| const int kMinRdpScreenWidth = 800; |
| const int kMinRdpScreenHeight = 600; |
| |
| // Default dots per inch used by RDP is 96 DPI. |
| const int kDefaultRdpDpi = 96; |
| |
| // The session attach notification should arrive within 30 seconds. |
| const int kSessionAttachTimeoutSeconds = 30; |
| |
| // DesktopSession implementation which attaches to the host's physical console. |
| // Receives IPC messages from the desktop process, running in the console |
| // session, via |WorkerProcessIpcDelegate|, and monitors console session |
| // attach/detach events via |WtsConsoleObserer|. |
| class ConsoleSession : public DesktopSessionWin { |
| public: |
| // Same as DesktopSessionWin(). |
| ConsoleSession( |
| scoped_refptr<AutoThreadTaskRunner> caller_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> io_task_runner, |
| DaemonProcess* daemon_process, |
| int id, |
| WtsTerminalMonitor* monitor); |
| virtual ~ConsoleSession(); |
| |
| protected: |
| // DesktopSession overrides. |
| virtual void SetScreenResolution(const ScreenResolution& resolution) OVERRIDE; |
| |
| // DesktopSessionWin overrides. |
| virtual void InjectSas() OVERRIDE; |
| |
| private: |
| scoped_ptr<SasInjector> sas_injector_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ConsoleSession); |
| }; |
| |
| // DesktopSession implementation which attaches to virtual RDP console. |
| // Receives IPC messages from the desktop process, running in the console |
| // session, via |WorkerProcessIpcDelegate|, and monitors console session |
| // attach/detach events via |WtsConsoleObserer|. |
| class RdpSession : public DesktopSessionWin { |
| public: |
| // Same as DesktopSessionWin(). |
| RdpSession( |
| scoped_refptr<AutoThreadTaskRunner> caller_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> io_task_runner, |
| DaemonProcess* daemon_process, |
| int id, |
| WtsTerminalMonitor* monitor); |
| virtual ~RdpSession(); |
| |
| // Performs the part of initialization that can fail. |
| bool Initialize(const ScreenResolution& resolution); |
| |
| // Mirrors IRdpDesktopSessionEventHandler. |
| void OnRdpConnected(); |
| void OnRdpClosed(); |
| |
| protected: |
| // DesktopSession overrides. |
| virtual void SetScreenResolution(const ScreenResolution& resolution) OVERRIDE; |
| |
| // DesktopSessionWin overrides. |
| virtual void InjectSas() OVERRIDE; |
| |
| private: |
| // An implementation of IRdpDesktopSessionEventHandler interface that forwards |
| // notifications to the owning desktop session. |
| class EventHandler : public IRdpDesktopSessionEventHandler { |
| public: |
| explicit EventHandler(base::WeakPtr<RdpSession> desktop_session); |
| virtual ~EventHandler(); |
| |
| // IUnknown interface. |
| STDMETHOD_(ULONG, AddRef)() OVERRIDE; |
| STDMETHOD_(ULONG, Release)() OVERRIDE; |
| STDMETHOD(QueryInterface)(REFIID riid, void** ppv) OVERRIDE; |
| |
| // IRdpDesktopSessionEventHandler interface. |
| STDMETHOD(OnRdpConnected)() OVERRIDE; |
| STDMETHOD(OnRdpClosed)() OVERRIDE; |
| |
| private: |
| ULONG ref_count_; |
| |
| // Points to the desktop session object receiving OnRdpXxx() notifications. |
| base::WeakPtr<RdpSession> desktop_session_; |
| |
| // This class must be used on a single thread. |
| base::ThreadChecker thread_checker_; |
| |
| DISALLOW_COPY_AND_ASSIGN(EventHandler); |
| }; |
| |
| // Used to create an RDP desktop session. |
| base::win::ScopedComPtr<IRdpDesktopSession> rdp_desktop_session_; |
| |
| // Used to match |rdp_desktop_session_| with the session it is attached to. |
| std::string terminal_id_; |
| |
| base::WeakPtrFactory<RdpSession> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RdpSession); |
| }; |
| |
| ConsoleSession::ConsoleSession( |
| scoped_refptr<AutoThreadTaskRunner> caller_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> io_task_runner, |
| DaemonProcess* daemon_process, |
| int id, |
| WtsTerminalMonitor* monitor) |
| : DesktopSessionWin(caller_task_runner, io_task_runner, daemon_process, id, |
| monitor) { |
| StartMonitoring(WtsTerminalMonitor::kConsole); |
| } |
| |
| ConsoleSession::~ConsoleSession() { |
| } |
| |
| void ConsoleSession::SetScreenResolution(const ScreenResolution& resolution) { |
| // Do nothing. The screen resolution of the console session is controlled by |
| // the DesktopSessionAgent instance running in that session. |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| } |
| |
| void ConsoleSession::InjectSas() { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| if (!sas_injector_) |
| sas_injector_ = SasInjector::Create(); |
| if (!sas_injector_->InjectSas()) |
| LOG(ERROR) << "Failed to inject Secure Attention Sequence."; |
| } |
| |
| RdpSession::RdpSession( |
| scoped_refptr<AutoThreadTaskRunner> caller_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> io_task_runner, |
| DaemonProcess* daemon_process, |
| int id, |
| WtsTerminalMonitor* monitor) |
| : DesktopSessionWin(caller_task_runner, io_task_runner, daemon_process, id, |
| monitor), |
| weak_factory_(this) { |
| } |
| |
| RdpSession::~RdpSession() { |
| } |
| |
| bool RdpSession::Initialize(const ScreenResolution& resolution) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| // Create the RDP wrapper object. |
| HRESULT result = rdp_desktop_session_.CreateInstance( |
| __uuidof(RdpDesktopSession)); |
| if (FAILED(result)) { |
| LOG(ERROR) << "Failed to create RdpSession object, 0x" |
| << std::hex << result << std::dec << "."; |
| return false; |
| } |
| |
| ScreenResolution local_resolution = resolution; |
| |
| // If the screen resolution is not specified, use the default screen |
| // resolution. |
| if (local_resolution.IsEmpty()) { |
| local_resolution = ScreenResolution( |
| webrtc::DesktopSize(kDefaultRdpScreenWidth, kDefaultRdpScreenHeight), |
| webrtc::DesktopVector(kDefaultRdpDpi, kDefaultRdpDpi)); |
| } |
| |
| // Get the screen dimensions assuming the default DPI. |
| webrtc::DesktopSize host_size = local_resolution.ScaleDimensionsToDpi( |
| webrtc::DesktopVector(kDefaultRdpDpi, kDefaultRdpDpi)); |
| |
| // Make sure that the host resolution is within the limits supported by RDP. |
| host_size = webrtc::DesktopSize( |
| std::min(kMaxRdpScreenWidth, |
| std::max(kMinRdpScreenWidth, host_size.width())), |
| std::min(kMaxRdpScreenHeight, |
| std::max(kMinRdpScreenHeight, host_size.height()))); |
| |
| // Create an RDP session. |
| base::win::ScopedComPtr<IRdpDesktopSessionEventHandler> event_handler( |
| new EventHandler(weak_factory_.GetWeakPtr())); |
| terminal_id_ = base::GenerateGUID(); |
| base::win::ScopedBstr terminal_id(base::UTF8ToUTF16(terminal_id_).c_str()); |
| result = rdp_desktop_session_->Connect(host_size.width(), |
| host_size.height(), |
| terminal_id, |
| event_handler); |
| if (FAILED(result)) { |
| LOG(ERROR) << "RdpSession::Create() failed, 0x" |
| << std::hex << result << std::dec << "."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void RdpSession::OnRdpConnected() { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| StopMonitoring(); |
| StartMonitoring(terminal_id_); |
| } |
| |
| void RdpSession::OnRdpClosed() { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| TerminateSession(); |
| } |
| |
| void RdpSession::SetScreenResolution(const ScreenResolution& resolution) { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| // TODO(alexeypa): implement resize-to-client for RDP sessions here. |
| // See http://crbug.com/137696. |
| NOTIMPLEMENTED(); |
| } |
| |
| void RdpSession::InjectSas() { |
| DCHECK(caller_task_runner()->BelongsToCurrentThread()); |
| |
| rdp_desktop_session_->InjectSas(); |
| } |
| |
| RdpSession::EventHandler::EventHandler( |
| base::WeakPtr<RdpSession> desktop_session) |
| : ref_count_(0), |
| desktop_session_(desktop_session) { |
| } |
| |
| RdpSession::EventHandler::~EventHandler() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (desktop_session_) |
| desktop_session_->OnRdpClosed(); |
| } |
| |
| ULONG STDMETHODCALLTYPE RdpSession::EventHandler::AddRef() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| return ++ref_count_; |
| } |
| |
| ULONG STDMETHODCALLTYPE RdpSession::EventHandler::Release() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (--ref_count_ == 0) { |
| delete this; |
| return 0; |
| } |
| |
| return ref_count_; |
| } |
| |
| STDMETHODIMP RdpSession::EventHandler::QueryInterface(REFIID riid, void** ppv) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (riid == IID_IUnknown || |
| riid == IID_IRdpDesktopSessionEventHandler) { |
| *ppv = static_cast<IRdpDesktopSessionEventHandler*>(this); |
| AddRef(); |
| return S_OK; |
| } |
| |
| *ppv = NULL; |
| return E_NOINTERFACE; |
| } |
| |
| STDMETHODIMP RdpSession::EventHandler::OnRdpConnected() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (desktop_session_) |
| desktop_session_->OnRdpConnected(); |
| |
| return S_OK; |
| } |
| |
| STDMETHODIMP RdpSession::EventHandler::OnRdpClosed() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!desktop_session_) |
| return S_OK; |
| |
| base::WeakPtr<RdpSession> desktop_session = desktop_session_; |
| desktop_session_.reset(); |
| desktop_session->OnRdpClosed(); |
| return S_OK; |
| } |
| |
| } // namespace |
| |
| // static |
| scoped_ptr<DesktopSession> DesktopSessionWin::CreateForConsole( |
| scoped_refptr<AutoThreadTaskRunner> caller_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> io_task_runner, |
| DaemonProcess* daemon_process, |
| int id, |
| const ScreenResolution& resolution) { |
| scoped_ptr<ConsoleSession> session(new ConsoleSession( |
| caller_task_runner, io_task_runner, daemon_process, id, |
| HostService::GetInstance())); |
| |
| return session.PassAs<DesktopSession>(); |
| } |
| |
| // static |
| scoped_ptr<DesktopSession> DesktopSessionWin::CreateForVirtualTerminal( |
| scoped_refptr<AutoThreadTaskRunner> caller_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> io_task_runner, |
| DaemonProcess* daemon_process, |
| int id, |
| const ScreenResolution& resolution) { |
| scoped_ptr<RdpSession> session(new RdpSession( |
| caller_task_runner, io_task_runner, daemon_process, id, |
| HostService::GetInstance())); |
| if (!session->Initialize(resolution)) |
| return scoped_ptr<DesktopSession>(); |
| |
| return session.PassAs<DesktopSession>(); |
| } |
| |
| DesktopSessionWin::DesktopSessionWin( |
| scoped_refptr<AutoThreadTaskRunner> caller_task_runner, |
| scoped_refptr<AutoThreadTaskRunner> io_task_runner, |
| DaemonProcess* daemon_process, |
| int id, |
| WtsTerminalMonitor* monitor) |
| : DesktopSession(daemon_process, id), |
| caller_task_runner_(caller_task_runner), |
| io_task_runner_(io_task_runner), |
| monitor_(monitor), |
| monitoring_notifications_(false) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| ReportElapsedTime("created"); |
| } |
| |
| DesktopSessionWin::~DesktopSessionWin() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| StopMonitoring(); |
| } |
| |
| void DesktopSessionWin::OnSessionAttachTimeout() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| LOG(ERROR) << "Session attach notification didn't arrived within " |
| << kSessionAttachTimeoutSeconds << " seconds."; |
| TerminateSession(); |
| } |
| |
| void DesktopSessionWin::StartMonitoring(const std::string& terminal_id) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!monitoring_notifications_); |
| DCHECK(!session_attach_timer_.IsRunning()); |
| |
| ReportElapsedTime("started monitoring"); |
| |
| session_attach_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds), |
| this, &DesktopSessionWin::OnSessionAttachTimeout); |
| |
| monitoring_notifications_ = true; |
| monitor_->AddWtsTerminalObserver(terminal_id, this); |
| } |
| |
| void DesktopSessionWin::StopMonitoring() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| if (monitoring_notifications_) { |
| ReportElapsedTime("stopped monitoring"); |
| |
| monitoring_notifications_ = false; |
| monitor_->RemoveWtsTerminalObserver(this); |
| } |
| |
| session_attach_timer_.Stop(); |
| OnSessionDetached(); |
| } |
| |
| void DesktopSessionWin::TerminateSession() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| StopMonitoring(); |
| |
| // This call will delete |this| so it should be at the very end of the method. |
| daemon_process()->CloseDesktopSession(id()); |
| } |
| |
| void DesktopSessionWin::OnChannelConnected(int32 peer_pid) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| ReportElapsedTime("channel connected"); |
| |
| // Obtain the handle of the desktop process. It will be passed to the network |
| // process to use to duplicate handles of shared memory objects from |
| // the desktop process. |
| desktop_process_.Set(OpenProcess(PROCESS_DUP_HANDLE, false, peer_pid)); |
| if (!desktop_process_.IsValid()) { |
| CrashDesktopProcess(FROM_HERE); |
| return; |
| } |
| |
| VLOG(1) << "IPC: daemon <- desktop (" << peer_pid << ")"; |
| } |
| |
| bool DesktopSessionWin::OnMessageReceived(const IPC::Message& message) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(DesktopSessionWin, message) |
| IPC_MESSAGE_HANDLER(ChromotingDesktopDaemonMsg_DesktopAttached, |
| OnDesktopSessionAgentAttached) |
| IPC_MESSAGE_HANDLER(ChromotingDesktopDaemonMsg_InjectSas, |
| InjectSas) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| |
| if (!handled) { |
| LOG(ERROR) << "Received unexpected IPC type: " << message.type(); |
| CrashDesktopProcess(FROM_HERE); |
| } |
| |
| return handled; |
| } |
| |
| void DesktopSessionWin::OnPermanentError(int exit_code) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| TerminateSession(); |
| } |
| |
| void DesktopSessionWin::OnSessionAttached(uint32 session_id) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!launcher_); |
| DCHECK(monitoring_notifications_); |
| |
| ReportElapsedTime("attached"); |
| |
| // Launch elevated on Win8 to be able to inject Alt+Tab. |
| bool launch_elevated = base::win::GetVersion() >= base::win::VERSION_WIN8; |
| |
| // Get the name of the executable to run. |kDesktopBinaryName| specifies |
| // uiAccess="true" in it's manifest. |
| base::FilePath desktop_binary; |
| bool result; |
| if (launch_elevated) { |
| result = GetInstalledBinaryPath(kDesktopBinaryName, &desktop_binary); |
| } else { |
| result = GetInstalledBinaryPath(kHostBinaryName, &desktop_binary); |
| } |
| |
| if (!result) { |
| TerminateSession(); |
| return; |
| } |
| |
| session_attach_timer_.Stop(); |
| |
| scoped_ptr<CommandLine> target(new CommandLine(desktop_binary)); |
| target->AppendSwitchASCII(kProcessTypeSwitchName, kProcessTypeDesktop); |
| // Copy the command line switches enabling verbose logging. |
| target->CopySwitchesFrom(*CommandLine::ForCurrentProcess(), |
| kCopiedSwitchNames, |
| arraysize(kCopiedSwitchNames)); |
| |
| // Create a delegate capable of launching a process in a different session. |
| scoped_ptr<WtsSessionProcessDelegate> delegate( |
| new WtsSessionProcessDelegate(io_task_runner_, |
| target.Pass(), |
| launch_elevated, |
| base::WideToUTF8( |
| kDaemonIpcSecurityDescriptor))); |
| if (!delegate->Initialize(session_id)) { |
| TerminateSession(); |
| return; |
| } |
| |
| // Create a launcher for the desktop process, using the per-session delegate. |
| launcher_.reset(new WorkerProcessLauncher(delegate.Pass(), this)); |
| } |
| |
| void DesktopSessionWin::OnSessionDetached() { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| launcher_.reset(); |
| |
| if (monitoring_notifications_) { |
| ReportElapsedTime("detached"); |
| |
| session_attach_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds), |
| this, &DesktopSessionWin::OnSessionAttachTimeout); |
| } |
| } |
| |
| void DesktopSessionWin::OnDesktopSessionAgentAttached( |
| IPC::PlatformFileForTransit desktop_pipe) { |
| if (!daemon_process()->OnDesktopSessionAgentAttached(id(), |
| desktop_process_, |
| desktop_pipe)) { |
| CrashDesktopProcess(FROM_HERE); |
| } |
| } |
| |
| void DesktopSessionWin::CrashDesktopProcess( |
| const tracked_objects::Location& location) { |
| DCHECK(caller_task_runner_->BelongsToCurrentThread()); |
| |
| launcher_->Crash(location); |
| } |
| |
| void DesktopSessionWin::ReportElapsedTime(const std::string& event) { |
| base::Time now = base::Time::Now(); |
| |
| std::string passed; |
| if (!last_timestamp_.is_null()) { |
| passed = base::StringPrintf(", %.2fs passed", |
| (now - last_timestamp_).InSecondsF()); |
| } |
| |
| base::Time::Exploded exploded; |
| now.LocalExplode(&exploded); |
| VLOG(1) << base::StringPrintf("session(%d): %s at %02d:%02d:%02d.%03d%s", |
| id(), |
| event.c_str(), |
| exploded.hour, |
| exploded.minute, |
| exploded.second, |
| exploded.millisecond, |
| passed.c_str()); |
| |
| last_timestamp_ = now; |
| } |
| |
| } // namespace remoting |