windows-nt/Source/XPSP1/NT/ds/security/authz/context.c
2020-09-26 16:20:57 +08:00

4353 lines
98 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
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 = 1000;
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;
PLSA_TRANSLATED_SID2 pSids;
SID_NAME_USE sidUse;
// information about the local machine
PPOLICY_ACCOUNT_DOMAIN_INFO pAccountInfo;
PPOLICY_PRIMARY_DOMAIN_INFO pPrimaryInfo;
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 OUT PSID_SET pSidSet
);
DWORD
AuthzpGetSidHistory(
IN OUT PSID_SET pSidSet
);
DWORD
AuthzpGetSamGroups(
IN PUNICODE_STRING pusDcName,
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
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)
{
//
// enable this after LsaLogonUser gets its error codes right
// for now we try the downlevel scenario for every possible
// failure of (even not enough memory)
// LsaLogonUser will return ERROR_NO_LOGON_SERVERS
// if it can't deal with a downlevel client
//
// if (dwError != ERROR_INVALID_PARAMETER &&
// dwError != ERROR_NOT_SUPPORTED)
// {
// 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.
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)
{
//
// enable this after LsaLogonUser gets its error codes right
// for now we try the downlevel scenario for every possible
// failure of (even not enough memory)
// LsaLogonUser will return ERROR_NO_LOGON_SERVERS
// if it can't deal with a downlevel client
//
// if (dwError != ERROR_INVALID_PARAMETER &&
// dwError != ERROR_NOT_SUPPORTED)
// {
// 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 bAddLocal = 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;
bAddLocal = 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;
bAddLocal = TRUE;
bAddAuthUsers = TRUE;
bAddAdministrators = TRUE;
pSidSet->dwFlags |= AUTHZ_SKIP_TOKEN_GROUPS;
}
else
{
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 os returned for wellknown sids.
// It is ok to ignore thhis error and continue.
//
if ((bStatus == FALSE) && (GetLastError() != ERROR_NON_DOMAIN_SID))
{
dwError = GetLastError();
goto Cleanup;
}
if (bEqual)
{
pSidSet->bSkipNonLocal = TRUE;
}
else
{
bAddEveryone = TRUE;
bAddLocal = 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++;
}
if (bAddLocal)
{
//
// add \LOCAL to the set
//
dwLengthSid = SECURITY_MAX_SID_SIZE;
bStatus = CreateWellKnownSid(
WinLocalSid,
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)
{
//
// add \LOCAL to the set
//
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;
NT_PRODUCT_TYPE ProductType;
//
// For now, S4U is not specified to work on the client.
//
if (!RtlGetNtProductType(&ProductType) ||
ProductType == NtProductWinNt)
{
dwError = ERROR_NOT_SUPPORTED;
goto Cleanup;
}
//
// 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,
&quota,
&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)
{
LocalFree((HLOCAL)pTokenUser);
}
if (pTokenGroups)
{
LocalFree((HLOCAL)pTokenGroups);
}
if (hToken)
{
CloseHandle(hToken);
}
if (pProfileBuffer)
{
LsaFreeReturnBuffer(pProfileBuffer);
}
if (hLsa)
{
LsaDeregisterLogonProcess(hLsa);
}
if (pPackage)
{
LocalFree((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;
SAM_HANDLE hSam = 0;
SAM_HANDLE hDomain = 0;
SAM_HANDLE hUser = 0;
//
// 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 don't need to go to a DC.
// Instead we use the local machine.
// 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))
{
pSidSet->pszUdDcName = 0;
bAddPrimaryGroup = TRUE;
goto LocalGroups;
}
else
{
//
// Find a DC and get its name.
//
dwError = AuthzpGetDcName(
pSidSet->pDomains->Domains->Name.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. 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->pDomains->Domains->Name.Buffer,
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->pPrimaryInfo->Name.Buffer,
&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->pPrimaryInfo->Name.Buffer,
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:
//
// If this is the local user case, we have to add the primary group for
// for the user.
//
if (bAddPrimaryGroup)
{
NTSTATUS status;
PUCHAR Buffer = NULL;
PSID pPrimaryGroupSid = NULL;
OBJECT_ATTRIBUTES obja = {0};
DWORD dwRelativeId = 0;
//
// Connect to local Sam.
//
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
);
//
// 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,
(VOID *) &Buffer
);
if (!NT_SUCCESS(status))
{
dwError = RtlNtStatusToDosError(status);
goto Cleanup;
}
//
// Convert the rid to Sid.
//
status = SamRidToSid(
hDomain,
((USER_PRIMARY_GROUP_INFORMATION *) Buffer)->PrimaryGroupId,
&pPrimaryGroupSid
);
(VOID) SamFreeMemory(Buffer);
if (!NT_SUCCESS(status))
{
dwError = RtlNtStatusToDosError(status);
goto Cleanup;
}
dwError = AuthzpAddSidToSidSet(
pSidSet,
pPrimaryGroupSid,
0,
SE_GROUP_ENABLED,
0,
0
);
SamFreeMemory(pPrimaryGroupSid);
if (dwError != ERROR_SUCCESS)
{
goto Cleanup;
}
}
//
// Collect local groups information.
//
dwError = AuthzpGetLocalGroups(
pSidSet
);
if (dwError != ERROR_SUCCESS)
{
goto Cleanup;
}
Cleanup:
if (bAddPrimaryGroup)
{
if (hUser)
{
SamCloseHandle(hUser);
}
if (hDomain)
{
SamCloseHandle(hDomain);
}
if (hSam)
{
SamCloseHandle(hSam);
}
}
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;
}
}
dwError = ldap_bind_s(
pLdap,
0,
0,
LDAP_AUTH_SSPI
);
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]
);
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:
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 = 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 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:
pServerName - Pointer to a UNICODE_STRING identifying the computer
to use for the lookup. If NULL is passed, the local computer will be used.
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;
}
//
// 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;
}
}
dwError = ldap_bind_s(
pLdap,
0,
0,
LDAP_AUTH_SSPI
);
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
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->pSids = 0;
pSidSet->sidUse = SidTypeUnknown;
pSidSet->pAccountInfo = 0;
pSidSet->pPrimaryInfo = 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->pSids = 0;
pSidSet->sidUse = SidTypeUnknown;
pSidSet->pAccountInfo = 0;
pSidSet->pPrimaryInfo = 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->pSids)
{
LsaFreeMemory(pSidSet->pSids);
}
if (pSidSet->pAccountInfo)
{
LsaFreeMemory(pSidSet->pAccountInfo);
}
if (pSidSet->pPrimaryInfo)
{
LsaFreeMemory(pSidSet->pPrimaryInfo);
}
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 an 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;
dwError = ERROR_SUCCESS;
Cleanup:
if (hPolicy)
{
LsaClose(hPolicy);
}
if (usName.Buffer)
{
LocalFree((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;
}
//
// 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;
}