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