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