Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1567 lines
43 KiB

/*++
Copyright (c) 1993 Microsoft Corporation
Module Name:
security.c
Abstract:
Security addons to the tree-manipulation module
for the RPL service.
Author:
Jon Newman (jonn) 16 - February - 1994
Revision History:
Jon Newman (jonn) 16 - February - 1994
Added RplGrant*Perms primitives
CODEWORK: Should have more sophisticated error handling
--*/
#include "local.h"
#include "tree.h"
#include "treei.h"
#include "security.h"
#include <ntseapi.h>
#include <ntrtl.h> // RtlAllocateAndInitializeSid
#include <ntlsa.h>
#include <ntsam.h>
#include <nturtl.h> // see IsNTFS
typedef enum _RPL_SD_BLOCK_TYPE {
RPL_SD_BLOCK_TYPE_ADMINONLY = 0,// Only RPLADMIN gets access
RPL_SD_BLOCK_TYPE_ALLUSERS, // RPLADMIN gets full access, RPLUSER
// gets RX access
RPL_SD_BLOCK_TYPE_ONEUSER, // RPLADMIN gets full access, one
// wksta account gets RWXCDA access
RPL_SD_BLOCK_TYPE_BADUSER // Skip setting permissions on this user
} RPL_SD_BLOCK_TYPE;
/*
* This is the block of information which is retained between the recursive
* calls in a single RplDoTree run. It remembers the SECURITY_DESCRIPTOR
* which was assigned to the last file/directory, so if the next
* file/directory wants the same permissions (as is usually the case)
* we do not have to fetch everything all over again. It also contains
* some information about what kind of SECURITY_DESCRIPTOR is currently
* stored, plus items needed to get a different SECURITY_DESCRIPTOR.
*/
typedef struct _RPL_SD_BLOCK {
PSECURITY_DESCRIPTOR psd;
RPL_SD_BLOCK_TYPE nChangeableAceType;
PSID psidSystem;
PSID psidAdministrators;
PSID psidSystemOperators;
PSID psidRPLUSER;
PSID psidGrantTo; // SID of account with name pwszGrantToName
PWCHAR pwszGrantToName;
// Name of account for BLOCK_TYPE_ONEUSER
POLICY_ACCOUNT_DOMAIN_INFO * pinfoAccountDomain;
SAM_HANDLE hsamAccountDomain;
} RPL_SD_BLOCK, *PRPL_SD_BLOCK;
//
// The DACL we assign to files has 3 or 4 ACEs.
//
#define RPL_INDEX_SYSTEM_ACE 0
#define RPL_INDEX_ADMINS_ACE 1
#define RPL_INDEX_RPLADMIN_ACE 2
#define RPL_INDEX_CHANGEABLE_ACE 3
//
// Forward declarations
//
DWORD SetRplPerms(
IN PWCHAR pwszPath,
IN OUT PVOID * ppv);
DWORD GetSdForFile(
OUT PSECURITY_DESCRIPTOR * ppsd,
IN OUT RPL_SD_BLOCK ** ppsdblock,
IN PWCHAR pwszFile,
OUT BOOL * pfSkipPermsOnFile
);
DWORD GetSd(
OUT PSECURITY_DESCRIPTOR * ppsd,
IN OUT RPL_SD_BLOCK ** ppsdblock,
IN RPL_SD_BLOCK_TYPE nChangeableAceType,
IN PWCHAR pwszGrantTo
);
DWORD AddInheritableAce(
IN OUT ACL * pacl,
IN ACCESS_MASK mask,
IN SID * psid );
DWORD InitSdBlock(
OUT RPL_SD_BLOCK ** ppsdblock
);
VOID FreeSdBlock(
IN RPL_SD_BLOCK ** ppsdblock
);
DWORD GetAccountDomain(
OUT SAM_HANDLE * phsamAccountDomain,
OUT POLICY_ACCOUNT_DOMAIN_INFO ** ppinfoAccountDomain,
IN ACCESS_MASK DesiredAccess
);
DWORD GetAccountSid(
IN SAM_HANDLE hsamAccountDomain,
IN POLICY_ACCOUNT_DOMAIN_INFO * pinfoAccountDomain,
IN WCHAR * pwszAccountName,
OUT PSID * ppsidAccountSid,
IN BOOL fIsLocalgroup,
OUT DWORD * pulRid OPTIONAL
);
DWORD IsNTFS(
IN WCHAR * pszResource,
OUT BOOL * pfIsNTFS );
DWORD RplAddWkstaAccount( LPWSTR pszWkstaName,
SAM_HANDLE hsamAccountDomain,
POLICY_ACCOUNT_DOMAIN_INFO * pinfoAccountDomain,
SAM_HANDLE hsamRPLUSER );
VOID FillUnicodeString( PUNICODE_STRING punistr,
LPWSTR lpwsz );
DWORD MyBuildSid( OUT PSID * ppsidAccountSid,
IN POLICY_ACCOUNT_DOMAIN_INFO * pinfoAccountDomain,
IN ULONG ulRid );
VOID MyFreeSid( IN OUT PSID * ppsid );
/*
* Like RplTreeCopy, but also sets permissions on target files/directories.
* Target must be in %RplRoot% subtree.
*
* CODEWORK do something with error buffer
*/
DWORD RplTreeCopyAndSetPerms(
IN PWCHAR pwszSource,
IN PWCHAR pwszTarget )
{
WCHAR Buffer[ 300];
DWORD Error = NO_ERROR;
PVOID pv = NULL;
DWORD Flags = RPL_TREE_COPY | RPL_TREE_AUXILIARY;
BOOL fIsNTFS = TRUE;
//
// Check if volume is NTFS, if so never mind about ACLs
//
Error = IsNTFS( pwszTarget, &fIsNTFS );
if (Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
} else if (!fIsNTFS) {
Flags &= ~RPL_TREE_AUXILIARY;
}
Buffer[ 0] = 0;
Error = RplDoTree( pwszSource, pwszTarget,
Flags,
Buffer, sizeof(Buffer),
SetRplPerms, &pv);
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
cleanup:
if ( pv != NULL) {
FreeSdBlock( (RPL_SD_BLOCK **)(&pv) );
}
return( Error);
}
/*
* Sets permissions on a file tree.
* Target must be in %RplRoot% subtree.
*
* CODEWORK do something with error buffer
*/
DWORD RplSetPermsOnTree(
IN PWCHAR pwszSource )
{
WCHAR Buffer[ 300];
DWORD Error = NO_ERROR;
PVOID pv = NULL;
BOOL fIsNTFS = TRUE;
//
// Check if volume is NTFS, if so do nothing
//
Error = IsNTFS( pwszSource, &fIsNTFS );
if (Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
} else if (!fIsNTFS) {
goto cleanup;
}
Buffer[ 0] = 0;
Error = RplDoTree( pwszSource, NULL,
RPL_TREE_AUXILIARY,
Buffer, sizeof(Buffer),
SetRplPerms, &pv);
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
cleanup:
if ( pv != NULL) {
FreeSdBlock( (RPL_SD_BLOCK **)(&pv) );
}
return( Error);
}
/*
* This is the callback routine called by RplDoTree to set permissions
* on a specific file/directory.
*/
DWORD SetRplPerms(
IN PWCHAR pwszPath,
IN OUT PVOID * ppv)
{
DWORD Error = NO_ERROR;
RPL_SD_BLOCK ** ppsdblock = (RPL_SD_BLOCK **)ppv;
PSECURITY_DESCRIPTOR psd = NULL;
BOOL fSkipPermsOnFile = FALSE;
RPL_ASSERT( pwszPath != NULL );
RPL_ASSERT( ppv != NULL );
Error = GetSdForFile( &psd, ppsdblock, pwszPath, &fSkipPermsOnFile );
if (Error != NO_ERROR || fSkipPermsOnFile) {
goto cleanup;
}
RPL_ASSERT( psd != NULL );
//
// Set the actual file security. Note that this call will succeed
// even if the volume is not NTFS, thus we must check for
// partition type elsewhere.
//
if ( !SetFileSecurity( pwszPath, DACL_SECURITY_INFORMATION, psd)) {
Error = GetLastError();
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
cleanup:
return( Error);
}
/*
* Get the SECURITY_DESCRIPTOR which is appropriate for a file/directory
* at this position in %RplRoot%. First we determine what kind of
* SECURITY_DESCRIPTOR we want, then we call GetSd() to build it.
*/
DWORD GetSdForFile(
OUT PSECURITY_DESCRIPTOR * ppsd,
IN OUT RPL_SD_BLOCK ** ppsdblock,
IN PWCHAR pwszFile,
OUT BOOL * pfSkipPermsOnFile
)
{
DWORD Error = NO_ERROR;
PWCHAR pwszRelativeFile = NULL;
PWCHAR pwszWkstaNameStart = NULL;
PWCHAR pwszWkstaNameEnd = NULL;
RPL_SD_BLOCK_TYPE blocktype = RPL_SD_BLOCK_TYPE_ADMINONLY;
WCHAR awchFile[MAX_PATH];
WCHAR wchSave, wchSave2;
DWORD cch;
RPL_ASSERT( ppsd != NULL );
RPL_ASSERT( ppsdblock != NULL );
RPL_ASSERT( pwszFile != NULL );
RPL_ASSERT( pfSkipPermsOnFile != NULL );
RPL_ASSERT( lstrlenW(pwszFile) >= (INT)RG_DirectoryLength );
RPL_ASSERT( lstrlenW(pwszFile) < MAX_PATH );
RPL_ASSERT( RG_DirectoryLength > 0 && RG_DirectoryLength < MAX_PATH );
*pfSkipPermsOnFile = FALSE;
//
// Make working copy of pwszFile
//
if (NULL == lstrcpy(awchFile,pwszFile)) {
Error = GetLastError();
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
//
// Ensure that awchFile starts with RG_Directory
//
// RG_Directory always ends with L'\\'. awchFile might be equal to
// RG_Directory without the L'\\', we must handle this.
//
wchSave = awchFile[RG_DirectoryLength];
wchSave2 = awchFile[RG_DirectoryLength-1];
if (lstrlenW(awchFile) == (int)RG_DirectoryLength-1) {
awchFile[RG_DirectoryLength-1] = L'\\';
}
awchFile[RG_DirectoryLength] = L'\0';
if ( 0 != lstrcmpi( awchFile,
RG_Directory )) {
RPL_ASSERT( FALSE );
Error = ERROR_INVALID_PARAMETER;
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
awchFile[RG_DirectoryLength] = wchSave;
awchFile[RG_DirectoryLength-1] = wchSave2;
pwszRelativeFile = awchFile + RG_DirectoryLength;
//
// If the path is in rplfiles\binfiles or rplfiles\profiles,
// set the changeable ACE to RPLUSER
//
cch = lstrlenW(L"RPLFILES\\BINFILES");
wchSave = pwszRelativeFile[ cch ];
pwszRelativeFile[ cch ] = L'\0';
if ( 0 == lstrcmpi( pwszRelativeFile, L"RPLFILES\\BINFILES") ) {
blocktype = RPL_SD_BLOCK_TYPE_ALLUSERS;
goto cleanup;
}
pwszRelativeFile[ cch ] = wchSave;
cch = lstrlenW(L"RPLFILES\\PROFILES");
wchSave = pwszRelativeFile[ cch ];
pwszRelativeFile[ cch ] = L'\0';
if ( 0 == lstrcmpi( pwszRelativeFile, L"RPLFILES\\PROFILES") ) {
blocktype = RPL_SD_BLOCK_TYPE_ALLUSERS;
goto cleanup;
}
pwszRelativeFile[ cch ] = wchSave;
//
// If the path is in rplfiles\machines\* or rplfiles\tmpfiles\*,
// set the changeable ACE to the individual workstation account
//
cch = lstrlenW(L"RPLFILES\\MACHINES\\");
wchSave = pwszRelativeFile[ cch ];
pwszRelativeFile[ cch ] = L'\0';
if ( 0 == lstrcmpi( pwszRelativeFile, L"RPLFILES\\MACHINES\\") ) {
pwszRelativeFile[ cch ] = wchSave;
blocktype = RPL_SD_BLOCK_TYPE_ONEUSER;
pwszWkstaNameStart = pwszWkstaNameEnd = pwszRelativeFile + cch;
while (*pwszWkstaNameEnd != L'\\' && *pwszWkstaNameEnd != L'\0') {
pwszWkstaNameEnd++;
}
*pwszWkstaNameEnd = L'\0';
goto cleanup;
}
pwszRelativeFile[ cch ] = wchSave;
cch = lstrlenW(L"RPLFILES\\TMPFILES\\");
wchSave = pwszRelativeFile[ cch ];
pwszRelativeFile[ cch ] = L'\0';
if ( 0 == lstrcmpi( pwszRelativeFile, L"RPLFILES\\TMPFILES\\") ) {
pwszRelativeFile[ cch ] = wchSave;
blocktype = RPL_SD_BLOCK_TYPE_ONEUSER;
pwszWkstaNameStart = pwszWkstaNameEnd = pwszRelativeFile + cch;
while (*pwszWkstaNameEnd != L'\\' && *pwszWkstaNameEnd != L'\0') {
pwszWkstaNameEnd++;
}
*pwszWkstaNameEnd = L'\0';
goto cleanup;
}
pwszRelativeFile[ cch ] = wchSave;
cleanup:
//
// We now know what kind of SECURITY_DESCRIPTOR we want, so get it.
//
if (Error == NO_ERROR && !(*pfSkipPermsOnFile)) {
Error = GetSd( ppsd, ppsdblock, blocktype, pwszWkstaNameStart );
}
return( Error);
}
/*
* This call gets an actual security descriptor which corresponds to
* nChangeableAceType and pwszGrantTo. The security descriptor
* is only valid until next call to GetSd().
*
* The caller has to free *ppsdblock eventually, but should not free
* *ppsd.
*/
DWORD GetSd(
OUT PSECURITY_DESCRIPTOR * ppsd,
IN OUT RPL_SD_BLOCK ** ppsdblock,
IN RPL_SD_BLOCK_TYPE nChangeableAceType,
IN PWCHAR pwszGrantTo
)
{
DWORD Error = NO_ERROR;
PACL paclDacl = NULL;
BOOL fDaclPresent = FALSE;
BOOL fDaclDefaulted = FALSE;
RPL_ASSERT( ppsd != NULL);
RPL_ASSERT( ppsdblock != NULL);
//
// Create the RPL_SD_BLOCK if it does not yet exist
//
if ( *ppsdblock == NULL )
{
Error = InitSdBlock( ppsdblock );
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
}
RPL_ASSERT( *ppsdblock != NULL );
//
// If the RPL_SD_BLOCK is already configured correctly, return success
//
if ( ( (*ppsdblock)->nChangeableAceType == nChangeableAceType )
&& ( (*ppsdblock)->nChangeableAceType != RPL_SD_BLOCK_TYPE_ONEUSER
|| (0 == lstrcmpW((*ppsdblock)->pwszGrantToName, pwszGrantTo)) )
&& ( (*ppsdblock)->nChangeableAceType != RPL_SD_BLOCK_TYPE_BADUSER
|| (0 == lstrcmpW((*ppsdblock)->pwszGrantToName, pwszGrantTo)) )
)
{
goto cleanup;
}
//
// Extract the DACL
//
if ( !GetSecurityDescriptorDacl( (*ppsdblock)->psd,
&fDaclPresent,
&paclDacl,
&fDaclDefaulted
)) {
Error = GetLastError();
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
RPL_ASSERT( fDaclPresent && !fDaclDefaulted );
//
// Clear the changeable ACE if present
//
if ( (*ppsdblock)->nChangeableAceType != RPL_SD_BLOCK_TYPE_ADMINONLY
&& (*ppsdblock)->nChangeableAceType != RPL_SD_BLOCK_TYPE_BADUSER )
{
if ( !DeleteAce( paclDacl, RPL_INDEX_CHANGEABLE_ACE )) {
Error = GetLastError();
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
// CODEWORK do we need to decrement ACE count here?
(*ppsdblock)->nChangeableAceType = RPL_SD_BLOCK_TYPE_ADMINONLY;
}
//
// Clear and rewrite psidGrantTo if necessary
//
if ( nChangeableAceType == RPL_SD_BLOCK_TYPE_ONEUSER
|| nChangeableAceType == RPL_SD_BLOCK_TYPE_BADUSER )
{
if ( (*ppsdblock)->psidGrantTo != NULL) {
MyFreeSid( &((*ppsdblock)->psidGrantTo) );
TREE_FREE( (*ppsdblock)->pwszGrantToName );
(*ppsdblock)->pwszGrantToName = NULL;
}
if (lstrlenW(pwszGrantTo) > RPL_MAX_WKSTA_NAME_LENGTH)
{
TREE_ASSERT( ( "Bad user %ws", pwszGrantTo));
nChangeableAceType = RPL_SD_BLOCK_TYPE_BADUSER;
}
else
{
Error = GetAccountSid( (*ppsdblock)->hsamAccountDomain,
(*ppsdblock)->pinfoAccountDomain,
pwszGrantTo,
&((*ppsdblock)->psidGrantTo),
FALSE,
NULL );
// CODEWORK trap error here?
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
}
(*ppsdblock)->pwszGrantToName =
TREE_ALLOC( (lstrlenW(pwszGrantTo)+1) * sizeof(WCHAR) );
if ( (*ppsdblock)->pwszGrantToName == NULL )
{
Error = ERROR_NOT_ENOUGH_MEMORY;
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
lstrcpyW( (*ppsdblock)->pwszGrantToName, pwszGrantTo );
}
//
// Add changeable ACE if desired
//
RPL_ASSERT( (*ppsdblock)->nChangeableAceType = RPL_SD_BLOCK_TYPE_ADMINONLY );
switch (nChangeableAceType) {
case RPL_SD_BLOCK_TYPE_ADMINONLY:
case RPL_SD_BLOCK_TYPE_BADUSER:
break;
case RPL_SD_BLOCK_TYPE_ALLUSERS:
Error = AddInheritableAce( paclDacl,
GENERIC_READ | GENERIC_EXECUTE,
(*ppsdblock)->psidRPLUSER );
break;
case RPL_SD_BLOCK_TYPE_ONEUSER:
Error = AddInheritableAce( paclDacl,
GENERIC_READ
| GENERIC_WRITE
| GENERIC_EXECUTE
| DELETE,
(*ppsdblock)->psidGrantTo );
break;
default:
ASSERT(FALSE);
break;
}
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
(*ppsdblock)->nChangeableAceType = nChangeableAceType;
//
// Save the upgraded ACL into the security descriptor
//
// CODEWORK is this needed?
//
if ( !SetSecurityDescriptorDacl( (*ppsdblock)->psd,
TRUE,
paclDacl,
FALSE
)) {
Error = GetLastError();
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
cleanup:
if (Error == NO_ERROR) {
*ppsd = (*ppsdblock)->psd;
}
return( Error);
}
SID_IDENTIFIER_AUTHORITY IDAuthorityNT = SECURITY_NT_AUTHORITY;
/*
* Create a new RPL_SD_BLOCK
*/
DWORD InitSdBlock(
OUT RPL_SD_BLOCK ** ppsdblock
)
{
DWORD Error = 0;
DWORD cbNewDacl = 0;
PACL paclDacl = NULL;
RPL_ASSERT( ppsdblock != NULL );
RPL_ASSERT( *ppsdblock == NULL );
//
// Allocate memory for the RPL_SD_BLOCK
//
*ppsdblock = TREE_ALLOC( sizeof(RPL_SD_BLOCK) );
if ( *ppsdblock == NULL) {
Error = ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
ZeroMemory( (*ppsdblock), sizeof(RPL_SD_BLOCK) );
//
// Get System SID
//
Error = RtlAllocateAndInitializeSid(
&IDAuthorityNT,
1,
SECURITY_LOCAL_SYSTEM_RID,
0, 0, 0, 0, 0, 0, 0,
&((*ppsdblock)->psidSystem)
);
if (Error != NO_ERROR) {
goto cleanup;
}
//
// Get Administrators SID
//
Error = RtlAllocateAndInitializeSid(
&IDAuthorityNT,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&((*ppsdblock)->psidAdministrators)
);
if (Error != NO_ERROR) {
goto cleanup;
}
//
// Get System Operators SID
//
Error = RtlAllocateAndInitializeSid(
&IDAuthorityNT,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_SYSTEM_OPS,
0, 0, 0, 0, 0, 0,
&((*ppsdblock)->psidSystemOperators)
);
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
//
// Get information about account domain
//
Error = GetAccountDomain( &((*ppsdblock)->hsamAccountDomain),
&((*ppsdblock)->pinfoAccountDomain),
GENERIC_EXECUTE );
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
//
// Get RPLUSER SID
//
Error = GetAccountSid( (*ppsdblock)->hsamAccountDomain,
(*ppsdblock)->pinfoAccountDomain,
RPL_GROUP_RPLUSER,
&((*ppsdblock)->psidRPLUSER),
TRUE,
NULL );
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
//
// Start assembling DACL
//
cbNewDacl = sizeof(ACL) + (3*sizeof(ACCESS_ALLOWED_ACE))
+ (3*GetSidLengthRequired(SID_MAX_SUB_AUTHORITIES))
- sizeof(DWORD);
paclDacl = (PACL) TREE_ALLOC( cbNewDacl);
if ( paclDacl == NULL) {
Error = ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
if ( !InitializeAcl( paclDacl, cbNewDacl, ACL_REVISION )) {
Error = GetLastError();
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
//
// Add System ACE
//
Error = AddInheritableAce( paclDacl,
GENERIC_ALL,
(*ppsdblock)->psidSystem );
if (Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
// CODEWORK do we still want to free psidSystem?
//
// Add Administrators ACE
//
Error = AddInheritableAce( paclDacl,
GENERIC_ALL,
(*ppsdblock)->psidAdministrators );
if (Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
// CODEWORK do we still want to free psidAdministrators?
//
// Add System Operators ACE
//
Error = AddInheritableAce( paclDacl,
GENERIC_ALL,
(*ppsdblock)->psidSystemOperators );
if (Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
// BUGBUG shouldn't add this for WINNT machines!
// CODEWORK do we still want to free psidSystemOperators?
//
// Build security descriptor
//
(*ppsdblock)->psd = TREE_ALLOC( sizeof(SECURITY_DESCRIPTOR) );
if ( (*ppsdblock)->psd == NULL) {
Error = ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
if ( !InitializeSecurityDescriptor( (*ppsdblock)->psd,
SECURITY_DESCRIPTOR_REVISION )) {
Error = GetLastError();
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
if ( !SetSecurityDescriptorDacl( (*ppsdblock)->psd,
TRUE,
paclDacl,
FALSE )) {
Error = GetLastError();
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
//
// Set nChangeableAceType in RPL_SD_BLOCK
//
(*ppsdblock)->nChangeableAceType = RPL_SD_BLOCK_TYPE_ADMINONLY;
cleanup:
if (Error != NO_ERROR)
{
FreeSdBlock( ppsdblock );
if (paclDacl != NULL) {
TREE_FREE( paclDacl );
paclDacl = NULL;
}
}
return(Error);
}
/*
* Add an ACCESS_ALLOWED_ACE to the ACL, with all inheritance flags set
*/
DWORD AddInheritableAce(
IN OUT ACL * pacl,
IN ACCESS_MASK mask,
IN SID * psid )
{
DWORD Error = NO_ERROR;
ACCESS_ALLOWED_ACE * pace = NULL;
if ( !AddAccessAllowedAce( pacl,
ACL_REVISION,
mask,
psid )) {
Error = GetLastError();
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
//
// new ACE should be the last ACE
//
if ( !GetAce( pacl, (pacl->AceCount) - 1, &pace )) {
Error = GetLastError();
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
RPL_ASSERT( EqualSid( psid, &(pace->SidStart)) );
(pace->Header.AceFlags) |= (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE);
cleanup:
return( Error);
}
/*
* The RplDoTree run is over, free the RPL_SD_BLOCK
*/
VOID FreeSdBlock(
IN RPL_SD_BLOCK ** ppsdblock
)
{
RPL_ASSERT( ppsdblock != NULL );
if (*ppsdblock != NULL)
{
if ((*ppsdblock)->psd != NULL) {
TREE_FREE( (*ppsdblock)->psd );
}
if ((*ppsdblock)->pwszGrantToName != NULL) {
TREE_FREE( (*ppsdblock)->pwszGrantToName );
}
if ((*ppsdblock)->psidSystem != NULL) {
RtlFreeSid( (*ppsdblock)->psidSystem );
}
if ((*ppsdblock)->psidAdministrators != NULL) {
RtlFreeSid( (*ppsdblock)->psidAdministrators );
}
if ((*ppsdblock)->psidSystemOperators != NULL) {
RtlFreeSid( (*ppsdblock)->psidSystemOperators);
}
MyFreeSid( &((*ppsdblock)->psidRPLUSER) );
MyFreeSid( &((*ppsdblock)->psidGrantTo) );
if ( (*ppsdblock)->pinfoAccountDomain != NULL) {
LsaFreeMemory( (*ppsdblock)->pinfoAccountDomain);
}
if ((*ppsdblock)->hsamAccountDomain != NULL) {
SamCloseHandle( (*ppsdblock)->hsamAccountDomain);
}
}
TREE_FREE( *ppsdblock );
*ppsdblock = NULL;
}
/*
* Get a SAM_HANDLE to the account domain, and other information about
* the account domain. This routine uses SAM and LSA directly.
*/
DWORD GetAccountDomain(
OUT SAM_HANDLE * phsamAccountDomain,
OUT POLICY_ACCOUNT_DOMAIN_INFO ** ppinfoAccountDomain,
IN ACCESS_MASK DesiredAccess
)
{
DWORD Error = NO_ERROR;
SAM_HANDLE hsamServer = NULL;
LSA_HANDLE hlsa = NULL;
OBJECT_ATTRIBUTES oa;
POLICY_ACCOUNT_DOMAIN_INFO * pacctdominfo = NULL;
SECURITY_QUALITY_OF_SERVICE sqos;
RPL_ASSERT( phsamAccountDomain != NULL );
RPL_ASSERT( ppinfoAccountDomain != NULL );
//
// Get server handle
//
Error = SamConnect( NULL,
&hsamServer,
SAM_SERVER_LOOKUP_DOMAIN,
NULL );
if ( Error != NERR_Success) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
//
// Set up object attributes (borrowed from uintlsa.cxx)
//
sqos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
sqos.ImpersonationLevel = SecurityImpersonation;
sqos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
sqos.EffectiveOnly = FALSE;
InitializeObjectAttributes( &oa, NULL, 0L, NULL, NULL );
oa.SecurityQualityOfService = &sqos;
//
// Get LSA handle
//
Error = LsaOpenPolicy( NULL,
&oa,
GENERIC_EXECUTE,
&hlsa );
if ( Error != NERR_Success) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
//
// Get primary domain information
//
Error = LsaQueryInformationPolicy( hlsa,
PolicyAccountDomainInformation,
ppinfoAccountDomain );
if ( Error != NERR_Success) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
//
// Open account domain handle
//
Error = SamOpenDomain( hsamServer,
DesiredAccess,
(*ppinfoAccountDomain)->DomainSid,
phsamAccountDomain );
if ( Error != NERR_Success) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
cleanup:
if ( hsamServer != NULL) {
SamCloseHandle( hsamServer);
}
if ( hlsa != NULL) {
LsaClose( hlsa);
}
if ( Error != NO_ERROR && (*ppinfoAccountDomain) != NULL) {
LsaFreeMemory( (*ppinfoAccountDomain));
*ppinfoAccountDomain = NULL;
}
if ( Error != NO_ERROR && phsamAccountDomain != NULL) {
SamCloseHandle( *phsamAccountDomain);
*phsamAccountDomain = NULL;
}
return( Error);
}
/*
* Get the SID for an account in the account domain. This method
* uses SAM directly to do the lookup, this is why we need to keep
* around a SAM domain handle.
*
* Returns NERR_RplWkstaNeedsUserAcct if the account is not found and
* !fIsLocalgroup, NERR_RplNeedsRPLUSERAcct if fIsLocalgroup
*
* Use MyFreeSid to free the SID returned in **ppsidAccountSid
*/
DWORD GetAccountSid(
IN SAM_HANDLE hsamAccountDomain,
IN POLICY_ACCOUNT_DOMAIN_INFO * pinfoAccountDomain,
IN WCHAR * pwszAccountName,
OUT PSID * ppsidAccountSid,
IN BOOL fIsLocalgroup,
OUT DWORD * pulRid OPTIONAL
)
{
DWORD Error = NO_ERROR;
UNICODE_STRING unistr;
PULONG RelativeIds = NULL;
PSID_NAME_USE Use = NULL;
DWORD cbNewSid = 0;
PUCHAR pcSubAuthorityCount = NULL;
PDWORD pdwLastSubAuthority = NULL;
RPL_ASSERT( hsamAccountDomain != NULL );
RPL_ASSERT( pinfoAccountDomain != NULL );
RPL_ASSERT( pwszAccountName != NULL );
RPL_ASSERT( ppsidAccountSid != NULL && *ppsidAccountSid == NULL );
//
// Lookup name
//
FillUnicodeString( &unistr, pwszAccountName );
Error = SamLookupNamesInDomain( hsamAccountDomain,
1,
&unistr,
&RelativeIds,
&Use );
if (Error != NO_ERROR) {
if (Error == STATUS_NONE_MAPPED) {
Error = (fIsLocalgroup) ? NERR_RplNeedsRPLUSERAcct
: NERR_RplWkstaNeedsUserAcct;
}
// TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
RPL_ASSERT( RelativeIds != NULL && Use != NULL );
if ( Use[0] != ((fIsLocalgroup) ? SidTypeAlias : SidTypeUser) ) {
Error = (fIsLocalgroup) ? NERR_RplNeedsRPLUSERAcct
: NERR_RplWkstaNeedsUserAcct;
goto cleanup;
}
//
// Construct account SID from RID and domain SID
//
Error = MyBuildSid( ppsidAccountSid, pinfoAccountDomain, RelativeIds[0] );
if (Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
cleanup:
if ( Error == NO_ERROR && pulRid != NULL) {
*pulRid = RelativeIds[0];
}
if (RelativeIds != NULL) {
SamFreeMemory( RelativeIds);
}
if (Use != NULL) {
SamFreeMemory( Use);
}
if ( Error != NO_ERROR ) {
MyFreeSid( ppsidAccountSid );
}
return( Error);
}
/*******************************************************************
NAME: IsNTFS
SYNOPSIS: This function checks the given file resource and attempts to
determine if it is on an NTFS partition.
ENTRY: pwszResource - Pointer to file/directory name in the form
"X:\aaa\bbb\ccc
pfIsNTFS - Pointer to BOOL that will receive the results
RETURNS: NO_ERROR if successful, error code otherwise
NOTES:
HISTORY:
JonN 23-Feb-1994 Copied from acledit\ntfsacl.cxx
********************************************************************/
DWORD IsNTFS(
IN WCHAR * pwszResource,
OUT BOOL * pfIsNTFS )
{
DWORD Error = NO_ERROR ;
HANDLE hDrive = NULL ;
WCHAR awchDosDriveName[4];
WCHAR awchNtDriveName[40];
UNICODE_STRING unistrNtDriveName;
BYTE buffFsInfo[ sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + 200 ] ;
PFILE_FS_ATTRIBUTE_INFORMATION FsInfo =
(PFILE_FS_ATTRIBUTE_INFORMATION)buffFsInfo ;
OBJECT_ATTRIBUTES oa ;
IO_STATUS_BLOCK StatusBlock ;
RPL_ASSERT( pwszResource != NULL
&& lstrlen(pwszResource) >= 3
&& pfIsNTFS != NULL ) ;
*pfIsNTFS = FALSE ;
//
// Determine the NT drive name
//
awchDosDriveName[0] = pwszResource[0];
awchDosDriveName[1] = pwszResource[1];
awchDosDriveName[2] = pwszResource[2];
awchDosDriveName[3] = L'\0';
RPL_ASSERT( awchDosDriveName[1] == L':'
&& awchDosDriveName[2] == L'\\' );
unistrNtDriveName.Buffer = awchNtDriveName;
unistrNtDriveName.Length = sizeof(awchNtDriveName);
unistrNtDriveName.MaximumLength = sizeof(awchNtDriveName);
if (!RtlDosPathNameToNtPathName_U( awchDosDriveName,
&unistrNtDriveName,
NULL,
NULL))
{
RPL_ASSERT( FALSE ) ;
Error = ERROR_NOT_ENOUGH_MEMORY ;
goto cleanup;
}
RPL_ASSERT( unistrNtDriveName.Length < unistrNtDriveName.MaximumLength );
unistrNtDriveName.Buffer[unistrNtDriveName.Length/sizeof(WCHAR)] = L'\0';
//
// Open the NT volume and query volume information
//
InitializeObjectAttributes( &oa,
&unistrNtDriveName,
OBJ_CASE_INSENSITIVE,
0,
0 );
Error = NtOpenFile( &hDrive,
SYNCHRONIZE | FILE_READ_DATA,
&oa,
&StatusBlock,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_SYNCHRONOUS_IO_ALERT);
if (Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
Error = NtQueryVolumeInformationFile(
hDrive,
&StatusBlock,
(PVOID) FsInfo,
sizeof(buffFsInfo),
FileFsAttributeInformation );
if (Error != NO_ERROR) {
if (Error == ERROR_ACCESS_DENIED) {
TREE_ASSERT( (
"IsNTFS - Unable to determine volume information (access denied) assuming the file system is NTFS"
) ) ;
Error = NO_ERROR ;
*pfIsNTFS = TRUE ;
} else {
TREE_ASSERT( ( "Error=%d", Error));
}
goto cleanup;
}
if ( FsInfo->FileSystemAttributes & FILE_PERSISTENT_ACLS )
{
*pfIsNTFS = TRUE ;
}
cleanup:
/* Close the volume if we ever opened it
*/
if ( hDrive != NULL )
{
NtClose( hDrive );
}
return( Error) ;
}
/*******************************************************************
NAME: RplCreateWkstaAccounts
SYNOPSIS: This function checks whether a user account exists for
each RPL workstation, and creates one if not. It also
adds all of these accounts to the RPLUSER local group.
RETURNS: NO_ERROR if successful, error code otherwise
NOTES:
HISTORY:
JonN 04-Mar-1994 Created
********************************************************************/
DWORD RplCheckWkstaAccounts( RPL_RPC_HANDLE ServerHandle )
{
DWORD Error = 0;
POLICY_ACCOUNT_DOMAIN_INFO * pinfoAccountDomain = NULL;
SAM_HANDLE hsamAccountDomain = NULL;
PSID psidRPLUSER = NULL;
ULONG ulRidRPLUSER = 0;
SAM_HANDLE hsamRPLUSER = NULL;
DWORD ErrorEnum = NO_ERROR;
RPL_WKSTA_ENUM rplwkstaenum;
RPL_WKSTA_INFO_0_CONTAINER rplwksta0container;
DWORD cEntries = 0;
DWORD hResumeHandle = 0;
DWORD i;
rplwkstaenum.Level = 0;
rplwkstaenum.WkstaInfo.Level0 = &rplwksta0container;
rplwkstaenum.WkstaInfo.Level0->EntriesRead = 0;
rplwkstaenum.WkstaInfo.Level0->Buffer = NULL;
Error = GetAccountDomain( &hsamAccountDomain,
&pinfoAccountDomain,
GENERIC_EXECUTE | DOMAIN_CREATE_USER );
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
Error = GetAccountSid( hsamAccountDomain,
pinfoAccountDomain,
RPL_GROUP_RPLUSER,
&psidRPLUSER,
TRUE,
&ulRidRPLUSER );
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
Error = SamOpenAlias( hsamAccountDomain,
ALIAS_ADD_MEMBER,
ulRidRPLUSER,
&hsamRPLUSER );
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
do {
ErrorEnum = NetrRplWkstaEnum( ServerHandle,
NULL,
&rplwkstaenum,
(ULONG)-1,
&cEntries,
&hResumeHandle );
switch (ErrorEnum) {
case STATUS_MORE_ENTRIES:
case ERROR_MORE_DATA:
case NO_ERROR:
case STATUS_NO_MORE_ENTRIES:
if (rplwkstaenum.WkstaInfo.Level0->Buffer == NULL) {
// no workstations
break;
}
for ( i = 0;
(Error == NO_ERROR)
&& (i < rplwkstaenum.WkstaInfo.Level0->EntriesRead);
i++ ) {
Error = RplAddWkstaAccount(
rplwkstaenum.WkstaInfo.Level0->Buffer[i].WkstaName,
hsamAccountDomain,
pinfoAccountDomain,
hsamRPLUSER );
}
MIDL_user_free( rplwkstaenum.WkstaInfo.Level0->Buffer );
rplwkstaenum.WkstaInfo.Level0->Buffer = NULL;
break;
default:
Error = ErrorEnum;
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
} while ( Error == STATUS_MORE_ENTRIES || Error == ERROR_MORE_DATA );
cleanup:
/*
* We have no way to free the resume handle
*/
if ( rplwkstaenum.WkstaInfo.Level0->Buffer != NULL ) {
MIDL_user_free( rplwkstaenum.WkstaInfo.Level0->Buffer );
}
if ( pinfoAccountDomain != NULL) {
LsaFreeMemory( pinfoAccountDomain);
}
if (hsamAccountDomain != NULL) {
SamCloseHandle( hsamAccountDomain);
}
if (hsamRPLUSER != NULL) {
SamCloseHandle( hsamRPLUSER);
}
if (hsamRPLUSER != NULL) {
SamCloseHandle( hsamRPLUSER);
}
MyFreeSid( &psidRPLUSER );
return( Error);
}
/*******************************************************************
NAME: RplAddWkstaAccount
SYNOPSIS: This function checks whether a user account exists for
a single workstation, and creates one if not. It also
adds the account to the RPLUSER local group.
RETURNS: NO_ERROR if successful, error code otherwise
NOTES:
HISTORY:
JonN 04-Mar-1994 Created
********************************************************************/
DWORD RplAddWkstaAccount( LPWSTR pszWkstaName,
SAM_HANDLE hsamAccountDomain,
POLICY_ACCOUNT_DOMAIN_INFO * pinfoAccountDomain,
SAM_HANDLE hsamRPLUSER )
{
DWORD Error = 0;
UNICODE_STRING unistr;
PSID psidUser = NULL;
ULONG ulRidUser = 0;
SAM_HANDLE hsamUser = NULL;
RPL_ASSERT( pszWkstaName != NULL
&& hsamAccountDomain != NULL
&& pinfoAccountDomain != NULL
&& hsamRPLUSER != NULL );
/*
* Get SID of existing account. It would be more efficient to look
* up all the accounts at once.
*/
Error = GetAccountSid( hsamAccountDomain,
pinfoAccountDomain,
pszWkstaName,
&psidUser,
FALSE,
&ulRidUser );
if ( Error == NERR_RplWkstaNeedsUserAcct )
{
/*
* Account doesn't exist, add it
*
* Note that this will fail unless user is administrator or
* at least account operator
*/
FillUnicodeString( &unistr, pszWkstaName );
Error = SamCreateUserInDomain( hsamAccountDomain,
&unistr,
0x0,
&hsamUser,
&ulRidUser );
if (Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
Error = MyBuildSid( &psidUser, pinfoAccountDomain, ulRidUser );
if (Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
}
/*
* Add account to RPLUSER alias
*
* Note that this will fail unless user is administrator or
* at least account operator
*/
Error = SamAddMemberToAlias( hsamRPLUSER, psidUser );
switch (Error) {
case STATUS_MEMBER_IN_ALIAS:
Error = NO_ERROR;
// fall through
case NO_ERROR:
break;
default:
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
cleanup:
MyFreeSid( &psidUser );
if (hsamUser != NULL) {
SamCloseHandle( hsamUser);
}
return( Error);
}
/*******************************************************************
NAME: RplAddRPLUSERGroup
SYNOPSIS: This function checks whether the RPLUSER local group
exists, and creates it if it doesn't.
RETURNS: NO_ERROR if successful, error code otherwise
NOTES:
HISTORY:
JonN 03-Mar-1994 Created
********************************************************************/
DWORD RplAddRPLUSERGroup( VOID )
{
DWORD Error = 0;
POLICY_ACCOUNT_DOMAIN_INFO * pinfoAccountDomain = NULL;
SAM_HANDLE hsamAccountDomain = NULL;
PSID psidRPLUSER = NULL;
UNICODE_STRING unistr;
SAM_HANDLE hsamRPLUSER = NULL;
ULONG ulRidRPLUSER = 0;
Error = GetAccountDomain( &hsamAccountDomain,
&pinfoAccountDomain,
GENERIC_EXECUTE | DOMAIN_CREATE_ALIAS );
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
Error = GetAccountSid( hsamAccountDomain,
pinfoAccountDomain,
RPL_GROUP_RPLUSER,
&psidRPLUSER,
TRUE,
NULL );
if ( Error == NO_ERROR) {
// RPLUSER already exists
goto cleanup;
} else if (Error != NERR_RplNeedsRPLUSERAcct)
{
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
FillUnicodeString( &unistr, RPL_GROUP_RPLUSER );
Error = SamCreateAliasInDomain( hsamAccountDomain,
&unistr,
0x0,
&hsamRPLUSER,
&ulRidRPLUSER );
if ( Error != NO_ERROR) {
TREE_ASSERT( ( "Error=%d", Error));
goto cleanup;
}
cleanup:
if ( pinfoAccountDomain != NULL) {
LsaFreeMemory( pinfoAccountDomain);
}
if (hsamAccountDomain != NULL) {
SamCloseHandle( hsamAccountDomain);
}
if (hsamRPLUSER != NULL) {
SamCloseHandle( hsamRPLUSER);
}
MyFreeSid( &psidRPLUSER );
return( Error);
}
VOID FillUnicodeString( PUNICODE_STRING punistr,
LPWSTR lpwsz )
{
ASSERT( punistr != NULL && lpwsz == NULL );
punistr->Buffer = lpwsz;
punistr->Length = lstrlenW(lpwsz) * sizeof(WCHAR);
punistr->MaximumLength = punistr->Length + sizeof(WCHAR);
}
DWORD MyBuildSid( OUT PSID * ppsidAccountSid,
IN POLICY_ACCOUNT_DOMAIN_INFO * pinfoAccountDomain,
IN ULONG ulRid )
{
DWORD Error = 0;
DWORD cbNewSid = 0;
PUCHAR pcSubAuthorityCount = NULL;
PDWORD pdwLastSubAuthority = NULL;
ASSERT( ppsidAccountSid != NULL && pinfoAccountDomain != NULL && ulRid != 0 );
cbNewSid = GetLengthSid(pinfoAccountDomain->DomainSid) + sizeof(DWORD);
*ppsidAccountSid = TREE_ALLOC( cbNewSid );
if ( *ppsidAccountSid == NULL) {
Error = ERROR_NOT_ENOUGH_MEMORY;
goto cleanup;
}
if ( !CopySid( cbNewSid, *ppsidAccountSid, pinfoAccountDomain->DomainSid)) {
Error = GetLastError();
goto cleanup;
}
pcSubAuthorityCount = GetSidSubAuthorityCount( *ppsidAccountSid );
RPL_ASSERT( pcSubAuthorityCount != NULL );
(*pcSubAuthorityCount)++;
pdwLastSubAuthority = GetSidSubAuthority(
*ppsidAccountSid, (DWORD)((*pcSubAuthorityCount)-1) );
RPL_ASSERT( pdwLastSubAuthority != NULL );
*pdwLastSubAuthority = ulRid;
cleanup:
if (Error != NO_ERROR) {
MyFreeSid( ppsidAccountSid );
}
return( Error);
}
VOID MyFreeSid( IN OUT PSID * ppsid )
{
RPL_ASSERT( ppsid != NULL );
if ( *ppsid != NULL) {
TREE_FREE( *ppsid );
*ppsid = NULL;
}
}