chromium/remoting/host/win/host_service.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This file implements the Windows service controlling Me2Me host processes
// running within user sessions.

#include "remoting/host/win/host_service.h"

#include <windows.h>

#include <sddl.h>
#include <wtsapi32.h>

#include "base/base_paths.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/message_loop/message_pump_type.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_executor.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "base/win/message_window.h"
#include "base/win/scoped_com_initializer.h"
#include "remoting/base/auto_thread.h"
#include "remoting/base/cpu_utils.h"
#include "remoting/base/logging.h"
#include "remoting/base/scoped_sc_handle_win.h"
#include "remoting/host/base/host_exit_codes.h"
#include "remoting/host/branding.h"
#include "remoting/host/daemon_process.h"
#include "remoting/host/win/com_security.h"
#include "remoting/host/win/core_resource.h"
#include "remoting/host/win/wts_terminal_observer.h"

namespace remoting {

namespace {

const char kIoThreadName[] = "I/O thread";

// Command line switches:

// "--console" runs the service interactively for debugging purposes.
const char kConsoleSwitchName[] = "console";

// Security descriptor allowing local processes running under SYSTEM or
// LocalService accounts to call COM methods exposed by the daemon.
const wchar_t kComProcessSd[] =
    SDDL_OWNER L":" SDDL_LOCAL_SYSTEM
    SDDL_GROUP L":" SDDL_LOCAL_SYSTEM
    SDDL_DACL L":"
    SDDL_ACE(SDDL_ACCESS_ALLOWED, SDDL_COM_EXECUTE_LOCAL, SDDL_LOCAL_SYSTEM)
    SDDL_ACE(SDDL_ACCESS_ALLOWED, SDDL_COM_EXECUTE_LOCAL, SDDL_LOCAL_SERVICE);

// Appended to |kComProcessSd| to specify that only callers running at medium or
// higher integrity level are allowed to call COM methods exposed by the daemon.
const wchar_t kComProcessMandatoryLabel[] =
    SDDL_SACL L":"
    SDDL_ACE(SDDL_MANDATORY_LABEL, SDDL_NO_EXECUTE_UP, SDDL_ML_MEDIUM);

}  // namespace

HostService* HostService::GetInstance() {
  return base::Singleton<HostService>::get();
}

bool HostService::InitWithCommandLine(const base::CommandLine* command_line) {
  base::CommandLine::StringVector args = command_line->GetArgs();
  if (!args.empty()) {
    LOG(ERROR) << "No positional parameters expected.";
    return false;
  }

  // Run interactively if needed.
  if (run_routine_ == &HostService::RunAsService &&
      command_line->HasSwitch(kConsoleSwitchName)) {
    run_routine_ = &HostService::RunInConsole;
  }

  return true;
}

int HostService::Run() {
  return (this->*run_routine_)();
}

bool HostService::AddWtsTerminalObserver(const std::string& terminal_id,
                                         WtsTerminalObserver* observer) {
  DCHECK(main_task_runner_->BelongsToCurrentThread());

  RegisteredObserver registered_observer;
  registered_observer.terminal_id = terminal_id;
  registered_observer.session_id = kInvalidSessionId;
  registered_observer.observer = observer;

  bool session_id_found = false;
  std::list<RegisteredObserver>::const_iterator i;
  for (i = observers_.begin(); i != observers_.end(); ++i) {
    // Get the attached session ID from another observer watching the same WTS
    // console if any.
    if (i->terminal_id == terminal_id) {
      registered_observer.session_id = i->session_id;
      session_id_found = true;
    }

    // Check that |observer| hasn't been registered already.
    if (i->observer == observer) {
      return false;
    }
  }

  // If |terminal_id| is new, enumerate all sessions to see if there is one
  // attached to |terminal_id|.
  if (!session_id_found) {
    registered_observer.session_id = LookupSessionId(terminal_id);
  }

  observers_.push_back(registered_observer);

  if (registered_observer.session_id != kInvalidSessionId) {
    observer->OnSessionAttached(registered_observer.session_id);
  }

  return true;
}

void HostService::RemoveWtsTerminalObserver(WtsTerminalObserver* observer) {
  DCHECK(main_task_runner_->BelongsToCurrentThread());

  std::list<RegisteredObserver>::const_iterator i;
  for (i = observers_.begin(); i != observers_.end(); ++i) {
    if (i->observer == observer) {
      observers_.erase(i);
      return;
    }
  }
}

HostService::HostService()
    : run_routine_(&HostService::RunAsService),
      service_status_handle_(0),
      stopped_event_(base::WaitableEvent::ResetPolicy::MANUAL,
                     base::WaitableEvent::InitialState::NOT_SIGNALED) {}

HostService::~HostService() {}

void HostService::OnSessionChange(uint32_t event, uint32_t session_id) {
  DCHECK(main_task_runner_->BelongsToCurrentThread());
  DCHECK_NE(session_id, kInvalidSessionId);

  // Process only attach/detach notifications.
  if (event != WTS_CONSOLE_CONNECT && event != WTS_CONSOLE_DISCONNECT &&
      event != WTS_REMOTE_CONNECT && event != WTS_REMOTE_DISCONNECT) {
    return;
  }

  // Assuming that notification can arrive later query the current state of
  // |session_id|.
  std::string terminal_id;
  bool attached = LookupTerminalId(session_id, &terminal_id);

  std::list<RegisteredObserver>::iterator i = observers_.begin();
  while (i != observers_.end()) {
    std::list<RegisteredObserver>::iterator next = i;
    ++next;

    // Issue a detach notification if the session was detached from a client or
    // if it is now attached to a different client.
    if (i->session_id == session_id &&
        (!attached || !(i->terminal_id == terminal_id))) {
      i->session_id = kInvalidSessionId;
      i->observer->OnSessionDetached();
      i = next;
      continue;
    }

    // The client currently attached to |session_id| was attached to a different
    // session before. Reconnect it to |session_id|.
    if (attached && i->terminal_id == terminal_id &&
        i->session_id != session_id) {
      WtsTerminalObserver* observer = i->observer;

      if (i->session_id != kInvalidSessionId) {
        i->session_id = kInvalidSessionId;
        i->observer->OnSessionDetached();
      }

      // Verify that OnSessionDetached() above didn't remove |observer|
      // from the list.
      std::list<RegisteredObserver>::iterator j = next;
      --j;
      if (j->observer == observer) {
        j->session_id = session_id;
        observer->OnSessionAttached(session_id);
      }
    }

    i = next;
  }
}

void HostService::CreateLauncher(
    scoped_refptr<AutoThreadTaskRunner> task_runner) {
  // Launch the I/O thread.
  scoped_refptr<AutoThreadTaskRunner> io_task_runner =
      AutoThread::CreateWithType(kIoThreadName, task_runner,
                                 base::MessagePumpType::IO);
  if (!io_task_runner.get()) {
    LOG(FATAL) << "Failed to start the I/O thread";
  }

  daemon_process_ = DaemonProcess::Create(
      task_runner, io_task_runner,
      base::BindOnce(&HostService::StopDaemonProcess, weak_ptr_));
}

int HostService::RunAsService() {
  SERVICE_TABLE_ENTRYW dispatch_table[] = {
      {const_cast<LPWSTR>(kWindowsServiceName), &HostService::ServiceMain},
      {nullptr, nullptr}};

  if (!StartServiceCtrlDispatcherW(dispatch_table)) {
    PLOG(ERROR) << "Failed to connect to the service control manager";
    return kInitializationFailed;
  }

  // Wait until the service thread completely exited to avoid concurrent
  // teardown of objects registered with base::AtExitManager and object
  // destoyed by the service thread.
  stopped_event_.Wait();

  return kSuccessExitCode;
}

void HostService::RunAsServiceImpl() {
  base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
  base::RunLoop run_loop;
  main_task_runner_ = main_task_executor.task_runner();
  weak_ptr_ = weak_factory_.GetWeakPtr();

  // Register the service control handler.
  service_status_handle_ = RegisterServiceCtrlHandlerExW(
      kWindowsServiceName, &HostService::ServiceControlHandler, this);
  if (service_status_handle_ == 0) {
    PLOG(ERROR) << "Failed to register the service control handler";
    return;
  }

  // Report running status of the service.
  SERVICE_STATUS service_status;
  ZeroMemory(&service_status, sizeof(service_status));
  service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
  service_status.dwCurrentState = SERVICE_RUNNING;
  service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN |
                                      SERVICE_ACCEPT_STOP |
                                      SERVICE_ACCEPT_SESSIONCHANGE;
  service_status.dwWin32ExitCode = kSuccessExitCode;
  if (!SetServiceStatus(service_status_handle_, &service_status)) {
    PLOG(ERROR) << "Failed to report service status to the service control "
                << "manager";
    return;
  }

  // Query for supported hardware as soon as the Windows service is started.
  // This greatly reduces the risk of hitting a crash due to an unsupported CPU
  // instruction since very little specialized code has been executed at this
  // point (e.g. connecting to the network, capturing video, etc.).
  if (!IsCpuSupported()) {
    LOG(ERROR) << "CPU not supported, shutting down to prevent random crashes";
    service_status.dwCurrentState = SERVICE_STOPPED;
    service_status.dwControlsAccepted = 0;
    service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
    service_status.dwServiceSpecificExitCode = kCpuNotSupported;
    if (!SetServiceStatus(service_status_handle_, &service_status)) {
      PLOG(ERROR) << "Failed to report service status to the service control "
                  << "manager";
    }
    return;
  }

  // Initialize COM.
  base::win::ScopedCOMInitializer com_initializer;
  if (!com_initializer.Succeeded()) {
    return;
  }

  if (!InitializeComSecurity(base::WideToUTF8(kComProcessSd),
                             base::WideToUTF8(kComProcessMandatoryLabel),
                             false)) {
    return;
  }

  CreateLauncher(scoped_refptr<AutoThreadTaskRunner>(
      new AutoThreadTaskRunner(main_task_runner_, run_loop.QuitClosure())));

  // Run the service.
  run_loop.Run();
  weak_factory_.InvalidateWeakPtrs();

  // Tell SCM that the service is stopped.
  service_status.dwCurrentState = SERVICE_STOPPED;
  service_status.dwControlsAccepted = 0;
  if (!SetServiceStatus(service_status_handle_, &service_status)) {
    PLOG(ERROR) << "Failed to report service status to the service control "
                << "manager";
    return;
  }
}

int HostService::RunInConsole() {
  base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
  base::RunLoop run_loop;
  main_task_runner_ = main_task_executor.task_runner();
  weak_ptr_ = weak_factory_.GetWeakPtr();

  int result = kInitializationFailed;

  // Initialize COM.
  base::win::ScopedCOMInitializer com_initializer;
  if (!com_initializer.Succeeded()) {
    return result;
  }

  if (!InitializeComSecurity(base::WideToUTF8(kComProcessSd),
                             base::WideToUTF8(kComProcessMandatoryLabel),
                             false)) {
    return result;
  }

  // Subscribe to Ctrl-C and other console events.
  if (!SetConsoleCtrlHandler(&HostService::ConsoleControlHandler, TRUE)) {
    PLOG(ERROR) << "Failed to set console control handler";
    return result;
  }

  // Create a window for receiving session change notifications.
  base::win::MessageWindow window;
  if (!window.Create(base::BindRepeating(&HostService::HandleMessage,
                                         base::Unretained(this)))) {
    PLOG(ERROR) << "Failed to create the session notification window";
    goto cleanup;
  }

  // Subscribe to session change notifications.
  if (WTSRegisterSessionNotification(window.hwnd(), NOTIFY_FOR_ALL_SESSIONS) !=
      FALSE) {
    CreateLauncher(scoped_refptr<AutoThreadTaskRunner>(
        new AutoThreadTaskRunner(main_task_runner_, run_loop.QuitClosure())));

    // Run the service.
    run_loop.Run();

    // Release the control handler.
    stopped_event_.Signal();

    WTSUnRegisterSessionNotification(window.hwnd());
    result = kSuccessExitCode;
  }

cleanup:
  weak_factory_.InvalidateWeakPtrs();

  // Unsubscribe from console events. Ignore the exit code. There is nothing
  // we can do about it now and the program is about to exit anyway. Even if
  // it crashes nothing is going to be broken because of it.
  SetConsoleCtrlHandler(&HostService::ConsoleControlHandler, FALSE);

  return result;
}

void HostService::StopDaemonProcess() {
  DCHECK(main_task_runner_->BelongsToCurrentThread());

  daemon_process_.reset();
}

bool HostService::HandleMessage(UINT message,
                                WPARAM wparam,
                                LPARAM lparam,
                                LRESULT* result) {
  if (message == WM_WTSSESSION_CHANGE) {
    OnSessionChange(wparam, lparam);
    *result = 0;
    return true;
  }

  return false;
}

// static
BOOL WINAPI HostService::ConsoleControlHandler(DWORD event) {
  HostService* self = HostService::GetInstance();
  switch (event) {
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:
    case CTRL_CLOSE_EVENT:
    case CTRL_LOGOFF_EVENT:
    case CTRL_SHUTDOWN_EVENT:
      self->main_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(&HostService::StopDaemonProcess, self->weak_ptr_));
      return TRUE;

    default:
      return FALSE;
  }
}

// static
DWORD WINAPI HostService::ServiceControlHandler(DWORD control,
                                                DWORD event_type,
                                                LPVOID event_data,
                                                LPVOID context) {
  HostService* self = reinterpret_cast<HostService*>(context);
  switch (control) {
    case SERVICE_CONTROL_INTERROGATE:
      return NO_ERROR;

    case SERVICE_CONTROL_SHUTDOWN:
    case SERVICE_CONTROL_STOP:
      self->main_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(&HostService::StopDaemonProcess, self->weak_ptr_));
      return NO_ERROR;

    case SERVICE_CONTROL_SESSIONCHANGE:
      self->main_task_runner_->PostTask(
          FROM_HERE,
          base::BindOnce(&HostService::OnSessionChange, self->weak_ptr_,
                         event_type,
                         reinterpret_cast<WTSSESSION_NOTIFICATION*>(event_data)
                             ->dwSessionId));
      return NO_ERROR;

    default:
      return ERROR_CALL_NOT_IMPLEMENTED;
  }
}

// static
VOID WINAPI HostService::ServiceMain(DWORD argc, WCHAR* argv[]) {
  HostService* self = HostService::GetInstance();

  // Run the service.
  self->RunAsServiceImpl();

  // Release the control handler and notify the main thread that it can exit
  // now.
  self->stopped_event_.Signal();
}

int DaemonProcessMain() {
  HostService* service = HostService::GetInstance();
  if (!service->InitWithCommandLine(base::CommandLine::ForCurrentProcess())) {
    return kInvalidCommandLineExitCode;
  }

  return service->Run();
}

}  // namespace remoting