1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/host/desktop_session_win.h"
10 #include "base/base_switches.h"
11 #include "base/command_line.h"
12 #include "base/files/file_path.h"
13 #include "base/guid.h"
14 #include "base/memory/ref_counted.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/memory/weak_ptr.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/threading/thread_checker.h"
20 #include "base/timer/timer.h"
21 #include "base/win/scoped_bstr.h"
22 #include "base/win/scoped_comptr.h"
23 #include "base/win/scoped_handle.h"
24 #include "base/win/windows_version.h"
25 #include "ipc/ipc_message_macros.h"
26 #include "ipc/ipc_platform_file.h"
27 #include "remoting/base/auto_thread_task_runner.h"
28 // MIDL-generated declarations and definitions.
29 #include "remoting/host/chromoting_lib.h"
30 #include "remoting/host/chromoting_messages.h"
31 #include "remoting/host/daemon_process.h"
32 #include "remoting/host/desktop_session.h"
33 #include "remoting/host/host_main.h"
34 #include "remoting/host/ipc_constants.h"
35 #include "remoting/host/sas_injector.h"
36 #include "remoting/host/screen_resolution.h"
37 #include "remoting/host/win/host_service.h"
38 #include "remoting/host/win/worker_process_launcher.h"
39 #include "remoting/host/win/wts_session_process_delegate.h"
40 #include "remoting/host/win/wts_terminal_monitor.h"
41 #include "remoting/host/win/wts_terminal_observer.h"
42 #include "remoting/host/worker_process_ipc_delegate.h"
44 using base::win::ScopedHandle;
50 // The security descriptor of the daemon IPC endpoint. It gives full access
51 // to SYSTEM and denies access by anyone else.
52 const wchar_t kDaemonIpcSecurityDescriptor[] =
53 SDDL_OWNER L":" SDDL_LOCAL_SYSTEM
54 SDDL_GROUP L":" SDDL_LOCAL_SYSTEM
56 SDDL_ACCESS_ALLOWED L";;" SDDL_GENERIC_ALL L";;;" SDDL_LOCAL_SYSTEM
59 // The command line parameters that should be copied from the service's command
60 // line to the desktop process.
61 const char* kCopiedSwitchNames[] = { switches::kV, switches::kVModule };
63 // The default screen dimensions for an RDP session.
64 const int kDefaultRdpScreenWidth = 1280;
65 const int kDefaultRdpScreenHeight = 768;
67 // RDC 6.1 (W2K8) supports dimensions of up to 4096x2048.
68 const int kMaxRdpScreenWidth = 4096;
69 const int kMaxRdpScreenHeight = 2048;
71 // The minimum effective screen dimensions supported by Windows are 800x600.
72 const int kMinRdpScreenWidth = 800;
73 const int kMinRdpScreenHeight = 600;
75 // Default dots per inch used by RDP is 96 DPI.
76 const int kDefaultRdpDpi = 96;
78 // The session attach notification should arrive within 30 seconds.
79 const int kSessionAttachTimeoutSeconds = 30;
81 // DesktopSession implementation which attaches to the host's physical console.
82 // Receives IPC messages from the desktop process, running in the console
83 // session, via |WorkerProcessIpcDelegate|, and monitors console session
84 // attach/detach events via |WtsConsoleObserer|.
85 class ConsoleSession : public DesktopSessionWin {
87 // Same as DesktopSessionWin().
89 scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
90 scoped_refptr<AutoThreadTaskRunner> io_task_runner,
91 DaemonProcess* daemon_process,
93 WtsTerminalMonitor* monitor);
94 virtual ~ConsoleSession();
97 // DesktopSession overrides.
98 virtual void SetScreenResolution(const ScreenResolution& resolution) override;
100 // DesktopSessionWin overrides.
101 virtual void InjectSas() override;
104 scoped_ptr<SasInjector> sas_injector_;
106 DISALLOW_COPY_AND_ASSIGN(ConsoleSession);
109 // DesktopSession implementation which attaches to virtual RDP console.
110 // Receives IPC messages from the desktop process, running in the console
111 // session, via |WorkerProcessIpcDelegate|, and monitors console session
112 // attach/detach events via |WtsConsoleObserer|.
113 class RdpSession : public DesktopSessionWin {
115 // Same as DesktopSessionWin().
117 scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
118 scoped_refptr<AutoThreadTaskRunner> io_task_runner,
119 DaemonProcess* daemon_process,
121 WtsTerminalMonitor* monitor);
122 virtual ~RdpSession();
124 // Performs the part of initialization that can fail.
125 bool Initialize(const ScreenResolution& resolution);
127 // Mirrors IRdpDesktopSessionEventHandler.
128 void OnRdpConnected();
132 // DesktopSession overrides.
133 virtual void SetScreenResolution(const ScreenResolution& resolution) override;
135 // DesktopSessionWin overrides.
136 virtual void InjectSas() override;
139 // An implementation of IRdpDesktopSessionEventHandler interface that forwards
140 // notifications to the owning desktop session.
141 class EventHandler : public IRdpDesktopSessionEventHandler {
143 explicit EventHandler(base::WeakPtr<RdpSession> desktop_session);
144 virtual ~EventHandler();
146 // IUnknown interface.
147 STDMETHOD_(ULONG, AddRef)() override;
148 STDMETHOD_(ULONG, Release)() override;
149 STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override;
151 // IRdpDesktopSessionEventHandler interface.
152 STDMETHOD(OnRdpConnected)() override;
153 STDMETHOD(OnRdpClosed)() override;
158 // Points to the desktop session object receiving OnRdpXxx() notifications.
159 base::WeakPtr<RdpSession> desktop_session_;
161 // This class must be used on a single thread.
162 base::ThreadChecker thread_checker_;
164 DISALLOW_COPY_AND_ASSIGN(EventHandler);
167 // Used to create an RDP desktop session.
168 base::win::ScopedComPtr<IRdpDesktopSession> rdp_desktop_session_;
170 // Used to match |rdp_desktop_session_| with the session it is attached to.
171 std::string terminal_id_;
173 base::WeakPtrFactory<RdpSession> weak_factory_;
175 DISALLOW_COPY_AND_ASSIGN(RdpSession);
178 ConsoleSession::ConsoleSession(
179 scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
180 scoped_refptr<AutoThreadTaskRunner> io_task_runner,
181 DaemonProcess* daemon_process,
183 WtsTerminalMonitor* monitor)
184 : DesktopSessionWin(caller_task_runner, io_task_runner, daemon_process, id,
186 StartMonitoring(WtsTerminalMonitor::kConsole);
189 ConsoleSession::~ConsoleSession() {
192 void ConsoleSession::SetScreenResolution(const ScreenResolution& resolution) {
193 // Do nothing. The screen resolution of the console session is controlled by
194 // the DesktopSessionAgent instance running in that session.
195 DCHECK(caller_task_runner()->BelongsToCurrentThread());
198 void ConsoleSession::InjectSas() {
199 DCHECK(caller_task_runner()->BelongsToCurrentThread());
202 sas_injector_ = SasInjector::Create();
203 if (!sas_injector_->InjectSas())
204 LOG(ERROR) << "Failed to inject Secure Attention Sequence.";
207 RdpSession::RdpSession(
208 scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
209 scoped_refptr<AutoThreadTaskRunner> io_task_runner,
210 DaemonProcess* daemon_process,
212 WtsTerminalMonitor* monitor)
213 : DesktopSessionWin(caller_task_runner, io_task_runner, daemon_process, id,
215 weak_factory_(this) {
218 RdpSession::~RdpSession() {
221 bool RdpSession::Initialize(const ScreenResolution& resolution) {
222 DCHECK(caller_task_runner()->BelongsToCurrentThread());
224 // Create the RDP wrapper object.
225 HRESULT result = rdp_desktop_session_.CreateInstance(
226 __uuidof(RdpDesktopSession));
227 if (FAILED(result)) {
228 LOG(ERROR) << "Failed to create RdpSession object, 0x"
229 << std::hex << result << std::dec << ".";
233 ScreenResolution local_resolution = resolution;
235 // If the screen resolution is not specified, use the default screen
237 if (local_resolution.IsEmpty()) {
238 local_resolution = ScreenResolution(
239 webrtc::DesktopSize(kDefaultRdpScreenWidth, kDefaultRdpScreenHeight),
240 webrtc::DesktopVector(kDefaultRdpDpi, kDefaultRdpDpi));
243 // Get the screen dimensions assuming the default DPI.
244 webrtc::DesktopSize host_size = local_resolution.ScaleDimensionsToDpi(
245 webrtc::DesktopVector(kDefaultRdpDpi, kDefaultRdpDpi));
247 // Make sure that the host resolution is within the limits supported by RDP.
248 host_size = webrtc::DesktopSize(
249 std::min(kMaxRdpScreenWidth,
250 std::max(kMinRdpScreenWidth, host_size.width())),
251 std::min(kMaxRdpScreenHeight,
252 std::max(kMinRdpScreenHeight, host_size.height())));
254 // Create an RDP session.
255 base::win::ScopedComPtr<IRdpDesktopSessionEventHandler> event_handler(
256 new EventHandler(weak_factory_.GetWeakPtr()));
257 terminal_id_ = base::GenerateGUID();
258 base::win::ScopedBstr terminal_id(base::UTF8ToUTF16(terminal_id_).c_str());
259 result = rdp_desktop_session_->Connect(host_size.width(),
263 if (FAILED(result)) {
264 LOG(ERROR) << "RdpSession::Create() failed, 0x"
265 << std::hex << result << std::dec << ".";
272 void RdpSession::OnRdpConnected() {
273 DCHECK(caller_task_runner()->BelongsToCurrentThread());
276 StartMonitoring(terminal_id_);
279 void RdpSession::OnRdpClosed() {
280 DCHECK(caller_task_runner()->BelongsToCurrentThread());
285 void RdpSession::SetScreenResolution(const ScreenResolution& resolution) {
286 DCHECK(caller_task_runner()->BelongsToCurrentThread());
288 // TODO(alexeypa): implement resize-to-client for RDP sessions here.
289 // See http://crbug.com/137696.
293 void RdpSession::InjectSas() {
294 DCHECK(caller_task_runner()->BelongsToCurrentThread());
296 rdp_desktop_session_->InjectSas();
299 RdpSession::EventHandler::EventHandler(
300 base::WeakPtr<RdpSession> desktop_session)
302 desktop_session_(desktop_session) {
305 RdpSession::EventHandler::~EventHandler() {
306 DCHECK(thread_checker_.CalledOnValidThread());
308 if (desktop_session_)
309 desktop_session_->OnRdpClosed();
312 ULONG STDMETHODCALLTYPE RdpSession::EventHandler::AddRef() {
313 DCHECK(thread_checker_.CalledOnValidThread());
318 ULONG STDMETHODCALLTYPE RdpSession::EventHandler::Release() {
319 DCHECK(thread_checker_.CalledOnValidThread());
321 if (--ref_count_ == 0) {
329 STDMETHODIMP RdpSession::EventHandler::QueryInterface(REFIID riid, void** ppv) {
330 DCHECK(thread_checker_.CalledOnValidThread());
332 if (riid == IID_IUnknown ||
333 riid == IID_IRdpDesktopSessionEventHandler) {
334 *ppv = static_cast<IRdpDesktopSessionEventHandler*>(this);
340 return E_NOINTERFACE;
343 STDMETHODIMP RdpSession::EventHandler::OnRdpConnected() {
344 DCHECK(thread_checker_.CalledOnValidThread());
346 if (desktop_session_)
347 desktop_session_->OnRdpConnected();
352 STDMETHODIMP RdpSession::EventHandler::OnRdpClosed() {
353 DCHECK(thread_checker_.CalledOnValidThread());
355 if (!desktop_session_)
358 base::WeakPtr<RdpSession> desktop_session = desktop_session_;
359 desktop_session_.reset();
360 desktop_session->OnRdpClosed();
367 scoped_ptr<DesktopSession> DesktopSessionWin::CreateForConsole(
368 scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
369 scoped_refptr<AutoThreadTaskRunner> io_task_runner,
370 DaemonProcess* daemon_process,
372 const ScreenResolution& resolution) {
373 scoped_ptr<ConsoleSession> session(new ConsoleSession(
374 caller_task_runner, io_task_runner, daemon_process, id,
375 HostService::GetInstance()));
377 return session.Pass();
381 scoped_ptr<DesktopSession> DesktopSessionWin::CreateForVirtualTerminal(
382 scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
383 scoped_refptr<AutoThreadTaskRunner> io_task_runner,
384 DaemonProcess* daemon_process,
386 const ScreenResolution& resolution) {
387 scoped_ptr<RdpSession> session(new RdpSession(
388 caller_task_runner, io_task_runner, daemon_process, id,
389 HostService::GetInstance()));
390 if (!session->Initialize(resolution))
393 return session.Pass();
396 DesktopSessionWin::DesktopSessionWin(
397 scoped_refptr<AutoThreadTaskRunner> caller_task_runner,
398 scoped_refptr<AutoThreadTaskRunner> io_task_runner,
399 DaemonProcess* daemon_process,
401 WtsTerminalMonitor* monitor)
402 : DesktopSession(daemon_process, id),
403 caller_task_runner_(caller_task_runner),
404 io_task_runner_(io_task_runner),
406 monitoring_notifications_(false) {
407 DCHECK(caller_task_runner_->BelongsToCurrentThread());
409 ReportElapsedTime("created");
412 DesktopSessionWin::~DesktopSessionWin() {
413 DCHECK(caller_task_runner_->BelongsToCurrentThread());
418 void DesktopSessionWin::OnSessionAttachTimeout() {
419 DCHECK(caller_task_runner_->BelongsToCurrentThread());
421 LOG(ERROR) << "Session attach notification didn't arrived within "
422 << kSessionAttachTimeoutSeconds << " seconds.";
426 void DesktopSessionWin::StartMonitoring(const std::string& terminal_id) {
427 DCHECK(caller_task_runner_->BelongsToCurrentThread());
428 DCHECK(!monitoring_notifications_);
429 DCHECK(!session_attach_timer_.IsRunning());
431 ReportElapsedTime("started monitoring");
433 session_attach_timer_.Start(
434 FROM_HERE, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds),
435 this, &DesktopSessionWin::OnSessionAttachTimeout);
437 monitoring_notifications_ = true;
438 monitor_->AddWtsTerminalObserver(terminal_id, this);
441 void DesktopSessionWin::StopMonitoring() {
442 DCHECK(caller_task_runner_->BelongsToCurrentThread());
444 if (monitoring_notifications_) {
445 ReportElapsedTime("stopped monitoring");
447 monitoring_notifications_ = false;
448 monitor_->RemoveWtsTerminalObserver(this);
451 session_attach_timer_.Stop();
455 void DesktopSessionWin::TerminateSession() {
456 DCHECK(caller_task_runner_->BelongsToCurrentThread());
460 // This call will delete |this| so it should be at the very end of the method.
461 daemon_process()->CloseDesktopSession(id());
464 void DesktopSessionWin::OnChannelConnected(int32 peer_pid) {
465 DCHECK(caller_task_runner_->BelongsToCurrentThread());
467 ReportElapsedTime("channel connected");
469 // Obtain the handle of the desktop process. It will be passed to the network
470 // process to use to duplicate handles of shared memory objects from
471 // the desktop process.
472 desktop_process_.Set(OpenProcess(PROCESS_DUP_HANDLE, false, peer_pid));
473 if (!desktop_process_.IsValid()) {
474 CrashDesktopProcess(FROM_HERE);
478 VLOG(1) << "IPC: daemon <- desktop (" << peer_pid << ")";
481 bool DesktopSessionWin::OnMessageReceived(const IPC::Message& message) {
482 DCHECK(caller_task_runner_->BelongsToCurrentThread());
485 IPC_BEGIN_MESSAGE_MAP(DesktopSessionWin, message)
486 IPC_MESSAGE_HANDLER(ChromotingDesktopDaemonMsg_DesktopAttached,
487 OnDesktopSessionAgentAttached)
488 IPC_MESSAGE_HANDLER(ChromotingDesktopDaemonMsg_InjectSas,
490 IPC_MESSAGE_UNHANDLED(handled = false)
491 IPC_END_MESSAGE_MAP()
494 LOG(ERROR) << "Received unexpected IPC type: " << message.type();
495 CrashDesktopProcess(FROM_HERE);
501 void DesktopSessionWin::OnPermanentError(int exit_code) {
502 DCHECK(caller_task_runner_->BelongsToCurrentThread());
507 void DesktopSessionWin::OnSessionAttached(uint32 session_id) {
508 DCHECK(caller_task_runner_->BelongsToCurrentThread());
510 DCHECK(monitoring_notifications_);
512 ReportElapsedTime("attached");
514 // Launch elevated on Win8 to be able to inject Alt+Tab.
515 bool launch_elevated = base::win::GetVersion() >= base::win::VERSION_WIN8;
517 // Get the name of the executable to run. |kDesktopBinaryName| specifies
518 // uiAccess="true" in it's manifest.
519 base::FilePath desktop_binary;
521 if (launch_elevated) {
522 result = GetInstalledBinaryPath(kDesktopBinaryName, &desktop_binary);
524 result = GetInstalledBinaryPath(kHostBinaryName, &desktop_binary);
532 session_attach_timer_.Stop();
534 scoped_ptr<CommandLine> target(new CommandLine(desktop_binary));
535 target->AppendSwitchASCII(kProcessTypeSwitchName, kProcessTypeDesktop);
536 // Copy the command line switches enabling verbose logging.
537 target->CopySwitchesFrom(*CommandLine::ForCurrentProcess(),
539 arraysize(kCopiedSwitchNames));
541 // Create a delegate capable of launching a process in a different session.
542 scoped_ptr<WtsSessionProcessDelegate> delegate(
543 new WtsSessionProcessDelegate(io_task_runner_,
547 kDaemonIpcSecurityDescriptor)));
548 if (!delegate->Initialize(session_id)) {
553 // Create a launcher for the desktop process, using the per-session delegate.
554 launcher_.reset(new WorkerProcessLauncher(delegate.Pass(), this));
557 void DesktopSessionWin::OnSessionDetached() {
558 DCHECK(caller_task_runner_->BelongsToCurrentThread());
562 if (monitoring_notifications_) {
563 ReportElapsedTime("detached");
565 session_attach_timer_.Start(
566 FROM_HERE, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds),
567 this, &DesktopSessionWin::OnSessionAttachTimeout);
571 void DesktopSessionWin::OnDesktopSessionAgentAttached(
572 IPC::PlatformFileForTransit desktop_pipe) {
573 if (!daemon_process()->OnDesktopSessionAgentAttached(id(),
574 desktop_process_.Get(),
576 CrashDesktopProcess(FROM_HERE);
580 void DesktopSessionWin::CrashDesktopProcess(
581 const tracked_objects::Location& location) {
582 DCHECK(caller_task_runner_->BelongsToCurrentThread());
584 launcher_->Crash(location);
587 void DesktopSessionWin::ReportElapsedTime(const std::string& event) {
588 base::Time now = base::Time::Now();
591 if (!last_timestamp_.is_null()) {
592 passed = base::StringPrintf(", %.2fs passed",
593 (now - last_timestamp_).InSecondsF());
596 base::Time::Exploded exploded;
597 now.LocalExplode(&exploded);
598 VLOG(1) << base::StringPrintf("session(%d): %s at %02d:%02d:%02d.%03d%s",
604 exploded.millisecond,
607 last_timestamp_ = now;
610 } // namespace remoting