| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/credential_provider/gaiacp/gcp_utils.h" |
| |
| #include <wincred.h> // For <ntsecapi.h> |
| #include <windows.h> |
| #include <winternl.h> |
| |
| #define _NTDEF_ // Prevent redefition errors, must come after <winternl.h> |
| #include <MDMRegistration.h> // For RegisterDeviceWithManagement() |
| #include <ntsecapi.h> // For LsaLookupAuthenticationPackage() |
| #include <sddl.h> // For ConvertSidToStringSid() |
| #include <security.h> // For NEGOSSP_NAME_A |
| #include <wbemidl.h> |
| |
| #include <atlbase.h> |
| #include <atlcom.h> |
| #include <atlcomcli.h> |
| #include <atlconv.h> |
| |
| #include <malloc.h> |
| #include <memory.h> |
| #include <stdlib.h> |
| |
| #include <iomanip> |
| #include <memory> |
| |
| #include "base/base64.h" |
| #include "base/command_line.h" |
| #include "base/files/file.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/json/json_writer.h" |
| #include "base/macros.h" |
| #include "base/no_destructor.h" |
| #include "base/path_service.h" |
| #include "base/scoped_native_library.h" |
| #include "base/stl_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/win/current_module.h" |
| #include "base/win/embedded_i18n/language_selector.h" |
| #include "base/win/registry.h" |
| #include "base/win/win_util.h" |
| #include "chrome/common/chrome_version.h" |
| #include "chrome/credential_provider/common/gcp_strings.h" |
| #include "chrome/credential_provider/gaiacp/gaia_resources.h" |
| #include "chrome/credential_provider/gaiacp/logging.h" |
| |
| namespace credential_provider { |
| |
| namespace { |
| |
| constexpr char kSentinelFilename[] = "gcpw_startup.sentinel"; |
| constexpr base::FilePath::CharType kCredentialProviderFolder[] = |
| L"Credential Provider"; |
| constexpr int64_t kMaxConsecutiveCrashCount = 5; |
| |
| constexpr base::win::i18n::LanguageSelector::LangToOffset |
| kLanguageOffsetPairs[] = { |
| #define HANDLE_LANGUAGE(l_, o_) {L## #l_, o_}, |
| DO_LANGUAGES |
| #undef HANDLE_LANGUAGE |
| }; |
| |
| base::FilePath GetStartupSentinelLocation() { |
| base::FilePath sentienal_path; |
| if (!base::PathService::Get(base::DIR_COMMON_APP_DATA, &sentienal_path)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "PathService::Get(DIR_COMMON_APP_DATA) hr=" << putHR(hr); |
| return base::FilePath(); |
| } |
| |
| sentienal_path = sentienal_path.Append(GetInstallParentDirectoryName()) |
| .Append(kCredentialProviderFolder); |
| |
| return sentienal_path.Append(TEXT(CHROME_VERSION_STRING)) |
| .AppendASCII(kSentinelFilename); |
| } |
| |
| const base::win::i18n::LanguageSelector& GetLanguageSelector() { |
| static base::NoDestructor<base::win::i18n::LanguageSelector> instance( |
| base::string16(), kLanguageOffsetPairs); |
| return *instance; |
| } |
| |
| HRESULT RegisterWithGoogleDeviceManagement(const base::string16& mdm_url, |
| const base::string16& email, |
| const std::string& data) { |
| base::ScopedNativeLibrary library( |
| base::FilePath(FILE_PATH_LITERAL("MDMRegistration.dll"))); |
| if (!library.is_valid()) { |
| LOGFN(ERROR) << "base::ScopedNativeLibrary hr=" << putHR(E_NOTIMPL); |
| return E_NOTIMPL; |
| } |
| |
| using RegisterDeviceWithManagementFunction = |
| decltype(&::RegisterDeviceWithManagement); |
| RegisterDeviceWithManagementFunction |
| register_device_with_management_function = |
| reinterpret_cast<RegisterDeviceWithManagementFunction>( |
| library.GetFunctionPointer("RegisterDeviceWithManagement")); |
| if (!register_device_with_management_function) { |
| LOGFN(ERROR) << "library.GetFunctionPointer hr=" << putHR(E_NOTIMPL); |
| return E_NOTIMPL; |
| } |
| |
| std::string data_encoded; |
| base::Base64Encode(data, &data_encoded); |
| return register_device_with_management_function( |
| email.c_str(), mdm_url.c_str(), base::UTF8ToWide(data_encoded).c_str()); |
| } |
| |
| // Opens |path| with options that prevent the file from being read or written |
| // via another handle. As long as the returned object is alive, it is guaranteed |
| // that |path| isn't in use. It can however be deleted. |
| base::File GetFileLock(const base::FilePath& path) { |
| return base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ | |
| base::File::FLAG_EXCLUSIVE_READ | |
| base::File::FLAG_EXCLUSIVE_WRITE | |
| base::File::FLAG_SHARE_DELETE); |
| } |
| |
| // Deletes a specific GCP version from the disk. |
| void DeleteVersionDirectory(const base::FilePath& version_path) { |
| // Lock all exes and dlls for exclusive access while allowing deletes. Mark |
| // the files for deletion and release them, causing them to actually be |
| // deleted. This allows the deletion of the version path itself. |
| std::vector<base::File> locks; |
| const int types = base::FileEnumerator::FILES; |
| base::FileEnumerator enumerator_version(version_path, false, types, |
| FILE_PATH_LITERAL("*")); |
| bool all_deletes_succeeded = true; |
| for (base::FilePath path = enumerator_version.Next(); !path.empty(); |
| path = enumerator_version.Next()) { |
| if (!path.MatchesExtension(FILE_PATH_LITERAL(".exe")) && |
| !path.MatchesExtension(FILE_PATH_LITERAL(".dll"))) { |
| continue; |
| } |
| |
| // Open the file for exclusive access while allowing deletes. |
| locks.push_back(GetFileLock(path)); |
| if (!locks.back().IsValid()) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "Could not lock " << path << " hr=" << putHR(hr); |
| all_deletes_succeeded = false; |
| continue; |
| } |
| |
| // Mark the file for deletion. |
| HRESULT hr = base::DeleteFile(path, false); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "Could not delete " << path; |
| all_deletes_succeeded = false; |
| } |
| } |
| |
| // Release the locks, actually deleting the files. It is now possible to |
| // delete the version path. |
| locks.clear(); |
| if (all_deletes_succeeded && !base::DeleteFile(version_path, true)) |
| LOGFN(ERROR) << "Could not delete version " << version_path.BaseName(); |
| } |
| |
| } // namespace |
| |
| base::FilePath GetInstallDirectory() { |
| base::FilePath dest_path; |
| if (!base::PathService::Get(base::DIR_PROGRAM_FILES, &dest_path)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "PathService::Get(DIR_PROGRAM_FILES) hr=" << putHR(hr); |
| return base::FilePath(); |
| } |
| |
| dest_path = dest_path.Append(GetInstallParentDirectoryName()) |
| .Append(kCredentialProviderFolder); |
| |
| return dest_path; |
| } |
| |
| void DeleteVersionsExcept(const base::FilePath& gcp_path, |
| const base::string16& product_version) { |
| base::FilePath version = base::FilePath(product_version); |
| const int types = base::FileEnumerator::DIRECTORIES; |
| base::FileEnumerator enumerator(gcp_path, false, types, |
| FILE_PATH_LITERAL("*")); |
| for (base::FilePath name = enumerator.Next(); !name.empty(); |
| name = enumerator.Next()) { |
| base::FilePath basename = name.BaseName(); |
| if (version == basename) |
| continue; |
| |
| // Found an older version on the machine that can be deleted. This is |
| // best effort only. If any errors occurred they are logged by |
| // DeleteVersionDirectory(). |
| DeleteVersionDirectory(gcp_path.Append(basename)); |
| } |
| } |
| |
| // StdParentHandles /////////////////////////////////////////////////////////// |
| |
| StdParentHandles::StdParentHandles() {} |
| |
| StdParentHandles::~StdParentHandles() {} |
| |
| // ScopedStartupInfo ////////////////////////////////////////////////////////// |
| |
| ScopedStartupInfo::ScopedStartupInfo() { |
| memset(&info_, 0, sizeof(info_)); |
| info_.hStdInput = INVALID_HANDLE_VALUE; |
| info_.hStdOutput = INVALID_HANDLE_VALUE; |
| info_.hStdError = INVALID_HANDLE_VALUE; |
| info_.cb = sizeof(info_); |
| } |
| |
| ScopedStartupInfo::ScopedStartupInfo(const wchar_t* desktop) |
| : ScopedStartupInfo() { |
| DCHECK(desktop); |
| desktop_.assign(desktop); |
| info_.lpDesktop = const_cast<wchar_t*>(desktop_.c_str()); |
| } |
| |
| ScopedStartupInfo::~ScopedStartupInfo() { |
| Shutdown(); |
| } |
| |
| HRESULT ScopedStartupInfo::SetStdHandles(base::win::ScopedHandle* hstdin, |
| base::win::ScopedHandle* hstdout, |
| base::win::ScopedHandle* hstderr) { |
| if ((info_.dwFlags & STARTF_USESTDHANDLES) == STARTF_USESTDHANDLES) { |
| LOGFN(ERROR) << "Already set"; |
| return E_UNEXPECTED; |
| } |
| |
| // CreateProcessWithTokenW will fail if any of the std handles provided are |
| // invalid and the STARTF_USESTDHANDLES flag is set. So supply the default |
| // standard handle if no handle is given for some of the handles. This tells |
| // the process it can create its own local handles for these pipes as needed. |
| info_.dwFlags |= STARTF_USESTDHANDLES; |
| if (hstdin && hstdin->IsValid()) { |
| info_.hStdInput = hstdin->Take(); |
| } else { |
| info_.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE); |
| } |
| if (hstdout && hstdout->IsValid()) { |
| info_.hStdOutput = hstdout->Take(); |
| } else { |
| info_.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); |
| } |
| if (hstderr && hstderr->IsValid()) { |
| info_.hStdError = hstderr->Take(); |
| } else { |
| info_.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); |
| } |
| |
| return S_OK; |
| } |
| |
| void ScopedStartupInfo::Shutdown() { |
| if ((info_.dwFlags & STARTF_USESTDHANDLES) == STARTF_USESTDHANDLES) { |
| info_.dwFlags &= ~STARTF_USESTDHANDLES; |
| |
| if (info_.hStdInput != ::GetStdHandle(STD_INPUT_HANDLE)) |
| ::CloseHandle(info_.hStdInput); |
| if (info_.hStdOutput != ::GetStdHandle(STD_OUTPUT_HANDLE)) |
| ::CloseHandle(info_.hStdOutput); |
| if (info_.hStdError != ::GetStdHandle(STD_ERROR_HANDLE)) |
| ::CloseHandle(info_.hStdError); |
| info_.hStdInput = INVALID_HANDLE_VALUE; |
| info_.hStdOutput = INVALID_HANDLE_VALUE; |
| info_.hStdError = INVALID_HANDLE_VALUE; |
| } |
| } |
| |
| // Waits for a process to terminate while capturing output from |output_handle| |
| // to the buffer |output_buffer| of size |buffer_size|. The buffer is expected |
| // to be relatively small. The exit code of the process is written to |
| // |exit_code|. |
| HRESULT WaitForProcess(base::win::ScopedHandle::Handle process_handle, |
| const StdParentHandles& parent_handles, |
| DWORD* exit_code, |
| char* output_buffer, |
| int buffer_size) { |
| LOGFN(INFO); |
| DCHECK(exit_code); |
| DCHECK_GT(buffer_size, 0); |
| |
| output_buffer[0] = 0; |
| |
| HANDLE output_handle = parent_handles.hstdout_read.Get(); |
| |
| for (bool is_done = false; !is_done;) { |
| char buffer[80]; |
| DWORD length = base::size(buffer) - 1; |
| HRESULT hr = S_OK; |
| |
| const DWORD kThreeMinutesInMs = 3 * 60 * 1000; |
| DWORD ret = ::WaitForSingleObject(output_handle, |
| kThreeMinutesInMs); // timeout ms |
| switch (ret) { |
| case WAIT_OBJECT_0: { |
| int index = ret - WAIT_OBJECT_0; |
| LOGFN(INFO) << "WAIT_OBJECT_" << index; |
| if (!::ReadFile(output_handle, buffer, length, &length, nullptr)) { |
| hr = HRESULT_FROM_WIN32(::GetLastError()); |
| if (hr != HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)) |
| LOGFN(ERROR) << "ReadFile(" << index << ") hr=" << putHR(hr); |
| } else { |
| LOGFN(INFO) << "ReadFile(" << index << ") length=" << length; |
| buffer[length] = 0; |
| } |
| break; |
| } |
| case WAIT_IO_COMPLETION: |
| // This is normal. Just ignore. |
| LOGFN(INFO) << "WaitForMultipleObjectsEx WAIT_IO_COMPLETION"; |
| break; |
| case WAIT_TIMEOUT: { |
| // User took too long to log in, so kill UI process. |
| LOGFN(INFO) << "WaitForMultipleObjectsEx WAIT_TIMEOUT, killing UI"; |
| ::TerminateProcess(process_handle, kUiecTimeout); |
| is_done = true; |
| break; |
| } |
| case WAIT_FAILED: |
| default: { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "WaitForMultipleObjectsEx hr=" << putHR(hr); |
| is_done = true; |
| break; |
| } |
| } |
| |
| // Copy the read buffer to the output buffer. If the pipe was broken, |
| // we can break our loop and wait for the process to die. |
| if (hr == HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)) { |
| LOGFN(INFO) << "Stop waiting for output buffer"; |
| break; |
| } else { |
| strcat_s(output_buffer, buffer_size, buffer); |
| } |
| } |
| |
| // At this point both stdout and stderr have been closed. Wait on the process |
| // handle for the process to terminate, getting the exit code. If the |
| // process does not terminate gracefully, kill it before returning. |
| DWORD ret = ::WaitForSingleObject(process_handle, 10000); |
| if (ret == 0) { |
| if (::GetExitCodeProcess(process_handle, exit_code)) { |
| LOGFN(INFO) << "Process terminated with exit code " << *exit_code; |
| } else { |
| LOGFN(INFO) << "Process terminated without exit code"; |
| *exit_code = kUiecAbort; |
| } |
| } else { |
| LOGFN(INFO) << "UI did not terminiate within 10 seconds, killing now"; |
| ::TerminateProcess(process_handle, kUiecKilled); |
| *exit_code = kUiecKilled; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT CreateLogonToken(const wchar_t* domain, |
| const wchar_t* username, |
| const wchar_t* password, |
| bool interactive, |
| base::win::ScopedHandle* token) { |
| DCHECK(domain); |
| DCHECK(username); |
| DCHECK(password); |
| DCHECK(token); |
| |
| DWORD logon_type = |
| interactive ? LOGON32_LOGON_INTERACTIVE : LOGON32_LOGON_BATCH; |
| base::win::ScopedHandle::Handle handle; |
| |
| if (!::LogonUserW(username, domain, password, logon_type, |
| LOGON32_PROVIDER_DEFAULT, &handle)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "LogonUserW hr=" << putHR(hr); |
| return hr; |
| } |
| base::win::ScopedHandle primary_token(handle); |
| |
| if (!::CreateRestrictedToken(primary_token.Get(), DISABLE_MAX_PRIVILEGE, 0, |
| nullptr, 0, nullptr, 0, nullptr, &handle)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "CreateRestrictedToken hr=" << putHR(hr); |
| return hr; |
| } |
| token->Set(handle); |
| return S_OK; |
| } |
| |
| HRESULT CreateJobForSignin(base::win::ScopedHandle* job) { |
| LOGFN(INFO); |
| DCHECK(job); |
| |
| job->Set(::CreateJobObject(nullptr, nullptr)); |
| if (!job->IsValid()) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "CreateJobObject hr=" << putHR(hr); |
| return hr; |
| } |
| |
| JOBOBJECT_BASIC_UI_RESTRICTIONS ui; |
| ui.UIRestrictionsClass = |
| JOB_OBJECT_UILIMIT_DESKTOP | // Create/switch desktops. |
| JOB_OBJECT_UILIMIT_HANDLES | // Only access own handles. |
| JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS | // Cannot set sys params. |
| JOB_OBJECT_UILIMIT_WRITECLIPBOARD; // Cannot write to clipboard. |
| if (!::SetInformationJobObject(job->Get(), JobObjectBasicUIRestrictions, &ui, |
| sizeof(ui))) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "SetInformationJobObject hr=" << putHR(hr); |
| return hr; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT CreatePipeForChildProcess(bool child_reads, |
| bool use_nul, |
| base::win::ScopedHandle* reading, |
| base::win::ScopedHandle* writing) { |
| // Make sure that all handles created here are inheritable. It is important |
| // that the child side handle is inherited. |
| SECURITY_ATTRIBUTES sa; |
| sa.nLength = sizeof(sa); |
| sa.bInheritHandle = TRUE; |
| sa.lpSecurityDescriptor = nullptr; |
| |
| if (use_nul) { |
| base::win::ScopedHandle h( |
| ::CreateFileW(L"nul:", FILE_GENERIC_READ | FILE_GENERIC_WRITE, |
| FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, |
| &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)); |
| if (!h.IsValid()) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "CreateFile(nul) hr=" << putHR(hr); |
| return hr; |
| } |
| |
| if (child_reads) { |
| reading->Set(h.Take()); |
| } else { |
| writing->Set(h.Take()); |
| } |
| } else { |
| base::win::ScopedHandle::Handle temp_handle1; |
| base::win::ScopedHandle::Handle temp_handle2; |
| if (!::CreatePipe(&temp_handle1, &temp_handle2, &sa, 0)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "CreatePipe(reading) hr=" << putHR(hr); |
| return hr; |
| } |
| reading->Set(temp_handle1); |
| writing->Set(temp_handle2); |
| |
| // Make sure parent side is not inherited. |
| if (!::SetHandleInformation(child_reads ? writing->Get() : reading->Get(), |
| HANDLE_FLAG_INHERIT, 0)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "SetHandleInformation(parent) hr=" << putHR(hr); |
| return hr; |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT InitializeStdHandles(CommDirection direction, |
| StdHandlesToCreate to_create, |
| ScopedStartupInfo* startupinfo, |
| StdParentHandles* parent_handles) { |
| LOGFN(INFO); |
| DCHECK(startupinfo); |
| DCHECK(parent_handles); |
| |
| base::win::ScopedHandle hstdin_read; |
| base::win::ScopedHandle hstdin_write; |
| if ((to_create & kStdInput) != 0) { |
| HRESULT hr = CreatePipeForChildProcess( |
| true, // child reads |
| direction == CommDirection::kChildToParentOnly, // use nul |
| &hstdin_read, &hstdin_write); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "CreatePipeForChildProcess(stdin) hr=" << putHR(hr); |
| return hr; |
| } |
| } |
| |
| base::win::ScopedHandle hstdout_read; |
| base::win::ScopedHandle hstdout_write; |
| if ((to_create & kStdOutput) != 0) { |
| HRESULT hr = CreatePipeForChildProcess( |
| false, // child reads |
| direction == CommDirection::kParentToChildOnly, // use nul |
| &hstdout_read, &hstdout_write); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "CreatePipeForChildProcess(stdout) hr=" << putHR(hr); |
| return hr; |
| } |
| } |
| |
| base::win::ScopedHandle hstderr_read; |
| base::win::ScopedHandle hstderr_write; |
| if ((to_create & kStdError) != 0) { |
| HRESULT hr = CreatePipeForChildProcess( |
| false, // child reads |
| direction == CommDirection::kParentToChildOnly, // use nul |
| &hstderr_read, &hstderr_write); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "CreatePipeForChildProcess(stderr) hr=" << putHR(hr); |
| return hr; |
| } |
| } |
| |
| HRESULT hr = |
| startupinfo->SetStdHandles(&hstdin_read, &hstdout_write, &hstderr_write); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "startupinfo->SetStdHandles hr=" << putHR(hr); |
| return hr; |
| } |
| |
| parent_handles->hstdin_write.Set(hstdin_write.Take()); |
| parent_handles->hstdout_read.Set(hstdout_read.Take()); |
| parent_handles->hstderr_read.Set(hstderr_read.Take()); |
| return S_OK; |
| } |
| |
| HRESULT GetPathToDllFromHandle(HINSTANCE dll_handle, |
| base::FilePath* path_to_dll) { |
| wchar_t path[MAX_PATH]; |
| DWORD length = base::size(path); |
| length = ::GetModuleFileName(dll_handle, path, length); |
| if (length == 0 || length >= base::size(path)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "GetModuleFileNameW hr=" << putHR(hr); |
| return hr; |
| } |
| |
| *path_to_dll = base::FilePath(base::StringPiece16(path, length)); |
| return S_OK; |
| } |
| |
| HRESULT GetEntryPointArgumentForRunDll(HINSTANCE dll_handle, |
| const wchar_t* entrypoint, |
| base::string16* entrypoint_arg) { |
| DCHECK(entrypoint); |
| DCHECK(entrypoint_arg); |
| |
| entrypoint_arg->clear(); |
| |
| // rundll32 expects the first command line argument to be the path to the |
| // DLL, followed by a comma and the name of the function to call. There can |
| // be no spaces around the comma. There can be no spaces in the path. It |
| // is recommended to use the short path name of the DLL. |
| base::FilePath path_to_dll; |
| HRESULT hr = GetPathToDllFromHandle(dll_handle, &path_to_dll); |
| if (FAILED(hr)) |
| return hr; |
| |
| wchar_t short_path[MAX_PATH]; |
| DWORD short_length = base::size(short_path); |
| short_length = |
| ::GetShortPathName(path_to_dll.value().c_str(), short_path, short_length); |
| if (short_length >= base::size(short_path)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "GetShortPathNameW hr=" << putHR(hr); |
| return hr; |
| } |
| |
| *entrypoint_arg = |
| base::string16(base::StringPrintf(L"%ls,%ls", short_path, entrypoint)); |
| |
| // In tests, the current module is the unittest exe, not the real dll. |
| // The unittest exe does not expose entrypoints, so return S_FALSE as a hint |
| // that this will not work. The command line is built anyway though so |
| // tests of the command line construction can be written. |
| return wcsicmp(wcsrchr(path_to_dll.value().c_str(), L'.'), L".dll") == 0 |
| ? S_OK |
| : S_FALSE; |
| } |
| |
| HRESULT GetCommandLineForEntrypoint(HINSTANCE dll_handle, |
| const wchar_t* entrypoint, |
| base::CommandLine* command_line) { |
| DCHECK(entrypoint); |
| DCHECK(command_line); |
| |
| // Build the full path to rundll32. |
| base::FilePath system_dir; |
| if (!base::PathService::Get(base::DIR_SYSTEM, &system_dir)) |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| |
| command_line->SetProgram( |
| system_dir.Append(FILE_PATH_LITERAL("rundll32.exe"))); |
| |
| base::string16 entrypoint_arg; |
| HRESULT hr = |
| GetEntryPointArgumentForRunDll(dll_handle, entrypoint, &entrypoint_arg); |
| if (SUCCEEDED(hr)) |
| command_line->AppendArgNative(entrypoint_arg); |
| |
| return hr; |
| } |
| |
| // Gets the serial number of the machine based on the recipe found at |
| // https://docs.microsoft.com/en-us/windows/desktop/WmiSdk/example-creating-a-wmi-application |
| HRESULT GetMachineSerialNumber(base::string16* serial_number) { |
| USES_CONVERSION; |
| DCHECK(serial_number); |
| |
| serial_number->clear(); |
| |
| // Make sure COM is initialized. |
| HRESULT hr = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "CoInitializeEx hr=" << putHR(hr); |
| return hr; |
| } |
| |
| hr = ::CoInitializeSecurity( |
| nullptr, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, |
| RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_NONE, nullptr); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "CoInitializeSecurity hr=" << putHR(hr); |
| return hr; |
| } |
| |
| CComPtr<IWbemLocator> locator; |
| hr = locator.CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "CoCreateInstance(CLSID_WbemLocator) hr=" << putHR(hr); |
| return hr; |
| } |
| |
| CComPtr<IWbemServices> services; |
| hr = locator->ConnectServer(CComBSTR(W2COLE(L"ROOT\\CIMV2")), nullptr, |
| nullptr, nullptr, 0, nullptr, nullptr, &services); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "locator->ConnectServer hr=" << putHR(hr); |
| return hr; |
| } |
| |
| CComPtr<IEnumWbemClassObject> enum_wbem; |
| hr = services->ExecQuery(CComBSTR(W2COLE(L"WQL")), |
| CComBSTR(W2COLE(L"select * from Win32_Bios")), |
| WBEM_FLAG_FORWARD_ONLY, nullptr, &enum_wbem); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "services->ExecQuery hr=" << putHR(hr); |
| return hr; |
| } |
| |
| while (SUCCEEDED(hr) && serial_number->empty()) { |
| CComPtr<IWbemClassObject> class_obj; |
| ULONG count = 1; |
| hr = enum_wbem->Next(WBEM_INFINITE, 1, &class_obj, &count); |
| if (count == 0) |
| break; |
| |
| VARIANT var; |
| hr = class_obj->Get(L"SerialNumber", 0, &var, 0, 0); |
| if (SUCCEEDED(hr) && var.vt == VT_BSTR) |
| serial_number->assign(OLE2CW(var.bstrVal)); |
| |
| VariantClear(&var); |
| } |
| |
| LOGFN(INFO) << "GetMachineSerialNumber sn=" << *serial_number |
| << " hr=" << putHR(hr); |
| |
| return hr; |
| } |
| |
| HRESULT EnrollToGoogleMdmIfNeeded(const base::DictionaryValue& properties) { |
| USES_CONVERSION; |
| LOGFN(INFO); |
| |
| // Enroll to Google MDM if not already enrolled. |
| |
| HRESULT hr = E_FAIL; |
| BOOL is_registered = base::win::IsDeviceRegisteredWithManagement(); |
| LOGFN(INFO) << "MDM is_registered=" << is_registered; |
| |
| if (!is_registered) { |
| base::string16 email = GetDictString(&properties, kKeyEmail); |
| base::string16 token = GetDictString(&properties, kKeyMdmIdToken); |
| base::string16 mdm_url = GetDictString(&properties, kKeyMdmUrl); |
| |
| if (email.empty()) { |
| LOGFN(ERROR) << "Email is empty"; |
| return E_INVALIDARG; |
| } |
| |
| if (token.empty()) { |
| LOGFN(ERROR) << "MDM id token is empty"; |
| return E_INVALIDARG; |
| } |
| |
| if (mdm_url.empty()) { |
| LOGFN(ERROR) << "No MDM URL specified"; |
| return E_INVALIDARG; |
| } |
| |
| LOGFN(INFO) << "MDM_URL=" << mdm_url |
| << " token=" << base::string16(token.c_str(), 10); |
| |
| // Build the json data needed by the server. |
| base::DictionaryValue registration_data; |
| base::string16 serial_number; |
| hr = GetMachineSerialNumber(&serial_number); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "GetMachineSerialNumber hr=" << putHR(hr); |
| return hr; |
| } |
| |
| registration_data.SetString("serial_number", serial_number); |
| registration_data.SetString("id_token", token); |
| std::string registration_data_str; |
| if (!base::JSONWriter::Write(registration_data, ®istration_data_str)) { |
| LOGFN(ERROR) << "JSONWriter::Write(registration_data)"; |
| return E_FAIL; |
| } |
| |
| hr = RegisterWithGoogleDeviceManagement(mdm_url, email, |
| registration_data_str); |
| LOGFN(INFO) << "RegisterWithGoogleDeviceManagement hr=" << putHR(hr); |
| } |
| |
| return hr; |
| } |
| |
| bool VerifyStartupSentinel() { |
| // Always try to write to the startup sentinel file. If writing or opening |
| // fails for any reason (file locked, no access etc) consider this a failure. |
| // If no sentinel file path can be found this probably means that we are |
| // running in a unit test so just let the verification pass in this case. |
| base::FilePath startup_sentinel_path = GetStartupSentinelLocation(); |
| if (!startup_sentinel_path.empty()) { |
| base::FilePath startup_sentinel_directory = startup_sentinel_path.DirName(); |
| if (!base::DirectoryExists(startup_sentinel_directory)) { |
| base::File::Error error; |
| if (!base::CreateDirectoryAndGetError(startup_sentinel_directory, |
| &error)) { |
| LOGFN(ERROR) << "Could not create sentinel directory='" |
| << startup_sentinel_directory << "' error=" << error; |
| return false; |
| } |
| } |
| base::File startup_sentinel( |
| startup_sentinel_path, |
| base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND); |
| |
| // Keep writing to the sentinel file until we have reached |
| // |kMaxConsecutiveCrashCount| at which point it is assumed that GCPW |
| // is crashing continuously and should be disabled. |
| if (!startup_sentinel.IsValid() || |
| startup_sentinel.GetLength() >= kMaxConsecutiveCrashCount) { |
| return false; |
| } |
| |
| return startup_sentinel.WriteAtCurrentPos("0", 1) == 1; |
| } |
| |
| return true; |
| } |
| |
| void DeleteStartupSentinel() { |
| base::FilePath startup_sentinel_path = GetStartupSentinelLocation(); |
| if (base::PathExists(startup_sentinel_path) && |
| !base::DeleteFile(startup_sentinel_path, false)) { |
| LOGFN(ERROR) << "Failed to delete sentinel file: " << startup_sentinel_path; |
| } |
| } |
| |
| base::string16 GetStringResource(int base_message_id) { |
| base::string16 localized_string; |
| |
| int message_id = base_message_id + GetLanguageSelector().offset(); |
| const ATLSTRINGRESOURCEIMAGE* image = |
| AtlGetStringResourceImage(_AtlBaseModule.GetModuleInstance(), message_id); |
| if (image) { |
| localized_string = base::string16(image->achString, image->nLength); |
| } else { |
| NOTREACHED() << "Unable to find resource id " << message_id; |
| } |
| |
| return localized_string; |
| } |
| |
| base::string16 GetSelectedLanguage() { |
| return GetLanguageSelector().matched_candidate(); |
| } |
| |
| base::string16 GetDictString(const base::DictionaryValue* dict, |
| const char* name) { |
| DCHECK(name); |
| auto* value = dict->FindKey(name); |
| return value && value->is_string() ? base::UTF8ToUTF16(value->GetString()) |
| : base::string16(); |
| } |
| |
| base::string16 GetDictString(const std::unique_ptr<base::DictionaryValue>& dict, |
| const char* name) { |
| return GetDictString(dict.get(), name); |
| } |
| |
| std::string GetDictStringUTF8(const base::DictionaryValue* dict, |
| const char* name) { |
| DCHECK(name); |
| auto* value = dict->FindKey(name); |
| return value && value->is_string() ? value->GetString() : std::string(); |
| } |
| |
| std::string GetDictStringUTF8( |
| const std::unique_ptr<base::DictionaryValue>& dict, |
| const char* name) { |
| return GetDictStringUTF8(dict.get(), name); |
| } |
| |
| base::FilePath::StringType GetInstallParentDirectoryName() { |
| #if defined(GOOGLE_CHROME_BUILD) |
| return FILE_PATH_LITERAL("Google"); |
| #else |
| return FILE_PATH_LITERAL("Chromium"); |
| #endif |
| } |
| |
| FakesForTesting::FakesForTesting() {} |
| |
| FakesForTesting::~FakesForTesting() {} |
| |
| } // namespace credential_provider |