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
45using base::win::ScopedHandle;
46
47namespace remoting {
48
49namespace {
50
51// The security descriptor of the daemon IPC endpoint. It gives full access
52// to SYSTEM and denies access by anyone else.
53const 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 desktop process.
62const char* kCopiedSwitchNames[] = { switches::kV, switches::kVModule };
63
64// The default screen dimensions for an RDP session.
65const int kDefaultRdpScreenWidth = 1280;
66const int kDefaultRdpScreenHeight = 768;
67
68// RDC 6.1 (W2K8) supports dimensions of up to 4096x2048.
69const int kMaxRdpScreenWidth = 4096;
70const int kMaxRdpScreenHeight = 2048;
71
72// The minimum effective screen dimensions supported by Windows are 800x600.
73const int kMinRdpScreenWidth = 800;
74const int kMinRdpScreenHeight = 600;
75
76// Default dots per inch used by RDP is 96 DPI.
77const int kDefaultRdpDpi = 96;
78
79// The session attach notification should arrive within 30 seconds.
80const 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|.
86class 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|.
114class 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
179ConsoleSession::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
190ConsoleSession::~ConsoleSession() {
191}
192
193void 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
199void 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
208RdpSession::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
219RdpSession::~RdpSession() {
220}
221
222bool 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(base::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
273void RdpSession::OnRdpConnected() {
274  DCHECK(caller_task_runner()->BelongsToCurrentThread());
275
276  StopMonitoring();
277  StartMonitoring(terminal_id_);
278}
279
280void RdpSession::OnRdpClosed() {
281  DCHECK(caller_task_runner()->BelongsToCurrentThread());
282
283  TerminateSession();
284}
285
286void 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
294void RdpSession::InjectSas() {
295  DCHECK(caller_task_runner()->BelongsToCurrentThread());
296
297  rdp_desktop_session_->InjectSas();
298}
299
300RdpSession::EventHandler::EventHandler(
301    base::WeakPtr<RdpSession> desktop_session)
302    : ref_count_(0),
303      desktop_session_(desktop_session) {
304}
305
306RdpSession::EventHandler::~EventHandler() {
307  DCHECK(thread_checker_.CalledOnValidThread());
308
309  if (desktop_session_)
310    desktop_session_->OnRdpClosed();
311}
312
313ULONG STDMETHODCALLTYPE RdpSession::EventHandler::AddRef() {
314  DCHECK(thread_checker_.CalledOnValidThread());
315
316  return ++ref_count_;
317}
318
319ULONG 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
330STDMETHODIMP 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
344STDMETHODIMP RdpSession::EventHandler::OnRdpConnected() {
345  DCHECK(thread_checker_.CalledOnValidThread());
346
347  if (desktop_session_)
348    desktop_session_->OnRdpConnected();
349
350  return S_OK;
351}
352
353STDMETHODIMP 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
368scoped_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
382scoped_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
397DesktopSessionWin::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
413DesktopSessionWin::~DesktopSessionWin() {
414  DCHECK(caller_task_runner_->BelongsToCurrentThread());
415
416  StopMonitoring();
417}
418
419void 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
427void 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
442void 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
456void 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
465void 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
482bool 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
502void DesktopSessionWin::OnPermanentError(int exit_code) {
503  DCHECK(caller_task_runner_->BelongsToCurrentThread());
504
505  TerminateSession();
506}
507
508void 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                                    base::WideToUTF8(
548                                        kDaemonIpcSecurityDescriptor)));
549  if (!delegate->Initialize(session_id)) {
550    TerminateSession();
551    return;
552  }
553
554  // Create a launcher for the desktop process, using the per-session delegate.
555  launcher_.reset(new WorkerProcessLauncher(
556      delegate.PassAs<WorkerProcessLauncher::Delegate>(), this));
557}
558
559void DesktopSessionWin::OnSessionDetached() {
560  DCHECK(caller_task_runner_->BelongsToCurrentThread());
561
562  launcher_.reset();
563
564  if (monitoring_notifications_) {
565    ReportElapsedTime("detached");
566
567    session_attach_timer_.Start(
568        FROM_HERE, base::TimeDelta::FromSeconds(kSessionAttachTimeoutSeconds),
569        this, &DesktopSessionWin::OnSessionAttachTimeout);
570  }
571}
572
573void DesktopSessionWin::OnDesktopSessionAgentAttached(
574      IPC::PlatformFileForTransit desktop_pipe) {
575  if (!daemon_process()->OnDesktopSessionAgentAttached(id(),
576                                                       desktop_process_.Get(),
577                                                       desktop_pipe)) {
578    CrashDesktopProcess(FROM_HERE);
579  }
580}
581
582void DesktopSessionWin::CrashDesktopProcess(
583    const tracked_objects::Location& location) {
584  DCHECK(caller_task_runner_->BelongsToCurrentThread());
585
586  launcher_->Crash(location);
587}
588
589void DesktopSessionWin::ReportElapsedTime(const std::string& event) {
590  base::Time now = base::Time::Now();
591
592  std::string passed;
593  if (!last_timestamp_.is_null()) {
594    passed = base::StringPrintf(", %.2fs passed",
595                                (now - last_timestamp_).InSecondsF());
596  }
597
598  base::Time::Exploded exploded;
599  now.LocalExplode(&exploded);
600  VLOG(1) << base::StringPrintf("session(%d): %s at %02d:%02d:%02d.%03d%s",
601                                id(),
602                                event.c_str(),
603                                exploded.hour,
604                                exploded.minute,
605                                exploded.second,
606                                exploded.millisecond,
607                                passed.c_str());
608
609  last_timestamp_ = now;
610}
611
612}  // namespace remoting
613