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);
|
||
}
|
||
|
||
|