chromium/chrome/installer/gcapi/gcapi.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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

// NOTE: This code is a legacy utility API for partners to check whether
//       Chrome can be installed and launched. Recent updates are being made
//       to add new functionality. These updates use code from Chromium, the old
//       coded against the win32 api directly. If you have an itch to shave a
//       yak, feel free to re-write the old code too.

#include "chrome/installer/gcapi/gcapi.h"

#include <windows.h>

#include <versionhelpers.h>

#include <sddl.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#define STRSAFE_NO_DEPRECATE
#include <objbase.h>

#include <strsafe.h>
#include <tlhelp32.h>
#include <wrl/client.h>

#include <algorithm>
#include <cstdlib>
#include <iterator>
#include <limits>
#include <set>
#include <string>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/win/registry.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_handle.h"
#include "base/win/wmi.h"
#include "chrome/installer/gcapi/gcapi_reactivation.h"
#include "chrome/installer/gcapi/google_update_util.h"
#include "chrome/installer/launcher_support/chrome_launcher_support.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/util_constants.h"
#include "chrome/updater/app/server/win/updater_legacy_idl.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"

using base::Time;
using base::win::RegKey;
using base::win::ScopedCOMInitializer;
using base::win::ScopedHandle;
using Microsoft::WRL::ComPtr;

namespace {

const wchar_t kGCAPITempKey[] = L"Software\\Google\\GCAPITemp";

const wchar_t kChromeRegVersion[] = L"pv";
const wchar_t kNoChromeOfferUntil[] =
    L"SOFTWARE\\Google\\No Chrome Offer Until";

const wchar_t kC1FPendingKey[] = L"Software\\Google\\Common\\Rlz\\Events\\C";
const wchar_t kC1FSentKey[] =
    L"Software\\Google\\Common\\Rlz\\StatefulEvents\\C";
const wchar_t kC1FKey[] = L"C1F";

const wchar_t kRelaunchBrandcodeValue[] = L"RelaunchBrandcode";
const wchar_t kRelaunchAllowedAfterValue[] = L"RelaunchAllowedAfter";

// Prefix used to match the window class for Chrome windows.
const wchar_t kChromeWindowClassPrefix[] = L"Chrome_WidgetWin_";

// Return the company name specified in the file version info resource.
bool GetCompanyName(const wchar_t* filename, wchar_t* buffer, DWORD out_len) {
  wchar_t file_version_info[8192];
  DWORD handle = 0;
  DWORD buffer_size = 0;

  buffer_size = ::GetFileVersionInfoSize(filename, &handle);
  // Cannot stats the file or our buffer size is too small (very unlikely).
  if (buffer_size == 0 || buffer_size > _countof(file_version_info))
    return false;

  buffer_size = _countof(file_version_info);
  memset(file_version_info, 0, buffer_size);
  if (!::GetFileVersionInfo(filename, handle, buffer_size, file_version_info))
    return false;

  DWORD data_len = 0;
  LPVOID data = nullptr;
  // Retrieve the language and codepage code if exists.
  buffer_size = 0;
  if (!::VerQueryValue(file_version_info, TEXT("\\VarFileInfo\\Translation"),
                       reinterpret_cast<LPVOID*>(&data),
                       reinterpret_cast<UINT*>(&data_len)))
    return false;
  if (data_len != 4)
    return false;

  wchar_t info_name[256];
  DWORD lang = 0;
  // Formulate the string to retrieve the company name of the specific
  // language codepage.
  memcpy(&lang, data, 4);
  ::StringCchPrintf(info_name, _countof(info_name),
                    L"\\StringFileInfo\\%02X%02X%02X%02X\\CompanyName",
                    (lang & 0xff00) >> 8, (lang & 0xff),
                    (lang & 0xff000000) >> 24, (lang & 0xff0000) >> 16);

  data_len = 0;
  if (!::VerQueryValue(file_version_info, info_name,
                       reinterpret_cast<LPVOID*>(&data),
                       reinterpret_cast<UINT*>(&data_len)))
    return false;
  if (data_len <= 0 || data_len >= (out_len / sizeof(wchar_t)))
    return false;

  memset(buffer, 0, out_len);
  ::StringCchCopyN(buffer, (out_len / sizeof(wchar_t)),
                   reinterpret_cast<const wchar_t*>(data), data_len);
  return true;
}

// Offsets the current date by |months|. |months| must be between 0 and 12.
// The returned date is in the YYYYMMDD format.
DWORD FormatDateOffsetByMonths(int months) {
  DCHECK(months >= 0 && months <= 12);

  SYSTEMTIME now;
  GetLocalTime(&now);
  now.wMonth += months;
  if (now.wMonth > 12) {
    now.wMonth -= 12;
    now.wYear += 1;
  }

  return now.wYear * 10000 + now.wMonth * 100 + now.wDay;
}

// Return true if we can re-offer Chrome; false, otherwise.
// Each partner can only offer Chrome once every six months.
bool CanReOfferChrome(BOOL set_flag) {
  wchar_t filename[MAX_PATH + 1];
  wchar_t company[MAX_PATH];

  // If we cannot retrieve the version info of the executable or company
  // name, we allow the Chrome to be offered because there is no past
  // history to be found.
  if (::GetModuleFileName(nullptr, filename, MAX_PATH) == 0)
    return true;
  if (!GetCompanyName(filename, company, sizeof(company)))
    return true;

  bool can_re_offer = true;
  DWORD disposition = 0;
  HKEY key = nullptr;
  if (::RegCreateKeyEx(HKEY_LOCAL_MACHINE, kNoChromeOfferUntil, 0, nullptr,
                       REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, nullptr,
                       &key, &disposition) == ERROR_SUCCESS) {
    // Get today's date, and format it as YYYYMMDD numeric value.
    DWORD today = FormatDateOffsetByMonths(0);

    // Cannot re-offer, if the timer already exists and is not expired yet.
    DWORD value_type = REG_DWORD;
    DWORD value_data = 0;
    DWORD value_length = sizeof(DWORD);
    if (::RegQueryValueEx(key, company, 0, &value_type,
                          reinterpret_cast<LPBYTE>(&value_data),
                          &value_length) == ERROR_SUCCESS &&
        REG_DWORD == value_type && value_data > today) {
      // The time has not expired, we cannot offer Chrome.
      can_re_offer = false;
    } else {
      // Delete the old or invalid value.
      ::RegDeleteValue(key, company);
      if (set_flag) {
        // Set expiration date for offer as six months from today,
        // represented as a YYYYMMDD numeric value.
        DWORD value = FormatDateOffsetByMonths(6);
        ::RegSetValueEx(key, company, 0, REG_DWORD, (LPBYTE)&value,
                        sizeof(DWORD));
      }
    }

    ::RegCloseKey(key);
  }

  return can_re_offer;
}

bool IsChromeInstalled(HKEY root_key) {
  RegKey key;
  return key.Open(root_key, gcapi_internals::kChromeRegClientsKey,
                  KEY_READ | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
         key.HasValue(kChromeRegVersion);
}

// Returns true if the |subkey| in |root| has the kC1FKey entry set to 1.
bool RegKeyHasC1F(HKEY root, const wchar_t* subkey) {
  RegKey key;
  DWORD value;
  return key.Open(root, subkey, KEY_READ | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
         key.ReadValueDW(kC1FKey, &value) == ERROR_SUCCESS &&
         value == static_cast<DWORD>(1);
}

bool IsC1FSent() {
  // The C1F RLZ key can either be in HKCU or in HKLM (the HKLM RLZ key is made
  // readable to all-users via rlz_lib::CreateMachineState()) and can either be
  // in sent or pending state. Return true if there is a match for any of these
  // 4 states.
  return RegKeyHasC1F(HKEY_CURRENT_USER, kC1FSentKey) ||
         RegKeyHasC1F(HKEY_CURRENT_USER, kC1FPendingKey) ||
         RegKeyHasC1F(HKEY_LOCAL_MACHINE, kC1FSentKey) ||
         RegKeyHasC1F(HKEY_LOCAL_MACHINE, kC1FPendingKey);
}

bool IsWindowsVersionSupported() {
  return IsWindows7OrGreater();
}

// Note this function should not be called on old Windows versions where these
// Windows API are not available. We always invoke this function after checking
// that current OS is Vista or later.
bool VerifyAdminGroup() {
  SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
  PSID Group;
  BOOL check = ::AllocateAndInitializeSid(
      &NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0,
      0, 0, 0, 0, 0, &Group);
  if (check) {
    if (!::CheckTokenMembership(nullptr, Group, &check))
      check = FALSE;
  }
  ::FreeSid(Group);
  return (check == TRUE);
}

bool VerifyHKLMAccess() {
  wchar_t str[] = L"test";
  bool result = false;
  DWORD disposition = 0;
  HKEY key = nullptr;

  if (::RegCreateKeyEx(HKEY_LOCAL_MACHINE, kGCAPITempKey, 0, nullptr,
                       REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE | KEY_WOW64_32KEY, nullptr, &key,
                       &disposition) == ERROR_SUCCESS) {
    if (::RegSetValueEx(key, str, 0, REG_SZ, (LPBYTE)str,
                        (DWORD)lstrlen(str)) == ERROR_SUCCESS) {
      result = true;
      RegDeleteValue(key, str);
    }

    RegCloseKey(key);

    //  If we create the main key, delete the entire key.
    if (disposition == REG_CREATED_NEW_KEY)
      RegDeleteKey(HKEY_LOCAL_MACHINE, kGCAPITempKey);
  }

  return result;
}

bool IsRunningElevated() {
  // This method should be called only for Vista or later.
  if (!IsWindowsVersionSupported() || !VerifyAdminGroup())
    return false;

  HANDLE process_token;
  if (!::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &process_token))
    return false;

  TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault;
  DWORD size_returned = 0;
  if (!::GetTokenInformation(process_token, TokenElevationType, &elevation_type,
                             sizeof(elevation_type), &size_returned)) {
    ::CloseHandle(process_token);
    return false;
  }

  ::CloseHandle(process_token);
  return (elevation_type == TokenElevationTypeFull);
}

bool GetUserIdForProcess(size_t pid, wchar_t** user_sid) {
  HANDLE process_handle =
      ::OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, static_cast<DWORD>(pid));
  if (process_handle == nullptr)
    return false;

  HANDLE process_token;
  bool result = false;
  if (::OpenProcessToken(process_handle, TOKEN_QUERY, &process_token)) {
    DWORD size = 0;
    ::GetTokenInformation(process_token, TokenUser, nullptr, 0, &size);
    if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER ||
        ::GetLastError() == ERROR_SUCCESS) {
      DWORD actual_size = 0;
      BYTE* token_user = new BYTE[size];
      if ((::GetTokenInformation(process_token, TokenUser, token_user, size,
                                 &actual_size)) &&
          (actual_size <= size)) {
        PSID sid = reinterpret_cast<TOKEN_USER*>(token_user)->User.Sid;
        if (::ConvertSidToStringSid(sid, user_sid))
          result = true;
      }
      delete[] token_user;
    }
    ::CloseHandle(process_token);
  }
  ::CloseHandle(process_handle);
  return result;
}

struct SetWindowPosParams {
  int x;
  int y;
  int width;
  int height;
  DWORD flags;
  HWND window_insert_after;
  bool success;
  std::set<HWND> shunted_hwnds;
};

BOOL CALLBACK ChromeWindowEnumProc(HWND hwnd, LPARAM lparam) {
  wchar_t window_class[MAX_PATH] = {};
  SetWindowPosParams* params = reinterpret_cast<SetWindowPosParams*>(lparam);

  if (!params->shunted_hwnds.count(hwnd) &&
      ::GetClassName(hwnd, window_class, std::size(window_class)) &&
      base::StartsWith(window_class, kChromeWindowClassPrefix,
                       base::CompareCase::INSENSITIVE_ASCII) &&
      ::SetWindowPos(hwnd, params->window_insert_after, params->x, params->y,
                     params->width, params->height, params->flags)) {
    params->shunted_hwnds.insert(hwnd);
    params->success = true;
  }

  // Return TRUE to ensure we hit all possible top-level Chrome windows as per
  // http://msdn.microsoft.com/en-us/library/windows/desktop/ms633498.aspx
  return TRUE;
}

// Returns true and populates |chrome_exe_path| with the path to chrome.exe if
// a valid installation can be found.
bool GetGoogleChromePath(base::FilePath* chrome_exe_path) {
  HKEY install_key = HKEY_LOCAL_MACHINE;
  if (!IsChromeInstalled(install_key)) {
    install_key = HKEY_CURRENT_USER;
    if (!IsChromeInstalled(install_key)) {
      return false;
    }
  }

  // Now grab the uninstall string from the appropriate ClientState key
  // and use that as the base for a path to chrome.exe.
  *chrome_exe_path = chrome_launcher_support::GetChromePathForInstallationLevel(
      install_key == HKEY_LOCAL_MACHINE
          ? chrome_launcher_support::SYSTEM_LEVEL_INSTALLATION
          : chrome_launcher_support::USER_LEVEL_INSTALLATION,
      false /* is_sxs */);
  return !chrome_exe_path->empty();
}

}  // namespace

BOOL __stdcall GoogleChromeCompatibilityCheck(BOOL set_flag,
                                              int shell_mode,
                                              DWORD* reasons) {
  DWORD local_reasons = 0;

  bool is_windows_version_supported = IsWindowsVersionSupported();
  // System requirements?
  if (!is_windows_version_supported)
    local_reasons |= GCCC_ERROR_OSNOTSUPPORTED;

  if (IsChromeInstalled(HKEY_LOCAL_MACHINE))
    local_reasons |= GCCC_ERROR_SYSTEMLEVELALREADYPRESENT;

  if (IsChromeInstalled(HKEY_CURRENT_USER))
    local_reasons |= GCCC_ERROR_USERLEVELALREADYPRESENT;

  if (shell_mode == GCAPI_INVOKED_UAC_ELEVATION) {
    // Only check that we have HKLM write permissions if we specify that
    // GCAPI is being invoked from an elevated shell, or in admin mode
    if (!VerifyHKLMAccess()) {
      local_reasons |= GCCC_ERROR_ACCESSDENIED;
    } else if (is_windows_version_supported && !VerifyAdminGroup()) {
      // For Vista or later check for elevation since even for admin user we
      // could be running in non-elevated mode. We require integrity level High.
      local_reasons |= GCCC_ERROR_INTEGRITYLEVEL;
    }
  }

  // Then only check whether we can re-offer, if everything else is OK.
  if (local_reasons == 0 && !CanReOfferChrome(set_flag))
    local_reasons |= GCCC_ERROR_ALREADYOFFERED;

  // Done. Copy/return results.
  if (reasons != nullptr)
    *reasons = local_reasons;

  return (local_reasons == 0);
}

BOOL __stdcall LaunchGoogleChrome() {
  base::FilePath chrome_exe_path;
  if (!GetGoogleChromePath(&chrome_exe_path))
    return false;

  ScopedCOMInitializer com_initializer;
  if (::CoInitializeSecurity(nullptr, -1, nullptr, nullptr,
                             RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
                             RPC_C_IMP_LEVEL_IDENTIFY, nullptr,
                             EOAC_DYNAMIC_CLOAKING, nullptr) != S_OK) {
    return false;
  }

  bool impersonation_success = false;
  absl::Cleanup revert_to_self = [&] {
    if (impersonation_success) {
      ::RevertToSelf();
    }
  };
  if (IsRunningElevated()) {
    wchar_t* curr_proc_sid;
    if (!GetUserIdForProcess(GetCurrentProcessId(), &curr_proc_sid)) {
      return false;
    }

    DWORD pid = 0;
    ::GetWindowThreadProcessId(::GetShellWindow(), &pid);
    if (pid <= 0) {
      ::LocalFree(curr_proc_sid);
      return false;
    }

    wchar_t* exp_proc_sid;
    if (GetUserIdForProcess(pid, &exp_proc_sid)) {
      if (_wcsicmp(curr_proc_sid, exp_proc_sid) == 0) {
        ScopedHandle process_handle(::OpenProcess(
            PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, TRUE, pid));
        if (process_handle.IsValid()) {
          HANDLE process_token = nullptr;
          HANDLE user_token = nullptr;
          if (::OpenProcessToken(process_handle.Get(),
                                 TOKEN_DUPLICATE | TOKEN_QUERY,
                                 &process_token) &&
              ::DuplicateTokenEx(process_token,
                                 TOKEN_IMPERSONATE | TOKEN_QUERY |
                                     TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE,
                                 nullptr, SecurityImpersonation, TokenPrimary,
                                 &user_token) &&
              (::ImpersonateLoggedOnUser(user_token) != 0)) {
            impersonation_success = true;
          }
          if (user_token)
            ::CloseHandle(user_token);
          if (process_token)
            ::CloseHandle(process_token);
        }
      }
      ::LocalFree(exp_proc_sid);
    }

    ::LocalFree(curr_proc_sid);
    if (!impersonation_success) {
      return false;
    }
  }

  base::CommandLine chrome_command(chrome_exe_path);

  // Chrome queries for the SxS IIDs first, with a fallback to the legacy IID,
  // to make sure that marshaling loads the proxy/stub from the correct (HKLM)
  // hive.
  // If Omaha's process launcher does not work, Omaha may not be installed at
  // system level. Try just running Chrome instead.
  ComPtr<IUnknown> unknown;
  ComPtr<IProcessLauncher> ipl;
  return (SUCCEEDED(::CoCreateInstance(__uuidof(ProcessLauncherClass), nullptr,
                                       CLSCTX_LOCAL_SERVER,
                                       IID_PPV_ARGS(&unknown))) &&
          (SUCCEEDED(unknown.CopyTo(__uuidof(IProcessLauncherSystem),
                                    IID_PPV_ARGS_Helper(&ipl))) ||
           SUCCEEDED(unknown.As(&ipl))) &&
          SUCCEEDED(ipl->LaunchCmdLine(
              chrome_command.GetCommandLineString().c_str()))) ||
         base::LaunchProcess(chrome_command.GetCommandLineString(),
                             base::LaunchOptions())
             .IsValid();
}

BOOL __stdcall LaunchGoogleChromeWithDimensions(int x,
                                                int y,
                                                int width,
                                                int height,
                                                bool in_background) {
  if (in_background) {
    base::FilePath chrome_exe_path;
    if (!GetGoogleChromePath(&chrome_exe_path))
      return false;

    // When launching in the background, use WMI to ensure that chrome.exe is
    // is not our child process. This prevents it from pushing itself to
    // foreground.
    base::CommandLine chrome_command(chrome_exe_path);

    ScopedCOMInitializer com_initializer;
    if (!base::win::WmiLaunchProcess(chrome_command.GetCommandLineString(),
                                     nullptr)) {
      // For some reason WMI failed. Try and launch the old fashioned way,
      // knowing that visual glitches will occur when the window pops up.
      if (!LaunchGoogleChrome())
        return false;
    }

  } else {
    if (!LaunchGoogleChrome())
      return false;
  }

  HWND hwnd_insert_after = in_background ? HWND_BOTTOM : nullptr;
  DWORD set_window_flags = in_background ? SWP_NOACTIVATE : SWP_NOZORDER;

  if (x == -1 && y == -1)
    set_window_flags |= SWP_NOMOVE;

  if (width == -1 && height == -1)
    set_window_flags |= SWP_NOSIZE;

  SetWindowPosParams enum_params = {
      x, y, width, height, set_window_flags, hwnd_insert_after, false};

  // Chrome may have been launched, but the window may not have appeared
  // yet. Wait for it to appear for 10 seconds, but exit if it takes longer
  // than that.
  int ms_elapsed = 0;
  int timeout = 10000;
  bool found_window = false;
  while (ms_elapsed < timeout) {
    // Enum all top-level windows looking for Chrome windows.
    ::EnumWindows(ChromeWindowEnumProc, reinterpret_cast<LPARAM>(&enum_params));

    // Give it five more seconds after finding the first window until we stop
    // shoving new windows into the background.
    if (!found_window && enum_params.success) {
      found_window = true;
      timeout = ms_elapsed + 5000;
    }

    Sleep(10);
    ms_elapsed += 10;
  }

  return found_window;
}

BOOL __stdcall LaunchGoogleChromeInBackground() {
  return LaunchGoogleChromeWithDimensions(-1, -1, -1, -1, true);
}

int __stdcall GoogleChromeDaysSinceLastRun() {
  int days_since_last_run = std::numeric_limits<int>::max();

  if (IsChromeInstalled(HKEY_LOCAL_MACHINE) ||
      IsChromeInstalled(HKEY_CURRENT_USER)) {
    RegKey client_state(HKEY_CURRENT_USER,
                        gcapi_internals::kChromeRegClientStateKey,
                        KEY_QUERY_VALUE | KEY_WOW64_32KEY);
    if (client_state.Valid()) {
      std::wstring last_run;
      int64_t last_run_value = 0;
      if (client_state.ReadValue(google_update::kRegLastRunTimeField,
                                 &last_run) == ERROR_SUCCESS &&
          base::StringToInt64(last_run, &last_run_value)) {
        Time last_run_time = Time::FromInternalValue(last_run_value);
        base::TimeDelta difference = Time::NowFromSystemTime() - last_run_time;

        // We can end up with negative numbers here, given changes in system
        // clock time or due to base::TimeDelta's int64_t -> int truncation.
        int new_days_since_last_run = difference.InDays();
        if (new_days_since_last_run >= 0 &&
            new_days_since_last_run < days_since_last_run) {
          days_since_last_run = new_days_since_last_run;
        }
      }
    }
  }

  if (days_since_last_run == std::numeric_limits<int>::max()) {
    days_since_last_run = -1;
  }

  return days_since_last_run;
}

BOOL __stdcall CanOfferReactivation(const wchar_t* brand_code,
                                    int shell_mode,
                                    DWORD* error_code) {
  DCHECK(error_code);

  if (!brand_code) {
    if (error_code)
      *error_code = REACTIVATE_ERROR_INVALID_INPUT;
    return FALSE;
  }

  int days_since_last_run = GoogleChromeDaysSinceLastRun();
  if (days_since_last_run >= 0 &&
      days_since_last_run < kReactivationMinDaysDormant) {
    if (error_code)
      *error_code = REACTIVATE_ERROR_NOTDORMANT;
    return FALSE;
  }

  // Only run the code below when this function is invoked from a standard,
  // non-elevated cmd shell.  This is because this section of code looks at
  // values in HKEY_CURRENT_USER, and we only want to look at the logged-in
  // user's HKCU, not the admin user's HKCU.
  if (shell_mode == GCAPI_INVOKED_STANDARD_SHELL) {
    if (!IsChromeInstalled(HKEY_LOCAL_MACHINE) &&
        !IsChromeInstalled(HKEY_CURRENT_USER)) {
      if (error_code)
        *error_code = REACTIVATE_ERROR_NOTINSTALLED;
      return FALSE;
    }

    if (HasBeenReactivated()) {
      if (error_code)
        *error_code = REACTIVATE_ERROR_ALREADY_REACTIVATED;
      return FALSE;
    }
  }

  return TRUE;
}

BOOL __stdcall ReactivateChrome(const wchar_t* brand_code,
                                int shell_mode,
                                DWORD* error_code) {
  if (!CanOfferReactivation(brand_code, shell_mode, error_code)) {
    return FALSE;
  }

  if (SetReactivationBrandCode(brand_code, shell_mode)) {
    return TRUE;
  }

  if (error_code) {
    *error_code = REACTIVATE_ERROR_REACTIVATION_FAILED;
  }
  return FALSE;
}

BOOL __stdcall CanOfferRelaunch(const wchar_t** partner_brandcode_list,
                                int partner_brandcode_list_length,
                                int shell_mode,
                                DWORD* error_code) {
  DCHECK(error_code);

  if (!partner_brandcode_list || partner_brandcode_list_length <= 0) {
    if (error_code)
      *error_code = RELAUNCH_ERROR_INVALID_INPUT;
    return FALSE;
  }

  // These conditions need to be satisfied for relaunch:
  // a) Chrome should be installed;
  if (!IsChromeInstalled(HKEY_LOCAL_MACHINE) &&
      (shell_mode != GCAPI_INVOKED_STANDARD_SHELL ||
       !IsChromeInstalled(HKEY_CURRENT_USER))) {
    if (error_code)
      *error_code = RELAUNCH_ERROR_NOTINSTALLED;
    return FALSE;
  }

  // b) the installed brandcode should belong to that partner (in
  // brandcode_list);
  std::wstring installed_brandcode;
  bool valid_brandcode = false;
  if (gcapi_internals::GetBrand(&installed_brandcode)) {
    for (int i = 0; i < partner_brandcode_list_length; ++i) {
      if (!_wcsicmp(installed_brandcode.c_str(), partner_brandcode_list[i])) {
        valid_brandcode = true;
        break;
      }
    }
  }

  if (!valid_brandcode) {
    if (error_code)
      *error_code = RELAUNCH_ERROR_INVALID_PARTNER;
    return FALSE;
  }

  // c) C1F ping should not have been sent;
  if (IsC1FSent()) {
    if (error_code)
      *error_code = RELAUNCH_ERROR_PINGS_SENT;
    return FALSE;
  }

  // d) a minimum period (30 days) must have passed since Chrome was last used;
  int days_since_last_run = GoogleChromeDaysSinceLastRun();
  if (days_since_last_run >= 0 &&
      days_since_last_run < kRelaunchMinDaysDormant) {
    if (error_code)
      *error_code = RELAUNCH_ERROR_NOTDORMANT;
    return FALSE;
  }

  // e) a minimum period (6 months) must have passed since the previous
  // relaunch offer for the current user;
  RegKey key;
  DWORD min_relaunch_date;
  if (key.Open(HKEY_CURRENT_USER, gcapi_internals::kChromeRegClientStateKey,
               KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
      key.ReadValueDW(kRelaunchAllowedAfterValue, &min_relaunch_date) ==
          ERROR_SUCCESS &&
      FormatDateOffsetByMonths(0) < min_relaunch_date) {
    if (error_code)
      *error_code = RELAUNCH_ERROR_ALREADY_RELAUNCHED;
    return FALSE;
  }

  return TRUE;
}

BOOL __stdcall SetRelaunchOffered(const wchar_t** partner_brandcode_list,
                                  int partner_brandcode_list_length,
                                  const wchar_t* relaunch_brandcode,
                                  int shell_mode,
                                  DWORD* error_code) {
  if (!CanOfferRelaunch(partner_brandcode_list, partner_brandcode_list_length,
                        shell_mode, error_code))
    return FALSE;

  // Store the relaunched brand code and the minimum date for relaunch (6 months
  // from now).
  RegKey key;
  if (key.Create(HKEY_CURRENT_USER, gcapi_internals::kChromeRegClientStateKey,
                 KEY_SET_VALUE | KEY_WOW64_32KEY) != ERROR_SUCCESS ||
      key.WriteValue(kRelaunchBrandcodeValue, relaunch_brandcode) !=
          ERROR_SUCCESS ||
      key.WriteValue(kRelaunchAllowedAfterValue, FormatDateOffsetByMonths(6)) !=
          ERROR_SUCCESS) {
    if (error_code)
      *error_code = RELAUNCH_ERROR_RELAUNCH_FAILED;
    return FALSE;
  }

  return TRUE;
}