| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/credential_provider/setup/setup_lib.h" |
| |
| #include <shlobj.h> |
| |
| #include <iomanip> |
| #include <string> |
| |
| #include "base/command_line.h" |
| #include "base/file_version_info.h" |
| #include "base/files/file.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/path_service.h" |
| #include "base/process/launch.h" |
| #include "base/scoped_native_library.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/win/atl.h" |
| #include "base/win/registry.h" |
| #include "base/win/scoped_handle.h" |
| #include "base/win/win_util.h" |
| #include "base/win/windows_version.h" |
| #include "chrome/credential_provider/common/gcp_strings.h" |
| #include "chrome/credential_provider/extension/extension_strings.h" |
| #include "chrome/credential_provider/extension/extension_utils.h" |
| #include "chrome/credential_provider/gaiacp/gcp_utils.h" |
| #include "chrome/credential_provider/gaiacp/logging.h" |
| #include "chrome/credential_provider/gaiacp/reg_utils.h" |
| #include "chrome/credential_provider/setup/gcpw_files.h" |
| #include "chrome/credential_provider/setup/setup_utils.h" |
| #include "chrome/installer/util/delete_after_reboot_helper.h" |
| |
| namespace credential_provider { |
| |
| namespace { |
| |
| // Creates the directory where GCP is to be installed. |
| base::FilePath CreateInstallDirectory() { |
| base::FilePath dest_path = GetInstallDirectory(); |
| |
| if (dest_path.empty()) { |
| return dest_path; |
| } |
| |
| if (!base::CreateDirectory(dest_path)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "base::CreateDirectory hr=" << putHR(hr); |
| return base::FilePath(); |
| } |
| |
| return dest_path; |
| } |
| |
| // Copies the files specified in |names| from |src_path| to |dest_path| creating |
| // any intermedidate subdirectories of |dest_path| as needed. Both |src_path| |
| // and |dest_path| are full paths. |names| are paths relative to |src_path| |
| // and are copied to the same relative path under |dest_path|. |
| HRESULT InstallFiles(const base::FilePath& src_path, |
| const base::FilePath& dest_path, |
| const base::FilePath::StringType names[], |
| size_t length) { |
| for (size_t i = 0; i < length; ++i) { |
| base::FilePath src = src_path.Append(names[i]); |
| base::FilePath dest = dest_path.Append(names[i]); |
| |
| // Make sure parent of destination file exists. |
| if (!base::CreateDirectory(dest.DirName())) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "CreateDirectory hr=" << putHR(hr) |
| << " name=" << names[i]; |
| return hr; |
| } |
| |
| if (!base::CopyFile(src, dest)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "CopyFile hr=" << putHR(hr) << " name=" << names[i]; |
| return hr; |
| } |
| LOGFN(INFO) << "Installed name=" << names[i]; |
| } |
| |
| return S_OK; |
| } |
| |
| // Registers the named DLLs by calling the DllRegisterServer entrypoint. The |
| // DLLs are specified with the |names| argument which are paths relative to |
| // |dest_path|. |fakes| is non-null during unit tests to install fakes into |
| // the loaded DLL. |
| HRESULT RegisterDlls(const base::FilePath& dest_path, |
| const base::FilePath::StringType names[], |
| size_t length, |
| FakesForTesting* fakes) { |
| bool has_failures = false; |
| |
| for (size_t i = 0; i < length; ++i) { |
| base::ScopedNativeLibrary library(dest_path.Append(names[i])); |
| |
| if (fakes) { |
| SetFakesForTestingFn set_fakes_for_testing_fn = |
| reinterpret_cast<SetFakesForTestingFn>( |
| library.GetFunctionPointer("SetFakesForTesting")); |
| if (set_fakes_for_testing_fn) |
| (*set_fakes_for_testing_fn)(fakes); |
| } |
| |
| FARPROC register_server_fn = reinterpret_cast<FARPROC>( |
| library.GetFunctionPointer("DllRegisterServer")); |
| HRESULT hr = S_OK; |
| |
| if (register_server_fn) { |
| hr = static_cast<HRESULT>((*register_server_fn)()); |
| LOGFN(VERBOSE) << "Registered name=" << names[i] << " hr=" << putHR(hr); |
| } else { |
| LOGFN(ERROR) << "Failed to register name=" << names[i]; |
| hr = E_NOTIMPL; |
| } |
| has_failures |= FAILED(hr); |
| } |
| |
| return has_failures ? E_UNEXPECTED : S_OK; |
| } |
| |
| // Unregisters the named DLLs by calling the DllUneegisterServer entrypoint. |
| // The DLLs are specified with the |names| argument which are paths relative to |
| // |dest_path|. |fakes| is non-null during unit tests to install fakes into |
| // the loaded DLL. |
| HRESULT UnregisterDlls(const base::FilePath& dest_path, |
| const base::FilePath::StringType names[], |
| size_t length, |
| FakesForTesting* fakes) { |
| bool has_failures = false; |
| |
| for (size_t i = 0; i < length; ++i) { |
| base::ScopedNativeLibrary library(dest_path.Append(names[i])); |
| |
| if (fakes) { |
| SetFakesForTestingFn pmfn = reinterpret_cast<SetFakesForTestingFn>( |
| library.GetFunctionPointer("SetFakesForTesting")); |
| if (pmfn) |
| (*pmfn)(fakes); |
| } |
| |
| FARPROC pfn = reinterpret_cast<FARPROC>( |
| library.GetFunctionPointer("DllUnregisterServer")); |
| HRESULT hr = pfn ? static_cast<HRESULT>((*pfn)()) : E_UNEXPECTED; |
| LOGFN(VERBOSE) << "Unregistered name=" << names[i] << " hr=" << putHR(hr); |
| has_failures |= FAILED(hr); |
| } |
| |
| return has_failures ? E_UNEXPECTED : S_OK; |
| } |
| |
| } // namespace |
| |
| HRESULT DoInstall(const base::FilePath& installer_path, |
| const std::wstring& product_version, |
| FakesForTesting* fakes) { |
| const base::FilePath gcp_path = CreateInstallDirectory(); |
| if (gcp_path.empty()) |
| return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); |
| |
| base::FilePath dest_path = gcp_path.Append(product_version); |
| LOGFN(VERBOSE) << "Install to: " << dest_path; |
| |
| // Make sure nothing under the destination directory is pending delete |
| // after reboot, so that files installed now won't get deleted later. |
| if (!RemoveFromMovesPendingReboot(dest_path)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "RemoveFromMovesPendingReboot hr=" << putHR(hr); |
| return hr; |
| } |
| |
| base::FilePath src_path = installer_path.DirName(); |
| auto install_files = |
| credential_provider::GCPWFiles::Get()->GetEffectiveInstallFiles(); |
| HRESULT hr = InstallFiles(src_path, dest_path, install_files.data(), |
| install_files.size()); |
| if (FAILED(hr)) |
| return hr; |
| |
| auto register_dlls = |
| credential_provider::GCPWFiles::Get()->GetRegistrationFiles(); |
| hr = RegisterDlls(dest_path, register_dlls.data(), register_dlls.size(), |
| fakes); |
| if (FAILED(hr)) |
| return hr; |
| |
| // If all is good, try to delete all other versions on best effort basis. |
| if (SUCCEEDED(hr)) |
| DeleteVersionsExcept(gcp_path, product_version); |
| |
| base::FilePath setup_exe_path = dest_path.Append(kCredentialProviderSetupExe); |
| hr = WriteUninstallRegistryValues(setup_exe_path); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "WriteUninstallRegistryValues failed hr=" << putHR(hr); |
| // Uninstall registry values are written for MSI wrapper. Failing to write |
| // them will only impact uninstalling through uninstall shortcuts on |
| // Windows. There is still a workaround to uninstall by calling |
| // "gcp_setup.exe --uninstall" from a terminal. So, ignoring the failure in |
| // this case until we support rollback of installation that fails mid-way |
| // through. |
| } |
| |
| hr = WriteCredentialProviderRegistryValues(dest_path); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "WriteCredentialProviderRegistryValues failed hr=" |
| << putHR(hr); |
| } |
| |
| if (extension::IsGCPWExtensionEnabled()) { |
| DWORD error_code = extension::InstallGCPWExtension( |
| dest_path.Append(kCredentialProviderExtensionExe)); |
| if (error_code != ERROR_SUCCESS) { |
| LOGFN(ERROR) << "InstallGCPWExtension failed win32=" << error_code; |
| return HRESULT_FROM_WIN32(error_code); |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT DoUninstall(const base::FilePath& installer_path, |
| const base::FilePath& dest_path, |
| FakesForTesting* fakes) { |
| bool has_failures = false; |
| |
| auto register_dlls = |
| credential_provider::GCPWFiles::Get()->GetRegistrationFiles(); |
| // Do all actions best effort and keep going. |
| has_failures |= FAILED(UnregisterDlls(dest_path, register_dlls.data(), |
| register_dlls.size(), fakes)); |
| |
| // If the DLLs are unregistered, Credential Provider will not be loaded by |
| // Winlogon. Therefore, it is safe to delete the startup sentinel file at this |
| // time. |
| if (!has_failures) |
| DeleteStartupSentinel(); |
| |
| has_failures |= |
| FAILED(HRESULT_FROM_WIN32(extension::UninstallGCPWExtension())); |
| |
| // Delete all files in the destination directory. This directory does not |
| // contain any configuration files or anything else user generated. |
| if (!base::DeletePathRecursively(dest_path)) { |
| has_failures = true; |
| ScheduleDirectoryForDeletion(dest_path); |
| } |
| |
| // |dest_path| is of the form %ProgramFile%\Google\GCP\VERSION. Now try to |
| // delete the parent directory if possible. |
| if (base::IsDirectoryEmpty(dest_path.DirName())) |
| has_failures |= !base::DeleteFile(dest_path.DirName()); |
| |
| StandaloneInstallerConfigurator* installer_config = |
| StandaloneInstallerConfigurator::Get(); |
| if (installer_config->IsStandaloneInstallation()) { |
| has_failures |= FAILED(HRESULT_FROM_WIN32( |
| StandaloneInstallerConfigurator::Get()->RemoveUninstallKey())); |
| } |
| |
| // TODO(rogerta): ask user to reboot if anything went wrong during uninstall. |
| |
| return has_failures ? E_UNEXPECTED : S_OK; |
| } |
| |
| HRESULT RelaunchUninstaller(const base::FilePath& installer_path) { |
| base::FilePath temp_path; |
| if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("gcp"), &temp_path)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "CreateNewTempDirectory hr=" << putHR(hr); |
| return hr; |
| } |
| |
| base::FilePath new_installer_path = |
| temp_path.Append(installer_path.BaseName()); |
| |
| if (!base::CopyFile(installer_path, new_installer_path)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "Could not copy uninstaller hr=" << putHR(hr) << " '" |
| << installer_path << "' --> '" << new_installer_path << "'"; |
| return hr; |
| } |
| |
| base::win::ScopedHandle::Handle this_process_handle_handle; |
| if (!::DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), |
| GetCurrentProcess(), &this_process_handle_handle, 0, |
| TRUE, // Inheritable. |
| DUPLICATE_SAME_ACCESS)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "Error duplicating parent process handle."; |
| return hr; |
| } |
| base::win::ScopedHandle this_process_handle(this_process_handle_handle); |
| |
| base::CommandLine cmdline(new_installer_path); |
| cmdline.AppendSwitch(switches::kUninstall); |
| cmdline.AppendSwitchPath(switches::kInstallPath, installer_path.DirName()); |
| cmdline.AppendSwitchNative(switches::kParentHandle, |
| base::NumberToWString(base::win::HandleToUint32( |
| this_process_handle_handle))); |
| |
| LOGFN(VERBOSE) << "Cmd: " << cmdline.GetCommandLineString(); |
| |
| base::LaunchOptions options; |
| options.handles_to_inherit.push_back(this_process_handle_handle); |
| options.current_directory = temp_path; |
| base::Process process(base::LaunchProcess(cmdline, options)); |
| |
| return process.IsValid() ? S_OK : E_FAIL; |
| } |
| |
| int EnableStatsCollection(const base::CommandLine& cmdline) { |
| DCHECK(cmdline.HasSwitch(switches::kEnableStats) || |
| cmdline.HasSwitch(switches::kDisableStats)); |
| |
| bool enable = !cmdline.HasSwitch(switches::kDisableStats); |
| |
| base::win::RegKey key; |
| LONG status = key.Create(HKEY_LOCAL_MACHINE, kRegUpdaterClientStateAppPath, |
| KEY_SET_VALUE | KEY_WOW64_32KEY); |
| if (status != ERROR_SUCCESS) { |
| LOGFN(ERROR) << "Unable to open omaha key=" << kRegUpdaterClientStateAppPath |
| << " status=" << status; |
| } else { |
| status = key.WriteValue(kRegUsageStatsName, enable ? 1 : 0); |
| if (status != ERROR_SUCCESS) { |
| LOGFN(ERROR) << "Unable to write " << kRegUsageStatsName |
| << " value status=" << status; |
| } |
| } |
| |
| return status == ERROR_SUCCESS ? 0 : -1; |
| } |
| |
| HRESULT WriteUninstallRegistryValues(const base::FilePath& setup_exe) { |
| base::win::RegKey key; |
| LONG status = key.Create(HKEY_LOCAL_MACHINE, kRegUpdaterClientStateAppPath, |
| KEY_SET_VALUE | KEY_WOW64_32KEY); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to open " << kRegUpdaterClientStateAppPath |
| << " hr=" << putHR(hr); |
| return hr; |
| } else { |
| status = |
| key.WriteValue(kRegUninstallStringField, setup_exe.value().c_str()); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegUninstallStringField |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| base::CommandLine uninstall_arguments(base::CommandLine::NO_PROGRAM); |
| uninstall_arguments.AppendSwitch(switches::kUninstall); |
| |
| status = key.WriteValue(kRegUninstallArgumentsField, |
| uninstall_arguments.GetCommandLineString().c_str()); |
| if (status != ERROR_SUCCESS) { |
| HRESULT hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to write " << kRegUninstallArgumentsField |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| } |
| |
| return HRESULT_FROM_WIN32(status); |
| } |
| |
| HRESULT WriteCredentialProviderRegistryValues( |
| const base::FilePath& install_path) { |
| HRESULT hr = |
| StandaloneInstallerConfigurator::Get()->AddUninstallKey(install_path); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "AddUninstallKey hr=" << putHR(hr); |
| return hr; |
| } |
| |
| base::win::RegKey key; |
| LONG status = key.Create(HKEY_LOCAL_MACHINE, kGcpRootKeyName, KEY_SET_VALUE); |
| if (status != ERROR_SUCCESS) { |
| hr = HRESULT_FROM_WIN32(status); |
| LOGFN(ERROR) << "Unable to create " << kGcpRootKeyName |
| << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| } // namespace credential_provider |