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// This file implements the Windows service controlling Me2Me host processes
6// running within user sessions.
7
8#include "remoting/host/win/host_service.h"
9
10#include <sddl.h>
11#include <windows.h>
12#include <wtsapi32.h>
13
14#include "base/base_paths.h"
15#include "base/base_switches.h"
16#include "base/bind.h"
17#include "base/command_line.h"
18#include "base/files/file_path.h"
19#include "base/message_loop/message_loop.h"
20#include "base/run_loop.h"
21#include "base/single_thread_task_runner.h"
22#include "base/strings/utf_string_conversions.h"
23#include "base/threading/thread.h"
24#include "base/win/message_window.h"
25#include "base/win/scoped_com_initializer.h"
26#include "remoting/base/auto_thread.h"
27#include "remoting/base/scoped_sc_handle_win.h"
28#include "remoting/host/branding.h"
29#include "remoting/host/daemon_process.h"
30#include "remoting/host/host_exit_codes.h"
31#include "remoting/host/logging.h"
32#include "remoting/host/win/com_security.h"
33#include "remoting/host/win/core_resource.h"
34#include "remoting/host/win/wts_terminal_observer.h"
35
36namespace remoting {
37
38namespace {
39
40const char kIoThreadName[] = "I/O thread";
41
42// Command line switches:
43
44// "--console" runs the service interactively for debugging purposes.
45const char kConsoleSwitchName[] = "console";
46
47// Security descriptor allowing local processes running under SYSTEM or
48// LocalService accounts to call COM methods exposed by the daemon.
49const wchar_t kComProcessSd[] =
50    SDDL_OWNER L":" SDDL_LOCAL_SYSTEM
51    SDDL_GROUP L":" SDDL_LOCAL_SYSTEM
52    SDDL_DACL L":"
53    SDDL_ACE(SDDL_ACCESS_ALLOWED, SDDL_COM_EXECUTE_LOCAL, SDDL_LOCAL_SYSTEM)
54    SDDL_ACE(SDDL_ACCESS_ALLOWED, SDDL_COM_EXECUTE_LOCAL, SDDL_LOCAL_SERVICE);
55
56// Appended to |kComProcessSd| to specify that only callers running at medium or
57// higher integrity level are allowed to call COM methods exposed by the daemon.
58const wchar_t kComProcessMandatoryLabel[] =
59    SDDL_SACL L":"
60    SDDL_ACE(SDDL_MANDATORY_LABEL, SDDL_NO_EXECUTE_UP, SDDL_ML_MEDIUM);
61
62}  // namespace
63
64HostService* HostService::GetInstance() {
65  return Singleton<HostService>::get();
66}
67
68bool HostService::InitWithCommandLine(const CommandLine* command_line) {
69  CommandLine::StringVector args = command_line->GetArgs();
70  if (!args.empty()) {
71    LOG(ERROR) << "No positional parameters expected.";
72    return false;
73  }
74
75  // Run interactively if needed.
76  if (run_routine_ == &HostService::RunAsService &&
77      command_line->HasSwitch(kConsoleSwitchName)) {
78    run_routine_ = &HostService::RunInConsole;
79  }
80
81  return true;
82}
83
84int HostService::Run() {
85  return (this->*run_routine_)();
86}
87
88bool HostService::AddWtsTerminalObserver(const std::string& terminal_id,
89                                         WtsTerminalObserver* observer) {
90  DCHECK(main_task_runner_->BelongsToCurrentThread());
91
92  RegisteredObserver registered_observer;
93  registered_observer.terminal_id = terminal_id;
94  registered_observer.session_id = kInvalidSessionId;
95  registered_observer.observer = observer;
96
97  bool session_id_found = false;
98  std::list<RegisteredObserver>::const_iterator i;
99  for (i = observers_.begin(); i != observers_.end(); ++i) {
100    // Get the attached session ID from another observer watching the same WTS
101    // console if any.
102    if (i->terminal_id == terminal_id) {
103      registered_observer.session_id = i->session_id;
104      session_id_found = true;
105    }
106
107    // Check that |observer| hasn't been registered already.
108    if (i->observer == observer)
109      return false;
110  }
111
112  // If |terminal_id| is new, enumerate all sessions to see if there is one
113  // attached to |terminal_id|.
114  if (!session_id_found)
115    registered_observer.session_id = LookupSessionId(terminal_id);
116
117  observers_.push_back(registered_observer);
118
119  if (registered_observer.session_id != kInvalidSessionId) {
120    observer->OnSessionAttached(registered_observer.session_id);
121  }
122
123  return true;
124}
125
126void HostService::RemoveWtsTerminalObserver(WtsTerminalObserver* observer) {
127  DCHECK(main_task_runner_->BelongsToCurrentThread());
128
129  std::list<RegisteredObserver>::const_iterator i;
130  for (i = observers_.begin(); i != observers_.end(); ++i) {
131    if (i->observer == observer) {
132      observers_.erase(i);
133      return;
134    }
135  }
136}
137
138HostService::HostService() :
139  run_routine_(&HostService::RunAsService),
140  service_status_handle_(0),
141  stopped_event_(true, false),
142  weak_factory_(this) {
143}
144
145HostService::~HostService() {
146}
147
148void HostService::OnSessionChange(uint32 event, uint32 session_id) {
149  DCHECK(main_task_runner_->BelongsToCurrentThread());
150  DCHECK_NE(session_id, kInvalidSessionId);
151
152  // Process only attach/detach notifications.
153  if (event != WTS_CONSOLE_CONNECT && event != WTS_CONSOLE_DISCONNECT &&
154      event != WTS_REMOTE_CONNECT && event != WTS_REMOTE_DISCONNECT) {
155    return;
156  }
157
158  // Assuming that notification can arrive later query the current state of
159  // |session_id|.
160  std::string terminal_id;
161  bool attached = LookupTerminalId(session_id, &terminal_id);
162
163  std::list<RegisteredObserver>::iterator i = observers_.begin();
164  while (i != observers_.end()) {
165    std::list<RegisteredObserver>::iterator next = i;
166    ++next;
167
168    // Issue a detach notification if the session was detached from a client or
169    // if it is now attached to a different client.
170    if (i->session_id == session_id &&
171        (!attached || !(i->terminal_id == terminal_id))) {
172      i->session_id = kInvalidSessionId;
173      i->observer->OnSessionDetached();
174      i = next;
175      continue;
176    }
177
178    // The client currently attached to |session_id| was attached to a different
179    // session before. Reconnect it to |session_id|.
180    if (attached && i->terminal_id == terminal_id &&
181        i->session_id != session_id) {
182      WtsTerminalObserver* observer = i->observer;
183
184      if (i->session_id != kInvalidSessionId) {
185        i->session_id = kInvalidSessionId;
186        i->observer->OnSessionDetached();
187      }
188
189      // Verify that OnSessionDetached() above didn't remove |observer|
190      // from the list.
191      std::list<RegisteredObserver>::iterator j = next;
192      --j;
193      if (j->observer == observer) {
194        j->session_id = session_id;
195        observer->OnSessionAttached(session_id);
196      }
197    }
198
199    i = next;
200  }
201}
202
203void HostService::CreateLauncher(
204    scoped_refptr<AutoThreadTaskRunner> task_runner) {
205  // Launch the I/O thread.
206  scoped_refptr<AutoThreadTaskRunner> io_task_runner =
207      AutoThread::CreateWithType(
208          kIoThreadName, task_runner, base::MessageLoop::TYPE_IO);
209  if (!io_task_runner) {
210    LOG(FATAL) << "Failed to start the I/O thread";
211    return;
212  }
213
214  daemon_process_ = DaemonProcess::Create(
215      task_runner,
216      io_task_runner,
217      base::Bind(&HostService::StopDaemonProcess, weak_ptr_));
218}
219
220int HostService::RunAsService() {
221  SERVICE_TABLE_ENTRYW dispatch_table[] = {
222    { const_cast<LPWSTR>(kWindowsServiceName), &HostService::ServiceMain },
223    { NULL, NULL }
224  };
225
226  if (!StartServiceCtrlDispatcherW(dispatch_table)) {
227    LOG_GETLASTERROR(ERROR)
228        << "Failed to connect to the service control manager";
229    return kInitializationFailed;
230  }
231
232  // Wait until the service thread completely exited to avoid concurrent
233  // teardown of objects registered with base::AtExitManager and object
234  // destoyed by the service thread.
235  stopped_event_.Wait();
236
237  return kSuccessExitCode;
238}
239
240void HostService::RunAsServiceImpl() {
241  base::MessageLoop message_loop(base::MessageLoop::TYPE_UI);
242  base::RunLoop run_loop;
243  main_task_runner_ = message_loop.message_loop_proxy();
244  weak_ptr_ = weak_factory_.GetWeakPtr();
245
246  // Register the service control handler.
247  service_status_handle_ = RegisterServiceCtrlHandlerExW(
248      kWindowsServiceName, &HostService::ServiceControlHandler, this);
249  if (service_status_handle_ == 0) {
250    LOG_GETLASTERROR(ERROR)
251        << "Failed to register the service control handler";
252    return;
253  }
254
255  // Report running status of the service.
256  SERVICE_STATUS service_status;
257  ZeroMemory(&service_status, sizeof(service_status));
258  service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
259  service_status.dwCurrentState = SERVICE_RUNNING;
260  service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN |
261                                      SERVICE_ACCEPT_STOP |
262                                      SERVICE_ACCEPT_SESSIONCHANGE;
263  service_status.dwWin32ExitCode = kSuccessExitCode;
264  if (!SetServiceStatus(service_status_handle_, &service_status)) {
265    LOG_GETLASTERROR(ERROR)
266        << "Failed to report service status to the service control manager";
267    return;
268  }
269
270  // Initialize COM.
271  base::win::ScopedCOMInitializer com_initializer;
272  if (!com_initializer.succeeded())
273    return;
274
275  if (!InitializeComSecurity(WideToUTF8(kComProcessSd),
276                             WideToUTF8(kComProcessMandatoryLabel), false)) {
277    return;
278  }
279
280  CreateLauncher(scoped_refptr<AutoThreadTaskRunner>(
281      new AutoThreadTaskRunner(main_task_runner_,
282                               run_loop.QuitClosure())));
283
284  // Run the service.
285  run_loop.Run();
286  weak_factory_.InvalidateWeakPtrs();
287
288  // Tell SCM that the service is stopped.
289  service_status.dwCurrentState = SERVICE_STOPPED;
290  service_status.dwControlsAccepted = 0;
291  if (!SetServiceStatus(service_status_handle_, &service_status)) {
292    LOG_GETLASTERROR(ERROR)
293        << "Failed to report service status to the service control manager";
294    return;
295  }
296}
297
298int HostService::RunInConsole() {
299  base::MessageLoop message_loop(base::MessageLoop::TYPE_UI);
300  base::RunLoop run_loop;
301  main_task_runner_ = message_loop.message_loop_proxy();
302  weak_ptr_ = weak_factory_.GetWeakPtr();
303
304  int result = kInitializationFailed;
305
306  // Initialize COM.
307  base::win::ScopedCOMInitializer com_initializer;
308  if (!com_initializer.succeeded())
309    return result;
310
311  if (!InitializeComSecurity(WideToUTF8(kComProcessSd),
312                             WideToUTF8(kComProcessMandatoryLabel), false)) {
313    return result;
314  }
315
316  // Subscribe to Ctrl-C and other console events.
317  if (!SetConsoleCtrlHandler(&HostService::ConsoleControlHandler, TRUE)) {
318    LOG_GETLASTERROR(ERROR)
319        << "Failed to set console control handler";
320    return result;
321  }
322
323  // Create a window for receiving session change notifications.
324  base::win::MessageWindow window;
325  if (!window.Create(base::Bind(&HostService::HandleMessage,
326                                base::Unretained(this)))) {
327    LOG_GETLASTERROR(ERROR)
328        << "Failed to create the session notification window";
329    goto cleanup;
330  }
331
332  // Subscribe to session change notifications.
333  if (WTSRegisterSessionNotification(window.hwnd(),
334                                     NOTIFY_FOR_ALL_SESSIONS) != FALSE) {
335    CreateLauncher(scoped_refptr<AutoThreadTaskRunner>(
336        new AutoThreadTaskRunner(main_task_runner_,
337                                 run_loop.QuitClosure())));
338
339    // Run the service.
340    run_loop.Run();
341
342    // Release the control handler.
343    stopped_event_.Signal();
344
345    WTSUnRegisterSessionNotification(window.hwnd());
346    result = kSuccessExitCode;
347  }
348
349cleanup:
350  weak_factory_.InvalidateWeakPtrs();
351
352  // Unsubscribe from console events. Ignore the exit code. There is nothing
353  // we can do about it now and the program is about to exit anyway. Even if
354  // it crashes nothing is going to be broken because of it.
355  SetConsoleCtrlHandler(&HostService::ConsoleControlHandler, FALSE);
356
357  return result;
358}
359
360void HostService::StopDaemonProcess() {
361  DCHECK(main_task_runner_->BelongsToCurrentThread());
362
363  daemon_process_.reset();
364}
365
366bool HostService::HandleMessage(
367    UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) {
368  if (message == WM_WTSSESSION_CHANGE) {
369    OnSessionChange(wparam, lparam);
370    *result = 0;
371    return true;
372  }
373
374  return false;
375}
376
377// static
378BOOL WINAPI HostService::ConsoleControlHandler(DWORD event) {
379  HostService* self = HostService::GetInstance();
380  switch (event) {
381    case CTRL_C_EVENT:
382    case CTRL_BREAK_EVENT:
383    case CTRL_CLOSE_EVENT:
384    case CTRL_LOGOFF_EVENT:
385    case CTRL_SHUTDOWN_EVENT:
386      self->main_task_runner_->PostTask(
387          FROM_HERE, base::Bind(&HostService::StopDaemonProcess,
388                                self->weak_ptr_));
389      return TRUE;
390
391    default:
392      return FALSE;
393  }
394}
395
396// static
397DWORD WINAPI HostService::ServiceControlHandler(DWORD control,
398                                                DWORD event_type,
399                                                LPVOID event_data,
400                                                LPVOID context) {
401  HostService* self = reinterpret_cast<HostService*>(context);
402  switch (control) {
403    case SERVICE_CONTROL_INTERROGATE:
404      return NO_ERROR;
405
406    case SERVICE_CONTROL_SHUTDOWN:
407    case SERVICE_CONTROL_STOP:
408      self->main_task_runner_->PostTask(
409          FROM_HERE, base::Bind(&HostService::StopDaemonProcess,
410                                self->weak_ptr_));
411      return NO_ERROR;
412
413    case SERVICE_CONTROL_SESSIONCHANGE:
414      self->main_task_runner_->PostTask(FROM_HERE, base::Bind(
415          &HostService::OnSessionChange, self->weak_ptr_, event_type,
416          reinterpret_cast<WTSSESSION_NOTIFICATION*>(event_data)->dwSessionId));
417      return NO_ERROR;
418
419    default:
420      return ERROR_CALL_NOT_IMPLEMENTED;
421  }
422}
423
424// static
425VOID WINAPI HostService::ServiceMain(DWORD argc, WCHAR* argv[]) {
426  HostService* self = HostService::GetInstance();
427
428  // Run the service.
429  self->RunAsServiceImpl();
430
431  // Release the control handler and notify the main thread that it can exit
432  // now.
433  self->stopped_event_.Signal();
434}
435
436int DaemonProcessMain() {
437  HostService* service = HostService::GetInstance();
438  if (!service->InitWithCommandLine(CommandLine::ForCurrentProcess())) {
439    return kUsageExitCode;
440  }
441
442  return service->Run();
443}
444
445} // namespace remoting
446