desktop_session_win.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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. 4 5#include "remoting/host/desktop_session_win.h" 6 7#include <limits> 8#include <sddl.h> 9 10#include "base/base_switches.h" 11#include "base/command_line.h" 12#include "base/files/file_path.h" 13#include "base/memory/ref_counted.h" 14#include "base/memory/scoped_ptr.h" 15#include "base/memory/weak_ptr.h" 16#include "base/path_service.h" 17#include "base/threading/thread_checker.h" 18#include "base/timer.h" 19#include "base/utf_string_conversions.h" 20#include "base/win/scoped_comptr.h" 21#include "base/win/scoped_handle.h" 22#include "ipc/ipc_message_macros.h" 23#include "ipc/ipc_platform_file.h" 24#include "net/base/ip_endpoint.h" 25#include "remoting/base/auto_thread_task_runner.h" 26// MIDL-generated declarations and definitions. 27#include "remoting/host/chromoting_lib.h" 28#include "remoting/host/chromoting_messages.h" 29#include "remoting/host/daemon_process.h" 30#include "remoting/host/desktop_session.h" 31#include "remoting/host/host_main.h" 32#include "remoting/host/ipc_constants.h" 33#include "remoting/host/sas_injector.h" 34#include "remoting/host/screen_resolution.h" 35#include "remoting/host/win/host_service.h" 36#include "remoting/host/win/worker_process_launcher.h" 37#include "remoting/host/win/wts_session_process_delegate.h" 38#include "remoting/host/win/wts_terminal_monitor.h" 39#include "remoting/host/win/wts_terminal_observer.h" 40#include "remoting/host/worker_process_ipc_delegate.h" 41 42using base::win::ScopedHandle; 43 44namespace remoting { 45 46namespace { 47 48// The security descriptor of the daemon IPC endpoint. It gives full access 49// to SYSTEM and denies access by anyone else. 50const wchar_t kDaemonIpcSecurityDescriptor[] = 51 SDDL_OWNER L":" SDDL_LOCAL_SYSTEM 52 SDDL_GROUP L":" SDDL_LOCAL_SYSTEM 53 SDDL_DACL L":(" 54 SDDL_ACCESS_ALLOWED L";;" SDDL_GENERIC_ALL L";;;" SDDL_LOCAL_SYSTEM 55 L")"; 56 57// The command line parameters that should be copied from the service's command 58// line to the host process. 59const char* kCopiedSwitchNames[] = { switches::kV, switches::kVModule }; 60 61// The default screen dimensions for an RDP session. 62const int kDefaultRdpScreenWidth = 1024; 63const int kDefaultRdpScreenHeight = 768; 64 65// RDC 6.1 (W2K8) supports dimensions of up to 4096x2048. 66const int kMaxRdpScreenWidth = 4096; 67const int kMaxRdpScreenHeight = 2048; 68 69// The minimum effective screen dimensions supported by Windows are 800x600. 70const int kMinRdpScreenWidth = 800; 71const int kMinRdpScreenHeight = 600; 72 73// Default dots per inch used by RDP is 96 DPI. 74const int kDefaultRdpDpi = 96; 75 76// The session attach notification should arrive within 30 seconds. 77const int kSessionAttachTimeoutSeconds = 30; 78 79// DesktopSession implementation which attaches to the host's physical console. 80// Receives IPC messages from the desktop process, running in the console 81// session, via |WorkerProcessIpcDelegate|, and monitors console session 82// attach/detach events via |WtsConsoleObserer|. 83class ConsoleSession : public DesktopSessionWin { 84 public: 85 // Same as DesktopSessionWin(). 86 ConsoleSession( 87 scoped_refptr<AutoThreadTaskRunner> caller_task_runner, 88 scoped_refptr<AutoThreadTaskRunner> io_task_runner, 89 DaemonProcess* daemon_process, 90 int id, 91 WtsTerminalMonitor* monitor); 92 virtual ~ConsoleSession(); 93 94 protected: 95 // DesktopSession overrides. 96 virtual void SetScreenResolution(const ScreenResolution& resolution) OVERRIDE; 97 98 // DesktopSessionWin overrides. 99 virtual void InjectSas() OVERRIDE; 100 101 private: 102 scoped_ptr<SasInjector> sas_injector_; 103 104 DISALLOW_COPY_AND_ASSIGN(ConsoleSession); 105}; 106 107// DesktopSession implementation which attaches to virtual RDP console. 108// Receives IPC messages from the desktop process, running in the console 109// session, via |WorkerProcessIpcDelegate|, and monitors console session 110// attach/detach events via |WtsConsoleObserer|. 111class RdpSession : public DesktopSessionWin { 112 public: 113 // Same as DesktopSessionWin(). 114 RdpSession( 115 scoped_refptr<AutoThreadTaskRunner> caller_task_runner, 116 scoped_refptr<AutoThreadTaskRunner> io_task_runner, 117 DaemonProcess* daemon_process, 118 int id, 119 WtsTerminalMonitor* monitor); 120 virtual ~RdpSession(); 121 122 // Performs the part of initialization that can fail. 123 bool Initialize(const ScreenResolution& resolution); 124 125 // Mirrors IRdpDesktopSessionEventHandler. 126 void OnRdpConnected(const net::IPEndPoint& client_endpoint); 127 void OnRdpClosed(); 128 129 protected: 130 // DesktopSession overrides. 131 virtual void SetScreenResolution(const ScreenResolution& resolution) OVERRIDE; 132 133 // DesktopSessionWin overrides. 134 virtual void InjectSas() OVERRIDE; 135 136 private: 137 // An implementation of IRdpDesktopSessionEventHandler interface that forwards 138 // notifications to the owning desktop session. 139 class EventHandler : public IRdpDesktopSessionEventHandler { 140 public: 141 explicit EventHandler(base::WeakPtr<RdpSession> desktop_session); 142 virtual ~EventHandler(); 143 144 // IUnknown interface. 145 STDMETHOD_(ULONG, AddRef)() OVERRIDE; 146 STDMETHOD_(ULONG, Release)() OVERRIDE; 147 STDMETHOD(QueryInterface)(REFIID riid, void** ppv) OVERRIDE; 148 149 // IRdpDesktopSessionEventHandler interface. 150 STDMETHOD(OnRdpConnected)(byte* client_endpoint, long length) OVERRIDE; 151 STDMETHOD(OnRdpClosed)() OVERRIDE; 152 153 private: 154 ULONG ref_count_; 155 156 // Points to the desktop session object receiving OnRdpXxx() notifications. 157 base::WeakPtr<RdpSession> desktop_session_; 158 159 // This class must be used on a single thread. 160 base::ThreadChecker thread_checker_; 161 162 DISALLOW_COPY_AND_ASSIGN(EventHandler); 163 }; 164 165 // Used to create an RDP desktop session. 166 base::win::ScopedComPtr<IRdpDesktopSession> rdp_desktop_session_; 167 168 base::WeakPtrFactory<RdpSession> weak_factory_; 169 170 DISALLOW_COPY_AND_ASSIGN(RdpSession); 171}; 172 173ConsoleSession::ConsoleSession( 174 scoped_refptr<AutoThreadTaskRunner> caller_task_runner, 175 scoped_refptr<AutoThreadTaskRunner> io_task_runner, 176 DaemonProcess* daemon_process, 177 int id, 178 WtsTerminalMonitor* monitor) 179 : DesktopSessionWin(caller_task_runner, io_task_runner, daemon_process, id, 180 monitor) { 181 StartMonitoring(net::IPEndPoint()); 182} 183 184ConsoleSession::~ConsoleSession() { 185} 186 187void ConsoleSession::SetScreenResolution(const ScreenResolution& resolution) { 188 // Do nothing. The screen resolution of the console session is controlled by 189 // the DesktopSessionAgent instance running in that session. 190 DCHECK(caller_task_runner()->BelongsToCurrentThread()); 191} 192 193void ConsoleSession::InjectSas() { 194 DCHECK(caller_task_runner()->BelongsToCurrentThread()); 195 196 if (!sas_injector_) 197 sas_injector_ = SasInjector::Create(); 198 if (!sas_injector_->InjectSas()) 199 LOG(ERROR) << "Failed to inject Secure Attention Sequence."; 200} 201 202RdpSession::RdpSession( 203 scoped_refptr<AutoThreadTaskRunner> caller_task_runner, 204 scoped_refptr<AutoThreadTaskRunner> io_task_runner, 205 DaemonProcess* daemon_process, 206 int id, 207 WtsTerminalMonitor* monitor) 208 : DesktopSessionWin(caller_task_runner, io_task_runner, daemon_process, id, 209 monitor), 210 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { 211} 212 213RdpSession::~RdpSession() { 214} 215 216bool RdpSession::Initialize(const ScreenResolution& resolution) { 217 DCHECK(caller_task_runner()->BelongsToCurrentThread()); 218 219 // Create the RDP wrapper object. 220 HRESULT result = rdp_desktop_session_.CreateInstance( 221 __uuidof(RdpDesktopSession)); 222 if (FAILED(result)) { 223 LOG(ERROR) << "Failed to create RdpSession object, 0x" 224 << std::hex << result << std::dec << "."; 225 return false; 226 } 227 228 // DaemonProcess::CreateDesktopSession() verifies that the resolution is 229 // valid. 230 DCHECK(resolution.IsValid()); 231 232 ScreenResolution local_resolution = resolution; 233 234 // If the screen resolution is not specified, use the default screen 235 // resolution. 236 if (local_resolution.IsEmpty()) { 237 local_resolution.dimensions_.set(kDefaultRdpScreenWidth, 238 kDefaultRdpScreenHeight); 239 local_resolution.dpi_.set(kDefaultRdpDpi, kDefaultRdpDpi); 240 } 241 242 // Get the screen dimensions assuming the default DPI. 243 SkISize host_size = local_resolution.ScaleDimensionsToDpi( 244 SkIPoint::Make(kDefaultRdpDpi, kDefaultRdpDpi)); 245 246 // Make sure that the host resolution is within the limits supported by RDP. 247 host_size = SkISize::Make( 248 std::min(kMaxRdpScreenWidth, 249 std::max(kMinRdpScreenWidth, host_size.width())), 250 std::min(kMaxRdpScreenHeight, 251 std::max(kMinRdpScreenHeight, host_size.height()))); 252 253 // Create an RDP session. 254 base::win::ScopedComPtr<IRdpDesktopSessionEventHandler> event_handler( 255 new EventHandler(weak_factory_.GetWeakPtr())); 256 result = rdp_desktop_session_->Connect(host_size.width(), 257 host_size.height(), 258 event_handler); 259 if (FAILED(result)) { 260 LOG(ERROR) << "RdpSession::Create() failed, 0x" 261 << std::hex << result << std::dec << "."; 262 return false; 263 } 264 265 return true; 266} 267 268void RdpSession::OnRdpConnected(const net::IPEndPoint& client_endpoint) { 269 DCHECK(caller_task_runner()->BelongsToCurrentThread()); 270 271 StopMonitoring(); 272 StartMonitoring(client_endpoint); 273} 274 275void RdpSession::OnRdpClosed() { 276 DCHECK(caller_task_runner()->BelongsToCurrentThread()); 277 278 OnPermanentError(); 279} 280 281void RdpSession::SetScreenResolution(const ScreenResolution& resolution) { 282 DCHECK(caller_task_runner()->BelongsToCurrentThread()); 283 284 // TODO(alexeypa): implement resize-to-client for RDP sessions here. 285 // See http://crbug.com/137696. 286 NOTIMPLEMENTED(); 287} 288 289void RdpSession::InjectSas() { 290 DCHECK(caller_task_runner()->BelongsToCurrentThread()); 291 292 // TODO(alexeypa): implement SAS injection for RDP sessions here. 293 // See http://crbug.com/137696. 294 NOTIMPLEMENTED(); 295} 296 297RdpSession::EventHandler::EventHandler( 298 base::WeakPtr<RdpSession> desktop_session) 299 : ref_count_(0), 300 desktop_session_(desktop_session) { 301} 302 303RdpSession::EventHandler::~EventHandler() { 304 DCHECK(thread_checker_.CalledOnValidThread()); 305 306 if (desktop_session_) 307 desktop_session_->OnRdpClosed(); 308} 309 310ULONG STDMETHODCALLTYPE RdpSession::EventHandler::AddRef() { 311 DCHECK(thread_checker_.CalledOnValidThread()); 312 313 return ++ref_count_; 314} 315 316ULONG STDMETHODCALLTYPE RdpSession::EventHandler::Release() { 317 DCHECK(thread_checker_.CalledOnValidThread()); 318 319 if (--ref_count_ == 0) { 320 delete this; 321 return 0; 322 } 323 324 return ref_count_; 325} 326 327STDMETHODIMP RdpSession::EventHandler::QueryInterface(REFIID riid, void** ppv) { 328 DCHECK(thread_checker_.CalledOnValidThread()); 329 330 if (riid == IID_IUnknown || 331 riid == IID_IRdpDesktopSessionEventHandler) { 332 *ppv = static_cast<IRdpDesktopSessionEventHandler*>(this); 333 AddRef(); 334 return S_OK; 335 } 336 337 *ppv = NULL; 338 return E_NOINTERFACE; 339} 340 341STDMETHODIMP RdpSession::EventHandler::OnRdpConnected( 342 byte* client_endpoint, 343 long length) { 344 DCHECK(thread_checker_.CalledOnValidThread()); 345 346 if (!desktop_session_) 347 return S_OK; 348 349 net::IPEndPoint endpoint; 350 if (!endpoint.FromSockAddr(reinterpret_cast<sockaddr*>(client_endpoint), 351 length)) { 352 LOG(ERROR) << "Failed to parse the endpoint passed to OnRdpConnected()."; 353 OnRdpClosed(); 354 return S_OK; 355 } 356 357 desktop_session_->OnRdpConnected(endpoint); 358 return S_OK; 359} 360 361STDMETHODIMP RdpSession::EventHandler::OnRdpClosed() { 362 DCHECK(thread_checker_.CalledOnValidThread()); 363 364 if (!desktop_session_) 365 return S_OK; 366 367 base::WeakPtr<RdpSession> desktop_session = desktop_session_; 368 desktop_session_.reset(); 369 desktop_session->OnRdpClosed(); 370 return S_OK; 371} 372 373} // namespace 374 375// static 376scoped_ptr<DesktopSession> DesktopSessionWin::CreateForConsole( 377 scoped_refptr<AutoThreadTaskRunner> caller_task_runner, 378 scoped_refptr<AutoThreadTaskRunner> io_task_runner, 379 DaemonProcess* daemon_process, 380 int id, 381 const ScreenResolution& resolution) { 382 scoped_ptr<ConsoleSession> session(new ConsoleSession( 383 caller_task_runner, io_task_runner, daemon_process, id, 384 HostService::GetInstance())); 385 386 return session.PassAs<DesktopSession>(); 387} 388 389// static 390scoped_ptr<DesktopSession> DesktopSessionWin::CreateForVirtualTerminal( 391 scoped_refptr<AutoThreadTaskRunner> caller_task_runner, 392 scoped_refptr<AutoThreadTaskRunner> io_task_runner, 393 DaemonProcess* daemon_process, 394 int id, 395 const ScreenResolution& resolution) { 396 scoped_ptr<RdpSession> session(new RdpSession( 397 caller_task_runner, io_task_runner, daemon_process, id, 398 HostService::GetInstance())); 399 if (!session->Initialize(resolution)) 400 return scoped_ptr<DesktopSession>(); 401 402 return session.PassAs<DesktopSession>(); 403} 404 405DesktopSessionWin::DesktopSessionWin( 406 scoped_refptr<AutoThreadTaskRunner> caller_task_runner, 407 scoped_refptr<AutoThreadTaskRunner> io_task_runner, 408 DaemonProcess* daemon_process, 409 int id, 410 WtsTerminalMonitor* monitor) 411 : DesktopSession(daemon_process, id), 412 caller_task_runner_(caller_task_runner), 413 io_task_runner_(io_task_runner), 414 monitor_(monitor), 415 monitoring_notifications_(false) { 416 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 417} 418 419DesktopSessionWin::~DesktopSessionWin() { 420 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 421 422 StopMonitoring(); 423} 424 425void DesktopSessionWin::OnSessionAttachTimeout() { 426 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 427 428 LOG(ERROR) << "Session attach notification didn't arrived within " 429 << kSessionAttachTimeoutSeconds << " seconds."; 430 OnPermanentError(); 431} 432 433void DesktopSessionWin::StartMonitoring( 434 const net::IPEndPoint& client_endpoint) { 435 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 436 DCHECK(!monitoring_notifications_); 437 DCHECK(!session_attach_timer_.IsRunning()); 438 439 session_attach_timer_.Start( 440 FROM_HERE, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds), 441 this, &DesktopSessionWin::OnSessionAttachTimeout); 442 443 monitoring_notifications_ = true; 444 monitor_->AddWtsTerminalObserver(client_endpoint, this); 445} 446 447void DesktopSessionWin::StopMonitoring() { 448 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 449 450 if (monitoring_notifications_) { 451 monitoring_notifications_ = false; 452 monitor_->RemoveWtsTerminalObserver(this); 453 } 454 455 session_attach_timer_.Stop(); 456 OnSessionDetached(); 457} 458 459void DesktopSessionWin::OnChannelConnected(int32 peer_pid) { 460 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 461 462 // Obtain the handle of the desktop process. It will be passed to the network 463 // process to use to duplicate handles of shared memory objects from 464 // the desktop process. 465 desktop_process_.Set(OpenProcess(PROCESS_DUP_HANDLE, false, peer_pid)); 466 if (!desktop_process_.IsValid()) { 467 CrashDesktopProcess(FROM_HERE); 468 return; 469 } 470 471 VLOG(1) << "IPC: daemon <- desktop (" << peer_pid << ")"; 472} 473 474bool DesktopSessionWin::OnMessageReceived(const IPC::Message& message) { 475 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 476 477 bool handled = true; 478 IPC_BEGIN_MESSAGE_MAP(DesktopSessionWin, message) 479 IPC_MESSAGE_HANDLER(ChromotingDesktopDaemonMsg_DesktopAttached, 480 OnDesktopSessionAgentAttached) 481 IPC_MESSAGE_HANDLER(ChromotingDesktopDaemonMsg_InjectSas, 482 InjectSas) 483 IPC_MESSAGE_UNHANDLED(handled = false) 484 IPC_END_MESSAGE_MAP() 485 486 if (!handled) { 487 LOG(ERROR) << "Received unexpected IPC type: " << message.type(); 488 CrashDesktopProcess(FROM_HERE); 489 } 490 491 return handled; 492} 493 494void DesktopSessionWin::OnPermanentError() { 495 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 496 497 StopMonitoring(); 498 499 // This call will delete |this| so it should be at the very end of the method. 500 daemon_process()->CloseDesktopSession(id()); 501} 502 503void DesktopSessionWin::OnSessionAttached(uint32 session_id) { 504 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 505 DCHECK(!launcher_); 506 DCHECK(monitoring_notifications_); 507 508 // Get the name of the executable the desktop process will run. 509 base::FilePath desktop_binary; 510 if (!GetInstalledBinaryPath(kDesktopBinaryName, &desktop_binary)) { 511 OnPermanentError(); 512 return; 513 } 514 515 session_attach_timer_.Stop(); 516 517 scoped_ptr<CommandLine> target(new CommandLine(desktop_binary)); 518 target->AppendSwitchASCII(kProcessTypeSwitchName, kProcessTypeDesktop); 519 // Copy the command line switches enabling verbose logging. 520 target->CopySwitchesFrom(*CommandLine::ForCurrentProcess(), 521 kCopiedSwitchNames, 522 arraysize(kCopiedSwitchNames)); 523 524 // Create a delegate capable of launching a process in a different session. 525 scoped_ptr<WtsSessionProcessDelegate> delegate( 526 new WtsSessionProcessDelegate( 527 caller_task_runner_, io_task_runner_, target.Pass(), session_id, true, 528 WideToUTF8(kDaemonIpcSecurityDescriptor))); 529 530 // Create a launcher for the desktop process, using the per-session delegate. 531 launcher_.reset(new WorkerProcessLauncher( 532 caller_task_runner_, delegate.Pass(), this)); 533} 534 535void DesktopSessionWin::OnSessionDetached() { 536 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 537 538 launcher_.reset(); 539 540 if (monitoring_notifications_) { 541 session_attach_timer_.Start( 542 FROM_HERE, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds), 543 this, &DesktopSessionWin::OnSessionAttachTimeout); 544 } 545} 546 547void DesktopSessionWin::OnDesktopSessionAgentAttached( 548 IPC::PlatformFileForTransit desktop_pipe) { 549 if (!daemon_process()->OnDesktopSessionAgentAttached(id(), 550 desktop_process_, 551 desktop_pipe)) { 552 CrashDesktopProcess(FROM_HERE); 553 } 554} 555 556void DesktopSessionWin::CrashDesktopProcess( 557 const tracked_objects::Location& location) { 558 DCHECK(caller_task_runner_->BelongsToCurrentThread()); 559 560 launcher_->Crash(location); 561} 562 563} // namespace remoting 564