|
|
/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
context.c
Abstract:
This module implements the internal worker routines to create and manipulate client context.
Author:
Kedar Dubhashi - March 2000
Environment:
User mode only.
Revision History:
Created - March 2000
--*/
#include "pch.h"
#pragma hdrstop
#include <authzp.h>
LUID AuthzTakeOwnershipPrivilege = {SE_TAKE_OWNERSHIP_PRIVILEGE, 0}; LUID AuthzSecurityPrivilege = {SE_SECURITY_PRIVILEGE, 0};
//
// Definitions used by AuthzpGetAllGroups.
//
const DWORD c_dwMaxSidCount = LSAI_CONTEXT_SID_LIMIT; static DWORD s_dwPageSize = 0;
typedef struct _SID_DESC { DWORD dwAttributes; DWORD dwLength; BYTE sid[SECURITY_MAX_SID_SIZE]; } SID_DESC, *PSID_DESC;
typedef struct _SID_SET { DWORD dwCount; DWORD dwMaxCount; PSID_DESC pSidDesc;
DWORD dwFlags; DWORD dwBaseCount;
// user information
PSID pUserSid; PSID pDomainSid; PUNICODE_STRING pusUserName; PUNICODE_STRING pusDomainName;
// user name & domain
PLSA_TRANSLATED_NAME pNames; PLSA_REFERENCED_DOMAIN_LIST pDomains; PWSTR pDomainsName; PLSA_TRANSLATED_SID2 pSids; SID_NAME_USE sidUse;
// information about the local machine
PPOLICY_ACCOUNT_DOMAIN_INFO pAccountInfo; PPOLICY_PRIMARY_DOMAIN_INFO pPrimaryInfo; PWSTR pPrimaryInfoName; BOOL bStandalone; BOOL bSkipNonLocal;
// user domain dc info
PDOMAIN_CONTROLLER_INFO pUdDcInfo; PDOMAIN_CONTROLLER_INFO pPdDcInfo; PDOMAIN_CONTROLLER_INFO pRdDcInfo;
// role information for user domain & primary domain
PDSROLE_PRIMARY_DOMAIN_INFO_BASIC pUdBasicInfo; PDSROLE_PRIMARY_DOMAIN_INFO_BASIC pPdBasicInfo; PDSROLE_PRIMARY_DOMAIN_INFO_BASIC pRdBasicInfo;
// name of the user domain DC
PWSTR pszUdDcName; PWSTR pszRdDcName; } SID_SET, *PSID_SET;
//
// Forward declarations of functions called by
// AuthzpGetAllGroups.
//
DWORD AuthzpAddWellKnownSids( IN OUT PSID_SET pSidSet );
DWORD AuthzpGetTokenGroupsXp( IN OUT PSID_SET pSidSet );
DWORD AuthzpGetTokenGroupsDownlevel( IN OUT PSID_SET pSidSet );
DWORD AuthzpGetAccountDomainGroupsDs( IN OUT PSID_SET pSidSet );
DWORD AuthzpGetAccountDomainGroupsSam( IN OUT PSID_SET pSidSet );
DWORD AuthzpGetResourceDomainGroups( IN OUT PSID_SET pSidSet );
DWORD AuthzpGetLocalGroups( IN BOOL bAddPrimaryGroup, IN OUT PSID_SET pSidSet );
DWORD AuthzpGetSidHistory( IN OUT PSID_SET pSidSet );
DWORD AuthzpGetPrimaryGroup( IN SAM_HANDLE hSam, IN OUT PSID_SET pSidSet );
DWORD AuthzpGetAliasMembership( IN SAM_HANDLE hSam, IN PSID pDomainSid, IN OUT PSID_SET pSidSet );
DWORD AuthzpInitializeSidSetByName( IN PUNICODE_STRING pusUserName, IN PUNICODE_STRING pusDomainName, IN DWORD dwFlags, IN PSID_SET pSidSet );
DWORD AuthzpIsDC( OUT PBOOL pbIsDC );
DWORD AuthzpInitializeSidSetBySid( IN PSID pUserSid, IN DWORD dwFlags, IN PSID_SET pSidSet );
DWORD AuthzpDeleteSidSet( IN PSID_SET pSidSet );
DWORD AuthzpAddSidToSidSet( IN PSID_SET pSidSet, IN PSID pSid, IN DWORD dwSidLength, IN DWORD dwAttributes, OUT PBOOL pbAdded OPTIONAL, OUT PSID* ppSid OPTIONAL );
DWORD AuthzpGetUserDomainSid( IN OUT PSID_SET pSidSet );
DWORD AuthzpGetUserDomainName( IN OUT PSID_SET pSidSet );
DWORD AuthzpGetLocalInfo( IN OUT PSID_SET pSidSet );
DWORD AuthzpGetDcName( IN LPCTSTR pszDomain, IN OUT PDOMAIN_CONTROLLER_INFO* ppDcInfo );
VOID AuthzpConvertSidToEdn( IN PSID pSid, OUT PWSTR pszSid );
BOOL AuthzpCopySidsAndAttributes( IN OUT PSID_AND_ATTRIBUTES DestSidAttr, IN PSID_AND_ATTRIBUTES SidAttr1, IN DWORD Count1, IN PSID_AND_ATTRIBUTES SidAttr2, IN DWORD Count2 )
/*++
Routine description:
This routine takes two sets of sid and attribute strucutes and concatenates them into a single one. The new structure is constructed into the buffer supplied by the caller.
Arguments:
DestSidAttr - Caller supplied buffer into which the resultant structure will be copied. The caller has already computed the size of the buffer required to hold the output.
SidAttr1 - The first sid and attributes structure.
Count1 - The number of elements in SidAttr1 structure.
SidAttr2 - The second sid and attributes structure.
Count2 - The number of elements in SidAttr2 structure.
Return Value:
A value of TRUE is returned if the routine is successful. Otherwise, a value of FALSE is returned. In the failure case, error value may be retrieved using GetLastError().
--*/
{ PUCHAR pCurrent = ((PUCHAR) DestSidAttr) + (sizeof(SID_AND_ATTRIBUTES) * (Count1 + Count2)); NTSTATUS Status = STATUS_SUCCESS; DWORD Length = 0; DWORD i = 0;
//
// Loop thru the first set and copy the sids and their attribtes.
//
for (i = 0; i < Count1; i++) { Length = RtlLengthSid(SidAttr1[i].Sid);
Status = RtlCopySid( Length, pCurrent, SidAttr1[i].Sid );
if (!NT_SUCCESS(Status)) { SetLastError(RtlNtStatusToDosError(Status)); return FALSE; }
DestSidAttr[i].Sid = (PSID) pCurrent; DestSidAttr[i].Attributes = SidAttr1[i].Attributes; pCurrent += Length; }
//
// Loop thru the second set and copy the sids and their attribtes.
//
for (; i < (Count1 + Count2); i++) { Length = RtlLengthSid(SidAttr2[i - Count1].Sid);
Status = RtlCopySid( Length, pCurrent, SidAttr2[i - Count1].Sid );
if (!NT_SUCCESS(Status)) { SetLastError(RtlNtStatusToDosError(Status)); return FALSE; }
DestSidAttr[i].Sid = (PSID) pCurrent; DestSidAttr[i].Attributes = SidAttr2[i - Count1].Attributes; pCurrent += Length; }
return TRUE; }
VOID AuthzpCopyLuidAndAttributes( IN OUT PAUTHZI_CLIENT_CONTEXT pCC, IN PLUID_AND_ATTRIBUTES Source, IN DWORD Count, IN OUT PLUID_AND_ATTRIBUTES Destination )
/*++
Routine description:
This routine takes a luid and attributes array and copies them into a caller supplied buffer. It also records presence of SecurityPrivilege and SeTakeOwnershipPrivilege into the client context flags.
Arguments:
pCC - Pointer to the client context structure into which the presence of privileges would be recorded.
Source - The array of privileges and attributes to be copied into a supplied buffer.
Count - Number of elements in the array.
Destination - Caller allocated buffer into which the input array will be copied.
Return Value:
A value of TRUE is returned if the routine is successful. Otherwise, a value of FALSE is returned. In the failure case, error value may be retrieved using GetLastError().
--*/
{ DWORD i = 0;
for (i = 0; i < Count; i++) { //
// Record the presence of SecurityPrivilege or SeTakeOwnershipPrivilege.
//
if ((RtlEqualLuid(&AuthzTakeOwnershipPrivilege, &Source[i].Luid)) && (Source[i].Attributes & SE_PRIVILEGE_ENABLED)) { pCC->Flags |= AUTHZ_TAKE_OWNERSHIP_PRIVILEGE_ENABLED; } else if ((RtlEqualLuid(&AuthzSecurityPrivilege, &Source[i].Luid)) && (Source[i].Attributes & SE_PRIVILEGE_ENABLED)) { pCC->Flags |= AUTHZ_SECURITY_PRIVILEGE_ENABLED; }
RtlCopyLuid(&(Destination[i].Luid), &(Source[i].Luid));
Destination[i].Attributes = Source[i].Attributes; } }
BOOL AuthzpGetAllGroupsByName( IN PUNICODE_STRING pusUserName, IN PUNICODE_STRING pusDomainName, IN DWORD dwFlags, OUT PSID_AND_ATTRIBUTES* ppSidAndAttributes, OUT PDWORD pdwSidCount, OUT PDWORD pdwSidLength )
/*++
Routine description:
This routine works as AuthzpGetAllGroupsBySid but takes a username domain name pair instead of a SID. It also accepts a UPN as the username and an empty domain name.
Arguments:
pusUserName - Name of the user. Can be a UPN.
pusDomainName - domain name of the user account or NULL in case the user name is a UPN.
Flags - AUTHZ_SKIP_TOKEN_GROUPS - Do not compute TokenGroups. AUTHZ_SKIP_WORLD_SID - Do not add the WORLD SID to the context.
ppSidAttr - Returns SidAndAttribute array. The routine allocates memory for this array.
pSidCount - Returns the number of sids in the array.
pSidLength - Returns the size of memory allocated to hold the array.
Return Value:
A value of TRUE is returned if the routine is successful. Otherwise, a value of FALSE is returned. In the failure case, error value may be retrieved using GetLastError().
--*/
{ DWORD dwError; BOOL bStatus = TRUE; SID_SET sidSet = {0}; PSID_DESC pSidDesc; PBYTE pSid; PSID_AND_ATTRIBUTES pSidAndAttribs; DWORD i;
//
// Initialize output parameters to zero.
//
*ppSidAndAttributes = 0; *pdwSidCount = 0; *pdwSidLength = 0;
//
// Initialize the SID set
//
dwError = AuthzpInitializeSidSetByName( pusUserName, pusDomainName, dwFlags, &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
if (sidSet.dwFlags & AUTHZ_SKIP_TOKEN_GROUPS) { //
// Initialize the user SID.
//
dwError = AuthzpGetUserDomainSid( &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
//
// Stick the user SID, the WORLD SID and others
// into the set if requested.
//
dwError = AuthzpAddWellKnownSids( &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; } } else { dwError = AuthzpGetTokenGroupsXp( &sidSet );
if (dwError != ERROR_SUCCESS) { //
// Detect the downlevel case.
//
// if (dwError != SEC_E_NO_S4U_PROT_SUPPORT)
// {
// goto Cleanup;
// }
//
// Initialize the user SID.
//
dwError = AuthzpGetUserDomainSid( &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
//
// Stick the user SID, the WORLD SID and others
// into the set if requested.
//
dwError = AuthzpAddWellKnownSids( &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
//
// In case AuthzpAddWellKnownSids finds that the SID is the
// Anonymous SID, it sets the AUTHZ_SKIP_TOKEN_GROUPS flag.
//
if (!(sidSet.dwFlags & AUTHZ_SKIP_TOKEN_GROUPS)) { //
// Try the downlevel scenario.
//
dwError = AuthzpGetTokenGroupsDownlevel( &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; } } }
}
//
// Allocate memory and copy all SIDs
// from the SID set into ppSidAndAttributes.
//
*pdwSidCount = sidSet.dwCount; *pdwSidLength = sidSet.dwCount * sizeof(SID_AND_ATTRIBUTES);
pSidDesc = sidSet.pSidDesc;
for (i=0;i < sidSet.dwCount;i++,pSidDesc++) { *pdwSidLength += pSidDesc->dwLength; }
*ppSidAndAttributes = (PSID_AND_ATTRIBUTES)AuthzpAlloc(*pdwSidLength);
if (*ppSidAndAttributes == 0) { dwError = GetLastError(); goto Cleanup; }
pSid = ((PBYTE)*ppSidAndAttributes) + sidSet.dwCount * sizeof(SID_AND_ATTRIBUTES); pSidDesc = sidSet.pSidDesc; pSidAndAttribs = *ppSidAndAttributes;
for (i=0;i < sidSet.dwCount;i++,pSidDesc++,pSidAndAttribs++) { pSidAndAttribs->Attributes = pSidDesc->dwAttributes; pSidAndAttribs->Sid = pSid;
RtlCopyMemory( pSid, pSidDesc->sid, pSidDesc->dwLength );
pSid += pSidDesc->dwLength; }
dwError = ERROR_SUCCESS;
Cleanup:
if (dwError != ERROR_SUCCESS) { SetLastError(dwError); bStatus = FALSE;
*pdwSidCount = 0; *pdwSidLength = 0;
if (*ppSidAndAttributes) { AuthzpFree(*ppSidAndAttributes); *ppSidAndAttributes = 0; } }
AuthzpDeleteSidSet(&sidSet);
return bStatus; }
BOOL AuthzpGetAllGroupsBySid( IN PSID pUserSid, IN DWORD dwFlags, OUT PSID_AND_ATTRIBUTES* ppSidAndAttributes, OUT PDWORD pdwSidCount, OUT PDWORD pdwSidLength )
/*++
Routine description:
This routine computes the groups a given user is a member of. It uses a data structure called a SID_SET for collecting the SIDs.
1. Initialize the SID_SET.
2. Put the user SID into the set. If requested, put the EVERYONE SID into the set. Add Well Known SIDs.
3. If requested, put the SIDs for non-local groups the user is a member of into the set. There are three scenarios for this step, depending on the version of the DC we are talking to:
xp: Use LsaLogonUser with the Kerberos S4U package and extract the groups from the token returned (AuthzpGetWXPDomainTokenGroups).
W2k: Use ldap and the SAM API to compute memberships in NT4: the account and primary domain and to get the SID history (AuthzpGetW2kDomainTokenGroups).
4. Transmogrify the SID_SET into a SID_AND_ATTRIBUTES array and free the SID_SET.
Arguments:
pUserSid - The user SID for which the groups should be computed.
Flags - AUTHZ_SKIP_TOKEN_GROUPS - Do not compute TokenGroups. AUTHZ_SKIP_WORLD_SID - Do not add the WORLD SID to the context. AUTHZ_REQUIRE_S4U_LOGON - Do not use the downlevel codepaths. Force S4U or fail. This is to enforce account restrictions. ppSidAttr - Returns SidAndAttribute array. The routine allocates memory for this array.
pSidCount - Returns the number of sids in the array.
pSidLength - Returns the size of memory allocated to hold the array.
Return Value:
A value of TRUE is returned if the routine is successful. Otherwise, a value of FALSE is returned. In the failure case, error value may be retrieved using GetLastError().
--*/
{ DWORD dwError; BOOL bStatus = TRUE; SID_SET sidSet = {0}; PSID_DESC pSidDesc; PBYTE pSid; PSID_AND_ATTRIBUTES pSidAndAttribs; DWORD i;
//
// Initialize output parameters to zero.
//
*ppSidAndAttributes = 0; *pdwSidCount = 0; *pdwSidLength = 0;
//
// Initialize the SID set
//
dwError = AuthzpInitializeSidSetBySid( pUserSid, dwFlags, &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
if (sidSet.dwFlags & AUTHZ_SKIP_TOKEN_GROUPS) { //
// Stick the user SID, the WORLD SID and others
// into the set if requested.
//
dwError = AuthzpAddWellKnownSids( &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; } } else { //
// Initialize user and domain name members of the sid set.
//
dwError = AuthzpGetUserDomainName( &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
if (sidSet.pNames->Use == SidTypeAlias || sidSet.pNames->Use == SidTypeGroup || sidSet.pNames->Use == SidTypeWellKnownGroup) { //
// LsaLogonUser cannot log on groups...
//
dwError = ERROR_NOT_SUPPORTED; } else { dwError = AuthzpGetTokenGroupsXp( &sidSet ); }
if (dwError != ERROR_SUCCESS) { //
// Xp logon code failed. If user prohibits the downlevel path,
// then exit now.
//
if (dwFlags & AUTHZ_REQUIRE_S4U_LOGON) { goto Cleanup; }
//
// Detect the downlevel case.
//
// if (dwError != SEC_E_NO_S4U_PROT_SUPPORT)
// {
// goto Cleanup;
// }
//
//
// Stick the user SID, the WORLD SID and others
// into the set if requested.
//
dwError = AuthzpAddWellKnownSids( &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
//
// In case AuthzpAddWellKnownSids finds that the SID is the
// Anonymous SID, it sets the AUTHZ_SKIP_TOKEN_GROUPS flag.
//
if (!(sidSet.dwFlags & AUTHZ_SKIP_TOKEN_GROUPS)) { //
// Try the downlevel scenario.
//
dwError = AuthzpGetTokenGroupsDownlevel( &sidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; } } } }
//
// Allocate memory and copy all SIDs
// from the SID set into ppSidAndAttributes.
//
*pdwSidCount = sidSet.dwCount; *pdwSidLength = sidSet.dwCount * sizeof(SID_AND_ATTRIBUTES);
pSidDesc = sidSet.pSidDesc;
for (i=0;i < sidSet.dwCount;i++,pSidDesc++) { *pdwSidLength += pSidDesc->dwLength; }
*ppSidAndAttributes = (PSID_AND_ATTRIBUTES)AuthzpAlloc(*pdwSidLength);
if (*ppSidAndAttributes == 0) { dwError = GetLastError(); goto Cleanup; }
pSid = ((PBYTE)*ppSidAndAttributes) + sidSet.dwCount * sizeof(SID_AND_ATTRIBUTES); pSidDesc = sidSet.pSidDesc; pSidAndAttribs = *ppSidAndAttributes;
for (i=0;i < sidSet.dwCount;i++,pSidDesc++,pSidAndAttribs++) { pSidAndAttribs->Attributes = pSidDesc->dwAttributes; pSidAndAttribs->Sid = pSid;
RtlCopyMemory( pSid, pSidDesc->sid, pSidDesc->dwLength );
pSid += pSidDesc->dwLength; }
dwError = ERROR_SUCCESS;
Cleanup:
if (dwError != ERROR_SUCCESS) { SetLastError(dwError); bStatus = FALSE;
*pdwSidCount = 0; *pdwSidLength = 0;
if (*ppSidAndAttributes) { AuthzpFree(*ppSidAndAttributes); *ppSidAndAttributes = 0; } }
AuthzpDeleteSidSet(&sidSet);
return bStatus; }
DWORD AuthzpAddWellKnownSids( IN OUT PSID_SET pSidSet ) { DWORD dwError; BOOL bStatus; BOOL bEqual; BOOL bAddEveryone = TRUE; BOOL bAddAuthUsers = TRUE; BOOL bAddAdministrators = FALSE; BYTE sid[SECURITY_MAX_SID_SIZE]; PSID pSid = (PSID)sid; DWORD dwLengthSid;
//
// Stick the user SID into the set
//
dwError = AuthzpAddSidToSidSet( pSidSet, pSidSet->pUserSid, 0, SE_GROUP_ENABLED, 0, 0 );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
pSidSet->dwBaseCount = 1;
//
// Test for some well known SIDs.
//
// If the SID passed in is the Anonymous SID, then check the registry
// value to determine if the Everyone SID should be included in the
// resulting client context.
//
if (IsWellKnownSid( pSidSet->pUserSid, WinAnonymousSid)) { bAddEveryone = FALSE; bAddAuthUsers = FALSE;
bStatus = AuthzpEveryoneIncludesAnonymous( &bAddEveryone );
if (bStatus == FALSE) { bAddEveryone = FALSE; }
pSidSet->dwFlags |= AUTHZ_SKIP_TOKEN_GROUPS; } else if (IsWellKnownSid( pSidSet->pUserSid, WinLocalSystemSid)) { bAddEveryone = TRUE; bAddAuthUsers = TRUE; bAddAdministrators = TRUE;
pSidSet->dwFlags |= AUTHZ_SKIP_TOKEN_GROUPS; } else { //
// This is a dummy context. Return now.
//
if (pSidSet->dwFlags & AUTHZ_SKIP_TOKEN_GROUPS) { return ERROR_SUCCESS; }
dwLengthSid = SECURITY_MAX_SID_SIZE;
bStatus = CreateWellKnownSid( WinBuiltinDomainSid, 0, pSid, &dwLengthSid );
if (bStatus == FALSE) { dwError = GetLastError(); goto Cleanup; }
bStatus = EqualDomainSid( pSidSet->pUserSid, pSid, &bEqual ); //
// ERROR_NON_DOMAIN_SID is returned for wellknown sids.
// It is ok to ignore this error and continue.
//
if ((bStatus == FALSE) && (GetLastError() != ERROR_NON_DOMAIN_SID)) { dwError = GetLastError(); goto Cleanup; }
if (bEqual) { pSidSet->bSkipNonLocal = TRUE; } else { bAddEveryone = TRUE; bAddAuthUsers = TRUE; } }
if (bAddEveryone) { dwLengthSid = SECURITY_MAX_SID_SIZE;
bStatus = CreateWellKnownSid( WinWorldSid, 0, pSid, &dwLengthSid );
if (bStatus == FALSE) { dwError = GetLastError(); goto Cleanup; }
dwError = AuthzpAddSidToSidSet( pSidSet, pSid, dwLengthSid, SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED, 0, 0 );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
pSidSet->dwBaseCount++; }
//
// Add NT AUTHORITY\Authenticated Users to the set
// only if the user does not have the Guest RID
//
if (bAddAuthUsers && *RtlSubAuthorityCountSid(pSidSet->pUserSid) > 0 && (*RtlSubAuthoritySid( pSidSet->pUserSid, (ULONG)(*RtlSubAuthorityCountSid( pSidSet->pUserSid)) - 1) != DOMAIN_USER_RID_GUEST) && (*RtlSubAuthoritySid( pSidSet->pUserSid, (ULONG)(*RtlSubAuthorityCountSid( pSidSet->pUserSid)) - 1) != DOMAIN_GROUP_RID_GUESTS)) { dwLengthSid = SECURITY_MAX_SID_SIZE;
bStatus = CreateWellKnownSid( WinAuthenticatedUserSid, 0, pSid, &dwLengthSid );
if (bStatus == FALSE) { dwError = GetLastError(); goto Cleanup; }
dwError = AuthzpAddSidToSidSet( pSidSet, pSid, dwLengthSid, SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED, 0, 0 );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
pSidSet->dwBaseCount++; }
if (bAddAdministrators) { dwLengthSid = SECURITY_MAX_SID_SIZE;
bStatus = CreateWellKnownSid( WinBuiltinAdministratorsSid, 0, pSid, &dwLengthSid );
if (bStatus == FALSE) { dwError = GetLastError(); goto Cleanup; }
dwError = AuthzpAddSidToSidSet( pSidSet, pSid, dwLengthSid, SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED, 0, 0 );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
pSidSet->dwBaseCount++; }
dwError = ERROR_SUCCESS;
Cleanup:
return dwError; }
DWORD AuthzpGetTokenGroupsXp( IN OUT PSID_SET pSidSet )
/*++
Routine description:
This routine connects to the domain specified by the SID and retrieves the list of groups to which the user belongs. This routine assumes we are talking to a WinXP DC. We take advantage of the new LsaLogonUser package, KerbS4ULogon.
Arguments:
pUserSid - user SID for which the lookup should be performed.
pSidSet - SID_SET in which we collect the SIDs of the groups we found in the token.
Return Value:
Win32 error code:
- ERROR_NOT_SUPPORTED if the DC does not support the call (pre ~2475 or client) - ERROR_INVALID_PARAMETER if the code is running on a pre XP platform
--*/
{ DWORD dwError = ERROR_SUCCESS; BOOL bStatus; NTSTATUS status; HANDLE hLsa = 0; LSA_STRING asProcessName; LSA_STRING asPackageName; ULONG ulAuthPackage; TOKEN_SOURCE sourceContext; PVOID pProfileBuffer = 0; ULONG ulProfileLength = 0; LUID luidLogonId; HANDLE hToken = 0; QUOTA_LIMITS quota; NTSTATUS subStatus; DWORD dwLength; DWORD i; PTOKEN_USER pTokenUser = 0; PTOKEN_GROUPS pTokenGroups = 0; PSID_AND_ATTRIBUTES pSidAndAttribs; ULONG ulPackageSize; PKERB_S4U_LOGON pPackage = 0;
//
// Set up the authentication package.
//
ulPackageSize = sizeof(KERB_S4U_LOGON); ulPackageSize += pSidSet->pusUserName->Length;
if (pSidSet->pusDomainName) { ulPackageSize += pSidSet->pusDomainName->Length; }
pPackage = (PKERB_S4U_LOGON)LocalAlloc( LMEM_FIXED, ulPackageSize );
if (pPackage == 0) { dwError = GetLastError(); goto Cleanup; }
pPackage->MessageType = KerbS4ULogon; pPackage->Flags = 0;
pPackage->ClientUpn.Length = pSidSet->pusUserName->Length; pPackage->ClientUpn.MaximumLength = pSidSet->pusUserName->Length; pPackage->ClientUpn.Buffer = (PWSTR)(pPackage + 1);
RtlCopyMemory( pPackage->ClientUpn.Buffer, pSidSet->pusUserName->Buffer, pSidSet->pusUserName->Length );
if (pSidSet->pusDomainName) { pPackage->ClientRealm.Length = pSidSet->pusDomainName->Length; pPackage->ClientRealm.MaximumLength = pSidSet->pusDomainName->Length; pPackage->ClientRealm.Buffer = (PWSTR) (((PBYTE)(pPackage->ClientUpn.Buffer)) + pPackage->ClientUpn.Length);
RtlCopyMemory( pPackage->ClientRealm.Buffer, pSidSet->pusDomainName->Buffer, pSidSet->pusDomainName->Length ); } else { pPackage->ClientRealm.Length = 0; pPackage->ClientRealm.MaximumLength = 0; pPackage->ClientRealm.Buffer = 0; }
//
// Our name is AuthzApi.
//
RtlInitString( &asProcessName, "AuthzApi" );
//
// Set up the process name and
// register with the LSA.
//
status = LsaConnectUntrusted( &hLsa );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
//
// Get the authentication package.
//
RtlInitString(&asPackageName, MICROSOFT_KERBEROS_NAME_A);
status = LsaLookupAuthenticationPackage( hLsa, &asPackageName, &ulAuthPackage );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
//
// Prepare the source context.
//
RtlCopyMemory( sourceContext.SourceName, "Authz ", sizeof(sourceContext.SourceName) );
status = NtAllocateLocallyUniqueId( &sourceContext.SourceIdentifier );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Do the logon.
//
status = LsaLogonUser( hLsa, &asProcessName, Network, ulAuthPackage, pPackage, ulPackageSize, 0, // no LocalGroups
&sourceContext, &pProfileBuffer, &ulProfileLength, &luidLogonId, &hToken, "a, &subStatus );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
//
// Figure out how much memory to allocate for the user info.
//
dwLength = 0;
bStatus = GetTokenInformation( hToken, TokenUser, 0, 0, &dwLength );
if (bStatus == FALSE) { dwError = GetLastError();
if (dwError != ERROR_INSUFFICIENT_BUFFER) { goto Cleanup; } }
pTokenUser = (PTOKEN_USER)LocalAlloc( LMEM_FIXED, dwLength );
if (pTokenUser == 0) { dwError = GetLastError(); goto Cleanup; }
//
// Extract the user SID from the token and
// add it to pSidSet.
//
bStatus = GetTokenInformation( hToken, TokenUser, pTokenUser, dwLength, &dwLength );
if (bStatus == FALSE) { dwError = GetLastError(); goto Cleanup; }
//
// Stick the user SID into the set.
//
if (!FLAG_ON(pTokenUser->User.Attributes, SE_GROUP_USE_FOR_DENY_ONLY)) { pTokenUser->User.Attributes |= SE_GROUP_ENABLED; }
dwError = AuthzpAddSidToSidSet( pSidSet, pTokenUser->User.Sid, 0, pTokenUser->User.Attributes, 0, 0 );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
//
// Figure out how much memory to allocate for the token groups.
//
dwLength = 0;
bStatus = GetTokenInformation( hToken, TokenGroups, 0, 0, &dwLength );
if (bStatus == FALSE) { dwError = GetLastError();
if (dwError != ERROR_INSUFFICIENT_BUFFER) { goto Cleanup; } }
pTokenGroups = (PTOKEN_GROUPS)LocalAlloc( LMEM_FIXED, dwLength );
if (pTokenGroups == 0) { dwError = GetLastError(); goto Cleanup; }
//
// Extract the user groups from the token and
// add them to pSidSet.
//
bStatus = GetTokenInformation( hToken, TokenGroups, pTokenGroups, dwLength, &dwLength );
if (bStatus == FALSE) { dwError = GetLastError(); goto Cleanup; }
//
// Stick the group SIDs into the set
// except for the Network and the LUID SID.
//
pSidAndAttribs = pTokenGroups->Groups;
for (i=0;i < pTokenGroups->GroupCount;i++,pSidAndAttribs++) { if (!IsWellKnownSid( pSidAndAttribs->Sid, WinNetworkSid) && !IsWellKnownSid( pSidAndAttribs->Sid, WinLogonIdsSid)) { dwError = AuthzpAddSidToSidSet( pSidSet, pSidAndAttribs->Sid, 0, pSidAndAttribs->Attributes, 0, 0 );
if (dwError != ERROR_SUCCESS) { goto Cleanup; } } }
dwError = ERROR_SUCCESS;
Cleanup:
if (pTokenUser) { AuthzpFree((HLOCAL)pTokenUser); }
if (pTokenGroups) { AuthzpFree((HLOCAL)pTokenGroups); }
if (hToken) { CloseHandle(hToken); }
if (pProfileBuffer) { LsaFreeReturnBuffer(pProfileBuffer); }
if (hLsa) { LsaDeregisterLogonProcess(hLsa); }
if (pPackage) { AuthzpFree((HLOCAL)pPackage); }
return dwError; }
DWORD AuthzpGetTokenGroupsDownlevel( IN OUT PSID_SET pSidSet )
/*++
Routine description:
This routine connects to the domain specified by the SID and retrieves the list of groups to which the user belongs. This routine assumes we are talking to a Win2k DC. First get the users domain universal and global groups memberships. Next check for nested memberships in the primary domain. The last step is getting the SID history for each SID collected so far.
Arguments:
pUserSid - user SID for which the lookup should be performed.
pSidSet - Returns the number of rids in the alias list
Return Value:
Win32 error code.
--*/
{ DWORD dwError; BOOL bUdIsNative = FALSE; BOOL bRdIsNative = FALSE; BOOL bAddPrimaryGroup = FALSE;
//
// Retrieve information about the machine.
//
dwError = AuthzpGetLocalInfo(pSidSet);
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
if (pSidSet->bStandalone || pSidSet->bSkipNonLocal) { //
// In the standalone case there is no need to hit the wire.
// We don't have to do anything here since local group
// memberships are computed later anyway.
//
bAddPrimaryGroup = TRUE; goto LocalGroups; }
//
// Compare the user domain SID to the machine domain SID.
// If they are equal, we can use the local machine for
// global group computing since the account is either
// a local account or we are sitting on a DC.
// The SID of the local machine is never zero in a non
// standalone / workgroup case.
//
if (pSidSet->pAccountInfo->DomainSid && RtlEqualSid( pSidSet->pDomainSid, pSidSet->pAccountInfo->DomainSid)) { BOOL bIsDC = FALSE;
pSidSet->pszUdDcName = 0;
//
// Find out if this is a DC.
//
dwError = AuthzpIsDC(&bIsDC);
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
//
// If this is not a DC then give up on global group computing.
//
if (FALSE == bIsDC) { bAddPrimaryGroup = TRUE; goto LocalGroups; }
//
// Local machine is a DC. Since AuthZ is not supported on nt4 we can
// safely assume it is at least w2k.
//
} else { //
// Find a DC and get its name.
//
dwError = AuthzpGetDcName( pSidSet->pusDomainName->Buffer, &pSidSet->pUdDcInfo );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
pSidSet->pszUdDcName = pSidSet->pUdDcInfo->DomainControllerName; }
//
// User domain can only be in native mode if DS is running.
//
if ((pSidSet->pUdDcInfo == 0) || (pSidSet->pUdDcInfo->Flags & DS_DS_FLAG) != 0) { //
// Collect information about the domain.
//
dwError = DsRoleGetPrimaryDomainInformation( pSidSet->pszUdDcName, DsRolePrimaryDomainInfoBasic, (PBYTE*)&pSidSet->pUdBasicInfo );
if (dwError != ERROR_SUCCESS) { //
// If the domain is in mixed mode and we are passing in a DNS
// name, the call fails with RPC_S_SERVER_UNAVAILABLE.
// We have to get rid of the DC name and get a flat one and
// then try again.
//
if (dwError == RPC_S_SERVER_UNAVAILABLE && pSidSet->pUdDcInfo && (pSidSet->pUdDcInfo->Flags & DS_INET_ADDRESS)) { NetApiBufferFree(pSidSet->pUdDcInfo); pSidSet->pUdDcInfo = 0;
dwError = DsGetDcName( 0, pSidSet->pDomainsName, 0, 0, 0, &pSidSet->pUdDcInfo );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
pSidSet->pszUdDcName = pSidSet->pUdDcInfo->DomainControllerName;
dwError = DsRoleGetPrimaryDomainInformation( pSidSet->pszUdDcName, DsRolePrimaryDomainInfoBasic, (PBYTE*)&pSidSet->pUdBasicInfo );
if (dwError != ERROR_SUCCESS) { goto Cleanup; } } else { goto Cleanup; } }
if ((pSidSet->pUdBasicInfo->Flags & DSROLE_PRIMARY_DS_RUNNING) && (pSidSet->pUdBasicInfo->Flags & DSROLE_PRIMARY_DS_MIXED_MODE) == 0) { bUdIsNative = TRUE; } }
//
// Check whether the account domain is in native or mixed mode
// and call the appropriate routine to get the groups.
//
if (bUdIsNative) { //
// User domain is in native mode.
//
dwError = AuthzpGetAccountDomainGroupsDs( pSidSet ); } else { //
// User domain is in mixed mode.
//
dwError = AuthzpGetAccountDomainGroupsSam( pSidSet ); }
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
//
// Check whether user domain and resource domain are different.
//
if (pSidSet->pPrimaryInfo->Sid && RtlEqualSid( pSidSet->pDomainSid, pSidSet->pPrimaryInfo->Sid)) { pSidSet->pszRdDcName = pSidSet->pszUdDcName; pSidSet->pRdDcInfo = pSidSet->pUdDcInfo; pSidSet->pRdBasicInfo = pSidSet->pUdBasicInfo; bRdIsNative = bUdIsNative; } else { //
// Find a DC and get its name.
//
dwError = AuthzpGetDcName( pSidSet->pPrimaryInfoName, &pSidSet->pPdDcInfo );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
pSidSet->pszRdDcName = pSidSet->pPdDcInfo->DomainControllerName; pSidSet->pRdDcInfo = pSidSet->pPdDcInfo;
//
// Resource domain can only be in native mode if DS is running.
//
if (pSidSet->pRdDcInfo->Flags & DS_DS_FLAG) { dwError = DsRoleGetPrimaryDomainInformation( pSidSet->pszRdDcName, DsRolePrimaryDomainInfoBasic, (PBYTE*)&pSidSet->pPdBasicInfo );
if (dwError != ERROR_SUCCESS) { //
// If the domain is in mixed mode and we are passing in a DNS
// name, the call fails. We have to get rid of the DC name
// and get a flat one and then try again.
//
if (dwError == RPC_S_SERVER_UNAVAILABLE && pSidSet->pPdDcInfo && (pSidSet->pPdDcInfo->Flags & DS_INET_ADDRESS)) { NetApiBufferFree(pSidSet->pPdDcInfo); pSidSet->pPdDcInfo = 0; pSidSet->pRdDcInfo = 0;
dwError = DsGetDcName( 0, pSidSet->pPrimaryInfoName, 0, 0, 0, &pSidSet->pPdDcInfo );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
pSidSet->pRdDcInfo = pSidSet->pPdDcInfo; pSidSet->pszRdDcName = pSidSet->pRdDcInfo->DomainControllerName;
dwError = DsRoleGetPrimaryDomainInformation( pSidSet->pszRdDcName, DsRolePrimaryDomainInfoBasic, (PBYTE*)&pSidSet->pPdBasicInfo );
if (dwError != ERROR_SUCCESS) { goto Cleanup; } } else { goto Cleanup; } }
pSidSet->pRdBasicInfo = pSidSet->pPdBasicInfo;
if ((pSidSet->pRdBasicInfo->Flags & DSROLE_PRIMARY_DS_RUNNING) && (pSidSet->pRdBasicInfo->Flags & DSROLE_PRIMARY_DS_MIXED_MODE) == 0) { bRdIsNative = TRUE; } } }
//
// Get domain local groups.
//
if (bRdIsNative) { //
// Primary domain operates in native mode.
// This means there could be domain local groups in the token.
//
dwError = AuthzpGetResourceDomainGroups( pSidSet);
if (dwError != ERROR_SUCCESS) { goto Cleanup; } }
LocalGroups:
//
// Collect local groups information.
// If this is the local user case, we have to add the primary group
// for the user.
//
dwError = AuthzpGetLocalGroups( bAddPrimaryGroup, pSidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
Cleanup:
return dwError; }
DWORD AuthzpGetAccountDomainGroupsDs( IN OUT PSID_SET pSidSet )
/*++
Routine description:
This routine connects to the user domain and queries AD for the list of groups (global and universal) the user belongs to.
Arguments:
pbNativeDomain - Pointer to a BOOL that will receive TRUE or FALSE depending on the domain operation mode (native or mixed, resp).
pSidSet - Pointer to set of SIDs. New groups will be added to this set.
Return Value:
Win32 error code.
--*/
{ DWORD dwError; PLDAP pLdap = 0; LDAPMessage* pResult = 0; LDAPMessage* pEntry = 0; PLDAP_BERVAL* ppValue = 0; PWCHAR ppszAttributes[] = {L"tokenGroupsGlobalAndUniversal", 0}; DWORD i; DWORD dwSidCount; WCHAR szSidEdn[SECURITY_MAX_SID_SIZE * 2 + 8];
AuthzpConvertSidToEdn( pSidSet->pUserSid, szSidEdn );
//
// We now have the user's SID in LDAP readable form. Fetch the
// tokenGroupsGlobalAndUniversal attribute.
//
pLdap = ldap_init( pSidSet->pszUdDcName ? pSidSet->pszUdDcName + 2 : 0, LDAP_PORT );
if (pLdap == 0) { dwError = LdapMapErrorToWin32(LdapGetLastError()); goto Cleanup; }
if (pSidSet->pszUdDcName) { dwError = ldap_set_option( pLdap, LDAP_OPT_AREC_EXCLUSIVE, LDAP_OPT_ON );
if (dwError != LDAP_SUCCESS) { dwError = LdapMapErrorToWin32(dwError); goto Cleanup; } }
//
// Set the sign and seal options to true.
//
dwError = ldap_set_option( pLdap, LDAP_OPT_SIGN, LDAP_OPT_ON );
if (dwError != LDAP_SUCCESS) { dwError = LdapMapErrorToWin32(dwError); goto Cleanup; }
dwError = ldap_set_option( pLdap, LDAP_OPT_ENCRYPT, LDAP_OPT_ON );
if (dwError != LDAP_SUCCESS) { dwError = LdapMapErrorToWin32(dwError); goto Cleanup; }
dwError = ldap_bind_s( pLdap, 0, 0, LDAP_AUTH_NEGOTIATE );
if (dwError != LDAP_SUCCESS) { dwError = LdapMapErrorToWin32(dwError); goto Cleanup; }
dwError = ldap_search_s( pLdap, szSidEdn, LDAP_SCOPE_BASE, L"objectClass=*", ppszAttributes, FALSE, &pResult );
if (dwError != LDAP_SUCCESS) { dwError = LdapMapErrorToWin32(dwError); goto Cleanup; }
pEntry = ldap_first_entry( pLdap, pResult );
if (pEntry == 0) { dwError = ERROR_ACCESS_DENIED; goto Cleanup; }
ppValue = ldap_get_values_len( pLdap, pEntry, ppszAttributes[0] );
if (ppValue == 0) { switch (pSidSet->sidUse) { case SidTypeAlias: case SidTypeWellKnownGroup: case SidTypeInvalid: case SidTypeUnknown: case SidTypeGroup: break; case SidTypeComputer: case SidTypeDomain: case SidTypeDeletedAccount: case SidTypeUser: default: dwError = ERROR_ACCESS_DENIED; goto Cleanup; } }
dwSidCount = ldap_count_values_len(ppValue);
//
// Merge the groups for our user into the result set.
//
for (i=0;i < dwSidCount;i++) { dwError = AuthzpAddSidToSidSet( pSidSet, (*ppValue[i]).bv_val, (*ppValue[i]).bv_len, SE_GROUP_ENABLED, 0, 0 );
if (dwError != ERROR_SUCCESS) { goto Cleanup; } }
dwError = ERROR_SUCCESS;
Cleanup:
if (ppValue) { ldap_value_free_len(ppValue); }
if (pResult) { ldap_msgfree(pResult); }
if (pLdap) { ldap_unbind(pLdap); }
return dwError; }
DWORD AuthzpGetAccountDomainGroupsSam( IN OUT PSID_SET pSidSet )
/*++
Routine description:
This routine connects to the domain specified by the SID and retrieves the list of groups to which the user belongs. This resembles what the NetUserGetGroups API does. We are not using it, because the Net APIs are name based and we are working with SIDs.
Arguments:
pusDcName - DC on which the lookup should be performed.
pSidSet - Returns the number of SIDs in the alias list.
Return Value:
Win32 error code.
--*/
{ NTSTATUS status; DWORD dwError = ERROR_SUCCESS; PGROUP_MEMBERSHIP pGroups = 0; PGROUP_MEMBERSHIP pGroup; DWORD dwGroupCount = 0; DWORD dwRelativeId = 0; DWORD i; PSID pSid = 0; SAM_HANDLE hSam = 0; SAM_HANDLE hDomain = 0; SAM_HANDLE hUser = 0; OBJECT_ATTRIBUTES obja = {0}; UNICODE_STRING usUdDcName = {0};
//
// If the sid is not a principal,
// it won't be a member of a SAM group.
//
if (pSidSet->sidUse != SidTypeUser && pSidSet->sidUse != SidTypeComputer) { goto Cleanup; }
//
// Connect to the SAM server on the DC.
// If we are on the DC, connect locally.
//
if (pSidSet->pszUdDcName) { RtlInitUnicodeString( &usUdDcName, pSidSet->pszUdDcName);
status = SamConnect( &usUdDcName, &hSam, SAM_SERVER_LOOKUP_DOMAIN, &obja ); } else { status = SamConnect( 0, &hSam, SAM_SERVER_LOOKUP_DOMAIN, &obja ); }
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Open the domain we are interested in.
//
status = SamOpenDomain( hSam, DOMAIN_LOOKUP, pSidSet->pDomainSid, &hDomain );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Finally, get a SAM handle to the user.
//
dwRelativeId = *RtlSubAuthoritySid( pSidSet->pUserSid, *RtlSubAuthorityCountSid(pSidSet->pUserSid) - 1 );
status = SamOpenUser( hDomain, USER_LIST_GROUPS, dwRelativeId, &hUser );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Request all groups the user is a member of.
//
status = SamGetGroupsForUser( hUser, &pGroups, &dwGroupCount );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Stuff the group SIDs into pSidSet.
//
pGroup = pGroups;
for (i=0;i < dwGroupCount;i++,pGroup++) { status = SamRidToSid( hDomain, pGroup->RelativeId, &pSid );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
dwError = AuthzpAddSidToSidSet( pSidSet, pSid, 0, pGroup->Attributes, 0, 0 );
SamFreeMemory(pSid); pSid = 0;
if (dwError != ERROR_SUCCESS) { goto Cleanup; } }
dwError = ERROR_SUCCESS;
Cleanup:
if (pGroups) { SamFreeMemory(pGroups); }
if (hUser) { SamCloseHandle(hUser); }
if (hDomain) { SamCloseHandle(hDomain); }
if (hSam) { SamCloseHandle(hSam); }
return dwError; }
DWORD AuthzpGetResourceDomainGroups( IN OUT PSID_SET pSidSet )
/*++
Routine description:
This routine connects to the primary (resource) domain and queries SAM for nested memberships.
Arguments:
pSidSet - Pointer to set of SIDs. New groups will be added to this set.
Return Value:
Win32 error code.
--*/
{ DWORD dwError = ERROR_SUCCESS; NTSTATUS status; OBJECT_ATTRIBUTES obja = {0}; SAM_HANDLE hSam = 0; UNICODE_STRING usRdDcName;
//
// Open a SAM handle to the resource domain.
//
if (pSidSet->pszRdDcName) { RtlInitUnicodeString( &usRdDcName, pSidSet->pszRdDcName);
status = SamConnect( &usRdDcName, &hSam, SAM_SERVER_LOOKUP_DOMAIN, &obja ); } else { status = SamConnect( 0, &hSam, SAM_SERVER_LOOKUP_DOMAIN, &obja ); }
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Call AuthzpGetAliasMembership to get nested memberships.
//
dwError = AuthzpGetAliasMembership( hSam, pSidSet->pPrimaryInfo->Sid, pSidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
//
// Retrieve the SID history.
//
dwError = AuthzpGetSidHistory( pSidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
dwError = ERROR_SUCCESS;
Cleanup:
if (hSam) { SamCloseHandle(hSam); }
return dwError; }
DWORD AuthzpGetLocalGroups( IN BOOL bAddPrimaryGroup, IN OUT PSID_SET pSidSet )
/*++
Routine description:
This routine connects to the domain specified by the caller and retrieves the list of groups to which the user belongs. We are checking the account domain and the builtin domain using Sam APIs.
Arguments:
bAddPrimaryGroup - Boolean that indicates wheter the primary group of the user should be computed and added to the sid set.
pSidSet - Pointer to the SID of the user for which group membership array will be returned.
Return Value:
Win32 error.
--*/
{ DWORD dwError = ERROR_SUCCESS; NTSTATUS status; SAM_HANDLE hSam = 0; OBJECT_ATTRIBUTES obja = {0}; BOOL bStatus; BYTE sid[SECURITY_MAX_SID_SIZE]; PSID pBuiltinSid = (PSID)sid; DWORD dwLengthSid = SECURITY_MAX_SID_SIZE;
//
// Open a handle to the SAM on the local computer.
//
status = SamConnect( 0, &hSam, SAM_SERVER_LOOKUP_DOMAIN, &obja );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Add primary group information if requested.
//
if (bAddPrimaryGroup) { dwError = AuthzpGetPrimaryGroup( hSam, pSidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; } }
//
// Retrieve recursive membership for the account domain.
//
dwError = AuthzpGetAliasMembership( hSam, pSidSet->pAccountInfo->DomainSid, pSidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
//
// Retrieve recursive membership for the BUILTIN domain.
//
bStatus = CreateWellKnownSid( WinBuiltinDomainSid, 0, pBuiltinSid, &dwLengthSid );
if (bStatus == FALSE) { dwError = GetLastError(); goto Cleanup; }
dwError = AuthzpGetAliasMembership( hSam, pBuiltinSid, pSidSet );
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
dwError = ERROR_SUCCESS;
Cleanup:
if (hSam) { SamCloseHandle(hSam); }
return dwError; }
DWORD AuthzpGetSidHistory( IN OUT PSID_SET pSidSet )
/*++
Routine description:
This routine queries ldap for the sidHistory attribute for every SID in the set and adds the history SIDs the the set as well.
Arguments:
pszDomainName - Name of the domain to connect to.
pSidSet - Pointer to set of SIDs. New groups will be added to this set.
Return Value:
Win32 error code.
--*/
{ DWORD dwError = ERROR_SUCCESS; PLDAP pLdap = 0; LDAPMessage* pResult = 0; LDAPMessage* pEntry = 0; PLDAP_BERVAL* ppValue = 0; PWCHAR ppszAttributes[] = {L"sidHistory", 0}; DWORD i, j; DWORD dwSidCount; DWORD dwValueCount; PSID_DESC pSidDesc; WCHAR szSidEdn[SECURITY_MAX_SID_SIZE * 2 + 8];
//
// Open a ldap connection to the primary domain.
// Get rid of the leading \\ before using the DC name.
//
pLdap = ldap_init( pSidSet->pszRdDcName ? pSidSet->pszRdDcName + 2 : 0, LDAP_PORT );
if (pLdap == 0) { dwError = LdapMapErrorToWin32(LdapGetLastError()); goto Cleanup; }
if (pSidSet->pszRdDcName) { dwError = ldap_set_option( pLdap, LDAP_OPT_AREC_EXCLUSIVE, LDAP_OPT_ON );
if (dwError != LDAP_SUCCESS) { dwError = LdapMapErrorToWin32(dwError); goto Cleanup; } }
//
// Set the sign and seal options to true.
//
dwError = ldap_set_option( pLdap, LDAP_OPT_SIGN, LDAP_OPT_ON );
if (dwError != LDAP_SUCCESS) { dwError = LdapMapErrorToWin32(dwError); goto Cleanup; }
dwError = ldap_set_option( pLdap, LDAP_OPT_ENCRYPT, LDAP_OPT_ON );
if (dwError != LDAP_SUCCESS) { dwError = LdapMapErrorToWin32(dwError); goto Cleanup; }
dwError = ldap_bind_s( pLdap, 0, 0, LDAP_AUTH_NEGOTIATE );
if (dwError != LDAP_SUCCESS) { dwError = LdapMapErrorToWin32(dwError); goto Cleanup; }
//
// Loop through all SIDs and retrieve the history attribute
// for each one of them.
//
dwSidCount = pSidSet->dwCount; pSidDesc = pSidSet->pSidDesc;
for (i=0;i < dwSidCount;i++,pSidDesc++) { AuthzpConvertSidToEdn( pSidDesc->sid, szSidEdn );
dwError = ldap_search_s( pLdap, szSidEdn, LDAP_SCOPE_BASE, L"objectClass=*", ppszAttributes, FALSE, &pResult );
if (dwError != LDAP_SUCCESS) { if (dwError == LDAP_NO_SUCH_OBJECT) { //
// The SID was not found, this is not an error.
//
dwError = ERROR_SUCCESS;
if (pResult) { ldap_msgfree(pResult); pResult = NULL; }
continue; }
dwError = LdapMapErrorToWin32(dwError); goto Cleanup; }
pEntry = ldap_first_entry( pLdap, pResult);
if (pEntry == 0) { dwError = ERROR_ACCESS_DENIED; goto Cleanup; }
ppValue = ldap_get_values_len( pLdap, pEntry, ppszAttributes[0] );
//
// Now we have the history attribute for our group.
// Merge it into the result set.
//
dwValueCount = ldap_count_values_len(ppValue);
for (j=0;j < dwValueCount;j++) { dwError = AuthzpAddSidToSidSet( pSidSet, (*ppValue[j]).bv_val, (*ppValue[j]).bv_len, SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED, 0, 0 );
if (dwError != ERROR_SUCCESS) { goto Cleanup; } }
if (ppValue) { ldap_value_free_len(ppValue); ppValue = 0; }
if (pResult) { ldap_msgfree(pResult); pResult = 0; } }
dwError = ERROR_SUCCESS;
Cleanup:
if (ppValue) { ldap_value_free_len(ppValue); }
if (pResult) { ldap_msgfree(pResult); }
if (pLdap) { ldap_unbind(pLdap); }
return dwError; }
DWORD AuthzpGetAliasMembership( IN SAM_HANDLE hSam, IN PSID pDomainSid, IN OUT PSID_SET pSidSet )
/*++
Routine description:
We try to find nested groups here. This only makes sense on domains in native mode. This routine calls SamGetAliasMembership iteratively until no more nested groups are returned.
Arguments:
hSam - Handle to the SAM database.
pDomainSid - SID of the domain to operate on.
ppSidSet - Set of SIDs that are checked for membership. Newly found group SIDs are added to the set.
Return Value:
Win32 error code.
--*/
{ DWORD dwError = ERROR_SUCCESS; NTSTATUS status; PSID pSid = 0; SAM_HANDLE hDomain = 0; DWORD dwSidCount; DWORD dwSidCountNew; DWORD dwSidListSize; DWORD i; BOOL bAdded; PSID* ppSidList = 0; PDWORD pRidList = 0; PDWORD pRid;
//
// Get a SAM handle to the domain.
//
status = SamOpenDomain( hSam, DOMAIN_GET_ALIAS_MEMBERSHIP, pDomainSid, &hDomain );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Retrieve the memberships iteratively.
//
dwSidCount = pSidSet->dwCount; dwSidListSize = dwSidCount;
ppSidList = (PSID*)AuthzpAlloc( dwSidCount * sizeof(PSID) );
if (ppSidList == 0) { dwError = GetLastError(); goto Cleanup; }
for (i=0;i < dwSidCount;i++) { ppSidList[i] = pSidSet->pSidDesc[i].sid; }
do { status = SamGetAliasMembership( hDomain, dwSidCount, ppSidList, &dwSidCountNew, &pRidList );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
if (dwSidCountNew > dwSidListSize) { AuthzpFree(ppSidList);
ppSidList = (PSID*)AuthzpAlloc( dwSidCountNew * sizeof(PSID) );
if (ppSidList == 0) { dwError = GetLastError(); goto Cleanup; }
dwSidListSize = dwSidCountNew; }
dwSidCount = 0; pRid = pRidList;
for (i=0;i < dwSidCountNew;i++,pRid++) { status = SamRidToSid( hDomain, *pRid, &pSid );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
dwError = AuthzpAddSidToSidSet( pSidSet, pSid, 0, SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED, &bAdded, ppSidList + dwSidCount );
SamFreeMemory(pSid); pSid = 0;
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
if (bAdded) { dwSidCount++; } }
if (pRidList) { SamFreeMemory(pRidList); pRidList = 0; } } while (dwSidCount);
dwError = ERROR_SUCCESS;
Cleanup:
if (pRidList) { SamFreeMemory(pRidList); }
if (ppSidList) { AuthzpFree(ppSidList); }
if (hDomain) { SamCloseHandle(hDomain); }
return dwError; }
DWORD AuthzpGetPrimaryGroup( IN SAM_HANDLE hSam, IN OUT PSID_SET pSidSet )
/*++
Routine description:
Add the primary group of the user to the sid set.
Arguments:
hSam - Handle to the SAM database.
pSidSet - Add the sid of the primary group to this set.
Return Value:
Win32 error code.
--*/
{ DWORD dwError = ERROR_SUCCESS; NTSTATUS status; SAM_HANDLE hDomain = 0; SAM_HANDLE hUser = 0; PUSER_PRIMARY_GROUP_INFORMATION pInfo = 0; PSID pPrimaryGroupSid = NULL; DWORD dwRelativeId = 0;
//
// Open the account domain.
//
status = SamOpenDomain( hSam, DOMAIN_LOOKUP, pSidSet->pDomainSid, &hDomain );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Extract the rid from the user sid.
//
dwRelativeId = *RtlSubAuthoritySid( pSidSet->pUserSid, *RtlSubAuthorityCountSid(pSidSet->pUserSid) - 1 );
//
// Open the user for read.
//
status = SamOpenUser( hDomain, USER_READ_GENERAL, dwRelativeId, &hUser );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Get the primary group information for the user.
//
status = SamQueryInformationUser( hUser, UserPrimaryGroupInformation, &pInfo );
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Convert the group rid to a sid.
//
status = SamRidToSid( hDomain, pInfo->PrimaryGroupId, &pPrimaryGroupSid );
SamFreeMemory(pInfo);
if (!NT_SUCCESS(status)) { dwError = RtlNtStatusToDosError(status); goto Cleanup; }
//
// Add the group sid to the set.
//
dwError = AuthzpAddSidToSidSet( pSidSet, pPrimaryGroupSid, 0, SE_GROUP_ENABLED, 0, 0 );
SamFreeMemory(pPrimaryGroupSid);
if (dwError != ERROR_SUCCESS) { goto Cleanup; }
dwError = ERROR_SUCCESS;
Cleanup:
if (hUser) { SamCloseHandle(hUser); }
if (hDomain) { SamCloseHandle(hDomain); }
return dwError; }
DWORD AuthzpInitializeSidSetByName( IN PUNICODE_STRING pusUserName, IN PUNICODE_STRING pusDomainName, IN DWORD dwFlags, IN PSID_SET pSidSet )
/*++
Routine description:
Initializes a sid set and reserves memory for the max amount of memory it will ever need. The memory is not allocated yet. This only happens as SIDs get added to the set. All members are initialized to meaningful values.
Arguments:
pSidSet - The sid set to operate on.
Return Value:
Win32 error code.
--*/
{ DWORD dwError = ERROR_SUCCESS; SYSTEM_INFO sysInfo;
if (s_dwPageSize == 0) { GetSystemInfo(&sysInfo);
s_dwPageSize = sysInfo.dwPageSize; }
pSidSet->pSidDesc = (PSID_DESC)VirtualAlloc( 0, c_dwMaxSidCount * sizeof(SID_DESC), MEM_RESERVE, PAGE_NOACCESS );
if (pSidSet->pSidDesc == 0) { dwError = GetLastError(); goto Cleanup; }
pSidSet->dwCount = 0; pSidSet->dwMaxCount = 0;
pSidSet->dwBaseCount = 0; pSidSet->dwFlags = dwFlags;
pSidSet->pUserSid = 0; pSidSet->pDomainSid = 0; pSidSet->pusUserName = pusUserName;
//
// Verify for once we got a valid domain.
// Otherwise we assume we got a UPN in pusUserName.
//
if (pusDomainName && pusDomainName->Length && pusDomainName->Buffer) { pSidSet->pusDomainName = pusDomainName; } else { pSidSet->pusDomainName = 0; }
pSidSet->pNames = 0; pSidSet->pDomains = 0; pSidSet->pDomainsName = 0; pSidSet->pSids = 0; pSidSet->sidUse = SidTypeUnknown;
pSidSet->pAccountInfo = 0; pSidSet->pPrimaryInfo = 0; pSidSet->pPrimaryInfoName = 0; pSidSet->bStandalone = TRUE; pSidSet->bSkipNonLocal = FALSE;
pSidSet->pUdDcInfo = 0; pSidSet->pPdDcInfo = 0; pSidSet->pRdDcInfo = 0;
pSidSet->pUdBasicInfo = 0; pSidSet->pPdBasicInfo = 0; pSidSet->pRdBasicInfo = 0;
pSidSet->pszUdDcName = 0; pSidSet->pszRdDcName = 0;
dwError = ERROR_SUCCESS;
Cleanup:
return dwError; }
DWORD AuthzpInitializeSidSetBySid( IN PSID pUserSid, IN DWORD dwFlags, IN PSID_SET pSidSet )
/*++
Routine description:
Initializes a sid set and reserves memory for the max amount of memory it will ever need. The memory is not allocated yet. This only happens as SIDs get added to the set. All members are initialized to meaningful values.
Arguments:
pSidSet - The sid set to operate on.
Return Value:
Win32 error code.
--*/
{ DWORD dwError = ERROR_SUCCESS; SYSTEM_INFO sysInfo;
if (s_dwPageSize == 0) { GetSystemInfo(&sysInfo);
s_dwPageSize = sysInfo.dwPageSize; }
if (!RtlValidSid(pUserSid)) { dwError = ERROR_INVALID_SID; goto Cleanup; }
pSidSet->pSidDesc = (PSID_DESC)VirtualAlloc( 0, c_dwMaxSidCount * sizeof(SID_DESC), MEM_RESERVE, PAGE_NOACCESS );
if (pSidSet->pSidDesc == 0) { dwError = GetLastError(); goto Cleanup; }
pSidSet->dwCount = 0; pSidSet->dwMaxCount = 0;
pSidSet->dwBaseCount = 0; pSidSet->dwFlags = dwFlags;
pSidSet->pUserSid = pUserSid; pSidSet->pDomainSid = 0; pSidSet->pusUserName = 0; pSidSet->pusDomainName = 0;
pSidSet->pNames = 0; pSidSet->pDomains = 0; pSidSet->pDomainsName = 0; pSidSet->pSids = 0; pSidSet->sidUse = SidTypeUnknown;
pSidSet->pAccountInfo = 0; pSidSet->pPrimaryInfo = 0; pSidSet->pPrimaryInfoName = 0; pSidSet->bStandalone = TRUE; pSidSet->bSkipNonLocal = FALSE;
pSidSet->pUdDcInfo = 0; pSidSet->pPdDcInfo = 0; pSidSet->pRdDcInfo = 0;
pSidSet->pUdBasicInfo = 0; pSidSet->pPdBasicInfo = 0; pSidSet->pRdBasicInfo = 0;
pSidSet->pszUdDcName = 0; pSidSet->pszRdDcName = 0;
dwError = ERROR_SUCCESS;
Cleanup:
return dwError; }
DWORD AuthzpDeleteSidSet( IN PSID_SET pSidSet )
/*++
Routine description:
Deletes all memory allocated to the sid set structure and resets all members to zero.
Arguments:
pSidSet - The sid set to operate on.
Return Value:
Win32 error code.
--*/
{ if (pSidSet->pSidDesc) { VirtualFree(pSidSet->pSidDesc, 0, MEM_RELEASE); }
if (pSidSet->pNames) { LsaFreeMemory(pSidSet->pNames); }
if (pSidSet->pDomains) { LsaFreeMemory(pSidSet->pDomains); }
if (pSidSet->pDomainsName) { AuthzpFree(pSidSet->pDomainsName); }
if (pSidSet->pSids) { LsaFreeMemory(pSidSet->pSids); }
if (pSidSet->pAccountInfo) { LsaFreeMemory(pSidSet->pAccountInfo); }
if (pSidSet->pPrimaryInfo) { LsaFreeMemory(pSidSet->pPrimaryInfo); }
if (pSidSet->pPrimaryInfoName) { AuthzpFree(pSidSet->pPrimaryInfoName); }
if (pSidSet->pUdDcInfo) { NetApiBufferFree(pSidSet->pUdDcInfo); }
if (pSidSet->pPdDcInfo) { NetApiBufferFree(pSidSet->pPdDcInfo); }
if (pSidSet->pUdBasicInfo) { DsRoleFreeMemory(pSidSet->pUdBasicInfo); }
if (pSidSet->pPdBasicInfo) { DsRoleFreeMemory(pSidSet->pPdBasicInfo); }
RtlZeroMemory( pSidSet, sizeof(SID_SET));
return ERROR_SUCCESS; }
DWORD AuthzpAddSidToSidSet( IN PSID_SET pSidSet, IN PSID pSid, IN DWORD dwSidLength OPTIONAL, IN DWORD dwAttributes, OUT PBOOL pbAdded OPTIONAL, OUT PSID* ppSid OPTIONAL )
/*++
Routine description:
Check if the given SID already exists in the set. If yes, return. Otherwise, add it to the set.
Arguments:
pSidSet - The sid set to operate on.
pSid - The SID to add to the set.
dwSidLength - Length of the SID in bytes. If zero is passed in, the routine calculates the length itself.
dwAttributes - Attributes of the SID like in the SID_AND_ATTRIBUTES structure.
pbAdded - Optional pointer that receives indication if the SID was indeed added or not (because it was a duplicate).
ppSid - Optional pointer to where the new sid is stored.
Return Value:
Win32 error code.
--*/
{ DWORD dwError = ERROR_SUCCESS; DWORD i; DWORD dwSize; BOOL bAdded = FALSE; PSID_DESC pSidDesc;
if (dwSidLength == 0) { dwSidLength = RtlLengthSid(pSid); }
pSidDesc = pSidSet->pSidDesc;
for (i=0;i < pSidSet->dwCount;i++,pSidDesc++) { if (dwSidLength == pSidDesc->dwLength) { if (RtlEqualSid( pSid, pSidDesc->sid)) { goto Cleanup; } } }
if (pSidSet->dwCount >= pSidSet->dwMaxCount) { if (pSidSet->dwCount >= c_dwMaxSidCount) { dwError = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; }
//
// Commit one more page in the buffer.
//
dwSize = (pSidSet->dwCount + 1) * sizeof(SID_DESC); dwSize += s_dwPageSize - 1; dwSize &= ~(s_dwPageSize - 1);
pSidDesc = (PSID_DESC)VirtualAlloc( pSidSet->pSidDesc, dwSize, MEM_COMMIT, PAGE_READWRITE );
if (pSidDesc != pSidSet->pSidDesc) { dwError = GetLastError(); goto Cleanup; }
pSidSet->dwMaxCount = dwSize / sizeof(SID_DESC); }
pSidDesc = pSidSet->pSidDesc + pSidSet->dwCount;
pSidDesc->dwAttributes = dwAttributes; pSidDesc->dwLength = dwSidLength;
RtlCopyMemory( pSidDesc->sid, pSid, dwSidLength );
bAdded = TRUE;
pSidSet->dwCount++;
if (ppSid) { *ppSid = pSidDesc->sid; }
dwError = ERROR_SUCCESS;
Cleanup:
if (pbAdded) { *pbAdded = bAdded; }
return dwError; }
DWORD AuthzpGetUserDomainSid( PSID_SET pSidSet ) { DWORD dwError; NTSTATUS status; LSA_HANDLE hPolicy = 0; OBJECT_ATTRIBUTES obja = {0}; SECURITY_QUALITY_OF_SERVICE sqos; WCHAR wc[2] = L"\\"; UNICODE_STRING usName = {0}; PUNICODE_STRING pusName = 0;
//
// Build the string domain - name string that should be
// translated.
//
if (pSidSet->pusDomainName) { usName.MaximumLength = pSidSet->pusDomainName->Length + sizeof(WCHAR) + pSidSet->pusUserName->Length + sizeof(WCHAR);
usName.Buffer = (PWSTR)LocalAlloc( LMEM_FIXED, usName.MaximumLength );
if (usName.Buffer == 0) { dwError = GetLastError(); goto Cleanup; }
RtlCopyMemory( usName.Buffer, pSidSet->pusDomainName->Buffer, pSidSet->pusDomainName->Length );
usName.Length = (USHORT)(usName.Length + pSidSet->pusDomainName->Length);
RtlCopyMemory( ((PBYTE)usName.Buffer) + usName.Length, wc + 0, sizeof(WCHAR) );
usName.Length += sizeof(WCHAR);
RtlCopyMemory( ((PBYTE)usName.Buffer) + usName.Length, pSidSet->pusUserName->Buffer, pSidSet->pusUserName->Length );
usName.Length = (USHORT)(usName.Length + pSidSet->pusUserName->Length);
RtlCopyMemory( ((PBYTE)usName.Buffer) + usName.Length, wc + 1, sizeof(WCHAR) );
pusName = &usName; } else { //
// Assume we got a UPN.
//
pusName = pSidSet->pusUserName; }
//
// set up the object attributes prior to opening the LSA
//
sqos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); sqos.ImpersonationLevel = SecurityImpersonation; sqos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; sqos.EffectiveOnly = FALSE;
obja.SecurityQualityOfService = &sqos;
//
// open the LSA policy
//
status = LsaOpenPolicy( 0, &obja, POLICY_LOOKUP_NAMES, &hPolicy );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
status = LsaLookupNames2( hPolicy, 0, // no flags
1, pusName, &pSidSet->pDomains, &pSidSet->pSids );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
if (pSidSet->pSids == 0) { dwError = ERROR_INVALID_PARAMETER; goto Cleanup; }
switch (pSidSet->pSids->Use) { case SidTypeDomain: case SidTypeInvalid: case SidTypeUnknown: dwError = ERROR_INVALID_PARAMETER; goto Cleanup; }
//
// The name was successfully translated.
// There should be exactly one domain and its index should be zero.
//
ASSERT(pSidSet->pDomains->Entries == 1); ASSERT(pSidSet->pDomains->Domains != 0); ASSERT(pSidSet->pSids->DomainIndex == 0);
pSidSet->pUserSid = pSidSet->pSids->Sid; pSidSet->pDomainSid = pSidSet->pDomains->Domains->Sid; pSidSet->sidUse = pSidSet->pSids->Use;
pSidSet->pDomainsName = (PWSTR) AuthzpAlloc(pSidSet->pDomains->Domains->Name.Length + sizeof(WCHAR));
if (pSidSet->pDomainsName == NULL) { dwError = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup;
}
wcsncpy(pSidSet->pDomainsName, pSidSet->pDomains->Domains->Name.Buffer, pSidSet->pDomains->Domains->Name.Length/sizeof(WCHAR)); pSidSet->pDomainsName[pSidSet->pDomains->Domains->Name.Length/sizeof(WCHAR)] = L'\0';
dwError = ERROR_SUCCESS;
Cleanup:
if (hPolicy) { LsaClose(hPolicy); }
if (usName.Buffer) { AuthzpFree((HLOCAL)usName.Buffer); }
return dwError; }
DWORD AuthzpGetUserDomainName( PSID_SET pSidSet ) { DWORD dwError; NTSTATUS status; LSA_HANDLE hPolicy = 0; OBJECT_ATTRIBUTES obja = {0}; SECURITY_QUALITY_OF_SERVICE sqos;
//
// set up the object attributes prior to opening the LSA
//
sqos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); sqos.ImpersonationLevel = SecurityImpersonation; sqos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; sqos.EffectiveOnly = FALSE;
obja.SecurityQualityOfService = &sqos;
//
// open the LSA policy
//
status = LsaOpenPolicy( 0, &obja, POLICY_LOOKUP_NAMES, &hPolicy );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
status = LsaLookupSids( hPolicy, 1, &pSidSet->pUserSid, &pSidSet->pDomains, &pSidSet->pNames );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
if (pSidSet->pNames == 0) { dwError = ERROR_INVALID_PARAMETER; goto Cleanup; }
switch (pSidSet->pNames->Use) { case SidTypeDomain: case SidTypeUnknown: case SidTypeInvalid: dwError = ERROR_INVALID_PARAMETER; goto Cleanup; }
//
// The SID was successfully translated.
// There should be exactly one domain and its index should be zero.
//
ASSERT(pSidSet->pDomains->Entries == 1); ASSERT(pSidSet->pDomains->Domains != 0); ASSERT(pSidSet->pNames->DomainIndex == 0);
pSidSet->pDomainSid = pSidSet->pDomains->Domains->Sid; pSidSet->pusUserName = &pSidSet->pNames->Name; pSidSet->pusDomainName = &pSidSet->pDomains->Domains->Name; pSidSet->sidUse = pSidSet->pNames->Use;
dwError = ERROR_SUCCESS;
Cleanup:
if (hPolicy) { LsaClose(hPolicy); }
return dwError; }
DWORD AuthzpGetLocalInfo( IN PSID_SET pSidSet ) { DWORD dwError; NTSTATUS status; LSA_HANDLE hPolicy = 0; OBJECT_ATTRIBUTES obja = {0}; SECURITY_QUALITY_OF_SERVICE sqos; NT_PRODUCT_TYPE ProductType; PPOLICY_LSA_SERVER_ROLE_INFO pRole = 0;
//
// Set up the object attributes prior to opening the LSA.
//
sqos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); sqos.ImpersonationLevel = SecurityImpersonation; sqos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; sqos.EffectiveOnly = FALSE;
obja.SecurityQualityOfService = &sqos;
//
// open LSA policy
//
status = LsaOpenPolicy( 0, &obja, POLICY_VIEW_LOCAL_INFORMATION, &hPolicy );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
status = LsaQueryInformationPolicy( hPolicy, PolicyAccountDomainInformation, (PVOID*)&pSidSet->pAccountInfo );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
status = LsaQueryInformationPolicy( hPolicy, PolicyPrimaryDomainInformation, (PVOID*)&pSidSet->pPrimaryInfo );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
pSidSet->pPrimaryInfoName = (PWSTR) AuthzpAlloc(pSidSet->pPrimaryInfo->Name.Length + sizeof(WCHAR));
if (pSidSet->pPrimaryInfoName == NULL) { dwError = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup;
}
wcsncpy(pSidSet->pPrimaryInfoName, pSidSet->pPrimaryInfo->Name.Buffer, pSidSet->pPrimaryInfo->Name.Length/sizeof(WCHAR)); pSidSet->pPrimaryInfoName[pSidSet->pPrimaryInfo->Name.Length/sizeof(WCHAR)] = L'\0';
//
// Determine the role of the machine.
//
if (RtlGetNtProductType(&ProductType) == FALSE) { dwError = ERROR_GEN_FAILURE; goto Cleanup; }
switch (ProductType) { case NtProductWinNt: case NtProductServer: pSidSet->bStandalone = pSidSet->pPrimaryInfo->Sid == 0 ? TRUE : FALSE; break; case NtProductLanManNt: status = LsaQueryInformationPolicy( hPolicy, PolicyLsaServerRoleInformation, (PVOID*)&pRole );
if (!NT_SUCCESS(status)) { dwError = LsaNtStatusToWinError(status); goto Cleanup; }
pSidSet->bStandalone = FALSE;
if (pRole->LsaServerRole == PolicyServerRolePrimary) { //
// If we think we're a primary domain controller, we'll need to
// guard against the case where we're actually standalone
// during setup
//
if (pSidSet->pPrimaryInfo->Sid == 0 || pSidSet->pAccountInfo->DomainSid == 0 || !RtlEqualSid( pSidSet->pPrimaryInfo->Sid, pSidSet->pAccountInfo->DomainSid)) { pSidSet->bStandalone = TRUE; } } break;
default: dwError = ERROR_GEN_FAILURE; goto Cleanup; }
dwError = ERROR_SUCCESS;
Cleanup:
if (pRole) { LsaFreeMemory(pRole); }
if (hPolicy) { LsaClose(hPolicy); }
return dwError; }
DWORD AuthzpGetDcName( IN LPCTSTR pszDomain, IN OUT PDOMAIN_CONTROLLER_INFO* ppDcInfo ) { DWORD dwError;
//
// First try to get a DC with DS running.
//
dwError = DsGetDcName( 0, pszDomain, 0, 0, DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME, ppDcInfo );
if (dwError == ERROR_NO_SUCH_DOMAIN) { //
// Try again with no flags set, because this is the only way
// an NT4 domain will reveal its secrets.
//
dwError = DsGetDcName( 0, pszDomain, 0, 0, 0, ppDcInfo ); }
return dwError; }
VOID AuthzpConvertSidToEdn( IN PSID pSid, OUT PWSTR pszSidEdn )
/*++
Print pSid into pszSidEdn as an Extended Distinguished Name.
pszSidEdn should provide room for at least SECURITY_MAX_SID_SIZE * 2 + 8 WCHARs.
--*/
{ DWORD dwLength = RtlLengthSid(pSid); DWORD i; PBYTE pbSid = (PBYTE)pSid; PWCHAR pChar = pszSidEdn; static WCHAR szHex[] = L"0123456789ABCDEF";
*pChar++ = L'<'; *pChar++ = L'S'; *pChar++ = L'I'; *pChar++ = L'D'; *pChar++ = L'='; for (i=0;i < dwLength;i++,pbSid++) { *pChar++ = szHex[*pbSid >> 4]; *pChar++ = szHex[*pbSid & 0x0F]; }
*pChar++ = L'>'; *pChar = L'\0'; }
BOOL AuthzpAllocateAndInitializeClientContext( OUT PAUTHZI_CLIENT_CONTEXT *ppCC, IN PAUTHZI_CLIENT_CONTEXT Server, IN DWORD Revision, IN LUID Identifier, IN LARGE_INTEGER ExpirationTime, IN DWORD Flags, IN DWORD SidCount, IN DWORD SidLength, IN PSID_AND_ATTRIBUTES Sids, IN DWORD RestrictedSidCount, IN DWORD RestrictedSidLength, IN PSID_AND_ATTRIBUTES RestrictedSids, IN DWORD PrivilegeCount, IN DWORD PrivilegeLength, IN PLUID_AND_ATTRIBUTES Privileges, IN LUID AuthenticationId, IN PAUTHZI_HANDLE AuthzHandleHead, IN PAUTHZI_RESOURCE_MANAGER pRM )
/*++
Routine description:
This routine initializes fields in a client context. It is called by all the AuthzInitializClientContextFrom* routines.
Arguments:
ppCC - Returns the newly allocated and initialized client context structure.
Rest of the parameters are copied into the client context. For explanation of these, see the definition of AUTHZI_CLIENT_CONTEXT.
Return Value:
A value of TRUE is returned if the routine is successful. Otherwise, a value of FALSE is returned. In the failure case, error value may be retrieved using GetLastError().
--*/
{ PAUTHZI_CLIENT_CONTEXT pCC = (PAUTHZI_CLIENT_CONTEXT) AuthzpAlloc(sizeof(AUTHZI_CLIENT_CONTEXT));
if (AUTHZ_ALLOCATION_FAILED(pCC)) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; }
*ppCC = pCC;
RtlZeroMemory( pCC, sizeof(AUTHZ_CLIENT_CONTEXT_HANDLE) );
pCC->AuthenticationId = AuthenticationId; pCC->AuthzHandleHead = AuthzHandleHead; pCC->ExpirationTime = ExpirationTime; pCC->Flags = Flags; pCC->Identifier = Identifier; pCC->pResourceManager = pRM; pCC->PrivilegeCount = PrivilegeCount; pCC->PrivilegeLength = PrivilegeLength; pCC->Privileges = Privileges; pCC->RestrictedSidCount = RestrictedSidCount; pCC->RestrictedSidLength = RestrictedSidLength; pCC->RestrictedSids = RestrictedSids; pCC->Revision = Revision; pCC->Server = Server; pCC->SidCount = SidCount; pCC->SidLength = SidLength; pCC->Sids = Sids;
return TRUE; }
BOOL AuthzpAddDynamicSidsToToken( IN PAUTHZI_CLIENT_CONTEXT pCC, IN PAUTHZI_RESOURCE_MANAGER pRM, IN PVOID DynamicGroupArgs, IN PSID_AND_ATTRIBUTES Sids, IN DWORD SidLength, IN DWORD SidCount, IN PSID_AND_ATTRIBUTES RestrictedSids, IN DWORD RestrictedSidLength, IN DWORD RestrictedSidCount, IN PLUID_AND_ATTRIBUTES Privileges, IN DWORD PrivilegeLength, IN DWORD PrivilegeCount, IN BOOL bAllocated )
/*++
Routine description:
This routine computes resource manager specific groups and add them to the client context. This is a worker routine for all AuthzInitializeFrom* routines.
Arguments:
pCC - Pointer to the client context structure for which the three fields will be set - sids, restricted sids, privileges.
pRM - Pointer to the resource manager structure, supplies the callback function to be used.
DynamicGroupArgs - Caller supplied argument pointer to be passed as an input to the callback function that'd compute dynamic groups
Sids - The sid and atttribute array for the normal part of the client context.
SidLength - Size of the buffer required to hold this array.
SidCount - Number of sids in the array.
RestrictedSids - The sid and atttribute array for the normal part of the client context.
RestrictedSidLength - Size of the buffer required to hold this array.
RestrictedSidCount - Number of restricted sids in the array.
Privileges - The privilege and attribute array.
PrivilegeLength - Size required to hold this array.
PrivilegeCount - The number of privileges in the array.
bAllocated - To specify whether the Sids and RestrictedSids pointers in client context have been allocated separately.
When the client context has been created thru a token, the two pointers point somewhere into a buffer and a new buffer has to be allocated to store these.
When the client context has been created thru a sid, the buffer is a valid allocated one. If no dynamic groups need to be added then we do not have to do anything int this case.
Return Value:
A value of TRUE is returned if the routine is successful. Otherwise, a value of FALSE is returned. In the failure case, error value may be retrieved using GetLastError().
--*/
{ BOOL b = TRUE; PSID_AND_ATTRIBUTES pRMSids = NULL; PSID_AND_ATTRIBUTES pRMRestrictedSids = NULL; PSID_AND_ATTRIBUTES pLocalSids = NULL; PSID_AND_ATTRIBUTES pLocalRestrictedSids = NULL; PLUID_AND_ATTRIBUTES pLocalPrivileges = NULL; DWORD RMSidCount = 0; DWORD RMRestrictedSidCount = 0; DWORD LocalSidLength = 0; DWORD LocalRestrictedSidLength = 0; DWORD i = 0;
//
// Compute dynamic groups.
//
if (AUTHZ_NON_NULL_PTR(pRM->pfnComputeDynamicGroups)) { b = pRM->pfnComputeDynamicGroups( (AUTHZ_CLIENT_CONTEXT_HANDLE) pCC, DynamicGroupArgs, &pRMSids, &RMSidCount, &pRMRestrictedSids, &RMRestrictedSidCount );
if (!b) goto Cleanup; }
//
// Copy the existing sids as well as the dynamic ones into a new buffer if
// needed.
//
if ((0 != RMSidCount) || !bAllocated) { LocalSidLength = SidLength + RMSidCount * sizeof(SID_AND_ATTRIBUTES);
for (i = 0; i < RMSidCount; i++) { LocalSidLength += RtlLengthSid(pRMSids[i].Sid); }
pLocalSids = (PSID_AND_ATTRIBUTES) AuthzpAlloc(LocalSidLength);
if (AUTHZ_ALLOCATION_FAILED(pLocalSids)) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); b = FALSE; goto Cleanup; }
pCC->SidCount = RMSidCount + SidCount; pCC->Sids = pLocalSids;
b = AuthzpCopySidsAndAttributes( pLocalSids, Sids, SidCount, pRMSids, RMSidCount );
if (!b) { goto Cleanup; }
if (!FLAG_ON(pCC->Sids[0].Attributes, SE_GROUP_USE_FOR_DENY_ONLY)) { pCC->Sids[0].Attributes |= SE_GROUP_ENABLED; }
pCC->SidLength = LocalSidLength; }
if ((0 != RMRestrictedSidCount) || !bAllocated) { LocalRestrictedSidLength = RestrictedSidLength + RMRestrictedSidCount * sizeof(SID_AND_ATTRIBUTES);
for (i = 0; i < RMRestrictedSidCount; i++) { LocalRestrictedSidLength += RtlLengthSid(pRMRestrictedSids[i].Sid); }
if (LocalRestrictedSidLength > 0) { pLocalRestrictedSids = (PSID_AND_ATTRIBUTES) AuthzpAlloc(LocalRestrictedSidLength);
if (AUTHZ_ALLOCATION_FAILED(pLocalRestrictedSids)) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); b = FALSE; goto Cleanup; } }
pCC->RestrictedSidCount = RMRestrictedSidCount + RestrictedSidCount; pCC->RestrictedSids = pLocalRestrictedSids;
b = AuthzpCopySidsAndAttributes( pLocalRestrictedSids, RestrictedSids, RestrictedSidCount, pRMRestrictedSids, RMRestrictedSidCount );
if (!b) goto Cleanup;
pCC->RestrictedSidLength = LocalRestrictedSidLength; }
//
// Privileges need to copied only in the case of initilize from token.
//
if (PrivilegeLength > 0) { pLocalPrivileges = (PLUID_AND_ATTRIBUTES) AuthzpAlloc(PrivilegeLength);
if (AUTHZ_ALLOCATION_FAILED(pLocalPrivileges)) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); b = FALSE; goto Cleanup; }
pCC->PrivilegeCount = PrivilegeCount; pCC->Privileges = pLocalPrivileges;
AuthzpCopyLuidAndAttributes( pCC, Privileges, PrivilegeCount, pLocalPrivileges ); } else { pCC->Privileges = NULL; }
Cleanup:
if (!b) { AuthzpFreeNonNull(pLocalSids); AuthzpFreeNonNull(pLocalRestrictedSids); AuthzpFreeNonNull(pLocalPrivileges); }
if (AUTHZ_NON_NULL_PTR(pRMSids)) { pRM->pfnFreeDynamicGroups(pRMSids); }
if (AUTHZ_NON_NULL_PTR(pRMRestrictedSids)) { pRM->pfnFreeDynamicGroups(pRMSids); }
return b; }
BOOL AuthzpComputeSkipFlagsForWellKnownSid( IN PSID UserSid, OUT PDWORD Flags )
/*++
Routine description:
This routine computes skip flags if the User sid is a wellknown sid.
Arguments:
UserSid - User sid for which SKIP flag will be computed.
Flags - to return the SKIP flags.
A value of AUTHZ_SKIP_TOKEN_GROUPS is returned for well-known and builtin sid. A value of 0 is returned in failure cases as well as other cases.
Return Value: A value of TRUE is returned if the routine is successful. Otherwise, a value of FALSE is returned. In the failure case, error value may be retrieved using GetLastError().
--*/
{
DWORD dwErr = ERROR_SUCCESS; BOOL b = TRUE; LPWSTR Name = NULL; LPWSTR RefDomainName = NULL; DWORD NameSize = 0; DWORD RefDomainNameSize = 0; SID_NAME_USE SidNameUse = SidTypeUnknown;
*Flags = 0;
//
// Dummy call to get the size of the buffer.
//
b = LookupAccountSidW( NULL, UserSid, NULL, &NameSize, NULL, &RefDomainNameSize, &SidNameUse );
if (FALSE == b) { dwErr = GetLastError();
//
// Return if we failed because of any error other than insufficient
// buffer.
//
if(dwErr != ERROR_INSUFFICIENT_BUFFER) { return FALSE; }
//
// LookupAccountSid returns the size in TCHARS.
//
NameSize *= sizeof(WCHAR); RefDomainNameSize *= sizeof(WCHAR);
//
// Allocate memory required to hold the names.
//
Name = (LPWSTR) AuthzpAlloc(NameSize);
if (AUTHZ_ALLOCATION_FAILED(Name)) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; }
RefDomainName = (LPWSTR) AuthzpAlloc(RefDomainNameSize);
if (AUTHZ_ALLOCATION_FAILED(RefDomainName)) { AuthzpFree(Name); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; }
//
// Do the real lookup.
//
b = LookupAccountSidW( NULL, UserSid, Name, &NameSize, RefDomainName, &RefDomainNameSize, &SidNameUse );
AuthzpFree(Name); AuthzpFree(RefDomainName);
if (FALSE == b) { return FALSE; }
//
// Add the SKIP flag for the cases required.
//
switch (SidNameUse) { case SidTypeAlias: case SidTypeWellKnownGroup: case SidTypeInvalid: case SidTypeUnknown: case SidTypeGroup: *Flags = AUTHZ_SKIP_TOKEN_GROUPS; return TRUE; case SidTypeComputer: case SidTypeDomain: case SidTypeDeletedAccount: case SidTypeUser: default: return TRUE; } }
//
// Should not get here.
//
ASSERT(FALSE); return TRUE; }
DWORD AuthzpIsDC( OUT PBOOL pbIsDC )
/*++
Routine description:
This routine decides whether a trip to the AD is required to fetch TokenGroups attribute.
Arguments:
pbIsDC - Returns whether or not this is a DC.
Return Value:
Returns ERROR_SUCCESS on success, appropriate failure value otherwise. --*/
{ static BOOL bFirstTime = TRUE; static BOOL bIsDC = FALSE;
DWORD dwErr = ERROR_SUCCESS; PDSROLE_PRIMARY_DOMAIN_INFO_BASIC BasicInfo = NULL;
if (bFirstTime) { //
// Get the information about the local machine.
//
dwErr = DsRoleGetPrimaryDomainInformation( NULL, DsRolePrimaryDomainInfoBasic, & (PBYTE) BasicInfo );
if (ERROR_SUCCESS != dwErr) { return dwErr; }
//
// If the local machine is a DC then TokenGroups should be computed anyway.
//
switch(BasicInfo->MachineRole) { case DsRole_RolePrimaryDomainController: case DsRole_RoleBackupDomainController:
bIsDC = TRUE;
break;
default: break; }
DsRoleFreeMemory(BasicInfo); bFirstTime = FALSE; }
*pbIsDC = bIsDC;
return ERROR_SUCCESS; }
|