6936 lines
182 KiB
C
6936 lines
182 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1990 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
utility.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This file contains utility services used by several other SAM files.
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Jim Kelly (JimK) 4-July-1991
|
|||
|
|
|||
|
Environment:
|
|||
|
|
|||
|
User Mode - Win32
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// Includes //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
#include <samsrvp.h>
|
|||
|
#include <ntlsa.h>
|
|||
|
#include <nlrepl.h>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// private service prototypes //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
#define VERBOSE_FLUSH 0
|
|||
|
|
|||
|
#if VERBOSE_FLUSH
|
|||
|
#define VerbosePrint(s) KdPrint(s)
|
|||
|
#else
|
|||
|
#define VerbosePrint(s)
|
|||
|
#endif
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampRefreshRegistry(
|
|||
|
VOID
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// Database/registry access lock services //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
SampAcquireReadLock(
|
|||
|
VOID
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine obtains read access to the SAM data structures and
|
|||
|
backing store.
|
|||
|
|
|||
|
Despite its apparent implications, read access is an exclusive access.
|
|||
|
This is to support the model set up in which global variables are used
|
|||
|
to track the "current" domain. In the future, if performance warrants,
|
|||
|
a read lock could imply shared access to SAM data structures.
|
|||
|
|
|||
|
The primary implication of a read lock at this time is that no
|
|||
|
changes to the SAM database will be made which require a backing
|
|||
|
store update.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
BOOLEAN Success;
|
|||
|
|
|||
|
//
|
|||
|
// Before changing this to a non-exclusive lock, the display information
|
|||
|
// module must be changed to use a separate locking mechanism. Davidc 5/12/92
|
|||
|
//
|
|||
|
|
|||
|
Success = RtlAcquireResourceExclusive( &SampLock, TRUE );
|
|||
|
ASSERT(Success);
|
|||
|
|
|||
|
//
|
|||
|
// Allow LSA a chance to perform an integrity check
|
|||
|
//
|
|||
|
|
|||
|
LsaIHealthCheck( LsaIHealthSamJustLocked );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
SampReleaseReadLock(
|
|||
|
VOID
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine releases shared read access to the SAM data structures and
|
|||
|
backing store.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
//
|
|||
|
// Allow LSA a chance to perform an integrity check
|
|||
|
//
|
|||
|
|
|||
|
LsaIHealthCheck( LsaIHealthSamAboutToFree );
|
|||
|
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
RtlReleaseResource( &SampLock );
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampAcquireWriteLock(
|
|||
|
VOID
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine acquires exclusive access to the SAM data structures and
|
|||
|
backing store.
|
|||
|
|
|||
|
This access is needed to perform a write operation.
|
|||
|
|
|||
|
This routine also initiates a new transaction for the write operation.
|
|||
|
|
|||
|
|
|||
|
NOTE: It is not acceptable to acquire this lock recursively. An
|
|||
|
attempt to do so will fail.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - Indicates the write lock was acquired and the transaction
|
|||
|
was successfully started.
|
|||
|
|
|||
|
Other values may be returned as a result of failure to initiate the
|
|||
|
transaction. These include any values returned by RtlStartRXact().
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
|
|||
|
(VOID)RtlAcquireResourceExclusive( &SampLock, TRUE );
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
NtStatus = RtlStartRXact( SampRXactContext );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Allow LSA a chance to perform an integrity check
|
|||
|
//
|
|||
|
|
|||
|
LsaIHealthCheck( LsaIHealthSamJustLocked );
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the transaction failed, release the lock.
|
|||
|
//
|
|||
|
|
|||
|
(VOID)RtlReleaseResource( &SampLock );
|
|||
|
|
|||
|
DbgPrint("SAM: StartRxact failed, status = 0x%lx\n", NtStatus);
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
SampSetTransactionDomain(
|
|||
|
IN ULONG DomainIndex
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine sets a domain for a transaction. This must be done
|
|||
|
if any domain-specific information is to be modified during a transaction.
|
|||
|
In this case, the domain modification count will be updated upon commit.
|
|||
|
|
|||
|
This causes the UnmodifiedFixed information for the specified domain to
|
|||
|
be copied to the CurrentFixed field for the in-memory representation of
|
|||
|
that domain.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainIndex - Index of the domain within which this transaction
|
|||
|
will occur.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - Indicates the write lock was acquired and the transaction
|
|||
|
was successfully started.
|
|||
|
|
|||
|
Other values may be returned as a result of failure to initiate the
|
|||
|
transaction. These include any values returned by RtlStartRXact().
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
ASSERT(SampTransactionWithinDomain == FALSE);
|
|||
|
|
|||
|
SampTransactionWithinDomain = TRUE;
|
|||
|
SampTransactionDomainIndex = DomainIndex;
|
|||
|
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed =
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].UnmodifiedFixed;
|
|||
|
|
|||
|
|
|||
|
return;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampFlushThread(
|
|||
|
IN PVOID ThreadParameter
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This thread is created when SAM's registry tree is changed.
|
|||
|
It will sleep for a while, and if no other changes occur,
|
|||
|
flush the changes to disk. If other changes keep occurring,
|
|||
|
it will wait for a certain amount of time and then flush
|
|||
|
anyway.
|
|||
|
|
|||
|
After flushing, the thread will wait a while longer. If no
|
|||
|
other changes occur, it will exit.
|
|||
|
|
|||
|
Note that if any errors occur, this thread will simply exit
|
|||
|
without flushing. The mainline code should create another thread,
|
|||
|
and hopefully it will be luckier. Unfortunately, the error is lost
|
|||
|
since there's nobody to give it to that will be able to do anything
|
|||
|
about it.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ThreadParameter - not used.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
TIME minDelayTime, maxDelayTime, exitDelayTime;
|
|||
|
LARGE_INTEGER startedWaitLoop;
|
|||
|
LARGE_INTEGER currentTime;
|
|||
|
NTSTATUS NtStatus;
|
|||
|
BOOLEAN Finished = FALSE;
|
|||
|
|
|||
|
UNREFERENCED_PARAMETER( ThreadParameter );
|
|||
|
|
|||
|
NtQuerySystemTime( &startedWaitLoop );
|
|||
|
|
|||
|
//
|
|||
|
// It would be more efficient to use constants here, but for now
|
|||
|
// we'll recalculate the times each time we start the thread
|
|||
|
// so that somebody playing with us can change the global
|
|||
|
// time variables to affect performance.
|
|||
|
//
|
|||
|
|
|||
|
minDelayTime.QuadPart = -1000 * 1000 * 10 *
|
|||
|
((LONGLONG)SampFlushThreadMinWaitSeconds);
|
|||
|
|
|||
|
maxDelayTime.QuadPart = -1000 * 1000 * 10 *
|
|||
|
((LONGLONG)SampFlushThreadMaxWaitSeconds);
|
|||
|
|
|||
|
exitDelayTime.QuadPart = -1000 * 1000 * 10 *
|
|||
|
((LONGLONG)SampFlushThreadExitDelaySeconds);
|
|||
|
|
|||
|
do {
|
|||
|
|
|||
|
VerbosePrint(("SAM: Flush thread sleeping\n"));
|
|||
|
|
|||
|
NtDelayExecution( FALSE, &minDelayTime );
|
|||
|
|
|||
|
VerbosePrint(("SAM: Flush thread woke up\n"));
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
#ifdef SAMP_DBG_CONTEXT_TRACKING
|
|||
|
SampDumpContexts();
|
|||
|
#endif
|
|||
|
|
|||
|
NtQuerySystemTime( ¤tTime );
|
|||
|
|
|||
|
if ( LastUnflushedChange.QuadPart == SampHasNeverTime.QuadPart ) {
|
|||
|
|
|||
|
LARGE_INTEGER exitBecauseNoWorkRecentlyTime;
|
|||
|
|
|||
|
//
|
|||
|
// No changes to flush. See if we should stick around.
|
|||
|
//
|
|||
|
|
|||
|
exitBecauseNoWorkRecentlyTime = SampAddDeltaTime(
|
|||
|
startedWaitLoop,
|
|||
|
exitDelayTime
|
|||
|
);
|
|||
|
|
|||
|
if ( exitBecauseNoWorkRecentlyTime.QuadPart < currentTime.QuadPart ) {
|
|||
|
|
|||
|
//
|
|||
|
// We've waited for changes long enough; note that
|
|||
|
// the thread is exiting.
|
|||
|
//
|
|||
|
|
|||
|
FlushThreadCreated = FALSE;
|
|||
|
Finished = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
LARGE_INTEGER noRecentChangesTime;
|
|||
|
LARGE_INTEGER tooLongSinceFlushTime;
|
|||
|
|
|||
|
//
|
|||
|
// There are changes to flush. See if it's time to do so.
|
|||
|
//
|
|||
|
|
|||
|
noRecentChangesTime = SampAddDeltaTime(
|
|||
|
LastUnflushedChange,
|
|||
|
minDelayTime
|
|||
|
);
|
|||
|
|
|||
|
tooLongSinceFlushTime = SampAddDeltaTime(
|
|||
|
startedWaitLoop,
|
|||
|
maxDelayTime
|
|||
|
);
|
|||
|
|
|||
|
if ( (noRecentChangesTime.QuadPart < currentTime.QuadPart) ||
|
|||
|
(tooLongSinceFlushTime.QuadPart < currentTime.QuadPart) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Min time has passed since last change, or Max time
|
|||
|
// has passed since last flush. Let's flush.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = NtFlushKey( SampKey );
|
|||
|
|
|||
|
#if SAMP_DIAGNOSTICS
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampDiagPrint( DISPLAY_STORAGE_FAIL,
|
|||
|
("SAM: Failed to flush RXact (0x%lx)\n",
|
|||
|
NtStatus) );
|
|||
|
IF_SAMP_GLOBAL( BREAK_ON_STORAGE_FAIL ) {
|
|||
|
ASSERT(NT_SUCCESS(NtStatus)); // See following comment
|
|||
|
}
|
|||
|
}
|
|||
|
#endif //SAMP_DIAGNOSTICS
|
|||
|
|
|||
|
//
|
|||
|
// Under normal conditions, we would have an
|
|||
|
// ASSERT(NT_SUCCESS(NtStatus)) here. However,
|
|||
|
// Because system shutdown can occur while we
|
|||
|
// are waiting to flush, we have a race condition.
|
|||
|
// When shutdown is made, another thread will be
|
|||
|
// notified and perform a flush. That leaves this
|
|||
|
// flush to potentially occur after the registry
|
|||
|
// has been notified of system shutdown - which
|
|||
|
// causes and error to be returned. Unfortunately,
|
|||
|
// the error is REGISTRY_IO_FAILED - a great help.
|
|||
|
//
|
|||
|
// Despite this, we will only exit this loop only
|
|||
|
// if we have success. This may cause us to enter
|
|||
|
// into another wait and attempt another hive flush
|
|||
|
// during shutdown, but the wait should never finish
|
|||
|
// (unless shutdown takes more than 30 seconds). In
|
|||
|
// other error situations though, we want to keep
|
|||
|
// trying the flush until we succeed. Jim Kelly
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
LastUnflushedChange = SampHasNeverTime;
|
|||
|
NtQuerySystemTime( &startedWaitLoop );
|
|||
|
|
|||
|
FlushThreadCreated = FALSE;
|
|||
|
Finished = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
SampReleaseWriteLock( FALSE );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
DbgPrint("SAM: Thread failed to get write lock, status = 0x%lx\n", NtStatus);
|
|||
|
|
|||
|
FlushThreadCreated = FALSE;
|
|||
|
Finished = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
} while ( !Finished );
|
|||
|
|
|||
|
return( STATUS_SUCCESS );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampCommitChanges(
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Thie service commits any changes made to the backstore while exclusive
|
|||
|
access was held.
|
|||
|
|
|||
|
If the operation was within a domain (which would have been indicated
|
|||
|
via the SampSetTransactionDomain() api), then the CurrentFixed field for
|
|||
|
that domain is added to the transaction before the transaction is
|
|||
|
committed.
|
|||
|
|
|||
|
NOTE: Write operations within a domain do not have to worry about
|
|||
|
updating the modified count for that domain. This routine
|
|||
|
will automatically increment the ModifiedCount for a domain
|
|||
|
when a commit is requested within that domain.
|
|||
|
|
|||
|
NOTE: When this routine returns any transaction will have either been
|
|||
|
committed or aborted. i.e. there will be no transaction in progress.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - Indicates the transaction was successfully commited.
|
|||
|
|
|||
|
Other values may be returned as a result of commital failure.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
BOOLEAN DomainInfoChanged = FALSE;
|
|||
|
BOOLEAN AbortDone = FALSE;
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
|
|||
|
//
|
|||
|
// If this transaction was within a domain then we have to:
|
|||
|
//
|
|||
|
// (1) Update the ModifiedCount of that domain,
|
|||
|
//
|
|||
|
// (2) Write out the CurrentFixed field for that
|
|||
|
// domain (using RtlAddActionToRXact(), so that it
|
|||
|
// is part of the current transaction).
|
|||
|
//
|
|||
|
// (3) Commit the RXACT.
|
|||
|
//
|
|||
|
// (4) If the commit is successful, then update the
|
|||
|
// in-memory copy of the un-modified fixed-length data.
|
|||
|
//
|
|||
|
// Otherwise, we just do the commit.
|
|||
|
//
|
|||
|
|
|||
|
if (SampTransactionWithinDomain == TRUE) {
|
|||
|
|
|||
|
if (SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ServerRole
|
|||
|
!= DomainServerRoleBackup) {
|
|||
|
|
|||
|
//
|
|||
|
// Don't update the modified count on backup controllers;
|
|||
|
// the replicator will explicitly set the modified count.
|
|||
|
//
|
|||
|
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart =
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart +
|
|||
|
1;
|
|||
|
|
|||
|
DomainInfoChanged = TRUE;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// See if the domain information changed - if it did, we
|
|||
|
// need to add the change to the RXACT.
|
|||
|
//
|
|||
|
|
|||
|
if ( RtlCompareMemory(
|
|||
|
&SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed,
|
|||
|
&SampDefinedDomains[SampTransactionDomainIndex].UnmodifiedFixed,
|
|||
|
sizeof(SAMP_V1_0A_FIXED_LENGTH_DOMAIN) ) !=
|
|||
|
sizeof( SAMP_V1_0A_FIXED_LENGTH_DOMAIN) ) {
|
|||
|
|
|||
|
DomainInfoChanged = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( DomainInfoChanged ) {
|
|||
|
|
|||
|
//
|
|||
|
// The domain object's fixed information has changed, so set
|
|||
|
// the changes in the domain object's private data.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampSetFixedAttributes(
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].
|
|||
|
Context,
|
|||
|
&SampDefinedDomains[SampTransactionDomainIndex].
|
|||
|
CurrentFixed
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Normally when we dereference the context,
|
|||
|
// SampStoreObjectAttributes() is called to add the
|
|||
|
// latest change to the RXACT. But that won't happen
|
|||
|
// for the domain object's change since this is the
|
|||
|
// commit code, so we have to flush it by hand here.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampStoreObjectAttributes(
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].Context,
|
|||
|
TRUE // Use the existing key handle
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// If we still have no errors, try to commit the whole mess
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
if ( ( !FlushImmediately ) && ( !FlushThreadCreated ) ) {
|
|||
|
|
|||
|
HANDLE Thread;
|
|||
|
DWORD Ignore;
|
|||
|
|
|||
|
//
|
|||
|
// If we can't create the flush thread, ignore error and
|
|||
|
// just flush by hand below.
|
|||
|
//
|
|||
|
|
|||
|
Thread = CreateThread(
|
|||
|
NULL,
|
|||
|
0L,
|
|||
|
(LPTHREAD_START_ROUTINE)SampFlushThread,
|
|||
|
NULL,
|
|||
|
0L,
|
|||
|
&Ignore
|
|||
|
);
|
|||
|
|
|||
|
if ( Thread != NULL ) {
|
|||
|
|
|||
|
FlushThreadCreated = TRUE;
|
|||
|
VerbosePrint(("Flush thread created, handle = 0x%lx\n", Thread));
|
|||
|
CloseHandle(Thread);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
NtStatus = RtlApplyRXactNoFlush( SampRXactContext );
|
|||
|
|
|||
|
#if SAMP_DIAGNOSTICS
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampDiagPrint( DISPLAY_STORAGE_FAIL,
|
|||
|
("SAM: Failed to apply RXact without flush (0x%lx)\n",
|
|||
|
NtStatus) );
|
|||
|
IF_SAMP_GLOBAL( BREAK_ON_STORAGE_FAIL ) {
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
}
|
|||
|
}
|
|||
|
#endif //SAMP_DIAGNOSTICS
|
|||
|
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
if ( ( FlushImmediately ) || ( !FlushThreadCreated ) ) {
|
|||
|
|
|||
|
NtStatus = NtFlushKey( SampKey );
|
|||
|
|
|||
|
#if SAMP_DIAGNOSTICS
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampDiagPrint( DISPLAY_STORAGE_FAIL,
|
|||
|
("SAM: Failed to flush RXact (0x%lx)\n",
|
|||
|
NtStatus) );
|
|||
|
IF_SAMP_GLOBAL( BREAK_ON_STORAGE_FAIL ) {
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
}
|
|||
|
}
|
|||
|
#endif //SAMP_DIAGNOSTICS
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
FlushImmediately = FALSE;
|
|||
|
LastUnflushedChange = SampHasNeverTime;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NtQuerySystemTime( &LastUnflushedChange );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Commit successful, set our unmodified to now be the current...
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
if (SampTransactionWithinDomain == TRUE) {
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].UnmodifiedFixed =
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
KdPrint(("SAM: Failed to commit changes to registry, status = 0x%lx\n", NtStatus));
|
|||
|
KdPrint(("SAM: Restoring database to earlier consistent state\n"));
|
|||
|
|
|||
|
//
|
|||
|
// Add an entry to the event log
|
|||
|
//
|
|||
|
|
|||
|
SampWriteEventLog(
|
|||
|
EVENTLOG_ERROR_TYPE,
|
|||
|
0, // Category
|
|||
|
SAMMSG_COMMIT_FAILED,
|
|||
|
NULL, // User Sid
|
|||
|
0, // Num strings
|
|||
|
sizeof(NTSTATUS), // Data size
|
|||
|
NULL, // String array
|
|||
|
(PVOID)&NtStatus // Data
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// The Rxact commital failed. We don't know how many registry
|
|||
|
// writes were done for this transaction. We can't guarantee
|
|||
|
// to successfully back them out anyway so all we can do is
|
|||
|
// back out all changes since the last flush. When this is done
|
|||
|
// we'll be back to a consistent database state although recent
|
|||
|
// apis that were reported as succeeding will be 'undone'.
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampRefreshRegistry();
|
|||
|
|
|||
|
if (!NT_SUCCESS(IgnoreStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// This is very serious. We failed to revert to a previous
|
|||
|
// database state and we can't proceed.
|
|||
|
// Shutdown SAM operations.
|
|||
|
//
|
|||
|
|
|||
|
SampServiceState = SampServiceTerminating;
|
|||
|
|
|||
|
KdPrint(("SAM: Failed to refresh registry, SAM has shutdown\n"));
|
|||
|
|
|||
|
//
|
|||
|
// Add an entry to the event log
|
|||
|
//
|
|||
|
|
|||
|
SampWriteEventLog(
|
|||
|
EVENTLOG_ERROR_TYPE,
|
|||
|
0, // Category
|
|||
|
SAMMSG_REFRESH_FAILED,
|
|||
|
NULL, // User Sid
|
|||
|
0, // Num strings
|
|||
|
sizeof(NTSTATUS), // Data size
|
|||
|
NULL, // String array
|
|||
|
(PVOID)&IgnoreStatus // Data
|
|||
|
);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Now all open contexts are invalid (contain invalid registry
|
|||
|
// handles). The in memory registry handles have been
|
|||
|
// re-opened so any new contexts should work ok.
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// All unflushed changes have just been erased.
|
|||
|
// There is nothing to flush
|
|||
|
//
|
|||
|
// If the refresh failed it is important to prevent any further
|
|||
|
// registry flushes until the system is rebooted
|
|||
|
//
|
|||
|
|
|||
|
FlushImmediately = FALSE;
|
|||
|
LastUnflushedChange = SampHasNeverTime;
|
|||
|
|
|||
|
//
|
|||
|
// The refresh effectively aborted the transaction
|
|||
|
//
|
|||
|
|
|||
|
AbortDone = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Always abort the transaction on failure
|
|||
|
//
|
|||
|
|
|||
|
if ( !NT_SUCCESS(NtStatus) && !AbortDone) {
|
|||
|
|
|||
|
NtStatus = RtlAbortRXact( SampRXactContext );
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampRefreshRegistry(
|
|||
|
VOID
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine backs out all unflushed changes in the registry.
|
|||
|
This operation invalidates any open handles to the SAM hive.
|
|||
|
Global handles that we keep around are closed and re-opened by
|
|||
|
this routine. The net result of this call will be that the database
|
|||
|
is taken back to a previous consistent state. All open SAM contexts
|
|||
|
are invalidated since they have invalid registry handles in them.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
STATUS_SUCCESS : Operation completed successfully
|
|||
|
|
|||
|
Failure returns: We are in deep trouble. Normal operations can
|
|||
|
not be resumed. SAM should be shutdown.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
NTSTATUS IgnoreStatus;
|
|||
|
HANDLE HiveKey;
|
|||
|
BOOLEAN WasEnabled;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
UNICODE_STRING String;
|
|||
|
ULONG i;
|
|||
|
|
|||
|
//
|
|||
|
// Get a key handle to the root of the SAM hive
|
|||
|
//
|
|||
|
|
|||
|
RtlInitUnicodeString( &String, L"\\Registry\\Machine\\SAM" );
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&String,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
0,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&HiveKey,
|
|||
|
KEY_QUERY_VALUE,
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAM: Failed to open SAM hive root key for refresh, status = 0x%lx\n", NtStatus));
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Enable restore privilege in preparation for the refresh
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, TRUE, FALSE, &WasEnabled);
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAM: Failed to enable restore privilege to refresh registry, status = 0x%lx\n", NtStatus));
|
|||
|
|
|||
|
IgnoreStatus = NtClose(HiveKey);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Refresh the SAM hive
|
|||
|
// This should not fail unless there is volatile storage in the
|
|||
|
// hive or we don't have TCB privilege
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
NtStatus = NtRestoreKey(HiveKey, NULL, REG_REFRESH_HIVE);
|
|||
|
|
|||
|
|
|||
|
IgnoreStatus = RtlAdjustPrivilege(SE_RESTORE_PRIVILEGE, WasEnabled, FALSE, &WasEnabled);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
IgnoreStatus = NtClose(HiveKey);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAM: Failed to refresh registry, status = 0x%lx\n", NtStatus));
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Now close the registry handles we keep in memory at all times
|
|||
|
// This effectively closes all server and domain context keys
|
|||
|
// since they are shared.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = NtClose(SampKey);
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
SampKey = INVALID_HANDLE_VALUE;
|
|||
|
|
|||
|
for (i = 0; i<SampDefinedDomainsCount; i++ ) {
|
|||
|
NtStatus = NtClose(SampDefinedDomains[i].Context->RootKey);
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
SampDefinedDomains[i].Context->RootKey = INVALID_HANDLE_VALUE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Mark all domain and server context handles as invalid since they've
|
|||
|
// now been closed
|
|||
|
//
|
|||
|
|
|||
|
SampInvalidateContextListKeys(&SampContextListHead, FALSE);
|
|||
|
|
|||
|
//
|
|||
|
// Re-open the SAM root key
|
|||
|
//
|
|||
|
|
|||
|
RtlInitUnicodeString( &String, L"\\Registry\\Machine\\Security\\SAM" );
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&String,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
0,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&SampKey,
|
|||
|
(KEY_READ | KEY_WRITE),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAM: Failed to re-open SAM root key after registry refresh, status = 0x%lx\n", NtStatus));
|
|||
|
ASSERT(FALSE);
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Re-initialize the in-memory domain contexts
|
|||
|
// Each domain will re-initialize it's open user/group/alias contexts
|
|||
|
//
|
|||
|
|
|||
|
for (i = 0; i<SampDefinedDomainsCount; i++ ) {
|
|||
|
|
|||
|
NtStatus = SampReInitializeSingleDomain(i);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAM: Failed to re-initialize domain %d context after registry refresh, status = 0x%lx\n", i, NtStatus));
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Cleanup the current transcation context
|
|||
|
// (It would be nice if there were a RtlDeleteRXactContext())
|
|||
|
//
|
|||
|
// Note we don't have to close the rootregistrykey in the
|
|||
|
// xact context since it was SampKey which we've already closed.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = RtlAbortRXact( SampRXactContext );
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
NtStatus = NtClose(SampRXactContext->RXactKey);
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
//
|
|||
|
// Re-initialize the transaction context.
|
|||
|
// We don't expect there to be a partially commited transaction
|
|||
|
// since we're reverting to a previously consistent and committed
|
|||
|
// database.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = RtlInitializeRXact( SampKey, FALSE, &SampRXactContext );
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAM: Failed to re-initialize rxact context registry refresh, status = 0x%lx\n", NtStatus));
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
ASSERT(NtStatus != STATUS_UNKNOWN_REVISION);
|
|||
|
ASSERT(NtStatus != STATUS_RXACT_STATE_CREATED);
|
|||
|
ASSERT(NtStatus != STATUS_RXACT_COMMIT_NECESSARY);
|
|||
|
ASSERT(NtStatus != STATUS_RXACT_INVALID_STATE);
|
|||
|
|
|||
|
return(STATUS_SUCCESS);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampReleaseWriteLock(
|
|||
|
IN BOOLEAN Commit
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine releases exclusive access to the SAM data structures and
|
|||
|
backing store.
|
|||
|
|
|||
|
If any changes were made to the backstore while exclusive access
|
|||
|
was held, then this service commits those changes. Otherwise, the
|
|||
|
transaction started when exclusive access was obtained is rolled back.
|
|||
|
|
|||
|
If the operation was within a domain (which would have been indicated
|
|||
|
via the SampSetTransactionDomain() api), then the CurrentFixed field for
|
|||
|
that domain is added to the transaction before the transaction is
|
|||
|
committed.
|
|||
|
|
|||
|
NOTE: Write operations within a domain do not have to worry about
|
|||
|
updating the modified count for that domain. This routine
|
|||
|
will automatically increment the ModifiedCount for a domain
|
|||
|
when a commit is requested within that domain.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Commit - A boolean value indicating whether modifications need to be
|
|||
|
committed in the backing store. A value of TRUE indicates the
|
|||
|
transaction should be committed. A value of FALSE indicates the
|
|||
|
transaction should be aborted (rolled-back).
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - Indicates the write lock was released and the transaction
|
|||
|
was successfully commited or rolled back.
|
|||
|
|
|||
|
Other values may be returned as a result of failure to commit or
|
|||
|
roll-back the transaction. These include any values returned by
|
|||
|
RtlApplyRXact() or RtlAbortRXact(). In the case of a commit, it
|
|||
|
may also represent errors returned by RtlAddActionToRXact().
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
|
|||
|
//
|
|||
|
// Commit or rollback the transaction based upon the Commit parameter...
|
|||
|
//
|
|||
|
|
|||
|
if (Commit == TRUE) {
|
|||
|
|
|||
|
NtStatus = SampCommitChanges();
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NtStatus = RtlAbortRXact( SampRXactContext );
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
}
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Allow LSA a chance to perform an integrity check
|
|||
|
//
|
|||
|
|
|||
|
LsaIHealthCheck( LsaIHealthSamAboutToFree );
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// And free the lock...
|
|||
|
//
|
|||
|
|
|||
|
(VOID)RtlReleaseResource( &SampLock );
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampCommitAndRetainWriteLock(
|
|||
|
VOID
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine attempts to commit all changes made so far. The write-lock
|
|||
|
is held for the duration of the commit and is retained by the caller upon
|
|||
|
return.
|
|||
|
|
|||
|
The transaction domain is left intact as well.
|
|||
|
|
|||
|
NOTE: Write operations within a domain do not have to worry about
|
|||
|
updating the modified count for that domain. This routine
|
|||
|
will automatically increment the ModifiedCount for a domain
|
|||
|
when a commit is requested within that domain.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - Indicates the transaction was successfully commited.
|
|||
|
|
|||
|
|
|||
|
Other values may be returned as a result of failure to commit or
|
|||
|
roll-back the transaction. These include any values returned by
|
|||
|
RtlApplyRXact() or RtlAddActionToRXact().
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
NTSTATUS TempStatus;
|
|||
|
|
|||
|
NtStatus = SampCommitChanges();
|
|||
|
|
|||
|
//
|
|||
|
// Start another transaction, since we're retaining the write lock.
|
|||
|
// Note we do this even if the commit failed so that cleanup code
|
|||
|
// won't get confused by the lack of a transaction.
|
|||
|
//
|
|||
|
|
|||
|
TempStatus = RtlStartRXact( SampRXactContext );
|
|||
|
ASSERT(NT_SUCCESS(TempStatus));
|
|||
|
|
|||
|
//
|
|||
|
// Return the worst status
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
NtStatus = TempStatus;
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// Unicode registry key manipulation services //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampRetrieveStringFromRegistry(
|
|||
|
IN HANDLE ParentKey,
|
|||
|
IN PUNICODE_STRING SubKeyName,
|
|||
|
OUT PUNICODE_STRING Body
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine retrieves a unicode string buffer from the specified registry
|
|||
|
sub-key and sets the output parameter "Body" to be that unicode string.
|
|||
|
|
|||
|
If the specified sub-key does not exist, then a null string will be
|
|||
|
returned.
|
|||
|
|
|||
|
The string buffer is returned in a block of memory which the caller is
|
|||
|
responsible for deallocating (using MIDL_user_free).
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ParentKey - Key to the parent registry key of the registry key
|
|||
|
containing the unicode string. For example, to retrieve
|
|||
|
the unicode string for a key called ALPHA\BETA\GAMMA, this
|
|||
|
is the key to ALPHA\BETA.
|
|||
|
|
|||
|
SubKeyName - The name of the sub-key whose value contains
|
|||
|
a unicode string to retrieve. This field should not begin with
|
|||
|
a back-slash (\). For example, to retrieve the unicode string
|
|||
|
for a key called ALPHA\BETA\GAMMA, the name specified by this
|
|||
|
field would be "BETA".
|
|||
|
|
|||
|
Body - The address of a UNICODE_STRING whose fields are to be filled
|
|||
|
in with the information retrieved from the sub-key. The Buffer
|
|||
|
field of this argument will be set to point to an allocated buffer
|
|||
|
containing the unicode string characters.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
STATUS_SUCCESS - The string was retrieved successfully.
|
|||
|
|
|||
|
STATUS_INSUFFICIENT_RESOURCES - Memory could not be allocated for the
|
|||
|
string to be returned in.
|
|||
|
|
|||
|
Other errors as may be returned by:
|
|||
|
|
|||
|
NtOpenKey()
|
|||
|
NtQueryInformationKey()
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
HANDLE SubKeyHandle;
|
|||
|
ULONG IgnoreKeyType, KeyValueLength;
|
|||
|
LARGE_INTEGER IgnoreLastWriteTime;
|
|||
|
|
|||
|
|
|||
|
ASSERT(Body != NULL);
|
|||
|
|
|||
|
//
|
|||
|
// Get a handle to the sub-key ...
|
|||
|
//
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
SubKeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
ParentKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&SubKeyHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Couldn't open the sub-key
|
|||
|
// If it is OBJECT_NAME_NOT_FOUND, then build a null string
|
|||
|
// to return. Otherwise, return nothing.
|
|||
|
//
|
|||
|
|
|||
|
if (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
|
|||
|
|
|||
|
Body->Buffer = MIDL_user_allocate( sizeof(UNICODE_NULL) );
|
|||
|
if (Body->Buffer == NULL) {
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
Body->Length = 0;
|
|||
|
Body->MaximumLength = sizeof(UNICODE_NULL);
|
|||
|
Body->Buffer[0] = 0;
|
|||
|
|
|||
|
return( STATUS_SUCCESS );
|
|||
|
|
|||
|
} else {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Get the length of the unicode string
|
|||
|
// We expect one of two things to come back here:
|
|||
|
//
|
|||
|
// 1) STATUS_BUFFER_OVERFLOW - In which case the KeyValueLength
|
|||
|
// contains the length of the string.
|
|||
|
//
|
|||
|
// 2) STATUS_SUCCESS - In which case there is no string out there
|
|||
|
// and we need to build an empty string for return.
|
|||
|
//
|
|||
|
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
SubKeyHandle,
|
|||
|
&IgnoreKeyType,
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
KeyValueLength = 0;
|
|||
|
Body->Buffer = MIDL_user_allocate( KeyValueLength + sizeof(WCHAR) ); // Length of null string
|
|||
|
if (Body->Buffer == NULL) {
|
|||
|
IgnoreStatus = NtClose( SubKeyHandle );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
Body->Buffer[0] = 0;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (NtStatus == STATUS_BUFFER_OVERFLOW) {
|
|||
|
Body->Buffer = MIDL_user_allocate( KeyValueLength + sizeof(WCHAR) );
|
|||
|
if (Body->Buffer == NULL) {
|
|||
|
IgnoreStatus = NtClose( SubKeyHandle );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
SubKeyHandle,
|
|||
|
&IgnoreKeyType,
|
|||
|
Body->Buffer,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
|
|||
|
} else {
|
|||
|
IgnoreStatus = NtClose( SubKeyHandle );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
MIDL_user_free( Body->Buffer );
|
|||
|
IgnoreStatus = NtClose( SubKeyHandle );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
Body->Length = (USHORT)(KeyValueLength);
|
|||
|
Body->MaximumLength = (USHORT)(KeyValueLength) + (USHORT)sizeof(WCHAR);
|
|||
|
UnicodeTerminate(Body);
|
|||
|
|
|||
|
|
|||
|
IgnoreStatus = NtClose( SubKeyHandle );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
return( STATUS_SUCCESS );
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampPutStringToRegistry(
|
|||
|
IN BOOLEAN RelativeToDomain,
|
|||
|
IN PUNICODE_STRING SubKeyName,
|
|||
|
IN PUNICODE_STRING Body
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine puts a unicode string into the specified registry
|
|||
|
sub-key.
|
|||
|
|
|||
|
If the specified sub-key does not exist, then it is created.
|
|||
|
|
|||
|
NOTE: The string is assigned via the RXACT mechanism. Therefore,
|
|||
|
it won't actually reside in the registry key until a commit
|
|||
|
is performed.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
RelativeToDomain - This boolean indicates whether or not the name
|
|||
|
of the sub-key provide via the SubKeyName parameter is relative
|
|||
|
to the current domain or to the top of the SAM registry tree.
|
|||
|
If the name is relative to the current domain, then this value
|
|||
|
is set to TRUE. Otherwise this value is set to FALSE.
|
|||
|
|
|||
|
SubKeyName - The name of the sub-key to be assigned the unicode string.
|
|||
|
This field should not begin with a back-slash (\). For example,
|
|||
|
to put a unicode string into a key called ALPHA\BETA\GAMMA, the
|
|||
|
name specified by this field would be "BETA".
|
|||
|
|
|||
|
Body - The address of a UNICODE_STRING to be placed in the registry.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
STATUS_SUCCESS - The string was added to the RXACT transaction
|
|||
|
successfully.
|
|||
|
|
|||
|
STATUS_INSUFFICIENT_RESOURCES - There was not enough heap memory
|
|||
|
or other limited resource available to fullfil the request.
|
|||
|
|
|||
|
Other errors as may be returned by:
|
|||
|
|
|||
|
RtlAddActionToRXact()
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus;
|
|||
|
UNICODE_STRING KeyName;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Need to build up the name of the key from the root of the RXACT
|
|||
|
// registry key. That is the root of the SAM registry database
|
|||
|
// in our case. If RelativeToDomain is FALSE, then the name passed
|
|||
|
// is already relative to the SAM registry database root.
|
|||
|
//
|
|||
|
|
|||
|
if (RelativeToDomain == TRUE) {
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampBuildDomainSubKeyName(
|
|||
|
&KeyName,
|
|||
|
SubKeyName
|
|||
|
);
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
} else {
|
|||
|
KeyName = (*SubKeyName);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NtStatus = RtlAddActionToRXact(
|
|||
|
SampRXactContext,
|
|||
|
RtlRXactOperationSetValue,
|
|||
|
&KeyName,
|
|||
|
0, // No KeyValueType
|
|||
|
Body->Buffer,
|
|||
|
Body->Length
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// free the KeyName buffer if necessary
|
|||
|
//
|
|||
|
|
|||
|
if (RelativeToDomain) {
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
return( STATUS_SUCCESS );
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// Unicode String related services - These use MIDL_user_allocate and //
|
|||
|
// MIDL_user_free so that the resultant strings can be given to the //
|
|||
|
// RPC runtime. //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampInitUnicodeString(
|
|||
|
IN OUT PUNICODE_STRING String,
|
|||
|
IN USHORT MaximumLength
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine initializes a unicode string to have zero length and
|
|||
|
no initial buffer.
|
|||
|
|
|||
|
|
|||
|
All allocation for this string will be done using MIDL_user_allocate.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
String - The address of a unicode string to initialize.
|
|||
|
|
|||
|
MaximumLength - The maximum length (in bytes) the string will need
|
|||
|
to grow to. The buffer associated with the string is allocated
|
|||
|
to be this size. Don't forget to allow 2 bytes for null termination.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
STATUS_SUCCESS - Successful completion.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
String->Length = 0;
|
|||
|
String->MaximumLength = MaximumLength;
|
|||
|
|
|||
|
String->Buffer = MIDL_user_allocate(MaximumLength);
|
|||
|
|
|||
|
if (String->Buffer != NULL) {
|
|||
|
String->Buffer[0] = 0;
|
|||
|
|
|||
|
return(STATUS_SUCCESS);
|
|||
|
} else {
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampAppendUnicodeString(
|
|||
|
IN OUT PUNICODE_STRING Target,
|
|||
|
IN PUNICODE_STRING StringToAdd
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine appends the string pointed to by StringToAdd to the
|
|||
|
string pointed to by Target. The contents of Target are replaced
|
|||
|
by the result.
|
|||
|
|
|||
|
|
|||
|
All allocation for this string will be done using MIDL_user_allocate.
|
|||
|
Any deallocations will be done using MIDL_user_free.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Target - The address of a unicode string to initialize to be appended to.
|
|||
|
|
|||
|
StringToAdd - The address of a unicode string to be added to the
|
|||
|
end of Target.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
STATUS_SUCCESS - Successful completion.
|
|||
|
|
|||
|
STATUS_INSUFFICIENT_RESOURCES - There was not sufficient heap to fullfil
|
|||
|
the requested operation.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
USHORT TotalLength;
|
|||
|
PWSTR NewBuffer;
|
|||
|
|
|||
|
|
|||
|
TotalLength = Target->Length + StringToAdd->Length + (USHORT)(sizeof(UNICODE_NULL));
|
|||
|
|
|||
|
//
|
|||
|
// If there isn't room in the target to append the new string,
|
|||
|
// allocate a buffer that is large enough and move the current
|
|||
|
// target into it.
|
|||
|
//
|
|||
|
|
|||
|
if (TotalLength > Target->MaximumLength) {
|
|||
|
|
|||
|
NewBuffer = MIDL_user_allocate( (ULONG)TotalLength );
|
|||
|
if (NewBuffer == NULL) {
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
|
|||
|
RtlCopyMemory( NewBuffer, Target->Buffer, (ULONG)(Target->Length) );
|
|||
|
|
|||
|
MIDL_user_free( Target->Buffer );
|
|||
|
Target->Buffer = NewBuffer;
|
|||
|
Target->MaximumLength = TotalLength;
|
|||
|
|
|||
|
} else {
|
|||
|
NewBuffer = Target->Buffer;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// There's now room in the target to append the string.
|
|||
|
//
|
|||
|
|
|||
|
(PCHAR)NewBuffer += Target->Length;
|
|||
|
|
|||
|
RtlCopyMemory( NewBuffer, StringToAdd->Buffer, (ULONG)(StringToAdd->Length) );
|
|||
|
|
|||
|
|
|||
|
Target->Length = TotalLength - (USHORT)(sizeof(UNICODE_NULL));
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Null terminate the resultant string
|
|||
|
//
|
|||
|
|
|||
|
UnicodeTerminate(Target);
|
|||
|
|
|||
|
return(STATUS_SUCCESS);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
SampFreeUnicodeString(
|
|||
|
IN PUNICODE_STRING String
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine frees the buffer associated with a unicode string
|
|||
|
(using MIDL_user_free()).
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Target - The address of a unicode string to free.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
if (String->Buffer != NULL) {
|
|||
|
MIDL_user_free( String->Buffer );
|
|||
|
}
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
SampFreeOemString(
|
|||
|
IN POEM_STRING String
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine frees the buffer associated with an OEM string
|
|||
|
(using MIDL_user_free()).
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Target - The address of an OEM string to free.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
if (String->Buffer != NULL) {
|
|||
|
MIDL_user_free( String->Buffer );
|
|||
|
}
|
|||
|
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampBuildDomainSubKeyName(
|
|||
|
OUT PUNICODE_STRING KeyName,
|
|||
|
IN PUNICODE_STRING SubKeyName OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine builds a unicode string name of the string passed
|
|||
|
via the SubKeyName argument. The resultant name is relative to
|
|||
|
the root of the SAM root registry key.
|
|||
|
|
|||
|
Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
|
|||
|
(ESTABLISHED USING SampSetTransactioDomain()). THIS
|
|||
|
SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
|
|||
|
AND BEFORE SampReleaseWriteLock().
|
|||
|
|
|||
|
|
|||
|
The name built up is comprized of three components:
|
|||
|
|
|||
|
1) The constant named domain parent key name ("DOMAINS").
|
|||
|
|
|||
|
2) A backslash
|
|||
|
|
|||
|
3) The name of the current transaction domain.
|
|||
|
|
|||
|
(optionally)
|
|||
|
|
|||
|
4) A backslash
|
|||
|
|
|||
|
5) The name of the domain's sub-key (specified by the SubKeyName
|
|||
|
argument).
|
|||
|
|
|||
|
|
|||
|
For example, if the current domain is called "MY_DOMAIN", then
|
|||
|
the relative name of the sub-key named "FRAMITZ" is :
|
|||
|
|
|||
|
"DOMAINS\MY_DOMAIN\FRAMITZ"
|
|||
|
|
|||
|
|
|||
|
All allocation for this string will be done using MIDL_user_allocate.
|
|||
|
Any deallocations will be done using MIDL_user_free.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
KeyName - The address of a unicode string whose buffer is to be filled
|
|||
|
in with the full name of the registry key. If successfully created,
|
|||
|
this string must be released with SampFreeUnicodeString() when no
|
|||
|
longer needed.
|
|||
|
|
|||
|
|
|||
|
SubKeyName - (optional) The name of the domain sub-key. If this parameter
|
|||
|
is not provided, then only the domain's name is produced.
|
|||
|
This string is not modified.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
USHORT TotalLength, SubKeyNameLength;
|
|||
|
|
|||
|
|
|||
|
ASSERT(SampTransactionWithinDomain == TRUE);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Initialize a string large enough to hold the name
|
|||
|
//
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(SubKeyName)) {
|
|||
|
SubKeyNameLength = SampBackSlash.Length + SubKeyName->Length;
|
|||
|
} else {
|
|||
|
SubKeyNameLength = 0;
|
|||
|
}
|
|||
|
|
|||
|
TotalLength = SampNameDomains.Length +
|
|||
|
SampBackSlash.Length +
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].InternalName.Length +
|
|||
|
SubKeyNameLength +
|
|||
|
(USHORT)(sizeof(UNICODE_NULL)); // for null terminator
|
|||
|
|
|||
|
NtStatus = SampInitUnicodeString( KeyName, TotalLength );
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( KeyName, &SampNameDomains);
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampFreeUnicodeString( KeyName );
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( KeyName, &SampBackSlash );
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampFreeUnicodeString( KeyName );
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString(
|
|||
|
KeyName,
|
|||
|
&SampDefinedDomains[SampTransactionDomainIndex].InternalName
|
|||
|
);
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampFreeUnicodeString( KeyName );
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(SubKeyName)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\"
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( KeyName, &SampBackSlash );
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampFreeUnicodeString( KeyName );
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\(sub key name)"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( KeyName, SubKeyName );
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampFreeUnicodeString( KeyName );
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampBuildAccountKeyName(
|
|||
|
IN SAMP_OBJECT_TYPE ObjectType,
|
|||
|
OUT PUNICODE_STRING AccountKeyName,
|
|||
|
IN PUNICODE_STRING AccountName OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine builds the name of either a group or user registry key.
|
|||
|
The name produced is relative to the SAM root and will be the name of
|
|||
|
key whose name is the name of the account.
|
|||
|
|
|||
|
|
|||
|
Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
|
|||
|
(ESTABLISHED USING SampSetTransactioDomain()). THIS
|
|||
|
SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
|
|||
|
AND BEFORE SampReleaseWriteLock().
|
|||
|
|
|||
|
|
|||
|
The name built up is comprized of the following components:
|
|||
|
|
|||
|
1) The constant named domain parent key name ("DOMAINS").
|
|||
|
|
|||
|
2) A backslash
|
|||
|
|
|||
|
3) The name of the current transaction domain.
|
|||
|
|
|||
|
4) A backslash
|
|||
|
|
|||
|
5) The constant name of the group or user registry key
|
|||
|
("GROUPS" or "USERS").
|
|||
|
|
|||
|
6) A backslash
|
|||
|
|
|||
|
7) The constant name of the registry key containing the
|
|||
|
account names ("NAMES").
|
|||
|
|
|||
|
and, if the AccountName is specified,
|
|||
|
|
|||
|
8) A backslash
|
|||
|
|
|||
|
9) The account name specified by the AccountName argument.
|
|||
|
|
|||
|
|
|||
|
For example, given a AccountName of "XYZ_GROUP" and the current domain
|
|||
|
is "ALPHA_DOMAIN", this would yield a resultant AccountKeyName of
|
|||
|
"DOMAINS\ALPHA_DOMAIN\GROUPS\NAMES\XYZ_GROUP".
|
|||
|
|
|||
|
|
|||
|
|
|||
|
All allocation for this string will be done using MIDL_user_allocate.
|
|||
|
Any deallocations will be done using MIDL_user_free.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ObjectType - Indicates whether the account is a user or group account.
|
|||
|
|
|||
|
AccountKeyName - The address of a unicode string whose buffer is to be
|
|||
|
filled in with the full name of the registry key. If successfully
|
|||
|
created, this string must be released with SampFreeUnicodeString()
|
|||
|
when no longer needed.
|
|||
|
|
|||
|
|
|||
|
AccountName - The name of the account. This string is not
|
|||
|
modified.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
STATUS_SUCCESS - The name has been built.
|
|||
|
|
|||
|
STATUS_INVALID_ACCOUNT_NAME - The name specified is not legitimate.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
USHORT TotalLength, AccountNameLength;
|
|||
|
PUNICODE_STRING AccountTypeKeyName;
|
|||
|
PUNICODE_STRING NamesSubKeyName;
|
|||
|
|
|||
|
|
|||
|
ASSERT(SampTransactionWithinDomain == TRUE);
|
|||
|
ASSERT( (ObjectType == SampGroupObjectType) ||
|
|||
|
(ObjectType == SampAliasObjectType) ||
|
|||
|
(ObjectType == SampUserObjectType) );
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// If an account name was provided, then it must meet certain
|
|||
|
// criteria.
|
|||
|
//
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(AccountName)) {
|
|||
|
if (
|
|||
|
//
|
|||
|
// Length must be legitimate
|
|||
|
//
|
|||
|
|
|||
|
(AccountName->Length == 0) ||
|
|||
|
(AccountName->Length > AccountName->MaximumLength) ||
|
|||
|
|
|||
|
//
|
|||
|
// Buffer pointer is available
|
|||
|
//
|
|||
|
|
|||
|
(AccountName->Buffer == NULL)
|
|||
|
|
|||
|
|
|||
|
) {
|
|||
|
return(STATUS_INVALID_ACCOUNT_NAME);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
switch (ObjectType) {
|
|||
|
case SampGroupObjectType:
|
|||
|
AccountTypeKeyName = &SampNameDomainGroups;
|
|||
|
NamesSubKeyName = &SampNameDomainGroupsNames;
|
|||
|
break;
|
|||
|
case SampAliasObjectType:
|
|||
|
AccountTypeKeyName = &SampNameDomainAliases;
|
|||
|
NamesSubKeyName = &SampNameDomainAliasesNames;
|
|||
|
break;
|
|||
|
case SampUserObjectType:
|
|||
|
AccountTypeKeyName = &SampNameDomainUsers;
|
|||
|
NamesSubKeyName = &SampNameDomainUsersNames;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Allocate a buffer large enough to hold the entire name.
|
|||
|
// Only count the account name if it is passed.
|
|||
|
//
|
|||
|
|
|||
|
AccountNameLength = 0;
|
|||
|
if (ARGUMENT_PRESENT(AccountName)) {
|
|||
|
AccountNameLength = AccountName->Length + SampBackSlash.Length;
|
|||
|
}
|
|||
|
|
|||
|
TotalLength = SampNameDomains.Length +
|
|||
|
SampBackSlash.Length +
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].InternalName.Length +
|
|||
|
SampBackSlash.Length +
|
|||
|
AccountTypeKeyName->Length +
|
|||
|
SampBackSlash.Length +
|
|||
|
NamesSubKeyName->Length +
|
|||
|
AccountNameLength +
|
|||
|
(USHORT)(sizeof(UNICODE_NULL)); // for null terminator
|
|||
|
|
|||
|
NtStatus = SampInitUnicodeString( AccountKeyName, TotalLength );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampNameDomains);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)"
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString(
|
|||
|
AccountKeyName,
|
|||
|
&SampDefinedDomains[SampTransactionDomainIndex].InternalName
|
|||
|
);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\GROUPS"
|
|||
|
// or
|
|||
|
// "DOMAINS\(domain name)\USERS"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, AccountTypeKeyName );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\GROUPS\"
|
|||
|
// or
|
|||
|
// "DOMAINS\(domain name)\USERS\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\GROUPS\NAMES"
|
|||
|
// or
|
|||
|
// "DOMAINS\(domain name)\USERS\NAMES"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, NamesSubKeyName );
|
|||
|
if (NT_SUCCESS(NtStatus) && ARGUMENT_PRESENT(AccountName)) {
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\GROUPS\NAMES\"
|
|||
|
// or
|
|||
|
// "DOMAINS\(domain name)\USERS\NAMES\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\GROUPS\(account name)"
|
|||
|
// or
|
|||
|
// "DOMAINS\(domain name)\USERS\(account name)"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, AccountName );
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampBuildAccountSubKeyName(
|
|||
|
IN SAMP_OBJECT_TYPE ObjectType,
|
|||
|
OUT PUNICODE_STRING AccountKeyName,
|
|||
|
IN ULONG AccountRid,
|
|||
|
IN PUNICODE_STRING SubKeyName OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine builds the name of a key for one of the fields of either
|
|||
|
a user or a group.
|
|||
|
|
|||
|
The name produced is relative to the SAM root.
|
|||
|
|
|||
|
|
|||
|
Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
|
|||
|
(ESTABLISHED USING SampSetTransactioDomain()). THIS
|
|||
|
SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
|
|||
|
AND BEFORE SampReleaseWriteLock().
|
|||
|
|
|||
|
|
|||
|
The name built up is comprized of the following components:
|
|||
|
|
|||
|
1) The constant named domain parent key name ("DOMAINS").
|
|||
|
|
|||
|
2) A backslash
|
|||
|
|
|||
|
3) The name of the current transaction domain.
|
|||
|
|
|||
|
4) A backslash
|
|||
|
|
|||
|
5) The constant name of the group or user registry key
|
|||
|
("Groups" or "Users").
|
|||
|
|
|||
|
6) A unicode representation of the reltive ID of the account
|
|||
|
|
|||
|
and if the optional SubKeyName is provided:
|
|||
|
|
|||
|
7) A backslash
|
|||
|
|
|||
|
8) the sub key's name.
|
|||
|
4) The account name specified by the AccountName argument.
|
|||
|
|
|||
|
|
|||
|
For example, given a AccountRid of 3187, a SubKeyName of "AdminComment"
|
|||
|
and the current domain is "ALPHA_DOMAIN", this would yield a resultant
|
|||
|
AccountKeyName of:
|
|||
|
|
|||
|
"DOMAINS\ALPHA_DOMAIN\GROUPS\00003187\AdminComment".
|
|||
|
|
|||
|
|
|||
|
|
|||
|
All allocation for this string will be done using MIDL_user_allocate.
|
|||
|
Any deallocations will be done using MIDL_user_free.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ObjectType - Indicates whether the account is a user or group account.
|
|||
|
|
|||
|
AccountKeyName - The address of a unicode string whose buffer is to be
|
|||
|
filled in with the full name of the registry key. If successfully
|
|||
|
created, this string must be released with SampFreeUnicodeString()
|
|||
|
when no longer needed.
|
|||
|
|
|||
|
|
|||
|
AccountName - The name of the account. This string is not
|
|||
|
modified.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
USHORT TotalLength, SubKeyNameLength;
|
|||
|
PUNICODE_STRING AccountTypeKeyName;
|
|||
|
UNICODE_STRING RidNameU;
|
|||
|
|
|||
|
ASSERT(SampTransactionWithinDomain == TRUE);
|
|||
|
ASSERT( (ObjectType == SampGroupObjectType) ||
|
|||
|
(ObjectType == SampAliasObjectType) ||
|
|||
|
(ObjectType == SampUserObjectType) );
|
|||
|
|
|||
|
|
|||
|
switch (ObjectType) {
|
|||
|
case SampGroupObjectType:
|
|||
|
AccountTypeKeyName = &SampNameDomainGroups;
|
|||
|
break;
|
|||
|
case SampAliasObjectType:
|
|||
|
AccountTypeKeyName = &SampNameDomainAliases;
|
|||
|
break;
|
|||
|
case SampUserObjectType:
|
|||
|
AccountTypeKeyName = &SampNameDomainUsers;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Determine how much space will be needed in the resultant name
|
|||
|
// buffer to allow for the sub-key-name.
|
|||
|
//
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(SubKeyName)) {
|
|||
|
SubKeyNameLength = SubKeyName->Length + SampBackSlash.Length;
|
|||
|
} else {
|
|||
|
SubKeyNameLength = 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Convert the account Rid to Unicode.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampRtlConvertUlongToUnicodeString(
|
|||
|
AccountRid,
|
|||
|
16,
|
|||
|
8,
|
|||
|
TRUE,
|
|||
|
&RidNameU
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// allocate a buffer large enough to hold the entire name
|
|||
|
//
|
|||
|
|
|||
|
TotalLength = SampNameDomains.Length +
|
|||
|
SampBackSlash.Length +
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].InternalName.Length +
|
|||
|
SampBackSlash.Length +
|
|||
|
AccountTypeKeyName->Length +
|
|||
|
RidNameU.Length +
|
|||
|
SubKeyNameLength +
|
|||
|
(USHORT)(sizeof(UNICODE_NULL)); // for null terminator
|
|||
|
|
|||
|
NtStatus = SampInitUnicodeString( AccountKeyName, TotalLength );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampNameDomains);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)"
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString(
|
|||
|
AccountKeyName,
|
|||
|
&SampDefinedDomains[SampTransactionDomainIndex].InternalName
|
|||
|
);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\GROUPS"
|
|||
|
// or
|
|||
|
// "DOMAINS\(domain name)\USERS"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, AccountTypeKeyName );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\GROUPS\"
|
|||
|
// or
|
|||
|
// "DOMAINS\(domain name)\USERS\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\GROUPS\(rid)"
|
|||
|
// or
|
|||
|
// "DOMAINS\(domain name)\USERS\(rid)"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &RidNameU );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus) && ARGUMENT_PRESENT(SubKeyName)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\GROUPS\(rid)\"
|
|||
|
// or
|
|||
|
// "DOMAINS\(domain name)\USERS\(rid)\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\GROUPS\(rid)\(sub-key-name)"
|
|||
|
// or
|
|||
|
// "DOMAINS\(domain name)\USERS\(rid)\(sub-key-name)"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, SubKeyName );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
MIDL_user_free(RidNameU.Buffer);
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampBuildAliasMembersKeyName(
|
|||
|
IN PSID AccountSid,
|
|||
|
OUT PUNICODE_STRING DomainKeyName,
|
|||
|
OUT PUNICODE_STRING AccountKeyName
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine builds the name of a key for the alias membership for an
|
|||
|
arbitrary account sid. Also produced is the name of the key for the
|
|||
|
domain of the account. This is the account key name without the last
|
|||
|
account rid component.
|
|||
|
|
|||
|
The names produced is relative to the SAM root.
|
|||
|
|
|||
|
|
|||
|
Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
|
|||
|
(ESTABLISHED USING SampSetTransactioDomain()). THIS
|
|||
|
SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
|
|||
|
AND BEFORE SampReleaseWriteLock().
|
|||
|
|
|||
|
|
|||
|
The names built up are comprised of the following components:
|
|||
|
|
|||
|
1) The constant named domain parent key name ("DOMAINS").
|
|||
|
|
|||
|
2) A backslash
|
|||
|
|
|||
|
3) The name of the current transaction domain.
|
|||
|
|
|||
|
4) A backslash
|
|||
|
|
|||
|
5) The constant name of the alias registry key ("Aliases").
|
|||
|
|
|||
|
6) A backslash
|
|||
|
|
|||
|
7) The constant name of the alias members registry key ("Members").
|
|||
|
|
|||
|
8) A backslash
|
|||
|
|
|||
|
9) A unicode representation of the SID of the account domain
|
|||
|
|
|||
|
and for the AccountKeyName only
|
|||
|
|
|||
|
10) A backslash
|
|||
|
|
|||
|
11) A unicode representation of the RID of the account
|
|||
|
|
|||
|
|
|||
|
For example, given a Account Sid of 1-2-3-3187
|
|||
|
and the current domain is "ALPHA_DOMAIN",
|
|||
|
this would yield a resultant AcccountKeyName of:
|
|||
|
|
|||
|
"DOMAINS\ALPHA_DOMAIN\ALIASES\MEMBERS\1-2-3\00003187".
|
|||
|
|
|||
|
and a DomainKeyName of:
|
|||
|
|
|||
|
"DOMAINS\ALPHA_DOMAIN\ALIASES\MEMBERS\1-2-3".
|
|||
|
|
|||
|
|
|||
|
|
|||
|
All allocation for these strings will be done using MIDL_user_allocate.
|
|||
|
Any deallocations will be done using MIDL_user_free.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
AccountSid - The account whose alias membership in the current domain
|
|||
|
is to be determined.
|
|||
|
|
|||
|
DomainKeyName - The address of a unicode string whose
|
|||
|
buffer is to be filled in with the full name of the domain registry key.
|
|||
|
If successfully created, this string must be released with
|
|||
|
SampFreeUnicodeString() when no longer needed.
|
|||
|
|
|||
|
AccountKeyName - The address of a unicode string whose
|
|||
|
buffer is to be filled in with the full name of the account registry key.
|
|||
|
If successfully created, this string must be released with
|
|||
|
SampFreeUnicodeString() when no longer needed.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - the domain and account key names are valid.
|
|||
|
|
|||
|
STATUS_INVALID_SID - the AccountSid is not valid. AccountSids must have
|
|||
|
a sub-authority count > 0
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
USHORT DomainTotalLength;
|
|||
|
USHORT AccountTotalLength;
|
|||
|
UNICODE_STRING DomainNameU, TempStringU;
|
|||
|
UNICODE_STRING RidNameU;
|
|||
|
PSID DomainSid = NULL;
|
|||
|
ULONG AccountRid;
|
|||
|
ULONG AccountSubAuthorities;
|
|||
|
|
|||
|
DomainNameU.Buffer = TempStringU.Buffer = RidNameU.Buffer = NULL;
|
|||
|
|
|||
|
ASSERT(SampTransactionWithinDomain == TRUE);
|
|||
|
|
|||
|
ASSERT(AccountSid != NULL);
|
|||
|
ASSERT(DomainKeyName != NULL);
|
|||
|
ASSERT(AccountKeyName != NULL);
|
|||
|
|
|||
|
//
|
|||
|
// Split the account sid into domain sid and account rid
|
|||
|
//
|
|||
|
|
|||
|
AccountSubAuthorities = (ULONG)*RtlSubAuthorityCountSid(AccountSid);
|
|||
|
|
|||
|
//
|
|||
|
// Check for at least one sub-authority
|
|||
|
//
|
|||
|
|
|||
|
if (AccountSubAuthorities < 1) {
|
|||
|
|
|||
|
return (STATUS_INVALID_SID);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Allocate space for the domain sid
|
|||
|
//
|
|||
|
|
|||
|
DomainSid = MIDL_user_allocate(RtlLengthSid(AccountSid));
|
|||
|
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
|
|||
|
if (DomainSid == NULL) {
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the domain sid
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = RtlCopySid(RtlLengthSid(AccountSid), DomainSid, AccountSid);
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
*RtlSubAuthorityCountSid(DomainSid) = (UCHAR)(AccountSubAuthorities - 1);
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the account rid
|
|||
|
//
|
|||
|
|
|||
|
AccountRid = *RtlSubAuthoritySid(AccountSid, AccountSubAuthorities - 1);
|
|||
|
|
|||
|
//
|
|||
|
// Convert the domain sid into a registry key name string
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = RtlConvertSidToUnicodeString( &DomainNameU, DomainSid, TRUE);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
DomainNameU.Buffer = NULL;
|
|||
|
goto BuildAliasMembersKeyNameError;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Convert the account rid into a registry key name string with
|
|||
|
// leading zeros.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampRtlConvertUlongToUnicodeString(
|
|||
|
AccountRid,
|
|||
|
16,
|
|||
|
8,
|
|||
|
TRUE,
|
|||
|
&RidNameU
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
goto BuildAliasMembersKeyNameError;
|
|||
|
}
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// allocate a buffer large enough to hold the entire name
|
|||
|
//
|
|||
|
|
|||
|
DomainTotalLength =
|
|||
|
SampNameDomains.Length +
|
|||
|
SampBackSlash.Length +
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].InternalName.Length +
|
|||
|
SampBackSlash.Length +
|
|||
|
SampNameDomainAliases.Length +
|
|||
|
SampBackSlash.Length +
|
|||
|
SampNameDomainAliasesMembers.Length +
|
|||
|
SampBackSlash.Length +
|
|||
|
DomainNameU.Length +
|
|||
|
(USHORT)(sizeof(UNICODE_NULL)); // for null terminator
|
|||
|
|
|||
|
AccountTotalLength = DomainTotalLength +
|
|||
|
SampBackSlash.Length +
|
|||
|
RidNameU.Length;
|
|||
|
|
|||
|
//
|
|||
|
// First build the domain key name
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampInitUnicodeString( DomainKeyName, DomainTotalLength );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampInitUnicodeString( AccountKeyName, AccountTotalLength );
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
SampFreeUnicodeString(DomainKeyName);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( DomainKeyName, &SampNameDomains);
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( DomainKeyName, &SampBackSlash );
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString(
|
|||
|
DomainKeyName,
|
|||
|
&SampDefinedDomains[SampTransactionDomainIndex].InternalName
|
|||
|
);
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( DomainKeyName, &SampBackSlash );
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\ALIASES"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( DomainKeyName, &SampNameDomainAliases);
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\ALIASES\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( DomainKeyName, &SampBackSlash );
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\ALIASES\MEMBERS"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( DomainKeyName, &SampNameDomainAliasesMembers);
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\ALIASES\MEMBERS\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( DomainKeyName, &SampBackSlash );
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\ALIASES\MEMBERS\(DomainSid)"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( DomainKeyName, &DomainNameU );
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
//
|
|||
|
// Now build the account name by copying the domain name
|
|||
|
// and suffixing the account Rid
|
|||
|
//
|
|||
|
|
|||
|
RtlCopyUnicodeString(AccountKeyName, DomainKeyName);
|
|||
|
ASSERT(AccountKeyName->Length = DomainKeyName->Length);
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\ALIASES\MEMBERS\(DomainSid)\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &SampBackSlash );
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)\ALIASES\MEMBERS\(DomainSid)\(AccountRid)"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( AccountKeyName, &RidNameU );
|
|||
|
ASSERT(NT_SUCCESS(NtStatus));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
MIDL_user_free(RidNameU.Buffer);
|
|||
|
}
|
|||
|
|
|||
|
BuildAliasMembersKeyNameFinish:
|
|||
|
|
|||
|
//
|
|||
|
// If necessary, free memory allocated for the DomainSid.
|
|||
|
//
|
|||
|
|
|||
|
if (DomainSid != NULL) {
|
|||
|
|
|||
|
MIDL_user_free(DomainSid);
|
|||
|
DomainSid = NULL;
|
|||
|
}
|
|||
|
if ( DomainNameU.Buffer != NULL ) {
|
|||
|
RtlFreeUnicodeString( &DomainNameU );
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
BuildAliasMembersKeyNameError:
|
|||
|
|
|||
|
goto BuildAliasMembersKeyNameFinish;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampValidateNewAccountName(
|
|||
|
PUNICODE_STRING NewAccountName
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine validates a new user, alias or group account name.
|
|||
|
This routine:
|
|||
|
|
|||
|
1) Validates that the name is properly constructed.
|
|||
|
|
|||
|
2) Is not already in use as a user, alias or group account name
|
|||
|
in any of the local SAM domains.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Name - The address of a unicode string containing the name to be
|
|||
|
looked for.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The new account name is valid, and not yet in use.
|
|||
|
|
|||
|
STATUS_ALIAS_EXISTS - The account name is already in use as a
|
|||
|
alias account name.
|
|||
|
|
|||
|
STATUS_GROUP_EXISTS - The account name is already in use as a
|
|||
|
group account name.
|
|||
|
|
|||
|
STATUS_USER_EXISTS - The account name is already in use as a user
|
|||
|
account name.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
SID_NAME_USE Use;
|
|||
|
ULONG Rid;
|
|||
|
ULONG DomainIndex, CurrentTransactionDomainIndex;
|
|||
|
|
|||
|
//
|
|||
|
// Save the current transaction domain indicator
|
|||
|
//
|
|||
|
|
|||
|
CurrentTransactionDomainIndex = SampTransactionDomainIndex;
|
|||
|
|
|||
|
//
|
|||
|
// Lookup the account in each of the local SAM domains
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
for (DomainIndex = 0;
|
|||
|
( (DomainIndex < SampDefinedDomainsCount) && NT_SUCCESS(NtStatus) );
|
|||
|
DomainIndex++) {
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
SampSetTransactionDomain( DomainIndex );
|
|||
|
|
|||
|
NtStatus = SampLookupAccountRid(
|
|||
|
SampUnknownObjectType,
|
|||
|
NewAccountName,
|
|||
|
STATUS_NO_SUCH_USER,
|
|||
|
&Rid,
|
|||
|
&Use
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// The only error allowed is that the account was not found.
|
|||
|
// Convert this to success, and continue searching SAM domains.
|
|||
|
// Propagate any other error.
|
|||
|
//
|
|||
|
|
|||
|
if (NtStatus != STATUS_NO_SUCH_USER) {
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// An account with the given Rid already exists. Return status
|
|||
|
// indicating the type of the conflicting account.
|
|||
|
//
|
|||
|
|
|||
|
switch (Use) {
|
|||
|
|
|||
|
case SidTypeUser:
|
|||
|
|
|||
|
NtStatus = STATUS_USER_EXISTS;
|
|||
|
break;
|
|||
|
|
|||
|
case SidTypeGroup:
|
|||
|
|
|||
|
NtStatus = STATUS_GROUP_EXISTS;
|
|||
|
break;
|
|||
|
|
|||
|
case SidTypeDomain:
|
|||
|
|
|||
|
NtStatus = STATUS_DOMAIN_EXISTS;
|
|||
|
break;
|
|||
|
|
|||
|
case SidTypeAlias:
|
|||
|
|
|||
|
NtStatus = STATUS_ALIAS_EXISTS;
|
|||
|
break;
|
|||
|
|
|||
|
case SidTypeWellKnownGroup:
|
|||
|
|
|||
|
NtStatus = STATUS_GROUP_EXISTS;
|
|||
|
break;
|
|||
|
|
|||
|
case SidTypeDeletedAccount:
|
|||
|
|
|||
|
NtStatus = STATUS_INVALID_PARAMETER;
|
|||
|
break;
|
|||
|
|
|||
|
case SidTypeInvalid:
|
|||
|
|
|||
|
NtStatus = STATUS_INVALID_PARAMETER;
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
|
|||
|
NtStatus = STATUS_INTERNAL_DB_CORRUPTION;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Restore the Current Transaction Domain
|
|||
|
//
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
SampSetTransactionDomain( CurrentTransactionDomainIndex );
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampValidateAccountNameChange(
|
|||
|
IN PUNICODE_STRING NewAccountName,
|
|||
|
IN PUNICODE_STRING OldAccountName
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine validates a user, group or alias account name that is
|
|||
|
to be set on an account. This routine:
|
|||
|
|
|||
|
1) Returns success if the name is the same as the existing name,
|
|||
|
except with a different case
|
|||
|
|
|||
|
1) Otherwise calls SampValidateNewAccountName to verify that the
|
|||
|
name is properly constructed and is not already in use as a
|
|||
|
user, alias or group account name.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
NewAccountName - The address of a unicode string containing the new
|
|||
|
name.
|
|||
|
|
|||
|
OldAccountName - The address of a unicode string containing the old
|
|||
|
name.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The account's name may be changed to the new
|
|||
|
account name
|
|||
|
|
|||
|
STATUS_ALIAS_EXISTS - The account name is already in use as a
|
|||
|
alias account name.
|
|||
|
|
|||
|
STATUS_GROUP_EXISTS - The account name is already in use as a
|
|||
|
group account name.
|
|||
|
|
|||
|
STATUS_USER_EXISTS - The account name is already in use as a user
|
|||
|
account name.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
//
|
|||
|
// Compare the old and new names without regard for case. If they
|
|||
|
// are the same, return success because the name was checked when we
|
|||
|
// first added it; we don't care about case changes.
|
|||
|
//
|
|||
|
|
|||
|
if ( 0 == RtlCompareUnicodeString(
|
|||
|
NewAccountName,
|
|||
|
OldAccountName,
|
|||
|
TRUE ) ) {
|
|||
|
|
|||
|
return( STATUS_SUCCESS );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Not just a case change; this is a different name. Validate it as
|
|||
|
// any new name.
|
|||
|
//
|
|||
|
|
|||
|
return( SampValidateNewAccountName( NewAccountName ) );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampRetrieveAccountCounts(
|
|||
|
OUT PULONG UserCount,
|
|||
|
OUT PULONG GroupCount,
|
|||
|
OUT PULONG AliasCount
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine retrieve the number of user and group accounts in a domain.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
|
|||
|
(ESTABLISHED USING SampSetTransactioDomain()). THIS
|
|||
|
SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
|
|||
|
AND BEFORE SampReleaseReadLock().
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
UserCount - Receives the number of user accounts in the domain.
|
|||
|
|
|||
|
GroupCount - Receives the number of group accounts in the domain.
|
|||
|
|
|||
|
AliasCount - Receives the number of alias accounts in the domain.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The values have been retrieved.
|
|||
|
|
|||
|
STATUS_INSUFFICIENT_RESOURCES - Not enough memory could be allocated
|
|||
|
to perform the requested operation.
|
|||
|
|
|||
|
Other values are unexpected errors. These may originate from
|
|||
|
internal calls to:
|
|||
|
|
|||
|
NtOpenKey()
|
|||
|
NtQueryInformationKey()
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
UNICODE_STRING KeyName;
|
|||
|
PUNICODE_STRING AccountTypeKeyName;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
HANDLE AccountHandle;
|
|||
|
ULONG KeyValueLength;
|
|||
|
LARGE_INTEGER IgnoreLastWriteTime;
|
|||
|
|
|||
|
|
|||
|
ASSERT(SampTransactionWithinDomain == TRUE);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Get the user count first
|
|||
|
//
|
|||
|
|
|||
|
AccountTypeKeyName = &SampNameDomainUsers;
|
|||
|
NtStatus = SampBuildDomainSubKeyName( &KeyName, AccountTypeKeyName );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Open this key and get its current value
|
|||
|
//
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&AccountHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// The count is stored as the KeyValueType
|
|||
|
//
|
|||
|
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
AccountHandle,
|
|||
|
UserCount,
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
IgnoreStatus = NtClose( AccountHandle );
|
|||
|
ASSERT( NT_SUCCESS(IgnoreStatus) );
|
|||
|
}
|
|||
|
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now get the group count
|
|||
|
//
|
|||
|
|
|||
|
AccountTypeKeyName = &SampNameDomainGroups;
|
|||
|
NtStatus = SampBuildDomainSubKeyName( &KeyName, AccountTypeKeyName );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Open this key and get its current value
|
|||
|
//
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&AccountHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// The count is stored as the KeyValueType
|
|||
|
//
|
|||
|
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
AccountHandle,
|
|||
|
GroupCount,
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
IgnoreStatus = NtClose( AccountHandle );
|
|||
|
ASSERT( NT_SUCCESS(IgnoreStatus) );
|
|||
|
}
|
|||
|
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now get the alias count
|
|||
|
//
|
|||
|
|
|||
|
AccountTypeKeyName = &SampNameDomainAliases;
|
|||
|
NtStatus = SampBuildDomainSubKeyName( &KeyName, AccountTypeKeyName );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Open this key and get its current value
|
|||
|
//
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&AccountHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// The count is stored as the KeyValueType
|
|||
|
//
|
|||
|
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
AccountHandle,
|
|||
|
AliasCount,
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
IgnoreStatus = NtClose( AccountHandle );
|
|||
|
ASSERT( NT_SUCCESS(IgnoreStatus) );
|
|||
|
}
|
|||
|
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
}
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampAdjustAccountCount(
|
|||
|
IN SAMP_OBJECT_TYPE ObjectType,
|
|||
|
IN BOOLEAN Increment
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine increments or decrements the count of either
|
|||
|
users or groups in a domain.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
|
|||
|
(ESTABLISHED USING SampSetTransactioDomain()). THIS
|
|||
|
SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
|
|||
|
AND BEFORE SampReleaseWriteLock().
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ObjectType - Indicates whether the account is a user or group account.
|
|||
|
|
|||
|
Increment - a BOOLEAN value indicating whether the user or group
|
|||
|
count is to be incremented or decremented. A value of TRUE
|
|||
|
will cause the count to be incremented. A value of FALSE will
|
|||
|
cause the value to be decremented.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The value has been adjusted and the new value added
|
|||
|
to the current RXACT transaction.
|
|||
|
|
|||
|
STATUS_INSUFFICIENT_RESOURCES - Not enough memory could be allocated
|
|||
|
to perform the requested operation.
|
|||
|
|
|||
|
Other values are unexpected errors. These may originate from
|
|||
|
internal calls to:
|
|||
|
|
|||
|
NtOpenKey()
|
|||
|
NtQueryInformationKey()
|
|||
|
RtlAddActionToRXact()
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
UNICODE_STRING KeyName;
|
|||
|
PUNICODE_STRING AccountTypeKeyName;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
HANDLE AccountHandle;
|
|||
|
ULONG Count, KeyValueLength;
|
|||
|
LARGE_INTEGER IgnoreLastWriteTime;
|
|||
|
|
|||
|
|
|||
|
ASSERT(SampTransactionWithinDomain == TRUE);
|
|||
|
ASSERT( (ObjectType == SampGroupObjectType) ||
|
|||
|
(ObjectType == SampAliasObjectType) ||
|
|||
|
(ObjectType == SampUserObjectType) );
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Build the name of the key whose count is to be incremented or
|
|||
|
// decremented.
|
|||
|
//
|
|||
|
|
|||
|
switch (ObjectType) {
|
|||
|
case SampGroupObjectType:
|
|||
|
AccountTypeKeyName = &SampNameDomainGroups;
|
|||
|
break;
|
|||
|
case SampAliasObjectType:
|
|||
|
AccountTypeKeyName = &SampNameDomainAliases;
|
|||
|
break;
|
|||
|
case SampUserObjectType:
|
|||
|
AccountTypeKeyName = &SampNameDomainUsers;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
NtStatus = SampBuildDomainSubKeyName( &KeyName, AccountTypeKeyName );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Open this key and get its current value
|
|||
|
//
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&AccountHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// The count is stored as the KeyValueType
|
|||
|
//
|
|||
|
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
AccountHandle,
|
|||
|
&Count,
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
if (Increment == TRUE) {
|
|||
|
Count += 1;
|
|||
|
} else {
|
|||
|
ASSERT( Count != 0 );
|
|||
|
Count -= 1;
|
|||
|
}
|
|||
|
|
|||
|
NtStatus = RtlAddActionToRXact(
|
|||
|
SampRXactContext,
|
|||
|
RtlRXactOperationSetValue,
|
|||
|
&KeyName,
|
|||
|
Count,
|
|||
|
NULL,
|
|||
|
0
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
IgnoreStatus = NtClose( AccountHandle );
|
|||
|
ASSERT( NT_SUCCESS(IgnoreStatus) );
|
|||
|
}
|
|||
|
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
return( STATUS_SUCCESS );
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampEnumerateAccountNamesCommon(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN SAMP_OBJECT_TYPE ObjectType,
|
|||
|
IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
|
|||
|
OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
|
|||
|
IN ULONG PreferedMaximumLength,
|
|||
|
IN ULONG Filter,
|
|||
|
OUT PULONG CountReturned
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine enumerates names of either user, group or alias accounts.
|
|||
|
This routine is intended to directly support
|
|||
|
|
|||
|
SamrEnumerateGroupsInDomain(),
|
|||
|
SamrEnumerateAliasesInDomain() and
|
|||
|
SamrEnumerateUsersInDomain().
|
|||
|
|
|||
|
This routine performs database locking, and context lookup (including
|
|||
|
access validation).
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
All allocation for OUT parameters will be done using MIDL_user_allocate.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainHandle - The domain handle whose users or groups are to be enumerated.
|
|||
|
|
|||
|
ObjectType - Indicates whether users or groups are to be enumerated.
|
|||
|
|
|||
|
EnumerationContext - API specific handle to allow multiple calls. The
|
|||
|
caller should return this value in successive calls to retrieve
|
|||
|
additional information.
|
|||
|
|
|||
|
Buffer - Receives a pointer to the buffer containing the
|
|||
|
requested information. The information returned is
|
|||
|
structured as an array of SAM_ENUMERATION_INFORMATION data
|
|||
|
structures. When this information is no longer needed, the
|
|||
|
buffer must be freed using SamFreeMemory().
|
|||
|
|
|||
|
PreferedMaximumLength - Prefered maximum length of returned data
|
|||
|
(in 8-bit bytes). This is not a hard upper limit, but serves
|
|||
|
as a guide to the server. Due to data conversion between
|
|||
|
systems with different natural data sizes, the actual amount
|
|||
|
of data returned may be greater than this value.
|
|||
|
|
|||
|
Filter - if ObjectType is users, the users can optionally be filtered
|
|||
|
by setting this field with bits from the AccountControlField that
|
|||
|
must match. Otherwise ignored.
|
|||
|
|
|||
|
CountReturned - Receives the number of entries returned.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully, and there
|
|||
|
are no additional entries. Entries may or may not have been
|
|||
|
returned from this call. The CountReturned parameter indicates
|
|||
|
whether any were.
|
|||
|
|
|||
|
STATUS_MORE_ENTRIES - There are more entries which may be obtained
|
|||
|
using successive calls to this API. This is a successful return.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have access to request the data.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
NTSTATUS IgnoreStatus;
|
|||
|
PSAMP_OBJECT Context;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
ACCESS_MASK DesiredAccess;
|
|||
|
|
|||
|
|
|||
|
ASSERT( (ObjectType == SampGroupObjectType) ||
|
|||
|
(ObjectType == SampAliasObjectType) ||
|
|||
|
(ObjectType == SampUserObjectType) );
|
|||
|
|
|||
|
//
|
|||
|
// Make sure we understand what RPC is doing for (to) us.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT (DomainHandle != NULL);
|
|||
|
ASSERT (EnumerationContext != NULL);
|
|||
|
ASSERT ( Buffer != NULL);
|
|||
|
ASSERT ((*Buffer) == NULL);
|
|||
|
ASSERT (CountReturned != NULL);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Establish type-specific information
|
|||
|
//
|
|||
|
|
|||
|
DesiredAccess = DOMAIN_LIST_ACCOUNTS;
|
|||
|
|
|||
|
|
|||
|
SampAcquireReadLock();
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
Context = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
Context,
|
|||
|
DesiredAccess,
|
|||
|
SampDomainObjectType,
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Call our private worker routine
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampEnumerateAccountNames(
|
|||
|
ObjectType,
|
|||
|
EnumerationContext,
|
|||
|
Buffer,
|
|||
|
PreferedMaximumLength,
|
|||
|
Filter,
|
|||
|
CountReturned,
|
|||
|
Context->TrustedClient
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object, discarding changes
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( Context, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free the read lock
|
|||
|
//
|
|||
|
|
|||
|
SampReleaseReadLock();
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampEnumerateAccountNames(
|
|||
|
IN SAMP_OBJECT_TYPE ObjectType,
|
|||
|
IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
|
|||
|
OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
|
|||
|
IN ULONG PreferedMaximumLength,
|
|||
|
IN ULONG Filter,
|
|||
|
OUT PULONG CountReturned,
|
|||
|
IN BOOLEAN TrustedClient
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This is the worker routine used to enumerate user, group or alias accounts
|
|||
|
|
|||
|
|
|||
|
Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
|
|||
|
(ESTABLISHED USING SampSetTransactioDomain()). THIS
|
|||
|
SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
|
|||
|
AND BEFORE SampReleaseReadLock().
|
|||
|
|
|||
|
|
|||
|
|
|||
|
All allocation for OUT parameters will be done using MIDL_user_allocate.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ObjectType - Indicates whether users or groups are to be enumerated.
|
|||
|
|
|||
|
EnumerationContext - API specific handle to allow multiple calls. The
|
|||
|
caller should return this value in successive calls to retrieve
|
|||
|
additional information.
|
|||
|
|
|||
|
Buffer - Receives a pointer to the buffer containing the
|
|||
|
requested information. The information returned is
|
|||
|
structured as an array of SAM_ENUMERATION_INFORMATION data
|
|||
|
structures. When this information is no longer needed, the
|
|||
|
buffer must be freed using SamFreeMemory().
|
|||
|
|
|||
|
PreferedMaximumLength - Prefered maximum length of returned data
|
|||
|
(in 8-bit bytes). This is not a hard upper limit, but serves
|
|||
|
as a guide to the server. Due to data conversion between
|
|||
|
systems with different natural data sizes, the actual amount
|
|||
|
of data returned may be greater than this value.
|
|||
|
|
|||
|
Filter - if ObjectType is users, the users can optionally be filtered
|
|||
|
by setting this field with bits from the AccountControlField that
|
|||
|
must match. Otherwise ignored.
|
|||
|
|
|||
|
CountReturned - Receives the number of entries returned.
|
|||
|
|
|||
|
TrustedClient - says whether the caller is trusted or not. If so,
|
|||
|
we'll ignore the SAMP_MAXIMUM_MEMORY_TO_USE restriction on data
|
|||
|
returns.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully, and there
|
|||
|
are no additional entries. Entries may or may not have been
|
|||
|
returned from this call. The CountReturned parameter indicates
|
|||
|
whether any were.
|
|||
|
|
|||
|
STATUS_MORE_ENTRIES - There are more entries which may be obtained
|
|||
|
using successive calls to this API. This is a successful return.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have access to request the data.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
SAMP_V1_0A_FIXED_LENGTH_USER UserV1aFixed;
|
|||
|
NTSTATUS NtStatus, TmpStatus;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
HANDLE TempHandle = NULL;
|
|||
|
ULONG i, NamesToReturn, MaxMemoryToUse;
|
|||
|
ULONG TotalLength,NewTotalLength;
|
|||
|
PSAMP_OBJECT UserContext = NULL;
|
|||
|
PSAMP_ENUMERATION_ELEMENT SampHead = NULL,
|
|||
|
NextEntry = NULL,
|
|||
|
NewEntry = NULL,
|
|||
|
SampTail = NULL;
|
|||
|
BOOLEAN MoreNames;
|
|||
|
BOOLEAN LengthLimitReached = FALSE;
|
|||
|
BOOLEAN FilteredName;
|
|||
|
PSAMPR_RID_ENUMERATION ArrayBuffer = NULL;
|
|||
|
ULONG ArrayBufferLength;
|
|||
|
LARGE_INTEGER IgnoreLastWriteTime;
|
|||
|
UNICODE_STRING AccountNamesKey;
|
|||
|
SID_NAME_USE IgnoreUse;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Open the registry key containing the account names
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountKeyName(
|
|||
|
ObjectType,
|
|||
|
&AccountNamesKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Now try to open this registry key so we can enumerate its
|
|||
|
// sub-keys
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&AccountNamesKey,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&TempHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Read names until we have exceeded the preferred maximum
|
|||
|
// length or we run out of names.
|
|||
|
//
|
|||
|
|
|||
|
NamesToReturn = 0;
|
|||
|
SampHead = NULL;
|
|||
|
SampTail = NULL;
|
|||
|
MoreNames = TRUE;
|
|||
|
|
|||
|
NewTotalLength = 0;
|
|||
|
TotalLength = 0;
|
|||
|
|
|||
|
if ( TrustedClient ) {
|
|||
|
|
|||
|
//
|
|||
|
// We place no restrictions on the amount of memory used
|
|||
|
// by a trusted client. Rely on their
|
|||
|
// PreferedMaximumLength to limit us instead.
|
|||
|
//
|
|||
|
|
|||
|
MaxMemoryToUse = 0xffffffff;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
MaxMemoryToUse = SAMP_MAXIMUM_MEMORY_TO_USE;
|
|||
|
}
|
|||
|
|
|||
|
while (MoreNames) {
|
|||
|
|
|||
|
UNICODE_STRING SubKeyName;
|
|||
|
USHORT LengthRequired;
|
|||
|
|
|||
|
//
|
|||
|
// Try reading with a DEFAULT length buffer first.
|
|||
|
//
|
|||
|
|
|||
|
LengthRequired = 32;
|
|||
|
|
|||
|
NewTotalLength = TotalLength +
|
|||
|
sizeof(UNICODE_STRING) +
|
|||
|
LengthRequired;
|
|||
|
|
|||
|
//
|
|||
|
// Stop if SAM or user specified length limit reached
|
|||
|
//
|
|||
|
|
|||
|
if ( ( (TotalLength != 0) &&
|
|||
|
(NewTotalLength >= PreferedMaximumLength) ) ||
|
|||
|
( NewTotalLength > MaxMemoryToUse )
|
|||
|
) {
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
break; // Out of while loop, MoreNames = TRUE
|
|||
|
}
|
|||
|
|
|||
|
NtStatus = SampInitUnicodeString(&SubKeyName, LengthRequired);
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
break; // Out of while loop
|
|||
|
}
|
|||
|
|
|||
|
NtStatus = RtlpNtEnumerateSubKey(
|
|||
|
TempHandle,
|
|||
|
&SubKeyName,
|
|||
|
*EnumerationContext,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
|
|||
|
if (NtStatus == STATUS_BUFFER_OVERFLOW) {
|
|||
|
|
|||
|
//
|
|||
|
// The subkey name is longer than our default size,
|
|||
|
// Free the old buffer.
|
|||
|
// Allocate the correct size buffer and read it again.
|
|||
|
//
|
|||
|
|
|||
|
SampFreeUnicodeString(&SubKeyName);
|
|||
|
|
|||
|
LengthRequired = SubKeyName.Length;
|
|||
|
|
|||
|
NewTotalLength = TotalLength +
|
|||
|
sizeof(UNICODE_STRING) +
|
|||
|
LengthRequired;
|
|||
|
|
|||
|
//
|
|||
|
// Stop if SAM or user specified length limit reached
|
|||
|
//
|
|||
|
|
|||
|
if ( ( (TotalLength != 0) &&
|
|||
|
(NewTotalLength >= PreferedMaximumLength) ) ||
|
|||
|
( NewTotalLength > MaxMemoryToUse )
|
|||
|
) {
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
break; // Out of while loop, MoreNames = TRUE
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Try reading the name again, we should be successful.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampInitUnicodeString(&SubKeyName, LengthRequired);
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
break; // Out of while loop
|
|||
|
}
|
|||
|
|
|||
|
NtStatus = RtlpNtEnumerateSubKey(
|
|||
|
TempHandle,
|
|||
|
&SubKeyName,
|
|||
|
*EnumerationContext,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Free up our buffer if we failed to read the key data
|
|||
|
//
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
SampFreeUnicodeString(&SubKeyName);
|
|||
|
|
|||
|
//
|
|||
|
// Map a no-more-entries status to success
|
|||
|
//
|
|||
|
|
|||
|
if (NtStatus == STATUS_NO_MORE_ENTRIES) {
|
|||
|
|
|||
|
MoreNames = FALSE;
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
break; // Out of while loop
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// We've allocated the subkey and read the data into it
|
|||
|
// Stuff it in an enumeration element.
|
|||
|
//
|
|||
|
|
|||
|
NewEntry = MIDL_user_allocate(sizeof(SAMP_ENUMERATION_ELEMENT));
|
|||
|
if (NewEntry == NULL) {
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
} else {
|
|||
|
|
|||
|
*(PUNICODE_STRING)&NewEntry->Entry.Name = SubKeyName;
|
|||
|
|
|||
|
//
|
|||
|
// Now get the Rid value of this named
|
|||
|
// account. We must be able to get the
|
|||
|
// name or we have an internal database
|
|||
|
// corruption.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampLookupAccountRid(
|
|||
|
ObjectType,
|
|||
|
(PUNICODE_STRING)&NewEntry->Entry.Name,
|
|||
|
STATUS_INTERNAL_DB_CORRUPTION,
|
|||
|
&NewEntry->Entry.RelativeId,
|
|||
|
&IgnoreUse
|
|||
|
);
|
|||
|
|
|||
|
ASSERT(NtStatus != STATUS_INTERNAL_DB_CORRUPTION);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
FilteredName = TRUE;
|
|||
|
|
|||
|
if ( ( ObjectType == SampUserObjectType ) &&
|
|||
|
( Filter != 0 ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// We only want to return users with a
|
|||
|
// UserAccountControl field that matches
|
|||
|
// the filter passed in. Check here.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
SampUserObjectType,
|
|||
|
NewEntry->Entry.RelativeId,
|
|||
|
TRUE, // Trusted client
|
|||
|
TRUE, // Account exists
|
|||
|
&UserContext
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SampRetrieveUserV1aFixed(
|
|||
|
UserContext,
|
|||
|
&UserV1aFixed
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
if ( ( UserV1aFixed.UserAccountControl &
|
|||
|
Filter ) == 0 ) {
|
|||
|
|
|||
|
FilteredName = FALSE;
|
|||
|
SampFreeUnicodeString( &SubKeyName );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
SampDeleteContext( UserContext );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
*EnumerationContext += 1;
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) && ( FilteredName ) ) {
|
|||
|
|
|||
|
NamesToReturn += 1;
|
|||
|
|
|||
|
TotalLength = TotalLength + (ULONG)
|
|||
|
NewEntry->Entry.Name.MaximumLength;
|
|||
|
|
|||
|
NewEntry->Next = NULL;
|
|||
|
|
|||
|
if( SampHead == NULL ) {
|
|||
|
|
|||
|
ASSERT( SampTail == NULL );
|
|||
|
|
|||
|
SampHead = SampTail = NewEntry;
|
|||
|
}
|
|||
|
else {
|
|||
|
|
|||
|
//
|
|||
|
// add this new entry to the list end.
|
|||
|
//
|
|||
|
|
|||
|
SampTail->Next = NewEntry;
|
|||
|
SampTail = NewEntry;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Entry was filtered out, or error getting
|
|||
|
// filter information.
|
|||
|
//
|
|||
|
|
|||
|
MIDL_user_free( NewEntry );
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Error looking up the RID
|
|||
|
//
|
|||
|
|
|||
|
MIDL_user_free( NewEntry );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Free up our subkey name
|
|||
|
//
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
SampFreeUnicodeString(&SubKeyName);
|
|||
|
break; // Out of whle loop
|
|||
|
}
|
|||
|
|
|||
|
} // while
|
|||
|
|
|||
|
|
|||
|
|
|||
|
TmpStatus = NtClose( TempHandle );
|
|||
|
ASSERT( NT_SUCCESS(TmpStatus) );
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
SampFreeUnicodeString( &AccountNamesKey );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// If we are returning the last of the names, then change our
|
|||
|
// enumeration context so that it starts at the beginning again.
|
|||
|
//
|
|||
|
|
|||
|
if (!( (NtStatus == STATUS_SUCCESS) && (MoreNames == FALSE))) {
|
|||
|
|
|||
|
NtStatus = STATUS_MORE_ENTRIES;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the number of names being returned
|
|||
|
//
|
|||
|
|
|||
|
(*CountReturned) = NamesToReturn;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Build a return buffer containing an array of the
|
|||
|
// SAM_ENUMERATION_INFORMATIONs pointed to by another
|
|||
|
// buffer containing the number of elements in that
|
|||
|
// array.
|
|||
|
//
|
|||
|
|
|||
|
(*Buffer) = MIDL_user_allocate( sizeof(SAMPR_ENUMERATION_BUFFER) );
|
|||
|
|
|||
|
if ( (*Buffer) == NULL) {
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
} else {
|
|||
|
|
|||
|
(*Buffer)->EntriesRead = (*CountReturned);
|
|||
|
|
|||
|
ArrayBufferLength = sizeof( SAM_RID_ENUMERATION ) *
|
|||
|
(*CountReturned);
|
|||
|
ArrayBuffer = MIDL_user_allocate( ArrayBufferLength );
|
|||
|
(*Buffer)->Buffer = ArrayBuffer;
|
|||
|
|
|||
|
if ( ArrayBuffer == NULL) {
|
|||
|
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
MIDL_user_free( (*Buffer) );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Walk the list of return entries, copying
|
|||
|
// them into the return buffer
|
|||
|
//
|
|||
|
|
|||
|
NextEntry = SampHead;
|
|||
|
i = 0;
|
|||
|
while (NextEntry != NULL) {
|
|||
|
|
|||
|
NewEntry = NextEntry;
|
|||
|
NextEntry = NewEntry->Next;
|
|||
|
|
|||
|
ArrayBuffer[i] = NewEntry->Entry;
|
|||
|
i += 1;
|
|||
|
|
|||
|
MIDL_user_free( NewEntry );
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if ( !NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Free the memory we've allocated
|
|||
|
//
|
|||
|
|
|||
|
NextEntry = SampHead;
|
|||
|
while (NextEntry != NULL) {
|
|||
|
|
|||
|
NewEntry = NextEntry;
|
|||
|
NextEntry = NewEntry->Next;
|
|||
|
|
|||
|
if (NewEntry->Entry.Name.Buffer != NULL ) MIDL_user_free( NewEntry->Entry.Name.Buffer );
|
|||
|
MIDL_user_free( NewEntry );
|
|||
|
}
|
|||
|
|
|||
|
(*EnumerationContext) = 0;
|
|||
|
(*CountReturned) = 0;
|
|||
|
(*Buffer) = NULL;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampLookupAccountRid(
|
|||
|
IN SAMP_OBJECT_TYPE ObjectType,
|
|||
|
IN PUNICODE_STRING Name,
|
|||
|
IN NTSTATUS NotFoundStatus,
|
|||
|
OUT PULONG Rid,
|
|||
|
OUT PSID_NAME_USE Use
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ObjectType - Indicates whether the name is a user, group or unknown
|
|||
|
type of object.
|
|||
|
|
|||
|
Name - The name of the account being looked up.
|
|||
|
|
|||
|
NotFoundStatus - Receives a status value to be returned if no name is
|
|||
|
found.
|
|||
|
|
|||
|
Rid - Receives the relative ID of account with the specified name.
|
|||
|
|
|||
|
Use - Receives an indication of the type of account.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully.
|
|||
|
|
|||
|
(NotFoundStatus) - No name by the specified name and type could be
|
|||
|
found. This value is passed to this routine.
|
|||
|
|
|||
|
Other values that may be returned by:
|
|||
|
|
|||
|
SampBuildAccountKeyName()
|
|||
|
NtOpenKey()
|
|||
|
NtQueryValueKey()
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, TmpStatus;
|
|||
|
UNICODE_STRING KeyName;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
HANDLE TempHandle;
|
|||
|
ULONG KeyValueLength;
|
|||
|
LARGE_INTEGER IgnoreLastWriteTime;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if ( (ObjectType == SampGroupObjectType ) ||
|
|||
|
(ObjectType == SampUnknownObjectType) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Search the groups for a match
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountKeyName(
|
|||
|
SampGroupObjectType,
|
|||
|
&KeyName,
|
|||
|
Name
|
|||
|
);
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&TempHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
(*Use) = SidTypeGroup;
|
|||
|
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
TempHandle,
|
|||
|
Rid,
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
TmpStatus = NtClose( TempHandle );
|
|||
|
ASSERT( NT_SUCCESS(TmpStatus) );
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// No group (or not group type)
|
|||
|
// Try an alias if appropriate
|
|||
|
//
|
|||
|
|
|||
|
if ( (ObjectType == SampAliasObjectType ) ||
|
|||
|
(ObjectType == SampUnknownObjectType) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Search the aliases for a match
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountKeyName(
|
|||
|
SampAliasObjectType,
|
|||
|
&KeyName,
|
|||
|
Name
|
|||
|
);
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&TempHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
(*Use) = SidTypeAlias;
|
|||
|
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
TempHandle,
|
|||
|
Rid,
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
TmpStatus = NtClose( TempHandle );
|
|||
|
ASSERT( NT_SUCCESS(TmpStatus) );
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// No group (or not group type) nor alias (or not alias type)
|
|||
|
// Try a user if appropriate
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
if ( (ObjectType == SampUserObjectType ) ||
|
|||
|
(ObjectType == SampUnknownObjectType) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Search the Users for a match
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountKeyName(
|
|||
|
SampUserObjectType,
|
|||
|
&KeyName,
|
|||
|
Name
|
|||
|
);
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&TempHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
(*Use) = SidTypeUser;
|
|||
|
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
TempHandle,
|
|||
|
Rid,
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreLastWriteTime
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
TmpStatus = NtClose( TempHandle );
|
|||
|
ASSERT( NT_SUCCESS(TmpStatus) );
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
if (NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
|
|||
|
NtStatus = NotFoundStatus;
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampLookupAccountName(
|
|||
|
IN ULONG Rid,
|
|||
|
OUT PUNICODE_STRING Name OPTIONAL,
|
|||
|
OUT PSAMP_OBJECT_TYPE ObjectType
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Looks up the specified rid in the current transaction domain.
|
|||
|
Returns its name and account type.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Rid - The relative ID of account
|
|||
|
|
|||
|
Name - Receives the name of the account if ObjectType != UnknownObjectType
|
|||
|
The name buffer can be freed using MIDL_user_free
|
|||
|
|
|||
|
ObjectType - Receives the type of account this rid represents
|
|||
|
|
|||
|
SampUnknownObjectType - the account doesn't exist
|
|||
|
SampUserObjectType
|
|||
|
SampGroupObjectType
|
|||
|
SampAliasObjectType
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully, object type contains
|
|||
|
the type of object this rid represents.
|
|||
|
|
|||
|
Other values that may be returned by:
|
|||
|
|
|||
|
SampBuildAccountKeyName()
|
|||
|
NtOpenKey()
|
|||
|
NtQueryValueKey()
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
PSAMP_OBJECT AccountContext;
|
|||
|
|
|||
|
//
|
|||
|
// Search the groups for a match
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
SampGroupObjectType,
|
|||
|
Rid,
|
|||
|
TRUE, // Trusted client
|
|||
|
TRUE, // Account exists
|
|||
|
&AccountContext
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
*ObjectType = SampGroupObjectType;
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(Name)) {
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
AccountContext,
|
|||
|
SAMP_GROUP_NAME,
|
|||
|
TRUE, // Make copy
|
|||
|
Name
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
SampDeleteContext(AccountContext);
|
|||
|
|
|||
|
return (NtStatus);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Search the aliases for a match
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
SampAliasObjectType,
|
|||
|
Rid,
|
|||
|
TRUE, // Trusted client
|
|||
|
TRUE, // Account exists
|
|||
|
&AccountContext
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
*ObjectType = SampAliasObjectType;
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(Name)) {
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
AccountContext,
|
|||
|
SAMP_ALIAS_NAME,
|
|||
|
TRUE, // Make copy
|
|||
|
Name
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
SampDeleteContext(AccountContext);
|
|||
|
|
|||
|
return (NtStatus);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Search the users for a match
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
SampUserObjectType,
|
|||
|
Rid,
|
|||
|
TRUE, // Trusted client
|
|||
|
TRUE, // Account exists
|
|||
|
&AccountContext
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
*ObjectType = SampUserObjectType;
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(Name)) {
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
AccountContext,
|
|||
|
SAMP_USER_ACCOUNT_NAME,
|
|||
|
TRUE, // Make copy
|
|||
|
Name
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
SampDeleteContext(AccountContext);
|
|||
|
|
|||
|
return (NtStatus);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// This account doesn't exist
|
|||
|
//
|
|||
|
|
|||
|
*ObjectType = SampUnknownObjectType;
|
|||
|
|
|||
|
return(STATUS_SUCCESS);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampOpenAccount(
|
|||
|
IN SAMP_OBJECT_TYPE ObjectType,
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN ACCESS_MASK DesiredAccess,
|
|||
|
IN ULONG AccountId,
|
|||
|
IN BOOLEAN WriteLockHeld,
|
|||
|
OUT SAMPR_HANDLE *AccountHandle
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API opens an existing user, group or alias account in the account database.
|
|||
|
The account is specified by a ID value that is relative to the SID of the
|
|||
|
domain. The operations that will be performed on the group must be
|
|||
|
declared at this time.
|
|||
|
|
|||
|
This call returns a handle to the newly opened account that may be
|
|||
|
used for successive operations on the account. This handle may be
|
|||
|
closed with the SamCloseHandle API.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
DesiredAccess - Is an access mask indicating which access types
|
|||
|
are desired to the account. These access types are reconciled
|
|||
|
with the Discretionary Access Control list of the account to
|
|||
|
determine whether the accesses will be granted or denied.
|
|||
|
|
|||
|
GroupId - Specifies the relative ID value of the user or group to be
|
|||
|
opened.
|
|||
|
|
|||
|
GroupHandle - Receives a handle referencing the newly opened
|
|||
|
user or group. This handle will be required in successive calls to
|
|||
|
operate on the account.
|
|||
|
|
|||
|
WriteLockHeld - if TRUE, the caller holds SAM's SampLock for WRITE
|
|||
|
access, so this routine does not have to obtain it.
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
STATUS_SUCCESS - The account was successfully opened.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
|||
|
access to complete the operation.
|
|||
|
|
|||
|
STATUS_NO_SUCH_GROUP - The specified group does not exist.
|
|||
|
|
|||
|
STATUS_NO_SUCH_USER - The specified user does not exist.
|
|||
|
|
|||
|
STATUS_NO_SUCH_ALIAS - The specified alias does not exist.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The domain handle passed is invalid.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus;
|
|||
|
NTSTATUS IgnoreStatus;
|
|||
|
PSAMP_OBJECT DomainContext, NewContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Grab a read lock, if a lock isn't already held.
|
|||
|
//
|
|||
|
|
|||
|
if ( !WriteLockHeld ) {
|
|||
|
SampAcquireReadLock();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to domain object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DOMAIN_LOOKUP, // DesiredAccess
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Try to create a context for the account.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
ObjectType,
|
|||
|
AccountId,
|
|||
|
DomainContext->TrustedClient,
|
|||
|
TRUE, // Account exists
|
|||
|
&NewContext
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Reference the object for the validation
|
|||
|
//
|
|||
|
|
|||
|
SampReferenceContext(NewContext);
|
|||
|
|
|||
|
//
|
|||
|
// Validate the caller's access.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampValidateObjectAccess(
|
|||
|
NewContext, //Context
|
|||
|
DesiredAccess, //DesiredAccess
|
|||
|
FALSE //ObjectCreation
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// Dereference object, discarding any changes
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext(NewContext, FALSE);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
//
|
|||
|
// Clean up the new context if we didn't succeed.
|
|||
|
//
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampDeleteContext( NewContext );
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object, discarding changes
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Return the account handle
|
|||
|
//
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
(*AccountHandle) = 0;
|
|||
|
} else {
|
|||
|
(*AccountHandle) = NewContext;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Free the lock, if we obtained it.
|
|||
|
//
|
|||
|
|
|||
|
if ( !WriteLockHeld ) {
|
|||
|
SampReleaseReadLock();
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampCreateAccountContext(
|
|||
|
IN SAMP_OBJECT_TYPE ObjectType,
|
|||
|
IN ULONG AccountId,
|
|||
|
IN BOOLEAN TrustedClient,
|
|||
|
IN BOOLEAN AccountExists,
|
|||
|
OUT PSAMP_OBJECT *AccountContext
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API creates a context for an account object. (User group or alias).
|
|||
|
If the account exists flag is specified, an attempt is made to open
|
|||
|
the object in the database and this api fails if it doesn't exist.
|
|||
|
If AccountExists = FALSE, this routine setups up the context such that
|
|||
|
data can be written into the context and the object will be created
|
|||
|
when they are committed.
|
|||
|
|
|||
|
The account is specified by a ID value that is relative to the SID of the
|
|||
|
current transaction domain.
|
|||
|
|
|||
|
This call returns a context handle for the newly opened account.
|
|||
|
This handle may be closed with the SampDeleteContext API.
|
|||
|
|
|||
|
No access check is performed by this function.
|
|||
|
|
|||
|
Note: THIS ROUTINE REFERENCES THE CURRENT TRANSACTION DOMAIN
|
|||
|
(ESTABLISHED USING SampSetTransactioDomain()). THIS
|
|||
|
SERVICE MAY ONLY BE CALLED AFTER SampSetTransactionDomain()
|
|||
|
AND BEFORE SampReleaseReadLock().
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
ObjectType - the type of object to open
|
|||
|
|
|||
|
AccountId - the id of the account in the current transaction domain
|
|||
|
|
|||
|
TrustedClient - TRUE if client is trusted - i.e. server side process.
|
|||
|
|
|||
|
AccountExists - specifies whether the account already exists.
|
|||
|
|
|||
|
AccountContext - Receives context pointer referencing the newly opened account.
|
|||
|
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
STATUS_SUCCESS - The account was successfully opened.
|
|||
|
|
|||
|
STATUS_NO_SUCH_GROUP - The specified group does not exist.
|
|||
|
|
|||
|
STATUS_NO_SUCH_USER - The specified user does not exist.
|
|||
|
|
|||
|
STATUS_NO_SUCH_ALIAS - The specified alias does not exist.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus, NotFoundStatus;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
PSAMP_OBJECT NewContext;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Establish type-specific information
|
|||
|
//
|
|||
|
|
|||
|
ASSERT( (ObjectType == SampGroupObjectType) ||
|
|||
|
(ObjectType == SampAliasObjectType) ||
|
|||
|
(ObjectType == SampUserObjectType) );
|
|||
|
|
|||
|
switch (ObjectType) {
|
|||
|
case SampGroupObjectType:
|
|||
|
NotFoundStatus = STATUS_NO_SUCH_GROUP;
|
|||
|
break;
|
|||
|
case SampAliasObjectType:
|
|||
|
NotFoundStatus = STATUS_NO_SUCH_ALIAS;
|
|||
|
break;
|
|||
|
case SampUserObjectType:
|
|||
|
NotFoundStatus = STATUS_NO_SUCH_USER;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Try to create a context for the account.
|
|||
|
//
|
|||
|
|
|||
|
NewContext = SampCreateContext(
|
|||
|
ObjectType,
|
|||
|
TrustedClient
|
|||
|
);
|
|||
|
|
|||
|
if (NewContext != NULL) {
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the account's rid
|
|||
|
//
|
|||
|
|
|||
|
switch (ObjectType) {
|
|||
|
case SampGroupObjectType:
|
|||
|
NewContext->TypeBody.Group.Rid = AccountId;
|
|||
|
break;
|
|||
|
case SampAliasObjectType:
|
|||
|
NewContext->TypeBody.Alias.Rid = AccountId;
|
|||
|
break;
|
|||
|
case SampUserObjectType:
|
|||
|
NewContext->TypeBody.User.Rid = AccountId;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Create the specified acocunt's root key name
|
|||
|
// and store it in the context.
|
|||
|
// This name remains around until the context is deleted.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountSubKeyName(
|
|||
|
ObjectType,
|
|||
|
&NewContext->RootName,
|
|||
|
AccountId,
|
|||
|
NULL // Don't give a sub-key name
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// If the account should exist, try and open the root key
|
|||
|
// to the object - fail if it doesn't exist.
|
|||
|
//
|
|||
|
|
|||
|
if (AccountExists) {
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&NewContext->RootName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&NewContext->RootKey,
|
|||
|
(KEY_READ | KEY_WRITE),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if ( !NT_SUCCESS(NtStatus) ) {
|
|||
|
NewContext->RootKey = INVALID_HANDLE_VALUE;
|
|||
|
NtStatus = NotFoundStatus;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
RtlInitUnicodeString(&NewContext->RootName, NULL);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Clean up the account context if we failed
|
|||
|
//
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampDeleteContext( NewContext );
|
|||
|
NewContext = NULL;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Return the context pointer
|
|||
|
//
|
|||
|
|
|||
|
*AccountContext = NewContext;
|
|||
|
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampIsAccountBuiltIn(
|
|||
|
IN ULONG Rid
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine checks to see if a specified account name is a well-known
|
|||
|
(built-in) account. Some restrictions apply to such accounts, such as
|
|||
|
they can not be deleted or renamed.
|
|||
|
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
Rid - The RID of the account.
|
|||
|
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
STATUS_SUCCESS - The account is not a well-known (restricted) account.
|
|||
|
|
|||
|
STATUS_SPECIAL_ACCOUNT - Indicates the account is a restricted
|
|||
|
account. This is an error status, based upon the assumption that
|
|||
|
this service will primarily be utilized to determine if an
|
|||
|
operation may allowed on an account.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
if (Rid < SAMP_RESTRICTED_ACCOUNT_COUNT) {
|
|||
|
|
|||
|
return(STATUS_SPECIAL_ACCOUNT);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
return(STATUS_SUCCESS);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampCreateFullSid(
|
|||
|
IN PSID DomainSid,
|
|||
|
IN ULONG Rid,
|
|||
|
OUT PSID *AccountSid
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function creates a domain account sid given a domain sid and
|
|||
|
the relative id of the account within the domain.
|
|||
|
|
|||
|
The returned Sid may be freed with MIDL_user_free.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
UCHAR AccountSubAuthorityCount;
|
|||
|
ULONG AccountSidLength;
|
|||
|
PULONG RidLocation;
|
|||
|
|
|||
|
//
|
|||
|
// Calculate the size of the new sid
|
|||
|
//
|
|||
|
|
|||
|
AccountSubAuthorityCount = *RtlSubAuthorityCountSid(DomainSid) + (UCHAR)1;
|
|||
|
AccountSidLength = RtlLengthRequiredSid(AccountSubAuthorityCount);
|
|||
|
|
|||
|
//
|
|||
|
// Allocate space for the account sid
|
|||
|
//
|
|||
|
|
|||
|
*AccountSid = MIDL_user_allocate(AccountSidLength);
|
|||
|
|
|||
|
if (*AccountSid == NULL) {
|
|||
|
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Copy the domain sid into the first part of the account sid
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = RtlCopySid(AccountSidLength, *AccountSid, DomainSid);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
//
|
|||
|
// Increment the account sid sub-authority count
|
|||
|
//
|
|||
|
|
|||
|
*RtlSubAuthorityCountSid(*AccountSid) = AccountSubAuthorityCount;
|
|||
|
|
|||
|
//
|
|||
|
// Add the rid as the final sub-authority
|
|||
|
//
|
|||
|
|
|||
|
RidLocation = RtlSubAuthoritySid(*AccountSid, AccountSubAuthorityCount-1);
|
|||
|
*RidLocation = Rid;
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampCreateAccountSid(
|
|||
|
IN PSAMP_OBJECT AccountContext,
|
|||
|
OUT PSID *AccountSid
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function creates the sid for an account object.
|
|||
|
|
|||
|
The returned Sid may be freed with MIDL_user_free.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
PSID DomainSid;
|
|||
|
ULONG AccountRid;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Get the Sid for the domain this object is in
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
DomainSid = SampDefinedDomains[AccountContext->DomainIndex].Sid;
|
|||
|
|
|||
|
//
|
|||
|
// Get the account Rid
|
|||
|
//
|
|||
|
|
|||
|
switch (AccountContext->ObjectType) {
|
|||
|
|
|||
|
case SampGroupObjectType:
|
|||
|
AccountRid = AccountContext->TypeBody.Group.Rid;
|
|||
|
break;
|
|||
|
case SampAliasObjectType:
|
|||
|
AccountRid = AccountContext->TypeBody.Alias.Rid;
|
|||
|
break;
|
|||
|
case SampUserObjectType:
|
|||
|
AccountRid = AccountContext->TypeBody.User.Rid;
|
|||
|
break;
|
|||
|
default:
|
|||
|
ASSERT(FALSE);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Build a full sid from the domain sid and the account rid
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateFullSid(DomainSid, AccountRid, AccountSid);
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
SampNotifyNetlogonOfDelta(
|
|||
|
IN SECURITY_DB_DELTA_TYPE DeltaType,
|
|||
|
IN SECURITY_DB_OBJECT_TYPE ObjectType,
|
|||
|
IN ULONG ObjectRid,
|
|||
|
IN PUNICODE_STRING ObjectName,
|
|||
|
IN DWORD ReplicateImmediately,
|
|||
|
IN PSAM_DELTA_DATA DeltaData OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called after any change is made to the SAM database
|
|||
|
on a PDC. It will pass the parameters, along with the database type
|
|||
|
and ModifiedCount to I_NetNotifyDelta() so that Netlogon will know
|
|||
|
that the database has been changed.
|
|||
|
|
|||
|
This routine MUST be called with SAM's write lock held; however, any
|
|||
|
changes must have already been committed to disk. That is, call
|
|||
|
SampCommitAndRetainWriteLock() first, then this routine, then
|
|||
|
SampReleaseWriteLock().
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DeltaType - Type of modification that has been made on the object.
|
|||
|
|
|||
|
ObjectType - Type of object that has been modified.
|
|||
|
|
|||
|
ObjectRid - The relative ID of the object that has been modified.
|
|||
|
This parameter is valid only when the object type specified is
|
|||
|
either SecurityDbObjectSamUser, SecurityDbObjectSamGroup or
|
|||
|
SecurityDbObjectSamAlias otherwise this parameter is set to
|
|||
|
zero.
|
|||
|
|
|||
|
ObjectName - The old name of the object when the object type specified
|
|||
|
is either SecurityDbObjectSamUser, SecurityDbObjectSamGroup or
|
|||
|
SecurityDbObjectSamAlias and the delta type is SecurityDbRename
|
|||
|
otherwise this parameter is set to zero.
|
|||
|
|
|||
|
ReplicateImmediately - TRUE if the change should be immediately
|
|||
|
replicated to all BDCs. A password change should set the flag
|
|||
|
TRUE.
|
|||
|
|
|||
|
DeltaData - pointer to delta-type specific structure to be passed
|
|||
|
- to netlogon.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
//
|
|||
|
// Only make the call if this is not a backup domain controller.
|
|||
|
//
|
|||
|
|
|||
|
if ( SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ServerRole
|
|||
|
!= DomainServerRoleBackup ) {
|
|||
|
|
|||
|
I_NetNotifyDelta(
|
|||
|
SecurityDbSam,
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount,
|
|||
|
DeltaType,
|
|||
|
ObjectType,
|
|||
|
ObjectRid,
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].Sid,
|
|||
|
ObjectName,
|
|||
|
ReplicateImmediately,
|
|||
|
DeltaData
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// Let any notification packages know about the delta.
|
|||
|
//
|
|||
|
|
|||
|
SampDeltaChangeNotify(
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].Sid,
|
|||
|
DeltaType,
|
|||
|
ObjectType,
|
|||
|
ObjectRid,
|
|||
|
ObjectName,
|
|||
|
&SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount,
|
|||
|
DeltaData
|
|||
|
);
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampSplitSid(
|
|||
|
IN PSID AccountSid,
|
|||
|
IN OUT PSID *DomainSid,
|
|||
|
OUT ULONG *Rid
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function splits a sid into its domain sid and rid. The caller
|
|||
|
can either provide a memory buffer for the returned DomainSid, or
|
|||
|
request that one be allocated. If the caller provides a buffer, the buffer
|
|||
|
is assumed to be of sufficient size. If allocated on the caller's behalf,
|
|||
|
the buffer must be freed when no longer required via MIDL_user_free.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
AccountSid - Specifies the Sid to be split. The Sid is assumed to be
|
|||
|
syntactically valid. Sids with zero subauthorities cannot be split.
|
|||
|
|
|||
|
DomainSid - Pointer to location containing either NULL or a pointer to
|
|||
|
a buffer in which the Domain Sid will be returned. If NULL is
|
|||
|
specified, memory will be allocated on behalf of the caller.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
NTSTATUS - Standard Nt Result Code
|
|||
|
|
|||
|
STATUS_SUCCESS - The call completed successfully.
|
|||
|
|
|||
|
STATUS_INSUFFICIENT_RESOURCES - Insufficient system resources,
|
|||
|
such as memory, to complete the call successfully.
|
|||
|
|
|||
|
STATUS_INVALID_SID - The Sid is has a subauthority count of 0.
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
UCHAR AccountSubAuthorityCount;
|
|||
|
ULONG AccountSidLength;
|
|||
|
|
|||
|
//
|
|||
|
// Calculate the size of the domain sid
|
|||
|
//
|
|||
|
|
|||
|
AccountSubAuthorityCount = *RtlSubAuthorityCountSid(AccountSid);
|
|||
|
|
|||
|
|
|||
|
if (AccountSubAuthorityCount < 1) {
|
|||
|
|
|||
|
NtStatus = STATUS_INVALID_SID;
|
|||
|
goto SplitSidError;
|
|||
|
}
|
|||
|
|
|||
|
AccountSidLength = RtlLengthSid(AccountSid);
|
|||
|
|
|||
|
//
|
|||
|
// If no buffer is required for the Domain Sid, we have to allocate one.
|
|||
|
//
|
|||
|
|
|||
|
if (*DomainSid == NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// Allocate space for the domain sid (allocate the same size as the
|
|||
|
// account sid so we can use RtlCopySid)
|
|||
|
//
|
|||
|
|
|||
|
*DomainSid = MIDL_user_allocate(AccountSidLength);
|
|||
|
|
|||
|
|
|||
|
if (*DomainSid == NULL) {
|
|||
|
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
goto SplitSidError;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Copy the Account sid into the Domain sid
|
|||
|
//
|
|||
|
|
|||
|
RtlMoveMemory(*DomainSid, AccountSid, AccountSidLength);
|
|||
|
|
|||
|
//
|
|||
|
// Decrement the domain sid sub-authority count
|
|||
|
//
|
|||
|
|
|||
|
(*RtlSubAuthorityCountSid(*DomainSid))--;
|
|||
|
|
|||
|
//
|
|||
|
// Copy the rid out of the account sid
|
|||
|
//
|
|||
|
|
|||
|
*Rid = *RtlSubAuthoritySid(AccountSid, AccountSubAuthorityCount-1);
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
|
|||
|
SplitSidFinish:
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
SplitSidError:
|
|||
|
|
|||
|
goto SplitSidFinish;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampDuplicateUnicodeString(
|
|||
|
IN PUNICODE_STRING OutString,
|
|||
|
IN PUNICODE_STRING InString
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine allocates memory for a new OutString and copies the
|
|||
|
InString string to it.
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
OutString - A pointer to a destination unicode string
|
|||
|
|
|||
|
InString - A pointer to an unicode string to be copied
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
ASSERT( OutString != NULL );
|
|||
|
ASSERT( InString != NULL );
|
|||
|
|
|||
|
if ( InString->Length > 0 ) {
|
|||
|
|
|||
|
OutString->Buffer = MIDL_user_allocate( InString->Length );
|
|||
|
|
|||
|
if (OutString->Buffer == NULL) {
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
|
|||
|
OutString->MaximumLength = InString->Length;
|
|||
|
|
|||
|
RtlCopyUnicodeString(OutString, InString);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
RtlInitUnicodeString(OutString, NULL);
|
|||
|
}
|
|||
|
|
|||
|
return(STATUS_SUCCESS);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampUnicodeToOemString(
|
|||
|
IN POEM_STRING OutString,
|
|||
|
IN PUNICODE_STRING InString
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine allocates memory for a new OutString and copies the
|
|||
|
InString string to it, converting to OEM string in the process.
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
OutString - A pointer to a destination OEM string.
|
|||
|
|
|||
|
InString - A pointer to a unicode string to be copied
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
ULONG
|
|||
|
OemLength,
|
|||
|
Index;
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
NtStatus;
|
|||
|
|
|||
|
ASSERT( OutString != NULL );
|
|||
|
ASSERT( InString != NULL );
|
|||
|
|
|||
|
if ( InString->Length > 0 ) {
|
|||
|
|
|||
|
OemLength = RtlUnicodeStringToOemSize(InString);
|
|||
|
if ( OemLength > MAXUSHORT ) {
|
|||
|
return STATUS_INVALID_PARAMETER_2;
|
|||
|
}
|
|||
|
|
|||
|
OutString->Length = (USHORT)(OemLength - 1);
|
|||
|
OutString->MaximumLength = (USHORT)OemLength;
|
|||
|
OutString->Buffer = MIDL_user_allocate(OemLength);
|
|||
|
if ( !OutString->Buffer ) {
|
|||
|
return STATUS_NO_MEMORY;
|
|||
|
}
|
|||
|
|
|||
|
NtStatus = RtlUnicodeToOemN(
|
|||
|
OutString->Buffer,
|
|||
|
OutString->Length,
|
|||
|
&Index,
|
|||
|
InString->Buffer,
|
|||
|
InString->Length
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
MIDL_user_free(OutString->Buffer);
|
|||
|
return NtStatus;
|
|||
|
}
|
|||
|
|
|||
|
OutString->Buffer[Index] = '\0';
|
|||
|
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
RtlInitString(OutString, NULL);
|
|||
|
}
|
|||
|
|
|||
|
return(STATUS_SUCCESS);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampChangeAccountOperatorAccessToMember(
|
|||
|
IN PRPC_SID MemberSid,
|
|||
|
IN SAMP_MEMBERSHIP_DELTA ChangingToAdmin,
|
|||
|
IN SAMP_MEMBERSHIP_DELTA ChangingToOperator
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called when a member is added to or removed from an
|
|||
|
ADMIN alias. If the member is from the BUILTIN or ACCOUNT domain,
|
|||
|
it will change the ACL(s) of the member to allow or disallow access
|
|||
|
by account operators if necessary.
|
|||
|
|
|||
|
This must be called BEFORE the member is actually added to the
|
|||
|
alias, and AFTER the member is actually removed from the alias to
|
|||
|
avoid security holes in the event that we are unable to complete the
|
|||
|
entire task.
|
|||
|
|
|||
|
When this routine is called, the transaction domain is alredy set
|
|||
|
to that of the alias. Note, however, that the member might be in a
|
|||
|
different domain, so the transaction domain may be adjusted in this
|
|||
|
routine.
|
|||
|
|
|||
|
THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
MemberSid - The full ID of the member being added to/ deleted from
|
|||
|
an ADMIN alias.
|
|||
|
|
|||
|
ChangingToAdmin - AddToAdmin if Member is being added to an ADMIN alias,
|
|||
|
RemoveFromAdmin if it's being removed.
|
|||
|
|
|||
|
ChangingToOperator - AddToAdmin if Member is being added to an OPERATOR
|
|||
|
alias, RemoveFromAdmin if it's being removed.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - either the ACL(s) was modified, or it didn't need
|
|||
|
to be.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
SAMP_V1_0A_FIXED_LENGTH_GROUP GroupV1Fixed;
|
|||
|
PSID MemberDomainSid = NULL;
|
|||
|
PULONG UsersInGroup = NULL;
|
|||
|
NTSTATUS NtStatus;
|
|||
|
ULONG MemberRid;
|
|||
|
ULONG OldTransactionDomainIndex = SampDefinedDomainsCount;
|
|||
|
ULONG NumberOfUsersInGroup;
|
|||
|
ULONG i;
|
|||
|
ULONG MemberDomainIndex;
|
|||
|
SAMP_OBJECT_TYPE MemberType;
|
|||
|
PSECURITY_DESCRIPTOR SecurityDescriptor;
|
|||
|
PSECURITY_DESCRIPTOR OldDescriptor;
|
|||
|
ULONG SecurityDescriptorLength;
|
|||
|
ULONG Revision;
|
|||
|
|
|||
|
ASSERT( SampTransactionWithinDomain );
|
|||
|
|
|||
|
//
|
|||
|
// See if the SID is from one of the local domains (BUILTIN or ACCOUNT).
|
|||
|
// If it's not, we don't have to worry about modifying ACLs.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampSplitSid( MemberSid, &MemberDomainSid, &MemberRid );
|
|||
|
|
|||
|
if ( !NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
for ( MemberDomainIndex = 0;
|
|||
|
MemberDomainIndex < SampDefinedDomainsCount;
|
|||
|
MemberDomainIndex++ ) {
|
|||
|
|
|||
|
if ( RtlEqualSid(
|
|||
|
MemberDomainSid,
|
|||
|
SampDefinedDomains[MemberDomainIndex].Sid ) ) {
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( MemberDomainIndex < SampDefinedDomainsCount ) {
|
|||
|
|
|||
|
//
|
|||
|
// The member is from one of the local domains. MemberDomainIndex
|
|||
|
// indexes that domain. First, check to see if the alias and member
|
|||
|
// are in the same domain.
|
|||
|
//
|
|||
|
|
|||
|
if ( MemberDomainIndex != SampTransactionDomainIndex ) {
|
|||
|
|
|||
|
//
|
|||
|
// The transaction domain is set to that of the alias, but
|
|||
|
// we need to set it to that of the member while we modify
|
|||
|
// the member.
|
|||
|
//
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
OldTransactionDomainIndex = SampTransactionDomainIndex;
|
|||
|
|
|||
|
SampSetTransactionDomain( MemberDomainIndex );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now we need to change the member ACL(s), IF the member is being
|
|||
|
// added to an admin alias for the first time. Find out whether
|
|||
|
// the member is a user or a group, and attack accordingly.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampLookupAccountName(
|
|||
|
MemberRid,
|
|||
|
NULL,
|
|||
|
&MemberType
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
switch (MemberType) {
|
|||
|
|
|||
|
case SampUserObjectType: {
|
|||
|
|
|||
|
NtStatus = SampChangeOperatorAccessToUser(
|
|||
|
MemberRid,
|
|||
|
ChangingToAdmin,
|
|||
|
ChangingToOperator
|
|||
|
);
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case SampGroupObjectType: {
|
|||
|
|
|||
|
PSAMP_OBJECT GroupContext;
|
|||
|
|
|||
|
//
|
|||
|
// Change ACL for every user in this group.
|
|||
|
// First get group member list.
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Try to create a context for the account.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
SampGroupObjectType,
|
|||
|
MemberRid,
|
|||
|
TRUE, // Trusted client
|
|||
|
TRUE, // Account exists
|
|||
|
&GroupContext
|
|||
|
);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Now set a flag in the group itself,
|
|||
|
// so that when users are added and removed
|
|||
|
// in the future it is known whether this
|
|||
|
// group is in an ADMIN alias or not.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampRetrieveGroupV1Fixed(
|
|||
|
GroupContext,
|
|||
|
&GroupV1Fixed
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
ULONG OldAdminStatus = 0;
|
|||
|
ULONG NewAdminStatus;
|
|||
|
SAMP_MEMBERSHIP_DELTA AdminChange = NoChange;
|
|||
|
SAMP_MEMBERSHIP_DELTA OperatorChange = NoChange;
|
|||
|
|
|||
|
if (GroupV1Fixed.AdminCount != 0 ) {
|
|||
|
OldAdminStatus++;
|
|||
|
}
|
|||
|
if (GroupV1Fixed.OperatorCount != 0) {
|
|||
|
OldAdminStatus++;
|
|||
|
}
|
|||
|
NewAdminStatus = OldAdminStatus;
|
|||
|
|
|||
|
//
|
|||
|
// Update the admin count. If we added one and the
|
|||
|
// count is now 1, then the group became administrative.
|
|||
|
// If we subtracted one and the count is zero,
|
|||
|
// then the group lost its administrive membership.
|
|||
|
//
|
|||
|
|
|||
|
if (ChangingToAdmin == AddToAdmin) {
|
|||
|
if (++GroupV1Fixed.AdminCount == 1) {
|
|||
|
NewAdminStatus++;
|
|||
|
AdminChange = AddToAdmin;
|
|||
|
}
|
|||
|
} else if (ChangingToAdmin == RemoveFromAdmin) {
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// For removing an admin count, we need to make
|
|||
|
// sure there is at least one. In the upgrade
|
|||
|
// case there may not be, since prior versions
|
|||
|
// of NT only had a boolean.
|
|||
|
//
|
|||
|
if (GroupV1Fixed.AdminCount > 0) {
|
|||
|
if (--GroupV1Fixed.AdminCount == 0) {
|
|||
|
NewAdminStatus --;
|
|||
|
AdminChange = RemoveFromAdmin;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Update the operator count
|
|||
|
//
|
|||
|
|
|||
|
if (ChangingToOperator == AddToAdmin) {
|
|||
|
if (++GroupV1Fixed.OperatorCount == 1) {
|
|||
|
NewAdminStatus++;
|
|||
|
OperatorChange = AddToAdmin;
|
|||
|
}
|
|||
|
} else if (ChangingToOperator == RemoveFromAdmin) {
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// For removing an Operator count, we need to make
|
|||
|
// sure there is at least one. In the upgrade
|
|||
|
// case there may not be, since prior versions
|
|||
|
// of NT only had a boolean.
|
|||
|
//
|
|||
|
if (GroupV1Fixed.OperatorCount > 0) {
|
|||
|
if (--GroupV1Fixed.OperatorCount == 0) {
|
|||
|
NewAdminStatus --;
|
|||
|
OperatorChange = RemoveFromAdmin;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampReplaceGroupV1Fixed(
|
|||
|
GroupContext,
|
|||
|
&GroupV1Fixed
|
|||
|
);
|
|||
|
//
|
|||
|
// If the status of the group changed,
|
|||
|
// modify the security descriptor to
|
|||
|
// prevent account operators from adding
|
|||
|
// anybody to this group
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) &&
|
|||
|
((NewAdminStatus != 0) != (OldAdminStatus != 0)) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Get the old security descriptor so we can
|
|||
|
// modify it.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampGetAccessAttribute(
|
|||
|
GroupContext,
|
|||
|
SAMP_GROUP_SECURITY_DESCRIPTOR,
|
|||
|
FALSE, // don't make copy
|
|||
|
&Revision,
|
|||
|
&OldDescriptor
|
|||
|
);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampModifyAccountSecurity(
|
|||
|
SampGroupObjectType,
|
|||
|
(BOOLEAN) ((NewAdminStatus != 0) ? TRUE : FALSE),
|
|||
|
OldDescriptor,
|
|||
|
&SecurityDescriptor,
|
|||
|
&SecurityDescriptorLength
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Write the new security descriptor into the object
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampSetAccessAttribute(
|
|||
|
GroupContext,
|
|||
|
SAMP_GROUP_SECURITY_DESCRIPTOR,
|
|||
|
SecurityDescriptor,
|
|||
|
SecurityDescriptorLength
|
|||
|
);
|
|||
|
|
|||
|
RtlDeleteSecurityObject( &SecurityDescriptor );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Update all the members of this group so that
|
|||
|
// their security descriptors are changed.
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) &&
|
|||
|
( (AdminChange != NoChange) ||
|
|||
|
(OperatorChange != NoChange) ) ) {
|
|||
|
|
|||
|
NtStatus = SampRetrieveGroupMembers(
|
|||
|
GroupContext,
|
|||
|
&NumberOfUsersInGroup,
|
|||
|
&UsersInGroup
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
for ( i = 0; i < NumberOfUsersInGroup; i++ ) {
|
|||
|
|
|||
|
NtStatus = SampChangeOperatorAccessToUser(
|
|||
|
UsersInGroup[i],
|
|||
|
AdminChange,
|
|||
|
OperatorChange
|
|||
|
);
|
|||
|
|
|||
|
if ( !( NT_SUCCESS( NtStatus ) ) ) {
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
MIDL_user_free( UsersInGroup );
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Add the modified group to the current transaction
|
|||
|
// Don't use the open key handle since we'll be deleting the context.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampStoreObjectAttributes(GroupContext, FALSE);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Clean up the group context
|
|||
|
//
|
|||
|
|
|||
|
SampDeleteContext(GroupContext);
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
default: {
|
|||
|
|
|||
|
//
|
|||
|
// A bad RID from a domain other than the domain
|
|||
|
// current at the time of the call could slip through
|
|||
|
// to this point. Return error.
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// If the account is in a different domain than the alias,
|
|||
|
// don't report an error if we're removing the member and
|
|||
|
// the member no longer exists.
|
|||
|
//
|
|||
|
// Possibly caused by deleting the object before deleting
|
|||
|
// the membership in the alias.
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// Now that this function is called during upgrade, we
|
|||
|
// can't fail if the account no longer exists. It is
|
|||
|
// not really so bad to add a non-existant member to
|
|||
|
// and alias so return success.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( OldTransactionDomainIndex != SampDefinedDomainsCount ) {
|
|||
|
|
|||
|
//
|
|||
|
// The transaction domain should be set to that of the alias, but
|
|||
|
// we switched it above to that of the member while we modified
|
|||
|
// the member. Now we need to switch it back.
|
|||
|
//
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
SampSetTransactionDomain( OldTransactionDomainIndex );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
MIDL_user_free( MemberDomainSid );
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampChangeOperatorAccessToUser(
|
|||
|
IN ULONG UserRid,
|
|||
|
IN SAMP_MEMBERSHIP_DELTA ChangingToAdmin,
|
|||
|
IN SAMP_MEMBERSHIP_DELTA ChangingToOperator
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine adjusts the user's AdminCount field as appropriate, and
|
|||
|
if the user is being removed from it's last ADMIN alias or added to
|
|||
|
its first ADMIN alias, the ACL is adjusted to allow/disallow access
|
|||
|
by account operators as appropriate.
|
|||
|
|
|||
|
This routine will also increment or decrement the domain's admin count,
|
|||
|
if this operation changes that.
|
|||
|
|
|||
|
NOTE:
|
|||
|
This routine is similar to SampChangeOperatorAccessToUser2().
|
|||
|
This routine should be used in cases where a user context does NOT
|
|||
|
already exist (and won't later on). You must be careful not to
|
|||
|
create two contexts, since they will be independently applied back
|
|||
|
to the registry, and the last one there will win.
|
|||
|
|
|||
|
THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
UserRid - The transaction-domain-relative ID of the user that is
|
|||
|
being added to or removed from an ADMIN alias.
|
|||
|
|
|||
|
ChangingToAdmin - AddToAdmin if Member is being added to an ADMIN alias,
|
|||
|
RemoveFromAdmin if it's being removed.
|
|||
|
|
|||
|
ChangingToOperator - AddToAdmin if Member is being added to an OPERATOR
|
|||
|
alias, RemoveFromAdmin if it's being removed.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - either the ACL was modified, or it didn't need
|
|||
|
to be.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
SAMP_V1_0A_FIXED_LENGTH_USER UserV1aFixed;
|
|||
|
NTSTATUS NtStatus;
|
|||
|
PSAMP_OBJECT UserContext;
|
|||
|
PSECURITY_DESCRIPTOR SecurityDescriptor;
|
|||
|
ULONG SecurityDescriptorLength;
|
|||
|
|
|||
|
//
|
|||
|
// Get the user's fixed data, and adjust the AdminCount.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
SampUserObjectType,
|
|||
|
UserRid,
|
|||
|
TRUE, // Trusted client
|
|||
|
TRUE, // Account exists
|
|||
|
&UserContext
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SampRetrieveUserV1aFixed(
|
|||
|
UserContext,
|
|||
|
&UserV1aFixed
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SampChangeOperatorAccessToUser2(
|
|||
|
UserContext,
|
|||
|
&UserV1aFixed,
|
|||
|
ChangingToAdmin,
|
|||
|
ChangingToOperator
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// If we've succeeded (at changing the admin count, and
|
|||
|
// the ACL if necessary) then write out the new admin
|
|||
|
// count.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampReplaceUserV1aFixed(
|
|||
|
UserContext,
|
|||
|
&UserV1aFixed
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Add the modified user context to the current transaction
|
|||
|
// Don't use the open key handle since we'll be deleting the context.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampStoreObjectAttributes(UserContext, FALSE);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Clean up account context
|
|||
|
//
|
|||
|
|
|||
|
SampDeleteContext(UserContext);
|
|||
|
}
|
|||
|
|
|||
|
if ( ( !NT_SUCCESS( NtStatus ) ) &&
|
|||
|
(( ChangingToAdmin == RemoveFromAdmin ) ||
|
|||
|
( ChangingToOperator == RemoveFromAdmin )) &&
|
|||
|
( NtStatus != STATUS_SPECIAL_ACCOUNT ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// When an account is *removed* from admin groups, we can
|
|||
|
// ignore errors from this routine. This routine is just
|
|||
|
// making the account accessible to account operators, but
|
|||
|
// it's no big deal if that doesn't work. The administrator
|
|||
|
// can still get at it, so we should proceed with the calling
|
|||
|
// operation.
|
|||
|
//
|
|||
|
// Obviously, we can't ignore errors if we're being added
|
|||
|
// to an admin group, because that could be a security hole.
|
|||
|
//
|
|||
|
// Also, we want to make sure that the Administrator is
|
|||
|
// never removed, so we DO propogate STATUS_SPECIAL_ACCOUNT.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampChangeOperatorAccessToUser2(
|
|||
|
IN PSAMP_OBJECT UserContext,
|
|||
|
IN PSAMP_V1_0A_FIXED_LENGTH_USER V1aFixed,
|
|||
|
IN SAMP_MEMBERSHIP_DELTA AddingToAdmin,
|
|||
|
IN SAMP_MEMBERSHIP_DELTA AddingToOperator
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine adjusts the user's AdminCount field as appropriate, and
|
|||
|
if the user is being removed from it's last ADMIN alias or added to
|
|||
|
its first ADMIN alias, the ACL is adjusted to allow/disallow access
|
|||
|
by account operators as appropriate.
|
|||
|
|
|||
|
This routine will also increment or decrement the domain's admin count,
|
|||
|
if this operation changes that.
|
|||
|
|
|||
|
NOTE:
|
|||
|
This routine is similar to SampAccountOperatorAccessToUser().
|
|||
|
This routine should be used in cases where a user account context
|
|||
|
already exists. You must be careful not to create two contexts,
|
|||
|
since they will be independently applied back to the registry, and
|
|||
|
the last one there will win.
|
|||
|
|
|||
|
THIS SERVICE MUST BE CALLED WITH THE SampLock HELD FOR WRITE ACCESS.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
UserContext - Context of user whose access is to be updated.
|
|||
|
|
|||
|
V1aFixed - Pointer to the V1aFixed length data for the user.
|
|||
|
The caller of this routine must ensure that this value is
|
|||
|
stored back out to disk on successful completion of this
|
|||
|
routine.
|
|||
|
|
|||
|
AddingToAdmin - AddToAdmin if Member is being added to an ADMIN alias,
|
|||
|
RemoveFromAdmin if it's being removed.
|
|||
|
|
|||
|
AddingToOperator - AddToAdmin if Member is being added to an OPERATOR
|
|||
|
alias, RemoveFromAdmin if it's being removed.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - either the ACL(s) was modified, or it didn't need
|
|||
|
to be.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
PSECURITY_DESCRIPTOR OldDescriptor;
|
|||
|
PSECURITY_DESCRIPTOR SecurityDescriptor;
|
|||
|
ULONG SecurityDescriptorLength;
|
|||
|
ULONG OldAdminStatus = 0, NewAdminStatus = 0;
|
|||
|
ULONG Revision;
|
|||
|
|
|||
|
//
|
|||
|
// Compute whether we are an admin now. From that we will figure
|
|||
|
// out how many times we were may not an admin to tell if we need
|
|||
|
// to update the security descriptor.
|
|||
|
//
|
|||
|
|
|||
|
if (V1aFixed->AdminCount != 0) {
|
|||
|
OldAdminStatus++;
|
|||
|
}
|
|||
|
if (V1aFixed->OperatorCount != 0) {
|
|||
|
OldAdminStatus++;
|
|||
|
}
|
|||
|
|
|||
|
NewAdminStatus = OldAdminStatus;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if ( AddingToAdmin == AddToAdmin ) {
|
|||
|
|
|||
|
V1aFixed->AdminCount++;
|
|||
|
NewAdminStatus++;
|
|||
|
SampDiagPrint( DISPLAY_ADMIN_CHANGES,
|
|||
|
("SAM DIAG: Incrementing admin count for user %d\n"
|
|||
|
" New admin count: %d\n",
|
|||
|
V1aFixed->UserId, V1aFixed->AdminCount ) );
|
|||
|
} else if (AddingToAdmin == RemoveFromAdmin) {
|
|||
|
|
|||
|
V1aFixed->AdminCount--;
|
|||
|
|
|||
|
if (V1aFixed->AdminCount == 0) {
|
|||
|
NewAdminStatus--;
|
|||
|
}
|
|||
|
|
|||
|
SampDiagPrint( DISPLAY_ADMIN_CHANGES,
|
|||
|
("SAM DIAG: Decrementing admin count for user %d\n"
|
|||
|
" New admin count: %d\n",
|
|||
|
V1aFixed->UserId, V1aFixed->AdminCount ) );
|
|||
|
|
|||
|
if ( V1aFixed->AdminCount == 0 ) {
|
|||
|
|
|||
|
//
|
|||
|
// Don't allow the Administrator account to lose
|
|||
|
// administrative power.
|
|||
|
//
|
|||
|
|
|||
|
if ( V1aFixed->UserId == DOMAIN_USER_RID_ADMIN ) {
|
|||
|
|
|||
|
NtStatus = STATUS_SPECIAL_ACCOUNT;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if ( AddingToOperator == AddToAdmin ) {
|
|||
|
|
|||
|
V1aFixed->OperatorCount++;
|
|||
|
NewAdminStatus++;
|
|||
|
SampDiagPrint( DISPLAY_ADMIN_CHANGES,
|
|||
|
("SAM DIAG: Incrementing operator count for user %d\n"
|
|||
|
" New admin count: %d\n",
|
|||
|
V1aFixed->UserId, V1aFixed->OperatorCount ) );
|
|||
|
|
|||
|
} else if (AddingToOperator == RemoveFromAdmin) {
|
|||
|
|
|||
|
//
|
|||
|
// Only decrement if the count is > 0, since in the upgrade case
|
|||
|
// this field we start out zero.
|
|||
|
//
|
|||
|
|
|||
|
if (V1aFixed->OperatorCount > 0) {
|
|||
|
V1aFixed->OperatorCount--;
|
|||
|
|
|||
|
if (V1aFixed->OperatorCount == 0) {
|
|||
|
NewAdminStatus--;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
SampDiagPrint( DISPLAY_ADMIN_CHANGES,
|
|||
|
("SAM DIAG: Decrementing operator count for user %d\n"
|
|||
|
" New admin count: %d\n",
|
|||
|
V1aFixed->UserId, V1aFixed->OperatorCount ) );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
if ( ( NewAdminStatus != 0 ) != ( OldAdminStatus != 0 ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// User's admin status is changing. We must change the
|
|||
|
// ACL.
|
|||
|
//
|
|||
|
|
|||
|
#ifdef SAMP_DIAGNOSTICS
|
|||
|
if (AddingToAdmin) {
|
|||
|
SampDiagPrint( DISPLAY_ADMIN_CHANGES,
|
|||
|
("SAM DIAG: Protecting user %d as ADMIN account\n",
|
|||
|
V1aFixed->UserId ) );
|
|||
|
} else {
|
|||
|
SampDiagPrint( DISPLAY_ADMIN_CHANGES,
|
|||
|
("SAM DIAG: Protecting user %d as non-admin account\n",
|
|||
|
V1aFixed->UserId ) );
|
|||
|
}
|
|||
|
#endif // SAMP_DIAGNOSTICS
|
|||
|
|
|||
|
//
|
|||
|
// Get the old security descriptor so we can
|
|||
|
// modify it.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampGetAccessAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_SECURITY_DESCRIPTOR,
|
|||
|
FALSE, // don't make copy
|
|||
|
&Revision,
|
|||
|
&OldDescriptor
|
|||
|
);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampModifyAccountSecurity(
|
|||
|
SampUserObjectType,
|
|||
|
(BOOLEAN) ((NewAdminStatus != 0) ? TRUE : FALSE),
|
|||
|
OldDescriptor,
|
|||
|
&SecurityDescriptor,
|
|||
|
&SecurityDescriptorLength
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Write the new security descriptor into the object
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampSetAccessAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_SECURITY_DESCRIPTOR,
|
|||
|
SecurityDescriptor,
|
|||
|
SecurityDescriptorLength
|
|||
|
);
|
|||
|
|
|||
|
RtlDeleteSecurityObject( &SecurityDescriptor );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Save the fixed-length attributes
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampReplaceUserV1aFixed(
|
|||
|
UserContext,
|
|||
|
V1aFixed
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if ( ( !NT_SUCCESS( NtStatus ) ) &&
|
|||
|
( AddingToAdmin != AddToAdmin ) &&
|
|||
|
( NtStatus != STATUS_SPECIAL_ACCOUNT ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// When an account is *removed* from admin groups, we can
|
|||
|
// ignore errors from this routine. This routine is just
|
|||
|
// making the account accessible to account operators, but
|
|||
|
// it's no big deal if that doesn't work. The administrator
|
|||
|
// can still get at it, so we should proceed with the calling
|
|||
|
// operation.
|
|||
|
//
|
|||
|
// Obviously, we can't ignore errors if we're being added
|
|||
|
// to an admin group, because that could be a security hole.
|
|||
|
//
|
|||
|
// Also, we want to make sure that the Administrator is
|
|||
|
// never removed, so we DO propogate STATUS_SPECIAL_ACCOUNT.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// Services Private to this process //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamINotifyDelta (
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN SECURITY_DB_DELTA_TYPE DeltaType,
|
|||
|
IN SECURITY_DB_OBJECT_TYPE ObjectType,
|
|||
|
IN ULONG ObjectRid,
|
|||
|
IN PUNICODE_STRING ObjectName,
|
|||
|
IN DWORD ReplicateImmediately,
|
|||
|
IN PSAM_DELTA_DATA DeltaData OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Performs a change to some 'virtual' data in a domain. This is used by
|
|||
|
netlogon to get the domain modification count updated for cases where
|
|||
|
fields stored in the database replicated to a down-level machine have
|
|||
|
changed. These fields don't exist in the NT SAM database but netlogon
|
|||
|
needs to keep the SAM database and the down-level database modification
|
|||
|
counts in sync.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainHandle - The handle of an opened domain to operate on.
|
|||
|
|
|||
|
All other parameters match those in I_NetNotifyDelta.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
STATUS_SUCCESS - Domain modification count updated successfully.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DOMAIN_ALL_ACCESS, // Trusted client should succeed
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Dump the context - don't save the non-existent changes
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Commit changes, if successful, and notify Netlogon of changes.
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
//
|
|||
|
// This will increment domain count and write it out
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCommitAndRetainWriteLock();
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
SampNotifyNetlogonOfDelta(
|
|||
|
DeltaType,
|
|||
|
ObjectType,
|
|||
|
ObjectRid,
|
|||
|
ObjectName,
|
|||
|
ReplicateImmediately,
|
|||
|
DeltaData
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamISetAuditingInformation(
|
|||
|
IN PPOLICY_AUDIT_EVENTS_INFO PolicyAuditEventsInfo
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function sets Policy Audit Event Info relevant to SAM Auditing
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
PolicyAuditEventsInfo - Pointer to structure containing the
|
|||
|
current Audit Events Information. SAM extracts values of
|
|||
|
relevance.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
NTSTATUS - Standard Nt Result Code
|
|||
|
|
|||
|
STATUS_SUCCESSFUL - The call completed successfully.
|
|||
|
|
|||
|
STATUS_UNSUCCESSFUL - The call was not successful because the
|
|||
|
SAM lock was not acquired.
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
|
|||
|
//
|
|||
|
// Acquire the SAM Database Write Lock.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Set boolean if Auditing is on for Account Management
|
|||
|
//
|
|||
|
|
|||
|
SampSetAuditingInformation( PolicyAuditEventsInfo );
|
|||
|
|
|||
|
//
|
|||
|
// Release the SAM Database Write Lock. No need to commit
|
|||
|
// the database transaction as there are no entries in the
|
|||
|
// transaction log.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampReleaseWriteLock( FALSE );
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampRtlConvertUlongToUnicodeString(
|
|||
|
IN ULONG Value,
|
|||
|
IN ULONG Base OPTIONAL,
|
|||
|
IN ULONG DigitCount,
|
|||
|
IN BOOLEAN AllocateDestinationString,
|
|||
|
OUT PUNICODE_STRING UnicodeString
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function converts an unsigned long integer a Unicode String.
|
|||
|
The string contains leading zeros and is Unicode-NULL terminated.
|
|||
|
Memory for the output buffer can optionally be allocated by the routine.
|
|||
|
|
|||
|
NOTE: This routine may be eligible for inclusion in the Rtl library
|
|||
|
(possibly after modification). It is modeled on
|
|||
|
RtlIntegerToUnicodeString
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Value - The unsigned long value to be converted.
|
|||
|
|
|||
|
Base - Specifies the radix that the converted string is to be
|
|||
|
converted to.
|
|||
|
|
|||
|
DigitCount - Specifies the number of digits, including leading zeros
|
|||
|
required for the result.
|
|||
|
|
|||
|
AllocateDestinationString - Specifies whether memory of the string
|
|||
|
buffer is to be allocated by this routine. If TRUE is specified,
|
|||
|
memory will be allocated via MIDL_user_allocate(). When this memory
|
|||
|
is no longer required, it must be freed via MIDL_user_free. If
|
|||
|
FALSE is specified, the string will be appended to the output
|
|||
|
at the point marked by the Length field onwards.
|
|||
|
|
|||
|
UnicodeString - Pointer to UNICODE_STRING structure which will receive
|
|||
|
the output string. The Length field will be set equal to the
|
|||
|
number of bytes occupied by the string (excluding NULL terminator).
|
|||
|
If memory for the destination string is being allocated by
|
|||
|
the routine, the MaximumLength field will be set equal to the
|
|||
|
length of the string in bytes including the null terminator.
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
NTSTATUS - Standard Nt Result Code.
|
|||
|
|
|||
|
STATUS_SUCCESS - The call completed successfully.
|
|||
|
|
|||
|
STATUS_NO_MEMORY - Insufficient memory for the output string buffer.
|
|||
|
|
|||
|
STATUS_BUFFER_OVERFLOW - Buffer supplied is too small to contain the
|
|||
|
output null-terminated string.
|
|||
|
|
|||
|
STATUS_INVALID_PARAMETER_MIX - One or more parameters are
|
|||
|
invalid in combination.
|
|||
|
|
|||
|
- The specified Relative Id is too large to fit when converted
|
|||
|
into an integer with DigitCount digits.
|
|||
|
|
|||
|
STATUS_INVALID_PARAMETER - One or more parameters are invalid.
|
|||
|
|
|||
|
- DigitCount specifies too large a number of digits.
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
UNICODE_STRING TempStringU, NumericStringU, OutputUnicodeStringU;
|
|||
|
USHORT OutputLengthAvailable, OutputLengthRequired, LeadingZerosLength;
|
|||
|
|
|||
|
OutputUnicodeStringU = *UnicodeString;
|
|||
|
|
|||
|
TempStringU.Buffer = NULL;
|
|||
|
|
|||
|
if (AllocateDestinationString) {
|
|||
|
|
|||
|
OutputUnicodeStringU.Buffer = NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Verify that the maximum number of digits rquested has not been
|
|||
|
// exceeded.
|
|||
|
//
|
|||
|
|
|||
|
if (DigitCount > SAMP_MAXIMUM_ACCOUNT_RID_DIGITS) {
|
|||
|
|
|||
|
goto ConvertUlongToUnicodeStringError;
|
|||
|
}
|
|||
|
|
|||
|
OutputLengthRequired = (USHORT)((DigitCount + 1) * sizeof(WCHAR));
|
|||
|
|
|||
|
//
|
|||
|
// Allocate the Destination String Buffer if requested
|
|||
|
//
|
|||
|
|
|||
|
if (AllocateDestinationString) {
|
|||
|
|
|||
|
NtStatus = STATUS_NO_MEMORY;
|
|||
|
OutputUnicodeStringU.MaximumLength = OutputLengthRequired;
|
|||
|
OutputUnicodeStringU.Length = (USHORT) 0;
|
|||
|
|
|||
|
OutputUnicodeStringU.Buffer = MIDL_user_allocate(
|
|||
|
OutputUnicodeStringU.MaximumLength
|
|||
|
);
|
|||
|
|
|||
|
if (OutputUnicodeStringU.Buffer == NULL) {
|
|||
|
|
|||
|
goto ConvertUlongToUnicodeStringError;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Compute the length available in the output string and compare it with
|
|||
|
// the length required.
|
|||
|
//
|
|||
|
|
|||
|
OutputLengthAvailable = OutputUnicodeStringU.MaximumLength -
|
|||
|
OutputUnicodeStringU.Length;
|
|||
|
|
|||
|
|
|||
|
NtStatus = STATUS_BUFFER_OVERFLOW;
|
|||
|
|
|||
|
if (OutputLengthRequired > OutputLengthAvailable) {
|
|||
|
|
|||
|
goto ConvertUlongToUnicodeStringError;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Create a Unicode String with capacity equal to the required
|
|||
|
// converted Rid Length
|
|||
|
//
|
|||
|
|
|||
|
TempStringU.MaximumLength = OutputLengthRequired;
|
|||
|
|
|||
|
TempStringU.Buffer = MIDL_user_allocate( TempStringU.MaximumLength );
|
|||
|
|
|||
|
NtStatus = STATUS_NO_MEMORY;
|
|||
|
|
|||
|
if (TempStringU.Buffer == NULL) {
|
|||
|
|
|||
|
goto ConvertUlongToUnicodeStringError;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Convert the unsigned long value to a hexadecimal Unicode String.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = RtlIntegerToUnicodeString( Value, Base, &TempStringU );
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
goto ConvertUlongToUnicodeStringError;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Prepend the requisite number of Unicode Zeros.
|
|||
|
//
|
|||
|
|
|||
|
LeadingZerosLength = OutputLengthRequired - sizeof(WCHAR) - TempStringU.Length;
|
|||
|
|
|||
|
if (LeadingZerosLength > 0) {
|
|||
|
|
|||
|
RtlInitUnicodeString( &NumericStringU, L"00000000000000000000000000000000" );
|
|||
|
|
|||
|
RtlCopyMemory(
|
|||
|
((PUCHAR)OutputUnicodeStringU.Buffer) + OutputUnicodeStringU.Length,
|
|||
|
NumericStringU.Buffer,
|
|||
|
LeadingZerosLength
|
|||
|
);
|
|||
|
|
|||
|
OutputUnicodeStringU.Length += LeadingZerosLength;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Append the converted string
|
|||
|
//
|
|||
|
|
|||
|
RtlAppendUnicodeStringToString( &OutputUnicodeStringU, &TempStringU);
|
|||
|
|
|||
|
*UnicodeString = OutputUnicodeStringU;
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
|
|||
|
ConvertUlongToUnicodeStringFinish:
|
|||
|
|
|||
|
if (TempStringU.Buffer != NULL) {
|
|||
|
|
|||
|
MIDL_user_free( TempStringU.Buffer);
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
ConvertUlongToUnicodeStringError:
|
|||
|
|
|||
|
if (AllocateDestinationString) {
|
|||
|
|
|||
|
if (OutputUnicodeStringU.Buffer != NULL) {
|
|||
|
|
|||
|
MIDL_user_free( OutputUnicodeStringU.Buffer);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
goto ConvertUlongToUnicodeStringFinish;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampRtlWellKnownPrivilegeCheck(
|
|||
|
BOOLEAN ImpersonateClient,
|
|||
|
IN ULONG PrivilegeId,
|
|||
|
IN OPTIONAL PCLIENT_ID ClientId
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This function checks if the given well known privilege is enabled for an
|
|||
|
impersonated client or for the current process.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ImpersonateClient - If TRUE, impersonate the client. If FALSE, don't
|
|||
|
impersonate the client (we may already be doing so).
|
|||
|
|
|||
|
PrivilegeId - Specifies the well known Privilege Id
|
|||
|
|
|||
|
ClientId - Specifies the client process/thread Id. If already
|
|||
|
impersonating the client, or impersonation is requested, this
|
|||
|
parameter should be omitted.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
NTSTATUS - Standard Nt Result Code
|
|||
|
|
|||
|
STATUS_SUCCESS - The call completed successfully and the client
|
|||
|
is either trusted or has the necessary privilege enabled.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS Status, SecondaryStatus;
|
|||
|
BOOLEAN PrivilegeHeld = FALSE;
|
|||
|
HANDLE ClientThread = NULL, ClientProcess = NULL, ClientToken = NULL;
|
|||
|
OBJECT_ATTRIBUTES NullAttributes;
|
|||
|
PRIVILEGE_SET Privilege;
|
|||
|
BOOLEAN ClientImpersonatedHere = FALSE;
|
|||
|
|
|||
|
InitializeObjectAttributes( &NullAttributes, NULL, 0, NULL, NULL );
|
|||
|
|
|||
|
//
|
|||
|
// If requested, impersonate the client.
|
|||
|
//
|
|||
|
|
|||
|
if (ImpersonateClient) {
|
|||
|
|
|||
|
Status = I_RpcMapWin32Status(RpcImpersonateClient( NULL ));
|
|||
|
|
|||
|
if ( !NT_SUCCESS(Status) ) {
|
|||
|
|
|||
|
goto WellKnownPrivilegeCheckError;
|
|||
|
}
|
|||
|
|
|||
|
ClientImpersonatedHere = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If a client process other than ourself has been specified , open it
|
|||
|
// for query information access.
|
|||
|
//
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(ClientId)) {
|
|||
|
|
|||
|
if (ClientId->UniqueProcess != NtCurrentProcess()) {
|
|||
|
|
|||
|
Status = NtOpenProcess(
|
|||
|
&ClientProcess,
|
|||
|
PROCESS_QUERY_INFORMATION, // To open primary token
|
|||
|
&NullAttributes,
|
|||
|
ClientId
|
|||
|
);
|
|||
|
|
|||
|
if ( !NT_SUCCESS(Status) ) {
|
|||
|
|
|||
|
goto WellKnownPrivilegeCheckError;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
ClientProcess = NtCurrentProcess();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If a client thread other than ourself has been specified , open it
|
|||
|
// for query information access.
|
|||
|
//
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(ClientId)) {
|
|||
|
|
|||
|
if (ClientId->UniqueThread != NtCurrentThread()) {
|
|||
|
|
|||
|
Status = NtOpenThread(
|
|||
|
&ClientThread,
|
|||
|
THREAD_QUERY_INFORMATION,
|
|||
|
&NullAttributes,
|
|||
|
ClientId
|
|||
|
);
|
|||
|
|
|||
|
if ( !NT_SUCCESS(Status) ) {
|
|||
|
|
|||
|
goto WellKnownPrivilegeCheckError;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
ClientThread = NtCurrentThread();
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
ClientThread = NtCurrentThread();
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Open the specified or current thread's impersonation token (if any).
|
|||
|
//
|
|||
|
|
|||
|
Status = NtOpenThreadToken(
|
|||
|
ClientThread,
|
|||
|
TOKEN_QUERY,
|
|||
|
TRUE,
|
|||
|
&ClientToken
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Make sure that we did not get any error in opening the impersonation
|
|||
|
// token other than that the token doesn't exist.
|
|||
|
//
|
|||
|
|
|||
|
if ( !NT_SUCCESS(Status) ) {
|
|||
|
|
|||
|
if ( Status != STATUS_NO_TOKEN ) {
|
|||
|
|
|||
|
goto WellKnownPrivilegeCheckError;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// The thread isn't impersonating...open the process's token.
|
|||
|
// A process Id must have been specified in the ClientId information
|
|||
|
// in this case.
|
|||
|
//
|
|||
|
|
|||
|
if (ClientProcess == NULL) {
|
|||
|
|
|||
|
Status = STATUS_INVALID_PARAMETER;
|
|||
|
goto WellKnownPrivilegeCheckError;
|
|||
|
}
|
|||
|
|
|||
|
Status = NtOpenProcessToken(
|
|||
|
ClientProcess,
|
|||
|
TOKEN_QUERY,
|
|||
|
&ClientToken
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// Make sure we succeeded in opening the token
|
|||
|
//
|
|||
|
|
|||
|
if ( !NT_SUCCESS(Status) ) {
|
|||
|
|
|||
|
goto WellKnownPrivilegeCheckError;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// OK, we have a token open. Now check for the privilege to execute this
|
|||
|
// service.
|
|||
|
//
|
|||
|
|
|||
|
Privilege.PrivilegeCount = 1;
|
|||
|
Privilege.Control = PRIVILEGE_SET_ALL_NECESSARY;
|
|||
|
Privilege.Privilege[0].Luid = RtlConvertLongToLuid(PrivilegeId);
|
|||
|
Privilege.Privilege[0].Attributes = 0;
|
|||
|
|
|||
|
Status = NtPrivilegeCheck(
|
|||
|
ClientToken,
|
|||
|
&Privilege,
|
|||
|
&PrivilegeHeld
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(Status)) {
|
|||
|
|
|||
|
goto WellKnownPrivilegeCheckError;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Generate any necessary audits
|
|||
|
//
|
|||
|
|
|||
|
SecondaryStatus = NtPrivilegedServiceAuditAlarm (
|
|||
|
&SampSamSubsystem,
|
|||
|
&SampSamSubsystem,
|
|||
|
ClientToken,
|
|||
|
&Privilege,
|
|||
|
PrivilegeHeld
|
|||
|
);
|
|||
|
// ASSERT( NT_SUCCESS(SecondaryStatus) );
|
|||
|
|
|||
|
|
|||
|
if ( !PrivilegeHeld ) {
|
|||
|
|
|||
|
Status = STATUS_PRIVILEGE_NOT_HELD;
|
|||
|
goto WellKnownPrivilegeCheckError;
|
|||
|
}
|
|||
|
|
|||
|
WellKnownPrivilegeCheckFinish:
|
|||
|
|
|||
|
//
|
|||
|
// If we impersonated the client, revert to ourself.
|
|||
|
//
|
|||
|
|
|||
|
if (ClientImpersonatedHere) {
|
|||
|
|
|||
|
SecondaryStatus = I_RpcMapWin32Status(RpcRevertToSelf());
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If necessary, close the client Process.
|
|||
|
//
|
|||
|
|
|||
|
if ((ARGUMENT_PRESENT(ClientId)) &&
|
|||
|
(ClientId->UniqueProcess != NtCurrentProcess()) &&
|
|||
|
(ClientProcess != NULL)) {
|
|||
|
|
|||
|
SecondaryStatus = NtClose( ClientProcess );
|
|||
|
ASSERT(NT_SUCCESS(SecondaryStatus));
|
|||
|
ClientProcess = NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If necessary, close the client token.
|
|||
|
//
|
|||
|
|
|||
|
if (ClientToken != NULL) {
|
|||
|
|
|||
|
SecondaryStatus = NtClose( ClientToken );
|
|||
|
ASSERT(NT_SUCCESS(SecondaryStatus));
|
|||
|
ClientToken = NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If necessary, close the client thread
|
|||
|
//
|
|||
|
|
|||
|
if ((ARGUMENT_PRESENT(ClientId)) &&
|
|||
|
(ClientId->UniqueThread != NtCurrentThread()) &&
|
|||
|
(ClientThread != NULL)) {
|
|||
|
|
|||
|
SecondaryStatus = NtClose( ClientThread );
|
|||
|
ASSERT(NT_SUCCESS(SecondaryStatus));
|
|||
|
ClientThread = NULL;
|
|||
|
}
|
|||
|
|
|||
|
return(Status);
|
|||
|
|
|||
|
WellKnownPrivilegeCheckError:
|
|||
|
|
|||
|
goto WellKnownPrivilegeCheckFinish;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
VOID
|
|||
|
SampWriteEventLog (
|
|||
|
IN USHORT EventType,
|
|||
|
IN USHORT EventCategory OPTIONAL,
|
|||
|
IN ULONG EventID,
|
|||
|
IN PSID UserSid OPTIONAL,
|
|||
|
IN USHORT NumStrings,
|
|||
|
IN ULONG DataSize,
|
|||
|
IN PUNICODE_STRING *Strings OPTIONAL,
|
|||
|
IN PVOID Data OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Routine that adds an entry to the event log
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
EventType - Type of event.
|
|||
|
|
|||
|
EventCategory - EventCategory
|
|||
|
|
|||
|
EventID - event log ID.
|
|||
|
|
|||
|
UserSid - SID of user involved.
|
|||
|
|
|||
|
NumStrings - Number of strings in Strings array
|
|||
|
|
|||
|
DataSize - Number of bytes in Data buffer
|
|||
|
|
|||
|
Strings - Array of unicode strings
|
|||
|
|
|||
|
Data - Pointer to data buffer
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
UNICODE_STRING Source;
|
|||
|
HANDLE LogHandle;
|
|||
|
|
|||
|
RtlInitUnicodeString(&Source, L"SAM");
|
|||
|
|
|||
|
//
|
|||
|
// Open the log
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = ElfRegisterEventSourceW (
|
|||
|
NULL, // Server
|
|||
|
&Source,
|
|||
|
&LogHandle
|
|||
|
);
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAM: Failed to registry event source with event log, status = 0x%lx\n", NtStatus));
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Write out the event
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = ElfReportEventW (
|
|||
|
LogHandle,
|
|||
|
EventType,
|
|||
|
EventCategory,
|
|||
|
EventID,
|
|||
|
UserSid,
|
|||
|
NumStrings,
|
|||
|
DataSize,
|
|||
|
Strings,
|
|||
|
Data,
|
|||
|
0, // Flags
|
|||
|
NULL, // Record Number
|
|||
|
NULL // Time written
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAM: Failed to report event to event log, status = 0x%lx\n", NtStatus));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Close the event log
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = ElfDeregisterEventSource (LogHandle);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAM: Failed to de-register event source with event log, status = 0x%lx\n", NtStatus));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
BOOL
|
|||
|
SampShutdownNotification(
|
|||
|
DWORD dwCtrlType
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called by the system when system shutdown is occuring.
|
|||
|
|
|||
|
It causes the SAM registry to be flushed if necessary.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
FALSE - to allow any other shutdown routines in this process to
|
|||
|
also be called.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS
|
|||
|
NtStatus;
|
|||
|
|
|||
|
|
|||
|
if (dwCtrlType == CTRL_SHUTDOWN_EVENT) {
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Don't wait for the flush thread to wake up.
|
|||
|
// Flush the registry now if necessary ...
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
ASSERT( NT_SUCCESS(NtStatus) ); //Nothing we can do if this fails
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// This flush use to be done only if FlushThreadCreated
|
|||
|
// was true. However, we seem to have a race condition
|
|||
|
// at setup that causes an initial replication to be
|
|||
|
// lost (resulting in an additional replication).
|
|||
|
// Until we resolve this problem, always flush on
|
|||
|
// shutdown.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = NtFlushKey( SampKey );
|
|||
|
|
|||
|
if (!NT_SUCCESS( NtStatus )) {
|
|||
|
DbgPrint("NtFlushKey failed, Status = %X\n",NtStatus);
|
|||
|
// ASSERT( NT_SUCCESS(NtStatus) );
|
|||
|
}
|
|||
|
|
|||
|
SampReleaseWriteLock( FALSE );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
return(FALSE);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampGetAccountDomainInfo(
|
|||
|
PPOLICY_ACCOUNT_DOMAIN_INFO *PolicyAccountDomainInfo
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine retrieves ACCOUNT domain information from the LSA
|
|||
|
policy database.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
PolicyAccountDomainInfo - Receives a pointer to a
|
|||
|
POLICY_ACCOUNT_DOMAIN_INFO structure containing the account
|
|||
|
domain info.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - Succeeded.
|
|||
|
|
|||
|
Other status values that may be returned from:
|
|||
|
|
|||
|
LsarQueryInformationPolicy()
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS
|
|||
|
NtStatus,
|
|||
|
IgnoreStatus;
|
|||
|
|
|||
|
LSAPR_HANDLE
|
|||
|
PolicyHandle;
|
|||
|
|
|||
|
|
|||
|
NtStatus = LsaIOpenPolicyTrusted( &PolicyHandle );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Query the account domain information
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = LsarQueryInformationPolicy(
|
|||
|
PolicyHandle,
|
|||
|
PolicyAccountDomainInformation,
|
|||
|
(PLSAPR_POLICY_INFORMATION *)PolicyAccountDomainInfo
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
if ( (*PolicyAccountDomainInfo)->DomainSid == NULL ) {
|
|||
|
|
|||
|
NtStatus = STATUS_INVALID_SID;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
IgnoreStatus = LsarClose( &PolicyHandle );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
#if DBG
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
ASSERT( (*PolicyAccountDomainInfo) != NULL );
|
|||
|
ASSERT( (*PolicyAccountDomainInfo)->DomainName.Buffer != NULL );
|
|||
|
}
|
|||
|
#endif //DBG
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|