NetGroupGetUsers returns error when group have members from other domain - winapi

I am using NetGroupGetUsers to get the members of Active Directory groups.
I have two domains and some groups have user members from both domains. When I try to list the members of this groups with mixed users, NetGroupGetUsers returns the error code 2147 (NERR_CfgParamNotFound).
Ex.:
"DOMAIN1" has "Group1" and "user1"
"DOMAIN2" has "user2"
"user1" and "user2" are both members of "Group1"
when I try to use NetGroupGetUsers to get "Group1" members it returns NERR_CfgParamNotFound
if I remove "user2" from "Group1", NetGroupGetUsers returns Success.
This case I described is not supported or there is a way to access this data using MFC Network Management?
Exemple of how I am using NetGroupGetUsers:
HRESULT MySampleFunction(CString strGroupName)
{
TCHAR tcGroup[CREDUI_MAX_USERNAME_LENGTH + 1] = { 0 };
TCHAR tcDomain[CREDUI_MAX_DOMAIN_TARGET_LENGTH + 1] = { 0 };
DWORD dwError = ::CredUIParseUserName(strGroupName, tcGroup, SizeOfArray(tcGroup), tcDomain, SizeOfArray(tcDomain));
if (dwError != NO_ERROR)
return MAKE_HRESULT(1, FACILITY_WIN32, dwError);
GROUP_USERS_INFO_0* pGroupUsersInfo = NULL;
DWORD dwEntriesRead = 0;
DWORD dwTotalEntries = 0;
NET_API_STATUS apiStatus = NetGroupGetUsers(tcDomain, tcGroup, 0, (LPBYTE*)&pGroupUsersInfo, MAX_PREFERRED_LENGTH, &dwEntriesRead, &dwTotalEntries, NULL);
if (apiStatus != NERR_Success)
return MAKE_HRESULT(1, FACILITY_WIN32, apiStatus);
if (pGroupUsersInfo != NULL)
NetApiBufferFree(pGroupUsersInfo);
return S_OK
}

Related

LsaGetLogonSessionData API returns session data without logonServer

I am using LsaGetLogonSessionData to retrieve the logonSessionData on a machine that is using AD and Kerberos in a Domain Controller as a login method.
However, the logonServer field is empty in logonSessionData. But from commandline, I'm able to get the logonServer by using nltest/dsgetdc or set l command.
Anyone knows why I am not able to get logonServer from the code?
Attaching code snippet as below:
PSECURITY_LOGON_SESSION_DATA logonSessionData;
for (ULONG i = 0; i < logonSessionCount; i++) {
if (::LsaGetLogonSessionData(logonSessionList + i, &logonSessionData) != 0) {
LsaFreeReturnBuffer(logonSessionList);
DWORD error = ::GetLastError();
LOG_ERROR("GetActiveLogonUser: LsaGetLogonSessionData failed ", error);
return error;
}
if (std::count(sessions.begin(), sessions.end(), logonSessionData->Session) &&
(logonSessionData->LogonType == Interactive || logonSessionData->LogonType == RemoteInteractive)) {
const std::wstring logonServer = logonSessionData->LogonServer.Buffer;
if (!logonServer.size()) continue;
std::wstring domain = logonSessionData->LogonDomain.Buffer;
std::wstring userName = logonSessionData->UserName.Buffer;
logonUsers[StringUtils::GetStringFromWString(domain + std::wstring(TEXT("-")) + userName)] =
logonSessionData->Session;
}
}

SetPerTcpConnectionEStats and GetPerTcpConnectionEStats is returning error code 1214 (invalid NETNAME)

I am trying to get stats similar to the ones shown in "Resource Monitor" in windows in a my c++ service. For that I have used the example shown at https://learn.microsoft.com/en-gb/windows/win32/api/iphlpapi/nf-iphlpapi-getpertcp6connectionestats?redirectedfrom=MSDN. But I am stuck because SetPerTcpConnectionEStats and GetPerTcpConnectionEStats is returning with error code 1214. The only difference btn. the code in the example at above mentioned link and mine is that I am not working on a particular local and remote port but on all the entries in the tcp table, but I don't think that should make any difference.
Can somebody help me out here?
I can reproduce this error if I work with all the entries. According to the sample you linked, in addition to local and remote port, GetTcpRow has a search parameter MIB_TCP_STATE_ESTAB. The state is the normal state for the data transfer phase of the TCP connection.
The following sample works for me.
DWORD RunEstatsTest(bool v6) //set as IPv4(FALSE)
{
PMIB_TCPTABLE tcpTable = NULL;
DWORD status, size = 0;
status = GetTcpTable(tcpTable, &size, TRUE);
if (status != ERROR_INSUFFICIENT_BUFFER) {
return status;
}
tcpTable = (PMIB_TCPTABLE)malloc(size);
if (tcpTable == NULL) {
return ERROR_OUTOFMEMORY;
}
status = GetTcpTable(tcpTable, &size, TRUE);
if (status != ERROR_SUCCESS) {
free(tcpTable);
return status;
}
for (int i = 0; i < tcpTable->dwNumEntries; i++) {
if (MIB_TCP_STATE_ESTAB == tcpTable->table[i].State)
{
ToggleAllEstats(&tcpTable->table[i], TRUE, v6);
GetAllEstats(&tcpTable->table[i], v6);
ToggleAllEstats(&tcpTable->table[i], FALSE, v6);
}
}
free(tcpTable);
return ERROR_SUCCESS;
}

File access check on remote location

I am using a code similar to below to check the access to the folders/files. Source
bool CanAccessFolder( LPCTSTR folderName, DWORD genericAccessRights )
{
bool bRet = false;
DWORD length = 0;
if (!::GetFileSecurity( folderName, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
| DACL_SECURITY_INFORMATION, NULL, NULL, &length ) &&
ERROR_INSUFFICIENT_BUFFER == ::GetLastError()) {
PSECURITY_DESCRIPTOR security = static_cast< PSECURITY_DESCRIPTOR >( ::malloc( length ) );
if (security && ::GetFileSecurity( folderName, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION
| DACL_SECURITY_INFORMATION, security, length, &length )) {
HANDLE hToken = NULL;
if (::OpenProcessToken( ::GetCurrentProcess(), TOKEN_IMPERSONATE | TOKEN_QUERY |
TOKEN_DUPLICATE | STANDARD_RIGHTS_READ, &hToken )) {
HANDLE hImpersonatedToken = NULL;
if (::DuplicateToken( hToken, SecurityImpersonation, &hImpersonatedToken )) {
GENERIC_MAPPING mapping = { 0xFFFFFFFF };
PRIVILEGE_SET privileges = { 0 };
DWORD grantedAccess = 0, privilegesLength = sizeof( privileges );
BOOL result = FALSE;
mapping.GenericRead = FILE_GENERIC_READ;
mapping.GenericWrite = FILE_GENERIC_WRITE;
mapping.GenericExecute = FILE_GENERIC_EXECUTE;
mapping.GenericAll = FILE_ALL_ACCESS;
::MapGenericMask( &genericAccessRights, &mapping );
if (::AccessCheck( security, hImpersonatedToken, genericAccessRights,
&mapping, &privileges, &privilegesLength, &grantedAccess, &result )) {
bRet = (result == TRUE);
}
::CloseHandle( hImpersonatedToken );
}
::CloseHandle( hToken );
}
::free( security );
}
}
return bRet;
}
This code is failing on the customer machines (where our app runs) and returning answer as false for local users which are not in the Windows Domains as well as for users which have Samba based network shares.
One of our in house Windows experts have commented that perhaps in the situation above, user SID returned by the network filer does NOT match the SID querying for permissions in the AccessCheck function.
He then mentioned that perhaps, to achieve such thing, we would have to correctly create a logged-on token of some kind and then impersonate it to get the correct access.
How do I impersonate the domain user (or some user that network filer permits) so that network filer can allow my code to get the correct access info ?
Any help will be appreciated.
AccessCheck not always returns true, it returns false if the security descriptor not allows the requested access rights to the client identified by the access token.
If you want to impersonate a domain user that network filer permits, use LogonUser function(not suitable for remote computer) to get the token of this domain user, then ImpersonateLoggedOnUser, lets the calling thread impersonate the security context of this domain user.

Get the local login name for the given Microsoft Account returned by NetWkstaUserEnum API on Windows 8 and above

I am using NetWkstaUserEnum() to get the local users name and its domain details.
Till Windows 7 it used to return only the login name and it worked fine. From Windows 8 onwards Microsoft Account was added and for this type of account the API started returning the Microsoft Account name instead of the local login name.
For example it returned username#outlook.com instead of usern_0000 which is the actual Windows local login name.
I cannot use NetUserEnum() as it does not return the domain name of the user.
So I need to get the local login name for the given Microsoft Account returned by NetWkstaUserEnum() API.
Any help will be appreciated.
Finally I was able to locate a way to get the Windows username for the given Microsoft Account.It uses NetUserGetInfo() to get the Microsoft Account name for the given username.
Code Snippet:
do
{
ntStatus = NetUserEnum(szSvr, 0, 0, (LPBYTE*)&userInfo0, dwPrefMaxLen, &dwEntriesRead, &dwTotalEntries, &dwResumeHandle);
if( (ntStatus == NERR_Success) || (ntStatus == ERROR_MORE_DATA) )
{
tmpinfo = userInfo0;
for( i = 0; (i < dwEntriesRead); i++ )
{
if(tmpinfo != NULL)
{
ntStatus = NetUserGetInfo(szSvr, tmpinfo->usri0_name, 24,(LPBYTE*)&userInfo24);
if(ntStatus == NERR_Success)
{
CString internetPrincipalName = (LPCWSTR) userInfo24->usri24_internet_principal_name;
if(LoginUsrStr.CompareNoCase(internetPrincipalName) == 0)
{
OutputDebugString("###### Account Found ######");
localAccount = (LPCWSTR) tmpinfo->usri0_name;
userFound = TRUE;
break;
}
}
}
tmpinfo++;
}
}
if( userInfo0 != NULL )
{
NetApiBufferFree( userInfo0 ) ;
userInfo0 = NULL ;
}
if( userInfo24 != NULL )
{
NetApiBufferFree( userInfo24 ) ;
userInfo24 = NULL ;
}
} while( userFound == FALSE && ntStatus == ERROR_MORE_DATA ) ;

ERROR_BAD_INHERITANCE_ACL from SetNamedSecurityInfo?

What does ERROR_BAD_INHERITANCE_ACL returned from SetNamedSecurityInfo imply? In this case I'm adding a user to a directory's ACL. I've looked at the directory in question and its rights seem reasonable before the call. But the calls fails.
Any thoughts?
Here is the code snippet doing the work (and as I paste it here, I'm wondering about the NO_MULTIPLE_TRUSTEE value):
pAAP is a pointer to a structure with the following members:
CString objName; // name of object
SE_OBJECT_TYPE ObjectType; // type of object
CString trustee; // trustee for new ACE (explicit user name)
CString targetComputer;
bool bNeedWrite;
DWORD dwRes = 0;
PACL pOldDACL = NULL, pNewDACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
EXPLICIT_ACCESS ea = {0};
CSID trusteeSID;
bool bGotSID = false;
if(0 == wcsncmp(pAAP->trustee, L"SID:", 4)) //4 = len of SID: //GLOK
bGotSID = CSID::FromString((LPWSTR)((LPCWSTR)pAAP->trustee + 4), trusteeSID);
else
bGotSID = CSID::FromAccount(pAAP->targetComputer, pAAP->trustee, trusteeSID);
if(false == bGotSID)
{
Log(logDEBUG, L"CSID::FromAccount failed for [%s] on [%s]. GLE=%s", pAAP->trustee, pAAP->targetComputer, GetSystemErrorMessage(GetLastError()));
_ASSERT(0);
goto Cleanup;
}
// Get a pointer to the existing DACL.
dwRes = GetNamedSecurityInfo(pAAP->objName.LockBuffer(), pAAP->ObjectType, DACL_SECURITY_INFORMATION,
NULL, NULL, &pOldDACL, NULL, &pSD);
pAAP->objName.UnlockBuffer();
if (ERROR_SUCCESS != dwRes)
{
Log(logDEBUG, L"GetNamedSecurityInfo failed on [%s] for [%s] on [%s]. GLE=%s", pAAP->objName, pAAP->trustee, pAAP->targetComputer, GetSystemErrorMessage(dwRes));
//_ASSERT(ERROR_FILE_NOT_FOUND == dwRes);
goto Cleanup;
}
// Initialize an EXPLICIT_ACCESS structure for the new ACE.
ea.grfAccessPermissions = pAAP->bNeedWrite ? GENERIC_ALL : GENERIC_READ;
ea.grfAccessMode = GRANT_ACCESS;
ea.grfInheritance= CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
ea.Trustee.ptstrName = (LPWSTR)(PSID)trusteeSID;
ea.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
// Create a new ACL that merges the new ACE into the existing DACL.
dwRes = SetEntriesInAcl(1, &ea, pOldDACL, &pNewDACL);
if (ERROR_SUCCESS != dwRes)
{
Log(logDEBUG, L"SetEntriesInAcl failed on [%s] for [%s] on [%s]. GLE=%s", pAAP->objName, pAAP->trustee, pAAP->targetComputer, GetSystemErrorMessage(dwRes));
//_ASSERT(0);
goto Cleanup;
}
// Attach the new ACL as the object's DACL.
dwRes = SetNamedSecurityInfo(pAAP->objName.LockBuffer(), pAAP->ObjectType, DACL_SECURITY_INFORMATION,
NULL, NULL, pNewDACL, NULL);
if (ERROR_SUCCESS != dwRes)
{
Log(logDEBUG, L"SetNamedSecurityInfo failed on [%s] for [%s] on [%s]. GLE=%s", pAAP->objName, pAAP->trustee, pAAP->targetComputer, GetSystemErrorMessage(dwRes));
//_ASSERT(dwRes == ERROR_BAD_INHERITANCE_ACL);
goto Cleanup;
}
Cleanup:
if(pSD != NULL)
LocalFree((HLOCAL) pSD);
if(pNewDACL != NULL)
LocalFree((HLOCAL) pNewDACL);
A code sample would definitely help. It's easy to get the logic to build and set the ACL subtly wrong.
I don't have the code in front of me, but the basic logic is:
acquire the process token with a sufficient access mask
GetNamedSecurityInfo
allocate a new ACL big enough for the new ACE, copy from the old to the new, and call AddAccessAllowedAceEx to add the user's SID
SetNamedSecurityInfo

Resources