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