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