| // 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/os_user_manager.h" |
| |
| #include <windows.h> |
| |
| #include <lm.h> |
| |
| #include <sddl.h> // For ConvertSidToStringSid() |
| #include <userenv.h> // For GetUserProfileDirectory() |
| #include <wincrypt.h> // For CryptXXX() |
| |
| #include <atlconv.h> |
| |
| #include <malloc.h> |
| #include <memory.h> |
| #include <stdlib.h> |
| |
| #include <iomanip> |
| #include <memory> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/macros.h" |
| #include "base/scoped_native_library.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/win/registry.h" |
| #include "base/win/win_util.h" |
| #include "chrome/credential_provider/gaiacp/gcp_utils.h" |
| #include "chrome/credential_provider/gaiacp/logging.h" |
| |
| namespace credential_provider { |
| |
| namespace { |
| |
| HRESULT GetDomainControllerServerForDomain(const wchar_t* domain, |
| LPBYTE* server) { |
| DCHECK(domain); |
| base::string16 local_domain = OSUserManager::GetLocalDomain(); |
| // If the domain is the local domain, then there is no domain controller. |
| if (wcsicmp(local_domain.c_str(), domain) == 0) { |
| return S_OK; |
| } |
| |
| NET_API_STATUS nsts = ::NetGetDCName(nullptr, domain, server); |
| if (nsts != NERR_Success) { |
| LOGFN(ERROR) << "NetGetDCName nsts=" << nsts; |
| } |
| return HRESULT_FROM_WIN32(nsts); |
| } |
| |
| } // namespace |
| |
| // static |
| OSUserManager** OSUserManager::GetInstanceStorage() { |
| static OSUserManager* instance = new OSUserManager(); |
| return &instance; |
| } |
| |
| // static |
| OSUserManager* OSUserManager::Get() { |
| return *GetInstanceStorage(); |
| } |
| |
| // static |
| void OSUserManager::SetInstanceForTesting(OSUserManager* instance) { |
| *GetInstanceStorage() = instance; |
| } |
| |
| // static |
| bool OSUserManager::IsDeviceDomainJoined() { |
| return base::win::IsEnrolledToDomain(); |
| } |
| |
| // static |
| base::string16 OSUserManager::GetLocalDomain() { |
| // If the domain is the current computer, then there is no domain controller. |
| wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1]; |
| DWORD length = base::size(computer_name); |
| if (!::GetComputerNameW(computer_name, &length)) |
| return base::string16(); |
| |
| return base::string16(computer_name, length); |
| } |
| |
| OSUserManager::~OSUserManager() {} |
| |
| #define IS_PASSWORD_STRONG_ENOUGH() \ |
| (cur_length > kMinPasswordLength) && \ |
| (has_upper + has_lower + has_digit + has_punct > 3) |
| |
| HRESULT OSUserManager::GenerateRandomPassword(wchar_t* password, int length) { |
| HRESULT hr; |
| HCRYPTPROV prov; |
| |
| // TODO(rogerta): read password policy GPOs to see what the password policy |
| // is for this machine in order to create one that adheres correctly. For |
| // now will generate a random password that fits typical strong password |
| // policies on windows. |
| const wchar_t kValidPasswordChars[] = |
| L"ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| L"abcdefghijklmnopqrstuvwxyz" |
| L"`1234567890-=" |
| L"~!@#$%^&*()_+" |
| L"[]\\;',./" |
| L"{}|:\"<>?"; |
| |
| if (length < kMinPasswordLength) |
| return E_INVALIDARG; |
| |
| if (!::CryptAcquireContext(&prov, nullptr, nullptr, PROV_RSA_FULL, |
| CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { |
| hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "CryptAcquireContext hr=" << putHR(hr); |
| return hr; |
| } |
| |
| int cur_length; |
| int has_upper; |
| int has_lower; |
| int has_digit; |
| int has_punct; |
| do { |
| cur_length = 0; |
| has_upper = 0; |
| has_lower = 0; |
| has_digit = 0; |
| has_punct = 0; |
| |
| wchar_t* p = password; |
| int remaining_length = length; |
| |
| while (remaining_length > 1) { |
| BYTE r; |
| if (!::CryptGenRandom(prov, sizeof(r), &r)) { |
| hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "CryptGenRandom hr=" << putHR(hr); |
| ::CryptReleaseContext(prov, 0); |
| return hr; |
| } |
| |
| wchar_t c = |
| kValidPasswordChars[r % (base::size(kValidPasswordChars) - 1)]; |
| *p++ = c; |
| ++cur_length; |
| --remaining_length; |
| |
| // Check if we have all the requirements for a strong password. |
| if (isupper(c)) |
| has_upper = 1; |
| if (islower(c)) |
| has_lower = 1; |
| if (isdigit(c)) |
| has_digit = 1; |
| if (ispunct(c)) |
| has_punct = 1; |
| |
| if (IS_PASSWORD_STRONG_ENOUGH()) |
| break; |
| } |
| |
| // Make sure password is terminated. |
| *p = 0; |
| |
| // Because this is a random function, there is a chance that the password |
| // might not be strong enough by the time the code reaches this point. This |
| // could happen if two categories of characters were never included. |
| // |
| // This highest probability of this happening would be if the caller |
| // specified the shortest password allowed and the missing characters came |
| // from the smallest two sets (say digits and lower case letters). |
| // |
| // This probability is equal to 1 minus the probability that all characters |
| // are upper case or punctuation: |
| // |
| // P(!strong) = 1 - P(all chars either upper or punctuation) |
| // |
| // There are valid 96 characters in all, of which 56 are not digits or |
| // lower case. The probability that any single character is upper case or |
| // punctuation is 56/96. If the minimum length is used, then: |
| // |
| // P(all chars either upper or punctuation) = (56/96)^23 = 4.1e-6 |
| // |
| // P(!strong) = 1 - 1.8e-4 = 0.999996 |
| // |
| // or about 4 in every million. The exponent is 23 and not 24 because the |
| // minimum password length includes the null terminiator. |
| // |
| // This means: |
| // - ~4 in every million will run this loop at least twice |
| // - ~4 in every 250 billion will run this loop at least thrice |
| // - ~4 in every 6.25e13 will run this loop at least four times |
| } while (!IS_PASSWORD_STRONG_ENOUGH()); |
| |
| ::CryptReleaseContext(prov, 0); |
| |
| return S_OK; |
| } |
| |
| HRESULT OSUserManager::GetUserFullname(const wchar_t* domain, |
| const wchar_t* username, |
| base::string16* fullname) { |
| DCHECK(fullname); |
| LPBYTE domain_server_buffer = nullptr; |
| HRESULT hr = |
| GetDomainControllerServerForDomain(domain, &domain_server_buffer); |
| if (FAILED(hr)) |
| return hr; |
| |
| std::unique_ptr<wchar_t, void (*)(wchar_t*)> domain_to_query( |
| reinterpret_cast<wchar_t*>(domain_server_buffer), [](wchar_t* p) { |
| if (p) |
| ::NetApiBufferFree(p); |
| }); |
| |
| LPBYTE buffer = nullptr; |
| NET_API_STATUS nsts = |
| ::NetUserGetInfo(domain_to_query.get(), username, 11, &buffer); |
| if (nsts != NERR_Success) { |
| LOGFN(ERROR) << "NetUserGetInfo(get full name) nsts=" << nsts; |
| return HRESULT_FROM_WIN32(nsts); |
| } |
| |
| USER_INFO_11* user_info = reinterpret_cast<USER_INFO_11*>(buffer); |
| *fullname = user_info->usri11_full_name; |
| return S_OK; |
| } |
| |
| HRESULT OSUserManager::AddUser(const wchar_t* username, |
| const wchar_t* password, |
| const wchar_t* fullname, |
| const wchar_t* comment, |
| bool add_to_users_group, |
| BSTR* sid, |
| DWORD* error) { |
| DCHECK(sid); |
| |
| base::string16 local_users_group_name; |
| // If adding to the local users group, make sure we can get the localized |
| // name for the group before proceeding. |
| if (add_to_users_group) { |
| HRESULT hr = LookupLocalizedNameForWellKnownSid(WinBuiltinUsersSid, |
| &local_users_group_name); |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "LookupLocalizedNameForWellKnownSid hr=" << putHR(hr); |
| return hr; |
| } |
| } |
| |
| USER_INFO_1 info; |
| memset(&info, 0, sizeof(info)); |
| info.usri1_comment = _wcsdup(comment); |
| info.usri1_flags = |
| UF_PASSWD_CANT_CHANGE | UF_DONT_EXPIRE_PASSWD | UF_NORMAL_ACCOUNT; |
| info.usri1_name = const_cast<wchar_t*>(username); |
| info.usri1_password = const_cast<wchar_t*>(password); |
| info.usri1_priv = USER_PRIV_USER; |
| |
| NET_API_STATUS nsts = |
| ::NetUserAdd(nullptr, 1, reinterpret_cast<LPBYTE>(&info), error); |
| free(info.usri1_comment); |
| |
| // Set the user's full name. |
| if (nsts == NERR_Success) { |
| USER_INFO_1011 info1011; |
| memset(&info1011, 0, sizeof(info1011)); |
| info1011.usri1011_full_name = const_cast<wchar_t*>(fullname); |
| nsts = ::NetUserSetInfo(nullptr, info.usri1_name, 1011, |
| reinterpret_cast<LPBYTE>(&info1011), error); |
| if (nsts != NERR_Success) { |
| LOGFN(ERROR) << "NetUserSetInfo(set full name) nsts=" << nsts; |
| } |
| } else { |
| LOGFN(ERROR) << "NetUserAdd nsts=" << nsts; |
| return HRESULT_FROM_WIN32(nsts); |
| } |
| |
| // Get the new user's SID and return it to caller. |
| LPBYTE buffer = nullptr; |
| nsts = ::NetUserGetInfo(nullptr, info.usri1_name, 4, &buffer); |
| if (nsts == NERR_Success) { |
| const USER_INFO_4* user_info = reinterpret_cast<const USER_INFO_4*>(buffer); |
| wchar_t* sidstr = nullptr; |
| if (::ConvertSidToStringSid(user_info->usri4_user_sid, &sidstr)) { |
| *sid = SysAllocString(T2COLE(sidstr)); |
| LOGFN(INFO) << "sid=" << sidstr; |
| ::LocalFree(sidstr); |
| } else { |
| LOGFN(ERROR) << "Could not convert SID to string"; |
| *sid = nullptr; |
| nsts = NERR_ProgNeedsExtraMem; |
| } |
| |
| if (nsts == NERR_Success && add_to_users_group) { |
| // Add to the well known local users group so that it appears on login |
| // screen. |
| LOCALGROUP_MEMBERS_INFO_0 member_info; |
| memset(&member_info, 0, sizeof(member_info)); |
| member_info.lgrmi0_sid = user_info->usri4_user_sid; |
| nsts = |
| ::NetLocalGroupAddMembers(nullptr, local_users_group_name.c_str(), 0, |
| reinterpret_cast<LPBYTE>(&member_info), 1); |
| if (nsts != NERR_Success && nsts != ERROR_MEMBER_IN_ALIAS) { |
| LOGFN(ERROR) << "NetLocalGroupAddMembers nsts=" << nsts; |
| } else { |
| nsts = NERR_Success; |
| } |
| } |
| |
| ::NetApiBufferFree(buffer); |
| } |
| |
| return (nsts == NERR_Success ? S_OK : HRESULT_FROM_WIN32(nsts)); |
| } |
| |
| HRESULT OSUserManager::ChangeUserPassword(const wchar_t* domain, |
| const wchar_t* username, |
| const wchar_t* old_password, |
| const wchar_t* new_password) { |
| LPBYTE domain_server_buffer = nullptr; |
| HRESULT hr = |
| GetDomainControllerServerForDomain(domain, &domain_server_buffer); |
| if (FAILED(hr)) |
| return hr; |
| |
| std::unique_ptr<wchar_t, void (*)(wchar_t*)> domain_to_query( |
| reinterpret_cast<wchar_t*>(domain_server_buffer), [](wchar_t* p) { |
| if (p) |
| ::NetApiBufferFree(p); |
| }); |
| |
| // Remove the UF_PASSWD_CANT_CHANGE flag temporarily so that the password can |
| // be changed. |
| LPBYTE buffer = nullptr; |
| NET_API_STATUS nsts = |
| ::NetUserGetInfo(domain_to_query.get(), username, 4, &buffer); |
| if (nsts != NERR_Success) { |
| LOGFN(ERROR) << "NetUserGetInfo(get password change flag) nsts=" << nsts; |
| return HRESULT_FROM_WIN32(nsts); |
| } |
| |
| USER_INFO_4* user_info = reinterpret_cast<USER_INFO_4*>(buffer); |
| DWORD original_user_flags = user_info->usri4_flags; |
| |
| bool flags_changed = false; |
| if ((user_info->usri4_flags & UF_PASSWD_CANT_CHANGE) != 0) { |
| user_info->usri4_flags &= ~UF_PASSWD_CANT_CHANGE; |
| nsts = |
| ::NetUserSetInfo(domain_to_query.get(), username, 4, buffer, nullptr); |
| if (nsts != NERR_Success) { |
| LOGFN(ERROR) << "NetUserSetInfo(allow password change) nsts=" << nsts; |
| ::NetApiBufferFree(buffer); |
| return HRESULT_FROM_WIN32(nsts); |
| } |
| |
| flags_changed = true; |
| } |
| |
| base::string16 password_domain = base::StringPrintf(L"\\\\%ls", domain); |
| |
| NET_API_STATUS changepassword_nsts = ::NetUserChangePassword( |
| password_domain.c_str(), username, old_password, new_password); |
| if (changepassword_nsts != NERR_Success) { |
| LOGFN(ERROR) << "Unable to change password for '" << username |
| << "' nsts=" << changepassword_nsts; |
| } |
| |
| if (flags_changed) { |
| user_info->usri4_flags = original_user_flags; |
| nsts = |
| ::NetUserSetInfo(domain_to_query.get(), username, 4, buffer, nullptr); |
| if (nsts != NERR_Success) { |
| LOGFN(ERROR) << "NetUserSetInfo(reset password change flag) nsts=" |
| << nsts; |
| } |
| } |
| |
| ::NetApiBufferFree(buffer); |
| |
| return HRESULT_FROM_WIN32(changepassword_nsts); |
| } |
| |
| HRESULT OSUserManager::SetUserPassword(const wchar_t* domain, |
| const wchar_t* username, |
| const wchar_t* password) { |
| LPBYTE domain_server_buffer = nullptr; |
| HRESULT hr = |
| GetDomainControllerServerForDomain(domain, &domain_server_buffer); |
| if (FAILED(hr)) |
| return hr; |
| |
| std::unique_ptr<wchar_t, void (*)(wchar_t*)> domain_to_query( |
| reinterpret_cast<wchar_t*>(domain_server_buffer), [](wchar_t* p) { |
| if (p) |
| ::NetApiBufferFree(p); |
| }); |
| |
| DWORD error = 0; |
| USER_INFO_1003 info1003; |
| NET_API_STATUS nsts; |
| memset(&info1003, 0, sizeof(info1003)); |
| info1003.usri1003_password = const_cast<wchar_t*>(password); |
| nsts = ::NetUserSetInfo(domain_to_query.get(), username, 1003, |
| reinterpret_cast<LPBYTE>(&info1003), &error); |
| if (nsts != NERR_Success) { |
| LOGFN(ERROR) << "Unable to change password for '" << username |
| << "' nsts=" << nsts; |
| } |
| |
| return HRESULT_FROM_WIN32(nsts); |
| } |
| |
| HRESULT OSUserManager::IsWindowsPasswordValid(const wchar_t* domain, |
| const wchar_t* username, |
| const wchar_t* password) { |
| // Check if the user exists before trying to log them on, because an error |
| // of ERROR_LOGON_FAILURE will be returned if the user does not exist |
| // or if the password is invalid. This function only wants to return |
| // S_FALSE on an ERROR_LOGON_FAILURE if the user exists. |
| PSID sid; |
| HRESULT hr = GetUserSID(domain, username, &sid); |
| |
| if (SUCCEEDED(hr)) { |
| ::LocalFree(sid); |
| base::win::ScopedHandle handle; |
| hr = CreateLogonToken(domain, username, password, /*interactive=*/true, |
| &handle); |
| if (SUCCEEDED(hr)) |
| return hr; |
| |
| if (hr == HRESULT_FROM_WIN32(ERROR_LOGON_FAILURE)) { |
| return S_FALSE; |
| // The following error codes represent sign in restrictions for the user |
| // that are returned if the user's password is valid. In these cases we |
| // don't want to return that the password is not valid. This is used to |
| // make sure that we don't think we need to update the user's password |
| // when in fact it is valid but they just can't sign in. |
| } else if (hr == HRESULT_FROM_WIN32(ERROR_ACCOUNT_RESTRICTION) || |
| hr == HRESULT_FROM_WIN32(ERROR_INVALID_LOGON_HOURS) || |
| hr == HRESULT_FROM_WIN32(ERROR_INVALID_WORKSTATION) || |
| hr == HRESULT_FROM_WIN32(ERROR_ACCOUNT_DISABLED) || |
| hr == HRESULT_FROM_WIN32(ERROR_LOGON_TYPE_NOT_GRANTED)) { |
| return S_OK; |
| } |
| } |
| |
| return hr; |
| } |
| |
| HRESULT OSUserManager::CreateLogonToken(const wchar_t* domain, |
| const wchar_t* username, |
| const wchar_t* password, |
| bool interactive, |
| base::win::ScopedHandle* token) { |
| return ::credential_provider::CreateLogonToken(domain, username, password, |
| interactive, token); |
| } |
| |
| HRESULT OSUserManager::GetUserSID(const wchar_t* domain, |
| const wchar_t* username, |
| base::string16* sid_string) { |
| DCHECK(sid_string); |
| sid_string->clear(); |
| |
| PSID sid; |
| HRESULT hr = GetUserSID(domain, username, &sid); |
| |
| if (SUCCEEDED(hr)) { |
| wchar_t* sid_buffer; |
| if (::ConvertSidToStringSid(sid, &sid_buffer)) { |
| *sid_string = sid_buffer; |
| ::LocalFree(sid_buffer); |
| } else { |
| hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "ConvertStringSidToSid hr=" << putHR(hr); |
| } |
| ::LocalFree(sid); |
| } |
| |
| return hr; |
| } |
| |
| HRESULT OSUserManager::GetUserSID(const wchar_t* domain, |
| const wchar_t* username, |
| PSID* sid) { |
| DCHECK(username); |
| DCHECK(sid); |
| |
| char sid_buffer[256]; |
| DWORD sid_length = base::size(sid_buffer); |
| wchar_t user_domain_buffer[kWindowsDomainBufferLength]; |
| DWORD domain_length = base::size(user_domain_buffer); |
| SID_NAME_USE use; |
| base::string16 username_with_domain = |
| base::string16(domain) + L"\\" + username; |
| |
| if (!::LookupAccountName(nullptr, username_with_domain.c_str(), sid_buffer, |
| &sid_length, user_domain_buffer, &domain_length, |
| &use)) { |
| return HRESULT_FROM_WIN32(::GetLastError()); |
| } |
| |
| // Check that the domain of the user found with LookupAccountName matches what |
| // is requested. |
| if (wcsicmp(domain, user_domain_buffer) != 0) { |
| LOGFN(ERROR) << "Domain mismatch " << domain << " " << user_domain_buffer; |
| |
| return HRESULT_FROM_WIN32(ERROR_NONE_MAPPED); |
| } |
| |
| *sid = ::LocalAlloc(LMEM_FIXED, sid_length); |
| ::CopySid(sid_length, *sid, sid_buffer); |
| |
| return S_OK; |
| } |
| |
| HRESULT OSUserManager::FindUserBySID(const wchar_t* sid, |
| wchar_t* username, |
| DWORD username_size, |
| wchar_t* domain, |
| DWORD domain_size) { |
| PSID psid; |
| if (!::ConvertStringSidToSidW(sid, &psid)) { |
| HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); |
| LOGFN(ERROR) << "ConvertStringSidToSidW sid=" << sid << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| HRESULT hr = S_OK; |
| DWORD name_length = username ? username_size : 0; |
| wchar_t local_domain_buffer[kWindowsDomainBufferLength]; |
| DWORD domain_length = base::size(local_domain_buffer); |
| SID_NAME_USE use; |
| if (!::LookupAccountSid(nullptr, psid, username, &name_length, |
| local_domain_buffer, &domain_length, &use)) { |
| hr = HRESULT_FROM_WIN32(::GetLastError()); |
| if (hr != HRESULT_FROM_WIN32(ERROR_NONE_MAPPED)) { |
| if (username_size == 0 && |
| hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) { |
| hr = S_OK; |
| } |
| } |
| } |
| |
| if (domain_size) { |
| if (domain_size <= domain_length) |
| return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); |
| wcscpy_s(domain, domain_size, local_domain_buffer); |
| } |
| |
| ::LocalFree(psid); |
| return hr; |
| } |
| |
| bool OSUserManager::IsUserDomainJoined(const base::string16& sid) { |
| wchar_t username[kWindowsUsernameBufferLength]; |
| wchar_t domain[kWindowsDomainBufferLength]; |
| |
| HRESULT hr = FindUserBySID(sid.c_str(), username, base::size(username), |
| domain, base::size(domain)); |
| |
| if (FAILED(hr)) { |
| LOGFN(ERROR) << "IsUserDomainJoined sid=" << sid << " hr=" << putHR(hr); |
| return hr; |
| } |
| |
| return !base::EqualsCaseInsensitiveASCII( |
| domain, OSUserManager::GetLocalDomain().c_str()); |
| } |
| |
| HRESULT OSUserManager::RemoveUser(const wchar_t* username, |
| const wchar_t* password) { |
| DCHECK(username); |
| DCHECK(password); |
| |
| // Get the user's profile directory. |
| base::win::ScopedHandle token; |
| wchar_t profiledir[MAX_PATH + 1]; |
| |
| base::string16 local_domain = OSUserManager::GetLocalDomain(); |
| |
| // Get the user's profile directory. Try a batch logon first, and if that |
| // fails then try an interactive logon. |
| HRESULT hr = CreateLogonToken(local_domain.c_str(), username, password, |
| /*interactive=*/false, &token); |
| if (FAILED(hr)) |
| hr = CreateLogonToken(local_domain.c_str(), username, password, |
| /*interactive=*/true, &token); |
| |
| if (SUCCEEDED(hr)) { |
| // Get the gaia user's profile directory so that it can be deleted. |
| DWORD length = base::size(profiledir) - 1; |
| if (!::GetUserProfileDirectory(token.Get(), profiledir, &length)) { |
| hr = HRESULT_FROM_WIN32(::GetLastError()); |
| if (hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) |
| LOGFN(ERROR) << "GetUserProfileDirectory hr=" << putHR(hr); |
| profiledir[0] = 0; |
| } |
| } else { |
| LOGFN(ERROR) << "CreateLogonToken hr=" << putHR(hr); |
| } |
| |
| // Remove the OS user. |
| NET_API_STATUS nsts = ::NetUserDel(nullptr, username); |
| if (nsts != NERR_Success) |
| LOGFN(ERROR) << "NetUserDel nsts=" << nsts; |
| |
| // Force delete the user's profile directory. |
| if (*profiledir && !base::DeleteFile(base::FilePath(profiledir), true)) |
| LOGFN(ERROR) << "base::DeleteFile"; |
| |
| return S_OK; |
| } |
| |
| HRESULT OSUserManager::ModifyUserAccessWithLogonHours(const wchar_t* domain, |
| const wchar_t* username, |
| bool allow) { |
| BYTE buffer[21] = {0x0}; |
| memset(buffer, allow ? 0xff : 0x0, sizeof(buffer)); |
| USER_INFO_1020 user_info{UNITS_PER_WEEK, buffer}; |
| |
| NET_API_STATUS nsts = ::NetUserSetInfo( |
| domain, username, 1020, reinterpret_cast<BYTE*>(&user_info), nullptr); |
| if (nsts != NERR_Success) { |
| LOGFN(ERROR) << "NetUserSetInfo(set logon time) nsts=" << nsts; |
| return HRESULT_FROM_WIN32(nsts); |
| } |
| |
| return S_OK; |
| } |
| |
| } // namespace credential_provider |