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