chromium/chrome/updater/app/server/win/com_classes_legacy.h

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

#ifndef CHROME_UPDATER_APP_SERVER_WIN_COM_CLASSES_LEGACY_H_
#define CHROME_UPDATER_APP_SERVER_WIN_COM_CLASSES_LEGACY_H_

#include <windows.h>

#include <wrl/implements.h>

#include <optional>
#include <string>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/path_service.h"
#include "base/process/process.h"
#include "base/types/expected.h"
#include "base/win/win_util.h"
#include "chrome/updater/app/server/win/updater_legacy_idl.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/util.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/app_command_runner.h"
#include "chrome/updater/win/setup/setup_util.h"

// Definitions for COM updater classes provided for backward compatibility
// with Google Update.

namespace updater {

namespace {

template <typename TDualInterface, typename... TInterfaces>
using WrlRuntimeDispatchClass = Microsoft::WRL::RuntimeClass<
    Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
    IDispatch,
    TDualInterface,
    TInterfaces...>;

}  // namespace

// The `IDispatchImpl` class implements `IDispatch` for interface
// `TDualInterface`, where `TDualInterface` is a dual interface. The IDispatch
// implementation relies on the typelib/typeinfo for interface `TDualInterface`.
//
// If the class supports more interfaces other than `TDualInterface`, these
// interfaces can be passed in via `TInterfaces...`.
//
// The `user_iid_map` and `system_iid_map` passed to the constructor are to
// allow for distinct TypeLibs to be registered and marshaled for user/system.
// See the code below for examples.
//
// Note that the `IDispatchImpl` class only implements the `IDispatch` methods
// for the `TDualInterface` interface.
template <typename TDualInterface, typename... TInterfaces>
class IDispatchImpl
    : public WrlRuntimeDispatchClass<TDualInterface, TInterfaces...> {
 public:
  IDispatchImpl(const base::flat_map<IID, IID, IidComparator>& user_iid_map,
                const base::flat_map<IID, IID, IidComparator>& system_iid_map)
      : iid_map_(IsSystemInstall() ? system_iid_map : user_iid_map),
        hr_load_typelib_(InitializeTypeInfo()) {}
  IDispatchImpl(const IDispatchImpl&) = default;
  IDispatchImpl& operator=(const IDispatchImpl&) = default;
  ~IDispatchImpl() override = default;

  // IUnknown override.
  IFACEMETHODIMP QueryInterface(REFIID riid, void** object) override {
    const auto find_iid = iid_map_.find(riid);
    return WrlRuntimeDispatchClass<TDualInterface, TInterfaces...>::
        QueryInterface(find_iid != iid_map_.end() ? find_iid->second : riid,
                       object);
  }

  // Overrides for IDispatch.
  IFACEMETHODIMP GetTypeInfoCount(UINT* type_info_count) override {
    if (FAILED(hr_load_typelib_)) {
      return hr_load_typelib_;
    }

    *type_info_count = 1;
    return S_OK;
  }

  IFACEMETHODIMP GetTypeInfo(UINT type_info_index,
                             LCID locale_id,
                             ITypeInfo** type_info) override {
    if (FAILED(hr_load_typelib_)) {
      return hr_load_typelib_;
    }

    return type_info_index == 0 ? type_info_.CopyTo(type_info) : E_INVALIDARG;
  }

  IFACEMETHODIMP GetIDsOfNames(REFIID iid,
                               LPOLESTR* names_to_be_mapped,
                               UINT count_of_names_to_be_mapped,
                               LCID locale_id,
                               DISPID* dispatch_ids) override {
    if (FAILED(hr_load_typelib_)) {
      return hr_load_typelib_;
    }

    return type_info_->GetIDsOfNames(names_to_be_mapped,
                                     count_of_names_to_be_mapped, dispatch_ids);
  }

  IFACEMETHODIMP Invoke(DISPID dispatch_id,
                        REFIID iid,
                        LCID locale_id,
                        WORD flags,
                        DISPPARAMS* dispatch_parameters,
                        VARIANT* result,
                        EXCEPINFO* exception_info,
                        UINT* arg_error_index) override {
    if (FAILED(hr_load_typelib_)) {
      return hr_load_typelib_;
    }

    HRESULT hr = type_info_->Invoke(
        Microsoft::WRL::ComPtr<TDualInterface>(this).Get(), dispatch_id, flags,
        dispatch_parameters, result, exception_info, arg_error_index);

    LOG_IF(ERROR, FAILED(hr)) << __func__ << " type_info_->Invoke failed, "
                              << dispatch_id << ", " << std::hex << hr;
    return hr;
  }

  // Loads the typelib and typeinfo for interface `TDualInterface`.
  HRESULT InitializeTypeInfo() {
    base::FilePath typelib_path;
    if (!base::PathService::Get(base::DIR_EXE, &typelib_path)) {
      return E_UNEXPECTED;
    }

    typelib_path =
        typelib_path.Append(GetExecutableRelativePath())
            .Append(GetComTypeLibResourceIndex(__uuidof(TDualInterface)));

    Microsoft::WRL::ComPtr<ITypeLib> type_lib;
    if (HRESULT hr = ::LoadTypeLib(typelib_path.value().c_str(), &type_lib);
        FAILED(hr)) {
      LOG(ERROR) << __func__ << " ::LoadTypeLib failed, " << typelib_path
                 << ", " << std::hex << hr
                 << ", IID: " << StringFromGuid(__uuidof(TDualInterface));
      return hr;
    }

    if (HRESULT hr =
            type_lib->GetTypeInfoOfGuid(__uuidof(TDualInterface), &type_info_);
        FAILED(hr)) {
      LOG(ERROR) << __func__ << " ::GetTypeInfoOfGuid failed" << ", "
                 << std::hex << hr
                 << ", IID: " << StringFromGuid(__uuidof(TDualInterface));
      return hr;
    }

    return S_OK;
  }

 private:
  const base::flat_map<IID, IID, IidComparator> iid_map_;
  Microsoft::WRL::ComPtr<ITypeInfo> type_info_;
  const HRESULT hr_load_typelib_;
};

// This class implements the legacy Omaha3 IGoogleUpdate3Web interface as
// expected by Chrome's on-demand client.
class LegacyOnDemandImpl : public IDispatchImpl<IGoogleUpdate3Web> {
 public:
  LegacyOnDemandImpl();
  LegacyOnDemandImpl(const LegacyOnDemandImpl&) = delete;
  LegacyOnDemandImpl& operator=(const LegacyOnDemandImpl&) = delete;

  // Overrides for IGoogleUpdate3Web.
  IFACEMETHODIMP createAppBundleWeb(IDispatch** app_bundle_web) override;

 private:
  ~LegacyOnDemandImpl() override;
};

// This class implements the legacy Omaha3 IProcessLauncher interface as
// expected by Chrome's setup client.
class LegacyProcessLauncherImpl
    : public Microsoft::WRL::RuntimeClass<
          Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
          IProcessLauncher,
          IProcessLauncherSystem,
          IProcessLauncher2,
          IProcessLauncher2System> {
 public:
  LegacyProcessLauncherImpl();
  LegacyProcessLauncherImpl(const LegacyProcessLauncherImpl&) = delete;
  LegacyProcessLauncherImpl& operator=(const LegacyProcessLauncherImpl&) =
      delete;

  // Overrides for IProcessLauncher/IProcessLauncher2.
  IFACEMETHODIMP LaunchCmdLine(const WCHAR* cmd_line) override;
  IFACEMETHODIMP LaunchBrowser(DWORD browser_type, const WCHAR* url) override;
  IFACEMETHODIMP LaunchCmdElevated(const WCHAR* app_id,
                                   const WCHAR* command_id,
                                   DWORD caller_proc_id,
                                   ULONG_PTR* proc_handle) override;
  IFACEMETHODIMP LaunchCmdLineEx(const WCHAR* cmd_line,
                                 DWORD* server_proc_id,
                                 ULONG_PTR* proc_handle,
                                 ULONG_PTR* stdout_handle) override;

 private:
  ~LegacyProcessLauncherImpl() override;
};

// This class implements the legacy Omaha3 IAppCommandWeb interface. AppCommands
// are a mechanism to run pre-registered command lines in the format
// `c:\path-to-exe\exe.exe param1 param2...param9` elevated. The params are
// optional and can also include replaceable parameters substituted at runtime.
//
// App commands are registered in the registry with the following formats:
// * New command layout format:
//     Update\Clients\`app_id`\Commands\`command_id`
//         REG_SZ "CommandLine" == {command format}
// * Older command layout format:
//     Update\Clients\`app_id`
//         REG_SZ `command_id` == {command format}
//
// Example {command format}: "c:\path-to\echo.exe %1 %2 %3 StaticParam4"
//
// As shown above, {command format} needs to be the complete path to an
// executable followed by optional parameters.
//
// For system applications, the registered executable path above must be in a
// secure location such as %ProgramFiles% for security, since it will be run
// elevated.
//
// Parameters can be placeholders (%1-%9) that can be filled by the numbered
// parameters in `IAppCommandWeb::execute`. Literal `%` characters must be
// escaped by doubling them.
//
// If parameters to `IAppCommandWeb::execute` are AA and BB
// respectively, a command format of:
//     `echo.exe %1 %%2 %%%2`
// becomes the command line
//     `echo.exe AA %2 %BB`
//
// Placeholders are not permitted in the process name.
//
// Placeholders may be embedded within words, and appropriate quoting of
// back-slash, double-quotes, space, and tab is applied if necessary.
class LegacyAppCommandWebImpl : public IDispatchImpl<IAppCommandWeb> {
 public:
  struct ErrorParams {
    int error_code = 0;
    int extra_code1 = 0;
  };

  using PingSender = base::RepeatingCallback<void(UpdaterScope scope,
                                                  const std::string& app_id,
                                                  const std::string& command_id,
                                                  ErrorParams error_params)>;
  LegacyAppCommandWebImpl();
  LegacyAppCommandWebImpl(const LegacyAppCommandWebImpl&) = delete;
  LegacyAppCommandWebImpl& operator=(const LegacyAppCommandWebImpl&) = delete;

  // Initializes an instance of `IAppCommandWeb` for the given `scope`,
  // `app_id`, `command_id`, and a `ping_sender`. Returns an error if the
  // command format does not exist in the registry, or if the command format in
  // the registry has an invalid formatting, or if the type information could
  // not be initialized.
  HRESULT RuntimeClassInitialize(UpdaterScope scope,
                                 const std::wstring& app_id,
                                 const std::wstring& command_id,
                                 PingSender ping_sender = base::BindRepeating(
                                     &LegacyAppCommandWebImpl::SendPing));

  // Overrides for IAppCommandWeb.
  IFACEMETHODIMP get_status(UINT* status) override;
  IFACEMETHODIMP get_exitCode(DWORD* exit_code) override;
  IFACEMETHODIMP get_output(BSTR* output) override;

  // Executes the AppCommand with the optional substitutions provided. `execute`
  // fails if the number of non-empty VARIANT substitutions provided to
  // `execute` are less than the number of parameter placeholders in the
  // loaded-from-the-registry command format. Each placeholder %N is replaced
  // with the corresponding `substitutionN`.
  // An empty (VT_EMPTY) or invalid (non BSTR) substitution causes the following
  // substitutions to be ignored; for example, if `substitution2` is VT_EMPTY,
  // then `substitution3` through `substitution9` will be ignored.
  IFACEMETHODIMP execute(VARIANT substitution1,
                         VARIANT substitution2,
                         VARIANT substitution3,
                         VARIANT substitution4,
                         VARIANT substitution5,
                         VARIANT substitution6,
                         VARIANT substitution7,
                         VARIANT substitution8,
                         VARIANT substitution9) override;

  const base::Process& process() const { return process_; }

 private:
  friend class LegacyAppCommandWebImplTest;

  static void SendPing(UpdaterScope scope,
                       const std::string& app_id,
                       const std::string& command_id,
                       ErrorParams error_params);

  ~LegacyAppCommandWebImpl() override;

  base::Process process_;
  HResultOr<AppCommandRunner> app_command_runner_;
  UpdaterScope scope_ = UpdaterScope::kSystem;
  std::string app_id_;
  std::string command_id_;
  PingSender ping_sender_ = base::DoNothing();
};

// This class implements the legacy Omaha3 IPolicyStatus* interfaces, which
// return the current updater policies for external constants, group policy,
// and device management.
//
// This class is used by chrome://policy to show the current updater policies.
class PolicyStatusImpl : public IDispatchImpl<IPolicyStatus4,
                                              IPolicyStatus3,
                                              IPolicyStatus2,
                                              IPolicyStatus> {
 public:
  PolicyStatusImpl();
  PolicyStatusImpl(const PolicyStatusImpl&) = delete;
  PolicyStatusImpl& operator=(const PolicyStatusImpl&) = delete;

  HRESULT RuntimeClassInitialize();

  // IPolicyStatus/IPolicyStatus2/IPolicyStatus3/IPolicyStatus4. See
  // `updater_legacy_idl.template` for the description of the properties below.
  IFACEMETHODIMP get_lastCheckPeriodMinutes(DWORD* minutes) override;
  IFACEMETHODIMP get_updatesSuppressedTimes(
      DWORD* start_hour,
      DWORD* start_min,
      DWORD* duration_min,
      VARIANT_BOOL* are_updates_suppressed) override;
  IFACEMETHODIMP get_downloadPreferenceGroupPolicy(BSTR* pref) override;
  IFACEMETHODIMP get_packageCacheSizeLimitMBytes(DWORD* limit) override;
  IFACEMETHODIMP get_packageCacheExpirationTimeDays(DWORD* days) override;
  IFACEMETHODIMP get_effectivePolicyForAppInstalls(BSTR app_id,
                                                   DWORD* policy) override;
  IFACEMETHODIMP get_effectivePolicyForAppUpdates(BSTR app_id,
                                                  DWORD* policy) override;
  IFACEMETHODIMP get_targetVersionPrefix(BSTR app_id, BSTR* prefix) override;
  IFACEMETHODIMP get_isRollbackToTargetVersionAllowed(
      BSTR app_id,
      VARIANT_BOOL* rollback_allowed) override;
  IFACEMETHODIMP get_updaterVersion(BSTR* version) override;
  IFACEMETHODIMP get_lastCheckedTime(DATE* last_checked) override;
  IFACEMETHODIMP refreshPolicies() override;
  IFACEMETHODIMP get_lastCheckPeriodMinutes(
      IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_updatesSuppressedTimes(
      IPolicyStatusValue** value,
      VARIANT_BOOL* are_updates_suppressed) override;
  IFACEMETHODIMP get_downloadPreferenceGroupPolicy(
      IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_packageCacheSizeLimitMBytes(
      IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_packageCacheExpirationTimeDays(
      IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_proxyMode(IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_proxyPacUrl(IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_proxyServer(IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_effectivePolicyForAppInstalls(
      BSTR app_id,
      IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_effectivePolicyForAppUpdates(
      BSTR app_id,
      IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_targetVersionPrefix(BSTR app_id,
                                         IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_isRollbackToTargetVersionAllowed(
      BSTR app_id,
      IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_targetChannel(BSTR app_id,
                                   IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_forceInstallApps(VARIANT_BOOL is_machine,
                                      IPolicyStatusValue** value) override;
  IFACEMETHODIMP get_cloudPolicyOverridesPlatformPolicy(
      IPolicyStatusValue** value) override;

 private:
  ~PolicyStatusImpl() override;

  scoped_refptr<PolicyService> policy_service_;
};

// This class implements the legacy Omaha3 IPolicyStatusValue interface. Each
// instance stores a single updater policy returned by the properties in
// IPolicyStatus2 and IPolicyStatus3.
class PolicyStatusValueImpl : public IDispatchImpl<IPolicyStatusValue> {
 public:
  PolicyStatusValueImpl();
  PolicyStatusValueImpl(const PolicyStatusValueImpl&) = delete;
  PolicyStatusValueImpl& operator=(const PolicyStatusValueImpl&) = delete;

  template <typename T>
  static HRESULT Create(const T& value,
                        IPolicyStatusValue** policy_status_value);

  HRESULT RuntimeClassInitialize(const std::string& source,
                                 const std::string& value,
                                 bool has_conflict,
                                 const std::string& conflict_source,
                                 const std::string& conflict_value);

  // IPolicyStatusValue. See `updater_legacy_idl.template` for the
  // description of the properties below.
  IFACEMETHODIMP get_source(BSTR* source) override;
  IFACEMETHODIMP get_value(BSTR* value) override;
  IFACEMETHODIMP get_hasConflict(VARIANT_BOOL* has_conflict) override;
  IFACEMETHODIMP get_conflictSource(BSTR* conflict_source) override;
  IFACEMETHODIMP get_conflictValue(BSTR* conflict_value) override;

 private:
  ~PolicyStatusValueImpl() override;

  std::wstring source_;
  std::wstring value_;
  VARIANT_BOOL has_conflict_;
  std::wstring conflict_source_;
  std::wstring conflict_value_;
};

}  // namespace updater

#endif  // CHROME_UPDATER_APP_SERVER_WIN_COM_CLASSES_LEGACY_H_