// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. #include "precomp.h" // UserExit macros #define UserExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) #define UserExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) #define UserExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) #define UserExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) #define UserExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) #define UserExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_USERUTIL, x, s, __VA_ARGS__) #define UserExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_USERUTIL, p, x, e, s, __VA_ARGS__) #define UserExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_USERUTIL, p, x, s, __VA_ARGS__) #define UserExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_USERUTIL, p, x, e, s, __VA_ARGS__) #define UserExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_USERUTIL, p, x, s, __VA_ARGS__) #define UserExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_USERUTIL, e, x, s, __VA_ARGS__) #define UserExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_USERUTIL, g, x, s, __VA_ARGS__) static BOOL CheckIsMemberHelper( __in_z LPCWSTR pwzGroupUserDomain, __in_ecount(cguiGroupData) const GROUP_USERS_INFO_0 *pguiGroupData, __in DWORD cguiGroupData ); /******************************************************************* UserBuildDomainUserName - builds a DOMAIN\USERNAME string ********************************************************************/ extern "C" HRESULT DAPI UserBuildDomainUserName( __out_ecount_z(cchDest) LPWSTR wzDest, __in int cchDest, __in_z LPCWSTR pwzName, __in_z LPCWSTR pwzDomain ) { HRESULT hr = S_OK; DWORD cchLeft = cchDest; WCHAR* pwz = wzDest; DWORD cchWz = cchDest; DWORD cch; cch = lstrlenW(pwzDomain); if (cch >= cchLeft) { hr = ERROR_MORE_DATA; UserExitOnFailure(hr, "Buffer size is not big enough to hold domain name: %ls", pwzDomain); } else if (cch > 0) { // handle the domain case hr = ::StringCchCopyNW(pwz, cchWz, pwzDomain, cchLeft - 1); // last parameter does not include '\0' UserExitOnFailure(hr, "Failed to copy Domain onto string."); cchLeft -= cch; pwz += cch; cchWz -= cch; if (1 >= cchLeft) { hr = ERROR_MORE_DATA; UserExitOnFailure(hr, "Insufficient buffer size while building domain user name"); } hr = ::StringCchCopyNW(pwz, cchWz, L"\\", cchLeft - 1); // last parameter does not include '\0' UserExitOnFailure(hr, "Failed to copy backslash onto string."); --cchLeft; ++pwz; --cchWz; } cch = lstrlenW(pwzName); if (cch >= cchLeft) { hr = ERROR_MORE_DATA; UserExitOnFailure(hr, "Buffer size is not big enough to hold user name: %ls", pwzName); } hr = ::StringCchCopyNW(pwz, cchWz, pwzName, cchLeft - 1); // last parameter does not include '\0' UserExitOnFailure(hr, "Failed to copy User name onto string."); LExit: return hr; } /******************************************************************* Checks whether a user is a member of a group - outputs the result via lpfMember ********************************************************************/ extern "C" HRESULT DAPI UserCheckIsMember( __in_z LPCWSTR pwzName, __in_z LPCWSTR pwzDomain, __in_z LPCWSTR pwzGroupName, __in_z LPCWSTR pwzGroupDomain, __out LPBOOL lpfMember ) { HRESULT hr = S_OK; UINT er = ERROR_SUCCESS; DWORD dwRead = 0; DWORD dwTotal = 0; LPCWSTR wz = NULL; GROUP_USERS_INFO_0 *pguiGroupData = NULL; WCHAR wzGroupUserDomain[MAX_DARWIN_COLUMN + 1]; // GROUPDOMAIN\GROUPNAME WCHAR wzUserDomain[MAX_DARWIN_COLUMN + 1]; // USERDOMAIN\USERNAME BSTR bstrUser = NULL; BSTR bstrGroup = NULL; IADsGroup *pGroup = NULL; VARIANT_BOOL vtBoolResult = VARIANT_FALSE; hr = UserBuildDomainUserName(wzGroupUserDomain, countof(wzGroupUserDomain), pwzGroupName, pwzGroupDomain); UserExitOnFailure(hr, "Failed to build group name from group domain %ls, group name %ls", pwzGroupDomain, pwzGroupName); hr = UserBuildDomainUserName(wzUserDomain, countof(wzUserDomain), pwzName, pwzDomain); UserExitOnFailure(hr, "Failed to build group name from group domain %ls, group name %ls", pwzGroupDomain, pwzGroupName); if (pwzDomain && *pwzDomain) { wz = pwzDomain; } er = ::NetUserGetGroups(wz, pwzName, 0, (LPBYTE *)&pguiGroupData, MAX_PREFERRED_LENGTH, &dwRead, &dwTotal); // Ignore these errors, and just go to the fallback checks if (ERROR_BAD_NETPATH == er || ERROR_INVALID_NAME == er || NERR_UserNotFound == er) { Trace(REPORT_VERBOSE, "failed to get groups for user %ls from domain %ls with error code 0x%x - continuing", pwzName, (wz != NULL) ? wz : L"", HRESULT_FROM_WIN32(er)); er = ERROR_SUCCESS; } UserExitOnWin32Error(er, hr, "Failed to get list of global groups for user while checking group membership information for user: %ls", pwzName); if (dwRead != dwTotal) { hr = HRESULT_FROM_WIN32(ERROR_MORE_DATA); UserExitOnRootFailure(hr, "Failed to get entire list of groups (global) for user while checking group membership information for user: %ls", pwzName); } if (CheckIsMemberHelper(wzGroupUserDomain, pguiGroupData, dwRead)) { *lpfMember = TRUE; ExitFunction1(hr = S_OK); } if (NULL != pguiGroupData) { ::NetApiBufferFree(pguiGroupData); pguiGroupData = NULL; } // If we fail with the global groups, try again with the local groups er = ::NetUserGetLocalGroups(NULL, wzUserDomain, 0, LG_INCLUDE_INDIRECT, (LPBYTE *)&pguiGroupData, MAX_PREFERRED_LENGTH, &dwRead, &dwTotal); // Ignore these errors, and just go to the fallback checks if (NERR_UserNotFound == er || NERR_DCNotFound == er || RPC_S_SERVER_UNAVAILABLE == er) { Trace(REPORT_VERBOSE, "failed to get local groups for user %ls from domain %ls with error code 0x%x - continuing", pwzName, (wz != NULL) ? wz : L"", HRESULT_FROM_WIN32(er)); er = ERROR_SUCCESS; } UserExitOnWin32Error(er, hr, "Failed to get list of groups for user while checking group membership information for user: %ls", pwzName); if (dwRead != dwTotal) { hr = HRESULT_FROM_WIN32(ERROR_MORE_DATA); UserExitOnRootFailure(hr, "Failed to get entire list of groups (local) for user while checking group membership information for user: %ls", pwzName); } if (CheckIsMemberHelper(wzGroupUserDomain, pguiGroupData, dwRead)) { *lpfMember = TRUE; ExitFunction1(hr = S_OK); } // If the above methods failed, let's try active directory hr = UserCreateADsPath(pwzDomain, pwzName, &bstrUser); UserExitOnFailure(hr, "failed to create user ADsPath in order to check group membership for group: %ls domain: %ls", pwzName, pwzDomain); hr = UserCreateADsPath(pwzGroupDomain, pwzGroupName, &bstrGroup); UserExitOnFailure(hr, "failed to create group ADsPath in order to check group membership for group: %ls domain: %ls", pwzGroupName, pwzGroupDomain); if (lstrlenW(pwzGroupDomain) > 0) { hr = ::ADsGetObject(bstrGroup, IID_IADsGroup, reinterpret_cast(&pGroup)); UserExitOnFailure(hr, "Failed to get group '%ls' from active directory.", reinterpret_cast(bstrGroup) ); hr = pGroup->IsMember(bstrUser, &vtBoolResult); UserExitOnFailure(hr, "Failed to check if user %ls is a member of group '%ls' using active directory.", reinterpret_cast(bstrUser), reinterpret_cast(bstrGroup) ); } if (vtBoolResult) { *lpfMember = TRUE; ExitFunction1(hr = S_OK); } hr = ::ADsGetObject(bstrGroup, IID_IADsGroup, reinterpret_cast(&pGroup)); UserExitOnFailure(hr, "Failed to get group '%ls' from active directory.", reinterpret_cast(bstrGroup) ); hr = pGroup->IsMember(bstrUser, &vtBoolResult); UserExitOnFailure(hr, "Failed to check if user %ls is a member of group '%ls' using active directory.", reinterpret_cast(bstrUser), reinterpret_cast(bstrGroup) ); if (vtBoolResult) { *lpfMember = TRUE; ExitFunction1(hr = S_OK); } LExit: ReleaseObject(pGroup); ReleaseBSTR(bstrUser); ReleaseBSTR(bstrGroup); if (NULL != pguiGroupData) { ::NetApiBufferFree(pguiGroupData); } return hr; } /******************************************************************* Takes a domain and name, and allocates a BSTR which represents DOMAIN\NAME's active directory path. The BSTR this function returns should be released manually using the ReleaseBSTR() macro. ********************************************************************/ extern "C" HRESULT DAPI UserCreateADsPath( __in_z LPCWSTR wzObjectDomain, __in_z LPCWSTR wzObjectName, __out BSTR *pbstrAdsPath ) { Assert(wzObjectDomain && wzObjectName && *wzObjectName); HRESULT hr = S_OK; LPWSTR pwzAdsPath = NULL; hr = StrAllocString(&pwzAdsPath, L"WinNT://", 0); UserExitOnFailure(hr, "failed to allocate AdsPath string"); if (*wzObjectDomain) { hr = StrAllocFormatted(&pwzAdsPath, L"%s/%s", wzObjectDomain, wzObjectName); UserExitOnFailure(hr, "failed to allocate AdsPath string"); } else if (NULL != wcsstr(wzObjectName, L"\\") || NULL != wcsstr(wzObjectName, L"/")) { hr = StrAllocConcat(&pwzAdsPath, wzObjectName, 0); UserExitOnFailure(hr, "failed to concat objectname: %ls", wzObjectName); } else { hr = StrAllocConcat(&pwzAdsPath, L"Localhost/", 0); UserExitOnFailure(hr, "failed to concat LocalHost/"); hr = StrAllocConcat(&pwzAdsPath, wzObjectName, 0); UserExitOnFailure(hr, "failed to concat object name: %ls", wzObjectName); } *pbstrAdsPath = ::SysAllocString(pwzAdsPath); if (NULL == *pbstrAdsPath) { hr = E_OUTOFMEMORY; } LExit: ReleaseStr(pwzAdsPath); return hr; } /******************************************************************* Helper function to check if pwzGroupUserDomain (in form of "domain\username" is a member of a given LOCALGROUP_USERS_INFO_0 structure. Useful to pass in the output from both NetUserGetGroups() and NetUserGetLocalGroups() ********************************************************************/ static BOOL CheckIsMemberHelper( __in_z LPCWSTR pwzGroupUserDomain, __in_ecount(cguiGroupData) const GROUP_USERS_INFO_0 *pguiGroupData, __in DWORD cguiGroupData ) { if (NULL == pguiGroupData) { return FALSE; } for (DWORD dwCounter = 0; dwCounter < cguiGroupData; ++dwCounter) { // If the user is a member of the group, set the output flag to true if (0 == lstrcmpiW(pwzGroupUserDomain, pguiGroupData[dwCounter].grui0_name)) { return TRUE; } } return FALSE; }