chromium/remoting/host/remote_open_url/url_forwarder_configurator_win.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/remote_open_url/url_forwarder_configurator_win.h"

#include <windows.h>

#include <wtsapi32.h>

#include <memory>
#include <string>

#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/sequence_checker.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/win/scoped_handle.h"
#include "base/win/windows_types.h"
#include "remoting/base/logging.h"
#include "remoting/host/base/switches.h"
#include "remoting/host/win/wts_session_change_observer.h"

namespace remoting {

namespace {

// If the user has already changed the default browser to the URL forwarder,
// then the setup process will finish immediately without showing any UI, so we
// delay reporting USER_INTERVENTION_REQUIRED so that the client doesn't pop up
// a toast when it's unnecessary.
constexpr base::TimeDelta kReportUserInterventionRequiredDelay =
    base::Milliseconds(500);

base::win::ScopedHandle GetCurrentUserToken() {
  HANDLE user_token = nullptr;
  if (!WTSQueryUserToken(WTS_CURRENT_SESSION, &user_token)) {
    PLOG(ERROR) << "Failed to get current user token";
    return base::win::ScopedHandle();
  }
  return base::win::ScopedHandle(user_token);
}

// If |switch_name| is empty, the process will be launched with no extra
// switches.
bool LaunchConfiguratorProcess(const std::string& switch_name,
                               base::win::ScopedHandle user_token) {
  DCHECK(user_token.IsValid());
  base::LaunchOptions launch_options;
  launch_options.as_user = user_token.Get();
  // The remoting_desktop.exe binary (where this code runs) has extra manifest
  // flags (uiAccess and requireAdministrator) that are undesirable for the
  // url_forwarder_configurator child process, so remoting_host.exe is used
  // instead.
  base::FilePath path;
  if (!base::PathService::Get(base::DIR_EXE, &path)) {
    LOG(ERROR) << "Failed to get executable path.";
    return false;
  }
  path = path.AppendASCII("remoting_host.exe");
  base::CommandLine command(path);
  command.AppendSwitchASCII(kProcessTypeSwitchName,
                            kProcessTypeUrlForwarderConfigurator);
  if (!switch_name.empty()) {
    command.AppendSwitch(switch_name);
  }
  int exit_code = -1;
  base::LaunchProcess(command, launch_options).WaitForExit(&exit_code);
  return exit_code == 0;
}

}  // namespace

UrlForwarderConfiguratorWin::UrlForwarderConfiguratorWin()
    : io_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
          {base::MayBlock(), base::WithBaseSyncPrimitives()})) {}

UrlForwarderConfiguratorWin::~UrlForwarderConfiguratorWin() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void UrlForwarderConfiguratorWin::IsUrlForwarderSetUp(
    IsUrlForwarderSetUpCallback callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto user_token = GetCurrentUserToken();
  if (user_token.IsValid()) {
    io_task_runner_->PostTaskAndReplyWithResult(
        FROM_HERE,
        base::BindOnce(&LaunchConfiguratorProcess, std::string(),
                       std::move(user_token)),
        std::move(callback));
    return;
  }

  if (GetLastError() != ERROR_NO_TOKEN) {
    std::move(callback).Run(false);
    return;
  }

  if (is_url_forwarder_set_up_callback_) {
    LOG(ERROR) << "Query is already in progress.";
    std::move(callback).Run(false);
    return;
  }
  DCHECK(!wts_session_change_observer_);

  HOST_LOG << "Can't determine URL Forwarder state as no user is signed in. "
           << "Will check again after user signs in.";
  is_url_forwarder_set_up_callback_ = std::move(callback);
  wts_session_change_observer_ = std::make_unique<WtsSessionChangeObserver>();
  bool start_success = wts_session_change_observer_->Start(
      base::BindRepeating(&UrlForwarderConfiguratorWin::OnWtsSessionChange,
                          base::Unretained(this)));
  if (!start_success) {
    std::move(is_url_forwarder_set_up_callback_).Run(false);
  }
}

void UrlForwarderConfiguratorWin::SetUpUrlForwarder(
    const SetUpUrlForwarderCallback& callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (set_up_url_forwarder_callback_) {
    LOG(ERROR) << "Setup is already in progress.";
    callback.Run(SetUpUrlForwarderResponse::FAILED);
    return;
  }

  set_up_url_forwarder_callback_ = callback;
  report_user_intervention_required_timer_.Start(
      FROM_HERE, kReportUserInterventionRequiredDelay, this,
      &UrlForwarderConfiguratorWin::OnReportUserInterventionRequired);
  auto user_token = GetCurrentUserToken();
  if (!user_token.IsValid()) {
    OnSetUpResponse(false);
    return;
  }
  io_task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&LaunchConfiguratorProcess, kSetUpUrlForwarderSwitchName,
                     std::move(user_token)),
      base::BindOnce(&UrlForwarderConfiguratorWin::OnSetUpResponse,
                     weak_factory_.GetWeakPtr()));
}

void UrlForwarderConfiguratorWin::OnSetUpResponse(bool success) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  report_user_intervention_required_timer_.AbandonAndStop();
  set_up_url_forwarder_callback_.Run(success
                                         ? SetUpUrlForwarderResponse::COMPLETE
                                         : SetUpUrlForwarderResponse::FAILED);
  set_up_url_forwarder_callback_.Reset();
}

void UrlForwarderConfiguratorWin::OnReportUserInterventionRequired() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  set_up_url_forwarder_callback_.Run(
      SetUpUrlForwarderResponse::USER_INTERVENTION_REQUIRED);
}

void UrlForwarderConfiguratorWin::OnWtsSessionChange(uint32_t event,
                                                     uint32_t session_id) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (event != WTS_SESSION_LOGON) {
    return;
  }

  DCHECK(is_url_forwarder_set_up_callback_);

  HOST_LOG << "User logged in. Checking URL forwarder setup state now.";
  auto user_token = GetCurrentUserToken();
  if (!user_token.IsValid()) {
    std::move(is_url_forwarder_set_up_callback_).Run(false);
    return;
  }
  io_task_runner_->PostTaskAndReplyWithResult(
      FROM_HERE,
      base::BindOnce(&LaunchConfiguratorProcess, std::string(),
                     std::move(user_token)),
      std::move(is_url_forwarder_set_up_callback_));
  wts_session_change_observer_.reset();
}

// static
std::unique_ptr<UrlForwarderConfigurator> UrlForwarderConfigurator::Create() {
  return std::make_unique<UrlForwarderConfiguratorWin>();
}

}  // namespace remoting