8013 lines
227 KiB
C
8013 lines
227 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1990 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
domain.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This file contains services related to the SAM "domain" object.
|
|||
|
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
Jim Kelly (JimK) 4-July-1991
|
|||
|
|
|||
|
Environment:
|
|||
|
|
|||
|
User Mode - Win32
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// Includes //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
#include <samsrvp.h>
|
|||
|
#include "ntlsa.h"
|
|||
|
#include "lmcons.h" // LM20_PWLEN
|
|||
|
#include "msaudite.h"
|
|||
|
#include <nlrepl.h> // I_NetNotifyMachineAccount prototype
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// private service prototypes //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampInitializeSingleDomain(
|
|||
|
ULONG Index
|
|||
|
);
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampOpenDomainKey(
|
|||
|
IN PSAMP_OBJECT DomainContext,
|
|||
|
IN PRPC_SID DomainId
|
|||
|
);
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampSetDomainPolicy( VOID );
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampBuildDomainKeyName(
|
|||
|
OUT PUNICODE_STRING DomainKeyName,
|
|||
|
IN PUNICODE_STRING DomainName OPTIONAL
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// RPC Dispatch routines //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamrOpenDomain(
|
|||
|
IN SAMPR_HANDLE ServerHandle,
|
|||
|
IN ACCESS_MASK DesiredAccess,
|
|||
|
IN PRPC_SID DomainId,
|
|||
|
OUT SAMPR_HANDLE *DomainHandle
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service is the RPC dispatch routine for SamrOpenDomain().
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ServerHandle - An active context handle to a Server object.
|
|||
|
|
|||
|
Access desired to the domain.
|
|||
|
|
|||
|
DomainId - The SID of the domain to open.
|
|||
|
|
|||
|
DomainHandle - If successful, will receive the context handle value
|
|||
|
for the newly opened domain. Otherwise, NULL is returned.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The object has been successfully openned.
|
|||
|
|
|||
|
STATUS_INSUFFICIENT_RESOURCES - The SAM server processes doesn't
|
|||
|
have sufficient resources to process or accept another connection
|
|||
|
at this time.
|
|||
|
|
|||
|
Other values as may be returned from:
|
|||
|
|
|||
|
NtAccessCheckAndAuditAlarm()
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
PSAMP_OBJECT ServerContext, DomainContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Grab a read lock
|
|||
|
//
|
|||
|
|
|||
|
SampAcquireReadLock();
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to server object.
|
|||
|
//
|
|||
|
|
|||
|
ServerContext = (PSAMP_OBJECT)ServerHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
ServerContext,
|
|||
|
SAM_SERVER_LOOKUP_DOMAIN, // DesiredAccess
|
|||
|
SampServerObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Try to create a context for the domain.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = SampCreateContext(
|
|||
|
SampDomainObjectType,
|
|||
|
ServerContext->TrustedClient
|
|||
|
);
|
|||
|
|
|||
|
if (DomainContext != NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// Open the specified domain's registry key.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampOpenDomainKey(
|
|||
|
DomainContext,
|
|||
|
DomainId
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Reference the object for the validation
|
|||
|
//
|
|||
|
|
|||
|
SampReferenceContext(DomainContext);
|
|||
|
|
|||
|
//
|
|||
|
// Validate the caller's access.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampValidateObjectAccess(
|
|||
|
DomainContext, //Context
|
|||
|
DesiredAccess, //DesiredAccess
|
|||
|
FALSE //ObjectCreation
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// Dereference object, discarding any changes
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext(DomainContext, FALSE);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
//
|
|||
|
// if we didn't pass the access test, then free up the context
|
|||
|
// block and return the error status returned from the access
|
|||
|
// validation routine. Otherwise, return the context handle
|
|||
|
// value.
|
|||
|
//
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampDeleteContext( DomainContext );
|
|||
|
} else {
|
|||
|
(*DomainHandle) = DomainContext;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the server object
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( ServerContext, FALSE );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free the read lock
|
|||
|
//
|
|||
|
|
|||
|
SampReleaseReadLock();
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamrQueryInformationDomain2(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN DOMAIN_INFORMATION_CLASS DomainInformationClass,
|
|||
|
OUT PSAMPR_DOMAIN_INFO_BUFFER *Buffer
|
|||
|
)
|
|||
|
{
|
|||
|
//
|
|||
|
// This is a thin veil to SamrQueryInformationDomain().
|
|||
|
// This is needed so that new-release systems can call
|
|||
|
// this routine without the danger of passing an info
|
|||
|
// level that release 1.0 systems didn't understand.
|
|||
|
//
|
|||
|
|
|||
|
return( SamrQueryInformationDomain(DomainHandle, DomainInformationClass, Buffer ) );
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamrQueryInformationDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN DOMAIN_INFORMATION_CLASS DomainInformationClass,
|
|||
|
OUT PSAMPR_DOMAIN_INFO_BUFFER *Buffer
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service retrieves information about a domain object.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainHandle - A handle obtained via a previous call to SamrOpenDomain().
|
|||
|
|
|||
|
DomainInformationClass - Indicates the type of information to retrieve.
|
|||
|
|
|||
|
Buffer - Receives the requested information. Several blocks of memory
|
|||
|
will be returned: (one) containing a pointer to the (second) which
|
|||
|
contains the requested information structure. This block may contain
|
|||
|
pointers, which will point to other blocks of allocated memory, such
|
|||
|
as string buffers. All of these blocks of memory must be
|
|||
|
(independently) deallocated using MIDL_user_free().
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
STATUS_SUCCESS - Indicates the service completed successfully.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller's handle does not have the appropriate
|
|||
|
access to the object.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
ACCESS_MASK DesiredAccess;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
ULONG i;
|
|||
|
|
|||
|
//
|
|||
|
// Used for tracking allocated blocks of memory - so we can deallocate
|
|||
|
// them in case of error. Don't exceed this number of allocated buffers.
|
|||
|
// ||
|
|||
|
// vv
|
|||
|
PVOID AllocatedBuffer[10];
|
|||
|
ULONG AllocatedBufferCount = 0;
|
|||
|
|
|||
|
#define RegisterBuffer(Buffer) \
|
|||
|
if ((Buffer) != NULL) { \
|
|||
|
ASSERT(AllocatedBufferCount < sizeof(AllocatedBuffer)/sizeof(*AllocatedBuffer)); \
|
|||
|
AllocatedBuffer[AllocatedBufferCount++] = (Buffer); \
|
|||
|
}
|
|||
|
|
|||
|
#define AllocateBuffer(BufferPointer, Size) \
|
|||
|
(BufferPointer) = MIDL_user_allocate((Size)); \
|
|||
|
RegisterBuffer((BufferPointer));
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Make sure we understand what RPC is doing for (to) us.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT (Buffer != NULL);
|
|||
|
ASSERT ((*Buffer) == NULL);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the desired access based upon the Info class
|
|||
|
//
|
|||
|
|
|||
|
switch (DomainInformationClass) {
|
|||
|
|
|||
|
case DomainPasswordInformation:
|
|||
|
case DomainLockoutInformation:
|
|||
|
|
|||
|
DesiredAccess = DOMAIN_READ_PASSWORD_PARAMETERS;
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case DomainGeneralInformation:
|
|||
|
case DomainLogoffInformation:
|
|||
|
case DomainOemInformation:
|
|||
|
case DomainNameInformation:
|
|||
|
case DomainServerRoleInformation:
|
|||
|
case DomainReplicationInformation:
|
|||
|
case DomainModifiedInformation:
|
|||
|
case DomainStateInformation:
|
|||
|
case DomainUasInformation:
|
|||
|
case DomainModifiedInformation2:
|
|||
|
|
|||
|
DesiredAccess = DOMAIN_READ_OTHER_PARAMETERS;
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case DomainGeneralInformation2:
|
|||
|
|
|||
|
DesiredAccess = DOMAIN_READ_PASSWORD_PARAMETERS |
|
|||
|
DOMAIN_READ_OTHER_PARAMETERS;
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
return(STATUS_INVALID_INFO_CLASS);
|
|||
|
} // end_switch
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Allocate the info structure
|
|||
|
//
|
|||
|
|
|||
|
AllocateBuffer(*Buffer, sizeof(SAMPR_DOMAIN_INFO_BUFFER) );
|
|||
|
if ((*Buffer) == NULL) {
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
|
|||
|
SampAcquireReadLock();
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DesiredAccess,
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// case on the type information requested
|
|||
|
//
|
|||
|
|
|||
|
switch (DomainInformationClass) {
|
|||
|
|
|||
|
case DomainUasInformation:
|
|||
|
|
|||
|
(*Buffer)->General.UasCompatibilityRequired =
|
|||
|
Domain->UnmodifiedFixed.UasCompatibilityRequired;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case DomainGeneralInformation2:
|
|||
|
|
|||
|
|
|||
|
(*Buffer)->General2.LockoutDuration =
|
|||
|
Domain->UnmodifiedFixed.LockoutDuration;
|
|||
|
|
|||
|
(*Buffer)->General2.LockoutObservationWindow =
|
|||
|
Domain->UnmodifiedFixed.LockoutObservationWindow;
|
|||
|
|
|||
|
(*Buffer)->General2.LockoutThreshold =
|
|||
|
Domain->UnmodifiedFixed.LockoutThreshold;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// WARNING - GeneralInformation2 falls into the
|
|||
|
// GeneralInformation code for the rest of its processing.
|
|||
|
// This action assumes that the beginning of a GeneralInformation2
|
|||
|
// structure is a GeneralInformation structure !!!
|
|||
|
//
|
|||
|
|
|||
|
// don't break;
|
|||
|
|
|||
|
case DomainGeneralInformation:
|
|||
|
|
|||
|
///////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// Warning, the previous case falls into this case. //
|
|||
|
// Be aware of this when working on this code. //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////
|
|||
|
|
|||
|
(*Buffer)->General.ForceLogoff =
|
|||
|
*((POLD_LARGE_INTEGER)&Domain->UnmodifiedFixed.ForceLogoff);
|
|||
|
|
|||
|
(*Buffer)->General.DomainModifiedCount =
|
|||
|
*((POLD_LARGE_INTEGER)&Domain->UnmodifiedFixed.ModifiedCount);
|
|||
|
|
|||
|
(*Buffer)->General.DomainServerState =
|
|||
|
Domain->UnmodifiedFixed.ServerState;
|
|||
|
|
|||
|
(*Buffer)->General.DomainServerRole =
|
|||
|
Domain->UnmodifiedFixed.ServerRole;
|
|||
|
|
|||
|
(*Buffer)->General.UasCompatibilityRequired =
|
|||
|
Domain->UnmodifiedFixed.UasCompatibilityRequired;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Copy the domain name from our in-memory structure.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES; // Default status if the allocate fails
|
|||
|
|
|||
|
AllocateBuffer((*Buffer)->General.DomainName.Buffer,
|
|||
|
Domain->ExternalName.MaximumLength );
|
|||
|
|
|||
|
if ((*Buffer)->General.DomainName.Buffer != NULL) {
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
|
|||
|
(*Buffer)->General.DomainName.Length = Domain->ExternalName.Length;
|
|||
|
(*Buffer)->General.DomainName.MaximumLength = Domain->ExternalName.MaximumLength;
|
|||
|
|
|||
|
RtlCopyMemory((*Buffer)->General.DomainName.Buffer,
|
|||
|
Domain->ExternalName.Buffer,
|
|||
|
Domain->ExternalName.MaximumLength
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// Now get copies of the strings we must retrieve from
|
|||
|
// the registry.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
DomainContext,
|
|||
|
SAMP_DOMAIN_OEM_INFORMATION,
|
|||
|
TRUE,
|
|||
|
(PUNICODE_STRING)&((*Buffer)->General.OemInformation)
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
RegisterBuffer((*Buffer)->General.OemInformation.Buffer);
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
DomainContext,
|
|||
|
SAMP_DOMAIN_REPLICA,
|
|||
|
TRUE,
|
|||
|
(PUNICODE_STRING)&((*Buffer)->General.ReplicaSourceNodeName) // Body
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
RegisterBuffer((*Buffer)->General.ReplicaSourceNodeName.Buffer);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Get the count of users and groups
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
NtStatus = SampRetrieveAccountCounts(
|
|||
|
&(*Buffer)->General.UserCount,
|
|||
|
&(*Buffer)->General.GroupCount,
|
|||
|
&(*Buffer)->General.AliasCount );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case DomainPasswordInformation:
|
|||
|
|
|||
|
(*Buffer)->Password.MinPasswordLength =
|
|||
|
Domain->UnmodifiedFixed.MinPasswordLength;
|
|||
|
(*Buffer)->Password.PasswordHistoryLength =
|
|||
|
Domain->UnmodifiedFixed.PasswordHistoryLength;
|
|||
|
(*Buffer)->Password.PasswordProperties =
|
|||
|
Domain->UnmodifiedFixed.PasswordProperties;
|
|||
|
(*Buffer)->Password.MaxPasswordAge =
|
|||
|
Domain->UnmodifiedFixed.MaxPasswordAge;
|
|||
|
(*Buffer)->Password.MinPasswordAge =
|
|||
|
Domain->UnmodifiedFixed.MinPasswordAge;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case DomainLogoffInformation:
|
|||
|
|
|||
|
(*Buffer)->Logoff.ForceLogoff =
|
|||
|
Domain->UnmodifiedFixed.ForceLogoff;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case DomainOemInformation:
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
DomainContext,
|
|||
|
SAMP_DOMAIN_OEM_INFORMATION,
|
|||
|
TRUE,
|
|||
|
(PUNICODE_STRING)&((*Buffer)->Oem.OemInformation)
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
RegisterBuffer((*Buffer)->Oem.OemInformation.Buffer);
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case DomainNameInformation:
|
|||
|
|
|||
|
//
|
|||
|
// Copy the domain name from our in-memory structure.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES; // Default status if the allocate fails
|
|||
|
|
|||
|
AllocateBuffer((*Buffer)->Name.DomainName.Buffer,
|
|||
|
Domain->ExternalName.MaximumLength);
|
|||
|
|
|||
|
if ((*Buffer)->Name.DomainName.Buffer != NULL) {
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
|
|||
|
(*Buffer)->Name.DomainName.Length = Domain->ExternalName.Length;
|
|||
|
(*Buffer)->Name.DomainName.MaximumLength = Domain->ExternalName.MaximumLength;
|
|||
|
|
|||
|
RtlCopyMemory((*Buffer)->Name.DomainName.Buffer,
|
|||
|
Domain->ExternalName.Buffer,
|
|||
|
Domain->ExternalName.MaximumLength
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case DomainServerRoleInformation:
|
|||
|
|
|||
|
(*Buffer)->Role.DomainServerRole =
|
|||
|
Domain->UnmodifiedFixed.ServerRole;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case DomainReplicationInformation:
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
DomainContext,
|
|||
|
SAMP_DOMAIN_REPLICA,
|
|||
|
TRUE,
|
|||
|
(PUNICODE_STRING)&((*Buffer)->Replication.ReplicaSourceNodeName) // Body
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
RegisterBuffer((*Buffer)->Replication.ReplicaSourceNodeName.Buffer);
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case DomainModifiedInformation2:
|
|||
|
|
|||
|
(*Buffer)->Modified2.ModifiedCountAtLastPromotion =
|
|||
|
Domain->UnmodifiedFixed.ModifiedCountAtLastPromotion;
|
|||
|
|
|||
|
//
|
|||
|
// This case falls through to DomainModifiedInformation
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
case DomainModifiedInformation:
|
|||
|
|
|||
|
/////////////////////////////////
|
|||
|
// //
|
|||
|
// WARNING //
|
|||
|
// //
|
|||
|
// The previous case falls //
|
|||
|
// into this one. //
|
|||
|
// //
|
|||
|
/////////////////////////////////
|
|||
|
|
|||
|
(*Buffer)->Modified.DomainModifiedCount =
|
|||
|
Domain->UnmodifiedFixed.ModifiedCount;
|
|||
|
(*Buffer)->Modified.CreationTime =
|
|||
|
Domain->UnmodifiedFixed.CreationTime;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case DomainStateInformation:
|
|||
|
|
|||
|
(*Buffer)->State.DomainServerState =
|
|||
|
Domain->UnmodifiedFixed.ServerState;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case DomainLockoutInformation:
|
|||
|
|
|||
|
(*Buffer)->Lockout.LockoutDuration =
|
|||
|
Domain->UnmodifiedFixed.LockoutDuration;
|
|||
|
(*Buffer)->Lockout.LockoutObservationWindow =
|
|||
|
Domain->UnmodifiedFixed.LockoutObservationWindow;
|
|||
|
(*Buffer)->Lockout.LockoutThreshold =
|
|||
|
Domain->UnmodifiedFixed.LockoutThreshold;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free the read lock
|
|||
|
//
|
|||
|
|
|||
|
SampReleaseReadLock();
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// If we didn't succeed, free any allocated memory
|
|||
|
//
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
for ( i=0; i<AllocatedBufferCount ; i++ ) {
|
|||
|
MIDL_user_free( AllocatedBuffer[i] );
|
|||
|
}
|
|||
|
*Buffer = NULL;
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS SamrSetInformationDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN DOMAIN_INFORMATION_CLASS DomainInformationClass,
|
|||
|
IN PSAMPR_DOMAIN_INFO_BUFFER DomainInformation
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API sets the domain information to the values passed in the
|
|||
|
buffer.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
DomainInformationClass - Class of information desired. The
|
|||
|
accesses required for each class is shown below:
|
|||
|
|
|||
|
Info Level Required Access Type
|
|||
|
------------------------- ----------------------------
|
|||
|
|
|||
|
DomainPasswordInformation DOMAIN_WRITE_PASSWORD_PARAMS
|
|||
|
|
|||
|
DomainGeneralInformation (not setable)
|
|||
|
|
|||
|
DomainLogoffInformation DOMAIN_WRITE_OTHER_PARAMETERS
|
|||
|
|
|||
|
DomainOemInformation DOMAIN_WRITE_OTHER_PARAMETERS
|
|||
|
|
|||
|
DomainNameInformation (Not valid for set operations.)
|
|||
|
|
|||
|
DomainServerRoleInformation DOMAIN_ADMINISTER_SERVER
|
|||
|
|
|||
|
DomainReplicationInformation DOMAIN_ADMINISTER_SERVER
|
|||
|
|
|||
|
DomainModifiedInformation (not valid for set operations)
|
|||
|
|
|||
|
DomainStateInformation DOMAIN_ADMINISTER_SERVER
|
|||
|
|
|||
|
DomainUasInformation DOMAIN_WRITE_OTHER_PARAMETERS
|
|||
|
|
|||
|
DomainGeneralInformation2 (not setable)
|
|||
|
|
|||
|
DomainLockoutInformation DOMAIN_WRITE_PASSWORD_PARAMS
|
|||
|
|
|||
|
|
|||
|
DomainInformation - Buffer where the domain information can be
|
|||
|
found.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
|||
|
access to complete the operation.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
|||
|
|
|||
|
STATUS_INVALID_INFO_CLASS - The class provided was invalid.
|
|||
|
|
|||
|
STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
|
|||
|
correct state (disabled or enabled) to perform the requested
|
|||
|
operation. The domain server must be disabled before role
|
|||
|
changes can be made.
|
|||
|
|
|||
|
STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
|
|||
|
incorrect role (primary or backup) to perform the requested
|
|||
|
operation.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
ACCESS_MASK DesiredAccess;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
BOOLEAN ReplicateImmediately = FALSE;
|
|||
|
ULONG DomainIndex;
|
|||
|
LARGE_INTEGER PromotionIncrement = DOMAIN_PROMOTION_INCREMENT;
|
|||
|
|
|||
|
#if DBG
|
|||
|
|
|||
|
LARGE_INTEGER
|
|||
|
TmpTime;
|
|||
|
|
|||
|
TIME_FIELDS
|
|||
|
DT1, DT2, DT3, DT4;
|
|||
|
|
|||
|
#endif //DBG
|
|||
|
|
|||
|
//
|
|||
|
// Make sure we understand what RPC is doing for (to) us.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT (DomainInformation != NULL);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the desired access based upon the Info class
|
|||
|
//
|
|||
|
|
|||
|
switch (DomainInformationClass) {
|
|||
|
|
|||
|
case DomainPasswordInformation:
|
|||
|
case DomainLockoutInformation:
|
|||
|
|
|||
|
ReplicateImmediately = TRUE;
|
|||
|
DesiredAccess = DOMAIN_WRITE_PASSWORD_PARAMS;
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case DomainLogoffInformation:
|
|||
|
case DomainOemInformation:
|
|||
|
|
|||
|
DesiredAccess = DOMAIN_WRITE_OTHER_PARAMETERS;
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case DomainReplicationInformation:
|
|||
|
case DomainStateInformation:
|
|||
|
case DomainServerRoleInformation:
|
|||
|
|
|||
|
DesiredAccess = DOMAIN_ADMINISTER_SERVER;
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case DomainModifiedInformation:
|
|||
|
case DomainNameInformation:
|
|||
|
case DomainGeneralInformation:
|
|||
|
case DomainGeneralInformation2:
|
|||
|
default:
|
|||
|
|
|||
|
return(STATUS_INVALID_INFO_CLASS);
|
|||
|
|
|||
|
} // end_switch
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DesiredAccess,
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
if ( ( NtStatus == STATUS_INVALID_DOMAIN_ROLE ) &&
|
|||
|
( DomainInformationClass == DomainServerRoleInformation ) ) {
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Non-trusted client isn't being allowed to write to backup
|
|||
|
// server. But admin MUST be able to set the server role back
|
|||
|
// to primary. So temporarily pretend that administering the
|
|||
|
// server isn't a write operation, just long enough for the
|
|||
|
// LookupContext to succeed.
|
|||
|
//
|
|||
|
// Note that before returning INVALID_DOMAIN_ROLE, LookupContext
|
|||
|
// verified that the caller otherwise has proper access - if the
|
|||
|
// caller didn't, then we would have gotten a different error.
|
|||
|
//
|
|||
|
|
|||
|
SampObjectInformation[ SampDomainObjectType ].WriteOperations &=
|
|||
|
~DOMAIN_ADMINISTER_SERVER;
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DesiredAccess,
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
SampObjectInformation[ SampDomainObjectType ].WriteOperations |=
|
|||
|
DOMAIN_ADMINISTER_SERVER;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
|
|||
|
DomainIndex = DomainContext->DomainIndex;
|
|||
|
Domain = &SampDefinedDomains[ DomainIndex ];
|
|||
|
|
|||
|
//
|
|||
|
// case on the type information provided
|
|||
|
//
|
|||
|
|
|||
|
switch (DomainInformationClass) {
|
|||
|
|
|||
|
case DomainPasswordInformation:
|
|||
|
|
|||
|
if (
|
|||
|
( DomainInformation->Password.PasswordHistoryLength >
|
|||
|
SAMP_MAXIMUM_PASSWORD_HISTORY_LENGTH ) ||
|
|||
|
|
|||
|
( DomainInformation->Password.MinPasswordAge.QuadPart > 0) ||
|
|||
|
|
|||
|
( DomainInformation->Password.MaxPasswordAge.QuadPart > 0) ||
|
|||
|
|
|||
|
( DomainInformation->Password.MaxPasswordAge.QuadPart >=
|
|||
|
DomainInformation->Password.MinPasswordAge.QuadPart) ||
|
|||
|
|
|||
|
( ( Domain->UnmodifiedFixed.UasCompatibilityRequired ) &&
|
|||
|
( DomainInformation->Password.MinPasswordLength > LM20_PWLEN ) )
|
|||
|
) {
|
|||
|
|
|||
|
//
|
|||
|
// One of the following is wrong:
|
|||
|
//
|
|||
|
// 1. The history length is larger than we can allow (and
|
|||
|
// still ensure everything will fit in a string)
|
|||
|
// 2. The MinPasswordAge isn't a delta time
|
|||
|
// 3. The MaxPasswordAge isn't a delta time
|
|||
|
// 4. The MaxPasswordAge isn't greater than the
|
|||
|
// MinPasswordAge (they're negative delta times)
|
|||
|
// 5. UAS compatibility is required, but MinPasswordLength
|
|||
|
// is greater than LM's max password length.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_INVALID_PARAMETER;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
Domain->CurrentFixed.MinPasswordLength =
|
|||
|
DomainInformation->Password.MinPasswordLength;
|
|||
|
|
|||
|
Domain->CurrentFixed.PasswordHistoryLength =
|
|||
|
DomainInformation->Password.PasswordHistoryLength;
|
|||
|
|
|||
|
Domain->CurrentFixed.PasswordProperties =
|
|||
|
DomainInformation->Password.PasswordProperties;
|
|||
|
|
|||
|
Domain->CurrentFixed.MaxPasswordAge =
|
|||
|
DomainInformation->Password.MaxPasswordAge;
|
|||
|
|
|||
|
Domain->CurrentFixed.MinPasswordAge =
|
|||
|
DomainInformation->Password.MinPasswordAge;
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case DomainLogoffInformation:
|
|||
|
|
|||
|
Domain->CurrentFixed.ForceLogoff =
|
|||
|
DomainInformation->Logoff.ForceLogoff;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case DomainOemInformation:
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
DomainContext,
|
|||
|
SAMP_DOMAIN_OEM_INFORMATION,
|
|||
|
(PUNICODE_STRING)&(DomainInformation->Oem.OemInformation)
|
|||
|
);
|
|||
|
break;
|
|||
|
|
|||
|
case DomainServerRoleInformation:
|
|||
|
|
|||
|
//
|
|||
|
// Only NTAS systems can be demoted.
|
|||
|
//
|
|||
|
|
|||
|
if (SampProductType != NtProductLanManNt) {
|
|||
|
|
|||
|
if ( (DomainInformation->Role.DomainServerRole ==
|
|||
|
DomainServerRoleBackup) //Trying to demote
|
|||
|
) {
|
|||
|
|
|||
|
NtStatus = STATUS_INVALID_DOMAIN_ROLE;
|
|||
|
break;
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Are we being promoted to primary?
|
|||
|
//
|
|||
|
|
|||
|
if ( (Domain->UnmodifiedFixed.ServerRole == DomainServerRoleBackup)
|
|||
|
&&
|
|||
|
(DomainInformation->Role.DomainServerRole == DomainServerRolePrimary)
|
|||
|
) {
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// We are being promoted. Increment the ModifiedCount
|
|||
|
// by the promotion increment.
|
|||
|
//
|
|||
|
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart =
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ModifiedCount.QuadPart +
|
|||
|
PromotionIncrement.QuadPart;
|
|||
|
|
|||
|
Domain->CurrentFixed.ModifiedCountAtLastPromotion =
|
|||
|
Domain->CurrentFixed.ModifiedCount;
|
|||
|
|
|||
|
Domain->CurrentFixed.ModifiedCountAtLastPromotion.QuadPart += 1;
|
|||
|
|
|||
|
SampDiagPrint( DISPLAY_ROLE_CHANGES,
|
|||
|
("SAM: Role Change: Promoting to primary\n"
|
|||
|
" Old ModifiedId: [0x%lx, 0x%lx]\n"
|
|||
|
" New ModifiedId: [0x%lx, 0x%lx]\n",
|
|||
|
Domain->UnmodifiedFixed.ModifiedCount.HighPart,
|
|||
|
Domain->UnmodifiedFixed.ModifiedCount.LowPart,
|
|||
|
Domain->CurrentFixed.ModifiedCount.HighPart,
|
|||
|
Domain->CurrentFixed.ModifiedCount.LowPart)
|
|||
|
);
|
|||
|
#if DBG
|
|||
|
} else {
|
|||
|
|
|||
|
|
|||
|
SampDiagPrint( DISPLAY_ROLE_CHANGES,
|
|||
|
("SAM: Role Change: Demoting to backup\n"
|
|||
|
" ModifiedId: [0x%lx, 0x%lx]\n",
|
|||
|
Domain->CurrentFixed.ModifiedCount.HighPart,
|
|||
|
Domain->CurrentFixed.ModifiedCount.LowPart )
|
|||
|
);
|
|||
|
|
|||
|
#endif //DBG
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
Domain->CurrentFixed.ServerRole =
|
|||
|
DomainInformation->Role.DomainServerRole;
|
|||
|
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case DomainReplicationInformation:
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
DomainContext,
|
|||
|
SAMP_DOMAIN_REPLICA,
|
|||
|
(PUNICODE_STRING)&(DomainInformation->Replication.ReplicaSourceNodeName) // Body
|
|||
|
);
|
|||
|
break;
|
|||
|
|
|||
|
case DomainStateInformation:
|
|||
|
|
|||
|
Domain->CurrentFixed.ServerState =
|
|||
|
DomainInformation->State.DomainServerState;
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case DomainLockoutInformation:
|
|||
|
|
|||
|
if (
|
|||
|
( DomainInformation->Lockout.LockoutDuration.QuadPart > 0) ||
|
|||
|
|
|||
|
( DomainInformation->Lockout.LockoutObservationWindow.QuadPart > 0 )
|
|||
|
|
|||
|
) {
|
|||
|
|
|||
|
//
|
|||
|
// One of the following is wrong:
|
|||
|
//
|
|||
|
// 1. The LockoutDuration isn't a delta time (or zero).
|
|||
|
// 2. The LockoutObservationWindow isn't a delta time (or zero).
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_INVALID_PARAMETER;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
#if DBG
|
|||
|
TmpTime.QuadPart = -Domain->CurrentFixed.LockoutObservationWindow.QuadPart;
|
|||
|
RtlTimeToElapsedTimeFields( &TmpTime, &DT1 );
|
|||
|
TmpTime.QuadPart = -Domain->CurrentFixed.LockoutDuration.QuadPart;
|
|||
|
RtlTimeToElapsedTimeFields( &TmpTime, &DT2 );
|
|||
|
TmpTime.QuadPart = -DomainInformation->Lockout.LockoutObservationWindow.QuadPart;
|
|||
|
RtlTimeToElapsedTimeFields( &TmpTime, &DT3 );
|
|||
|
TmpTime.QuadPart = -DomainInformation->Lockout.LockoutDuration.QuadPart;
|
|||
|
RtlTimeToElapsedTimeFields( &TmpTime, &DT4 );
|
|||
|
|
|||
|
SampDiagPrint( DISPLAY_LOCKOUT,
|
|||
|
("SAM: SetInformationDomain: Changing Lockout values.\n"
|
|||
|
" Old:\n"
|
|||
|
" Window : [0x%lx, 0x%lx] %d:%d:%d\n"
|
|||
|
" Duration : [0x%lx, 0x%lx] %d:%d:%d\n"
|
|||
|
" Threshold: %ld\n"
|
|||
|
" New:\n"
|
|||
|
" Window : [0x%lx, 0x%lx] %d:%d:%d\n"
|
|||
|
" Duration : [0x%lx, 0x%lx] %d:%d:%d\n"
|
|||
|
" Threshold: %ld\n",
|
|||
|
Domain->CurrentFixed.LockoutObservationWindow.HighPart,
|
|||
|
Domain->CurrentFixed.LockoutObservationWindow.LowPart,
|
|||
|
DT1.Hour, DT1.Minute, DT1.Second,
|
|||
|
Domain->CurrentFixed.LockoutDuration.HighPart,
|
|||
|
Domain->CurrentFixed.LockoutDuration.LowPart,
|
|||
|
DT2.Hour, DT2.Minute, DT2.Second,
|
|||
|
Domain->CurrentFixed.LockoutThreshold,
|
|||
|
DomainInformation->Lockout.LockoutObservationWindow.HighPart,
|
|||
|
DomainInformation->Lockout.LockoutObservationWindow.LowPart,
|
|||
|
DT3.Hour, DT3.Minute, DT3.Second,
|
|||
|
DomainInformation->Lockout.LockoutDuration.HighPart,
|
|||
|
DomainInformation->Lockout.LockoutDuration.LowPart,
|
|||
|
DT4.Hour, DT4.Minute, DT4.Second,
|
|||
|
DomainInformation->Lockout.LockoutThreshold)
|
|||
|
);
|
|||
|
#endif //DBG
|
|||
|
|
|||
|
Domain->CurrentFixed.LockoutDuration =
|
|||
|
DomainInformation->Lockout.LockoutDuration;
|
|||
|
|
|||
|
Domain->CurrentFixed.LockoutObservationWindow =
|
|||
|
DomainInformation->Lockout.LockoutObservationWindow;
|
|||
|
|
|||
|
Domain->CurrentFixed.LockoutThreshold =
|
|||
|
DomainInformation->Lockout.LockoutThreshold;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Generate an audit if necessary
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus) &&
|
|||
|
SampDoAccountAuditing(DomainIndex)) {
|
|||
|
|
|||
|
LsaIAuditSamEvent(
|
|||
|
STATUS_SUCCESS,
|
|||
|
SE_AUDITID_DOMAIN_POLICY_CHANGE, // AuditId
|
|||
|
Domain->Sid, // Domain SID
|
|||
|
NULL, // Member Rid (not used)
|
|||
|
NULL, // Member Sid (not used)
|
|||
|
NULL, // Account Name (not used)
|
|||
|
&Domain->ExternalName, // Domain
|
|||
|
NULL, // Account Rid (not used)
|
|||
|
NULL // Privileges used
|
|||
|
);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Have the changes written out to the RXACT, and
|
|||
|
// de-reference the object.
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SampDeReferenceContext( DomainContext, TRUE );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Commit changes, if successful, and notify Netlogon of changes.
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
NtStatus = SampCommitAndRetainWriteLock();
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
SampNotifyNetlogonOfDelta(
|
|||
|
SecurityDbChange,
|
|||
|
SecurityDbObjectSamDomain,
|
|||
|
0L,
|
|||
|
(PUNICODE_STRING) NULL,
|
|||
|
(DWORD) ReplicateImmediately,
|
|||
|
NULL // Delta data
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampCreateGroupInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN PRPC_UNICODE_STRING AccountName,
|
|||
|
IN ACCESS_MASK DesiredAccess,
|
|||
|
IN BOOLEAN WriteLockHeld,
|
|||
|
OUT SAMPR_HANDLE *GroupHandle,
|
|||
|
IN OUT PULONG RelativeId
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API creates a new group in the account database. Initially,
|
|||
|
this group does not contain any users. Note that creating a group
|
|||
|
is a protected operation, and requires the DOMAIN_CREATE_GROUP
|
|||
|
access type.
|
|||
|
|
|||
|
This call returns a handle to the newly created group that may be
|
|||
|
used for successive operations on the group. This handle may be
|
|||
|
closed with the SamCloseHandle API.
|
|||
|
|
|||
|
A newly created group will have the following initial field value
|
|||
|
settings. If another value is desired, it must be explicitly
|
|||
|
changed using the group object manipulation services.
|
|||
|
|
|||
|
|
|||
|
Name - The name of the group will be as specified in the
|
|||
|
creation API.
|
|||
|
|
|||
|
Attributes - The following attributes will be set:
|
|||
|
|
|||
|
Mandatory
|
|||
|
EnabledByDefault
|
|||
|
|
|||
|
MemberCount - Zero. Initially the group has no members.
|
|||
|
|
|||
|
RelativeId - will be a uniquelly allocated ID.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
|
|||
|
AccountName - Points to the name of the new account. A case-insensitive
|
|||
|
comparison must not find a group or user with this name already defined.
|
|||
|
|
|||
|
|
|||
|
DesiredAccess - Is an access mask indicating which access types
|
|||
|
are desired to the group.
|
|||
|
|
|||
|
GroupHandle - Receives a handle referencing the newly created
|
|||
|
group. This handle will be required in successive calls to
|
|||
|
operate on the group.
|
|||
|
|
|||
|
RelativeId - Receives the relative ID of the newly created group
|
|||
|
account. The SID of the new group account is this relative ID
|
|||
|
value prefixed with the domain's SID value. This RID will be a
|
|||
|
new, uniquely allocated value - unless a non-zero RID was passed
|
|||
|
in, in which case that RID is used (nothing is done if a group
|
|||
|
with that RID already exists).
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The group was added successfully.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
|||
|
access to complete the operation.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
|||
|
|
|||
|
STATUS_INVALID_ACCOUNT_NAME - The name was poorly formed, e.g.
|
|||
|
contains non-printable characters.
|
|||
|
|
|||
|
STATUS_GROUP_EXISTS - The name is already in use as a group.
|
|||
|
|
|||
|
STATUS_USER_EXISTS - The name is already in use as a user.
|
|||
|
|
|||
|
STATUS_ALIAS_EXISTS - The name is already in use as an alias.
|
|||
|
|
|||
|
STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
|
|||
|
correct state (disabled or enabled) to perform the requested
|
|||
|
operation. The domain server must be enabled before groups
|
|||
|
can be created in it.
|
|||
|
|
|||
|
STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
|
|||
|
incorrect role (primary or backup) to perform the requested
|
|||
|
operation. The domain server must be a primary server to
|
|||
|
create group accounts.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus = STATUS_SUCCESS, IgnoreStatus;
|
|||
|
PSAMP_OBJECT DomainContext, GroupContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
|
|||
|
ULONG NewAccountRid, NewSecurityDescriptorLength;
|
|||
|
UNICODE_STRING KeyName;
|
|||
|
PSECURITY_DESCRIPTOR NewSecurityDescriptor;
|
|||
|
SAMP_V1_0A_FIXED_LENGTH_GROUP V1Fixed;
|
|||
|
PRIVILEGE_SET PrivilegeSet;
|
|||
|
|
|||
|
|
|||
|
if (GroupHandle == NULL) {
|
|||
|
return(STATUS_INVALID_PARAMETER);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the privilege set.
|
|||
|
//
|
|||
|
|
|||
|
PrivilegeSet.PrivilegeCount = 0;
|
|||
|
PrivilegeSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
|
|||
|
PrivilegeSet.Privilege[0].Luid = RtlConvertLongToLuid(0L);
|
|||
|
PrivilegeSet.Privilege[0].Attributes = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Make sure a name was provided
|
|||
|
//
|
|||
|
|
|||
|
if (AccountName == NULL) {
|
|||
|
return(STATUS_INVALID_ACCOUNT_NAME);
|
|||
|
}
|
|||
|
if (AccountName->Length > AccountName->MaximumLength) {
|
|||
|
return(STATUS_INVALID_ACCOUNT_NAME);
|
|||
|
}
|
|||
|
if (AccountName->Buffer == NULL) {
|
|||
|
return(STATUS_INVALID_ACCOUNT_NAME);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if ( !WriteLockHeld ) {
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to domain object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DOMAIN_CREATE_GROUP, // DesiredAccess
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
GroupContext = NULL;
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the name is valid and not already in use before we
|
|||
|
// use it to create the new group.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampValidateNewAccountName((PUNICODE_STRING)AccountName);
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
|
|||
|
if ( (*RelativeId) == 0 ) {
|
|||
|
|
|||
|
//
|
|||
|
// No RID specified, so allocate a new account RID
|
|||
|
//
|
|||
|
|
|||
|
NewAccountRid = Domain->CurrentFixed.NextRid;
|
|||
|
|
|||
|
Domain->CurrentFixed.NextRid += 1;
|
|||
|
(*RelativeId) = NewAccountRid;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// A RID was passed in, so we want to use that rather than
|
|||
|
// selecting a new one.
|
|||
|
//
|
|||
|
|
|||
|
NewAccountRid = (*RelativeId);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Increment the group count
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAdjustAccountCount(SampGroupObjectType, TRUE );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Create the registry key that has the group's name.
|
|||
|
// This simply serves as a name to RID mapping. Save
|
|||
|
// the name when done; we'll put it in the context.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountKeyName(
|
|||
|
SampGroupObjectType,
|
|||
|
&KeyName,
|
|||
|
(PUNICODE_STRING)AccountName
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = RtlAddActionToRXact(
|
|||
|
SampRXactContext,
|
|||
|
RtlRXactOperationSetValue,
|
|||
|
&KeyName,
|
|||
|
NewAccountRid,
|
|||
|
NULL,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
SampFreeUnicodeString(&KeyName);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Now create a group context block
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
SampGroupObjectType,
|
|||
|
NewAccountRid,
|
|||
|
DomainContext->TrustedClient,
|
|||
|
FALSE, // Account exists
|
|||
|
&GroupContext
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// The existing reference count of 1 is for RPC.
|
|||
|
// Reference the context again for the writes we're
|
|||
|
// about to do to initialize it.
|
|||
|
//
|
|||
|
|
|||
|
SampReferenceContext( GroupContext );
|
|||
|
|
|||
|
//
|
|||
|
// If MAXIMUM_ALLOWED is requested, add GENERIC_ALL
|
|||
|
//
|
|||
|
|
|||
|
if (DesiredAccess & MAXIMUM_ALLOWED) {
|
|||
|
|
|||
|
DesiredAccess |= GENERIC_ALL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If ACCESS_SYSTEM_SECURITY is requested and we are
|
|||
|
// a non-trusted client, check that we have
|
|||
|
// SE_SECURITY_PRIVILEGE.
|
|||
|
//
|
|||
|
|
|||
|
if ((DesiredAccess & ACCESS_SYSTEM_SECURITY) &&
|
|||
|
(!DomainContext->TrustedClient)) {
|
|||
|
|
|||
|
NtStatus = SampRtlWellKnownPrivilegeCheck(
|
|||
|
TRUE,
|
|||
|
SE_SECURITY_PRIVILEGE,
|
|||
|
NULL
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
if (NtStatus == STATUS_PRIVILEGE_NOT_HELD) {
|
|||
|
|
|||
|
NtStatus = STATUS_ACCESS_DENIED;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
PrivilegeSet.PrivilegeCount = 1;
|
|||
|
PrivilegeSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
|
|||
|
PrivilegeSet.Privilege[0].Luid = RtlConvertLongToLuid(SE_SECURITY_PRIVILEGE);
|
|||
|
PrivilegeSet.Privilege[0].Attributes = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the caller can be given the requested access
|
|||
|
// to the new object
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
GroupContext->GrantedAccess = DesiredAccess;
|
|||
|
|
|||
|
RtlMapGenericMask(
|
|||
|
&GroupContext->GrantedAccess,
|
|||
|
&SampObjectInformation[SampGroupObjectType].GenericMapping
|
|||
|
);
|
|||
|
|
|||
|
if ((SampObjectInformation[SampGroupObjectType].InvalidMappedAccess
|
|||
|
& GroupContext->GrantedAccess) != 0) {
|
|||
|
|
|||
|
NtStatus = STATUS_ACCESS_DENIED;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
GroupContext = NULL;
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the V1_fixed attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Create the V1_Fixed key
|
|||
|
//
|
|||
|
|
|||
|
V1Fixed.RelativeId = NewAccountRid;
|
|||
|
V1Fixed.Attributes = (SE_GROUP_MANDATORY |
|
|||
|
SE_GROUP_ENABLED_BY_DEFAULT);
|
|||
|
V1Fixed.AdminCount = 0;
|
|||
|
V1Fixed.OperatorCount = 0;
|
|||
|
V1Fixed.Revision = SAMP_REVISION;
|
|||
|
|
|||
|
NtStatus = SampSetFixedAttributes(
|
|||
|
GroupContext,
|
|||
|
(PVOID *)&V1Fixed
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the SecurityDescriptor attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampGetNewAccountSecurity(
|
|||
|
SampGroupObjectType,
|
|||
|
FALSE, // Not member of ADMINISTRATORS alias
|
|||
|
DomainContext->TrustedClient,
|
|||
|
FALSE, //RestrictCreatorAccess
|
|||
|
NewAccountRid,
|
|||
|
&NewSecurityDescriptor,
|
|||
|
&NewSecurityDescriptorLength
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetAccessAttribute(
|
|||
|
GroupContext,
|
|||
|
SAMP_GROUP_SECURITY_DESCRIPTOR,
|
|||
|
NewSecurityDescriptor,
|
|||
|
NewSecurityDescriptorLength
|
|||
|
);
|
|||
|
|
|||
|
MIDL_user_free( NewSecurityDescriptor );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the NAME attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
GroupContext,
|
|||
|
SAMP_GROUP_NAME,
|
|||
|
(PUNICODE_STRING)AccountName
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the AdminComment attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
GroupContext,
|
|||
|
SAMP_GROUP_ADMIN_COMMENT,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the MEMBERS attribute (with no members)
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUlongArrayAttribute(
|
|||
|
GroupContext,
|
|||
|
SAMP_GROUP_MEMBERS,
|
|||
|
NULL,
|
|||
|
0,
|
|||
|
0
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If we created an object, dereference it. Write out its attributes
|
|||
|
// if everything was created OK.
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object, write out any change to current xaction.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT(GroupContext != NULL);
|
|||
|
NtStatus = SampDeReferenceContext( GroupContext, TRUE );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (GroupContext != NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object, ignore changes
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( GroupContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Commit changes and notify netlogon
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
NtStatus = SampCommitAndRetainWriteLock();
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
SAMP_ACCOUNT_DISPLAY_INFO AccountInfo;
|
|||
|
|
|||
|
//
|
|||
|
// Update the display information
|
|||
|
//
|
|||
|
|
|||
|
AccountInfo.Name = *((PUNICODE_STRING)AccountName);
|
|||
|
AccountInfo.Rid = NewAccountRid;
|
|||
|
AccountInfo.AccountControl = V1Fixed.Attributes;
|
|||
|
RtlInitUnicodeString(&AccountInfo.Comment, NULL);
|
|||
|
RtlInitUnicodeString(&AccountInfo.FullName, NULL);
|
|||
|
|
|||
|
IgnoreStatus = SampUpdateDisplayInformation(NULL,
|
|||
|
&AccountInfo,
|
|||
|
SampGroupObjectType);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
SampNotifyNetlogonOfDelta(
|
|||
|
SecurityDbNew,
|
|||
|
SecurityDbObjectSamGroup,
|
|||
|
*RelativeId,
|
|||
|
(PUNICODE_STRING) NULL,
|
|||
|
(DWORD) FALSE, // Replicate immediately
|
|||
|
NULL // Delta data
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// Generate Audit
|
|||
|
//
|
|||
|
|
|||
|
if (SampDoAccountAuditing(DomainContext->DomainIndex)) {
|
|||
|
|
|||
|
LsaIAuditSamEvent(
|
|||
|
STATUS_SUCCESS,
|
|||
|
SE_AUDITID_GLOBAL_GROUP_CREATED, // AuditId
|
|||
|
Domain->Sid, // Domain SID
|
|||
|
NULL, // Member Rid (not used)
|
|||
|
NULL, // Member Sid (not used)
|
|||
|
(PUNICODE_STRING) AccountName, // Account Name
|
|||
|
&Domain->ExternalName, // Domain
|
|||
|
&GroupContext->TypeBody.User.Rid, // Account Rid
|
|||
|
&PrivilegeSet // Privileges used
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Return the context handle on success
|
|||
|
// Delete the context block and return a NULL handle on failure
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
ASSERT(GroupContext != NULL);
|
|||
|
(*GroupHandle) = GroupContext;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (GroupContext != NULL) {
|
|||
|
SampDeleteContext(GroupContext);
|
|||
|
}
|
|||
|
|
|||
|
(*GroupHandle) = (SAMPR_HANDLE)0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Release the lock
|
|||
|
//
|
|||
|
|
|||
|
if ( !WriteLockHeld ) {
|
|||
|
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamrCreateGroupInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN PRPC_UNICODE_STRING AccountName,
|
|||
|
IN ACCESS_MASK DesiredAccess,
|
|||
|
OUT SAMPR_HANDLE *GroupHandle,
|
|||
|
OUT PULONG RelativeId
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This is just a wrapper for SampCreateGroupInDomain() that ensures that
|
|||
|
RelativeId points to a RID of zero first.
|
|||
|
|
|||
|
A non-zero RID means that SampCreateGroupInDomain() was called by
|
|||
|
SamICreateAccountByRid(), which specifies a RID to be used.
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
Same as SampCreateGroupInDomain().
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
Same as SampCreateGroupInDomain().
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
|
|||
|
(*RelativeId) = 0;
|
|||
|
|
|||
|
NtStatus = SampCreateGroupInDomain(
|
|||
|
DomainHandle,
|
|||
|
AccountName,
|
|||
|
DesiredAccess,
|
|||
|
FALSE,
|
|||
|
GroupHandle,
|
|||
|
RelativeId
|
|||
|
);
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS SamrEnumerateGroupsInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
|
|||
|
OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
|
|||
|
IN ULONG PreferedMaximumLength,
|
|||
|
OUT PULONG CountReturned
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API lists all the groups defined in the account database.
|
|||
|
Since there may be more groups than can fit into a buffer, the
|
|||
|
caller is provided with a handle that can be used across calls to
|
|||
|
the API. On the initial call, EnumerationContext should point to a
|
|||
|
SAM_ENUMERATE_HANDLE variable that is set to 0.
|
|||
|
|
|||
|
If the API returns STATUS_MORE_ENTRIES, then the API should be
|
|||
|
called again with EnumerationContext. When the API returns
|
|||
|
STATUS_SUCCESS or any error return, the context becomes invalid for
|
|||
|
future use.
|
|||
|
|
|||
|
This API requires DOMAIN_LIST_GROUPS access to the Domain object.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
EnumerationContext - API specific handle to allow multiple calls
|
|||
|
(see below). This is a zero based index.
|
|||
|
|
|||
|
Buffer - Receives a pointer to the buffer containing the
|
|||
|
requested information. The information returned is
|
|||
|
structured as an array of SAM_RID_ENUMERATION 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.
|
|||
|
|
|||
|
CountReturned - Number of entries returned.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully, and there
|
|||
|
are no addition entries.
|
|||
|
|
|||
|
STATUS_MORE_ENTRIES - There are more entries, so call again.
|
|||
|
This is a successful return.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have privilege required to
|
|||
|
request that data.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus;
|
|||
|
|
|||
|
NtStatus = SampEnumerateAccountNamesCommon(
|
|||
|
DomainHandle,
|
|||
|
SampGroupObjectType,
|
|||
|
EnumerationContext,
|
|||
|
Buffer,
|
|||
|
PreferedMaximumLength,
|
|||
|
0L, // no filter
|
|||
|
CountReturned
|
|||
|
);
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampCreateAliasInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN PRPC_UNICODE_STRING AccountName,
|
|||
|
IN ACCESS_MASK DesiredAccess,
|
|||
|
IN BOOLEAN WriteLockHeld,
|
|||
|
OUT SAMPR_HANDLE *AliasHandle,
|
|||
|
IN OUT PULONG RelativeId
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API creates a new alias in the account database. Initially,
|
|||
|
this alias does not contain any users. Note that creating a alias
|
|||
|
is a protected operation, and requires the DOMAIN_CREATE_ALIAS
|
|||
|
access type.
|
|||
|
|
|||
|
This call returns a handle to the newly created alias that may be
|
|||
|
used for successive operations on the alias. This handle may be
|
|||
|
closed with the SamCloseHandle API.
|
|||
|
|
|||
|
A newly created alias will have the following initial field value
|
|||
|
settings. If another value is desired, it must be explicitly
|
|||
|
changed using the alias object manipulation services.
|
|||
|
|
|||
|
|
|||
|
Name - The name of the alias will be as specified in the
|
|||
|
creation API.
|
|||
|
|
|||
|
MemberCount - Zero. Initially the alias has no members.
|
|||
|
|
|||
|
RelativeId - will be a uniquelly allocated ID.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
|
|||
|
AccountName - Points to the name of the new account. A case-insensitive
|
|||
|
comparison must not find an alias or user with this name already defined.
|
|||
|
|
|||
|
|
|||
|
DesiredAccess - Is an access mask indicating which access types
|
|||
|
are desired to the alias.
|
|||
|
|
|||
|
AliasHandle - Receives a handle referencing the newly created
|
|||
|
alias. This handle will be required in successive calls to
|
|||
|
operate on the alias.
|
|||
|
|
|||
|
RelativeId - Receives the relative ID of the newly created alias
|
|||
|
account. The SID of the new alias account is this relative
|
|||
|
ID value prefixed with the domain's SID value.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The alias was added successfully.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
|||
|
access to complete the operation.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
|||
|
|
|||
|
STATUS_INVALID_ACCOUNT_NAME - The name was poorly formed, e.g.
|
|||
|
contains non-printable characters.
|
|||
|
|
|||
|
STATUS_GROUP_EXISTS - The name is already in use as a group.
|
|||
|
|
|||
|
STATUS_USER_EXISTS - The name is already in use as a user.
|
|||
|
|
|||
|
STATUS_ALIAS_EXISTS - The name is already in use as an alias.
|
|||
|
|
|||
|
STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
|
|||
|
correct state (disabled or enabled) to perform the requested
|
|||
|
operation. The domain server must be enabled before aliases
|
|||
|
can be created in it.
|
|||
|
|
|||
|
STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
|
|||
|
incorrect role (primary or backup) to perform the requested
|
|||
|
operation. The domain server must be a primary server to
|
|||
|
create alias accounts.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus = STATUS_SUCCESS, IgnoreStatus;
|
|||
|
PSAMP_OBJECT DomainContext, AliasContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
ULONG NewAccountRid, NewSecurityDescriptorLength;
|
|||
|
UNICODE_STRING KeyName;
|
|||
|
PSECURITY_DESCRIPTOR NewSecurityDescriptor;
|
|||
|
SAMP_V1_FIXED_LENGTH_ALIAS V1Fixed;
|
|||
|
PRIVILEGE_SET Privileges;
|
|||
|
|
|||
|
|
|||
|
if (AliasHandle == NULL) {
|
|||
|
return(STATUS_INVALID_PARAMETER);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the privilege set.
|
|||
|
//
|
|||
|
|
|||
|
Privileges.PrivilegeCount = 0;
|
|||
|
Privileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
|
|||
|
Privileges.Privilege[0].Luid = RtlConvertLongToLuid(0L);
|
|||
|
Privileges.Privilege[0].Attributes = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Make sure a name was provided
|
|||
|
//
|
|||
|
|
|||
|
if (AccountName == NULL) {
|
|||
|
return(STATUS_INVALID_ACCOUNT_NAME);
|
|||
|
}
|
|||
|
if (AccountName->Length > AccountName->MaximumLength) {
|
|||
|
return(STATUS_INVALID_ACCOUNT_NAME);
|
|||
|
}
|
|||
|
if (AccountName->Buffer == NULL) {
|
|||
|
return(STATUS_INVALID_ACCOUNT_NAME);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if ( !WriteLockHeld ) {
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to domain object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DOMAIN_CREATE_ALIAS, // DesiredAccess
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
AliasContext = NULL;
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the name is valid and not already in use before we
|
|||
|
// use it to create the new alias.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampValidateNewAccountName((PUNICODE_STRING)AccountName);
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
|
|||
|
if ( (*RelativeId) == 0 ) {
|
|||
|
|
|||
|
//
|
|||
|
// Allocate a new account RID
|
|||
|
//
|
|||
|
|
|||
|
NewAccountRid = Domain->CurrentFixed.NextRid;
|
|||
|
|
|||
|
Domain->CurrentFixed.NextRid += 1;
|
|||
|
(*RelativeId) = NewAccountRid;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Use the RID that was passed in.
|
|||
|
//
|
|||
|
|
|||
|
NewAccountRid = (*RelativeId);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Increment the alias count
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAdjustAccountCount(SampAliasObjectType, TRUE );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Create the registry key that has the alias's name.
|
|||
|
// This simply serves as a name to RID mapping. Save
|
|||
|
// the name when done; we'll put it in the context.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountKeyName(
|
|||
|
SampAliasObjectType,
|
|||
|
&KeyName,
|
|||
|
(PUNICODE_STRING)AccountName
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = RtlAddActionToRXact(
|
|||
|
SampRXactContext,
|
|||
|
RtlRXactOperationSetValue,
|
|||
|
&KeyName,
|
|||
|
NewAccountRid,
|
|||
|
NULL,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
SampFreeUnicodeString(&KeyName);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Now create an alias context block
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
SampAliasObjectType,
|
|||
|
NewAccountRid,
|
|||
|
DomainContext->TrustedClient,
|
|||
|
FALSE, // Account exists
|
|||
|
&AliasContext
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// The existing reference count of 1 is for RPC.
|
|||
|
// Reference the context again for the writes we're
|
|||
|
// about to do to initialize it.
|
|||
|
//
|
|||
|
|
|||
|
SampReferenceContext( AliasContext );
|
|||
|
|
|||
|
//
|
|||
|
// If MAXIMUM_ALLOWED is requested, add GENERIC_ALL
|
|||
|
//
|
|||
|
|
|||
|
if (DesiredAccess & MAXIMUM_ALLOWED) {
|
|||
|
|
|||
|
DesiredAccess |= GENERIC_ALL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If ACCESS_SYSTEM_SECURITY is requested and we are
|
|||
|
// a non-trusted client, check that we have
|
|||
|
// SE_SECURITY_PRIVILEGE.
|
|||
|
//
|
|||
|
|
|||
|
if ((DesiredAccess & ACCESS_SYSTEM_SECURITY) &&
|
|||
|
(!DomainContext->TrustedClient)) {
|
|||
|
|
|||
|
NtStatus = SampRtlWellKnownPrivilegeCheck(
|
|||
|
TRUE,
|
|||
|
SE_SECURITY_PRIVILEGE,
|
|||
|
NULL
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
if (NtStatus == STATUS_PRIVILEGE_NOT_HELD) {
|
|||
|
|
|||
|
NtStatus = STATUS_ACCESS_DENIED;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
Privileges.PrivilegeCount = 1;
|
|||
|
Privileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
|
|||
|
Privileges.Privilege[0].Luid = RtlConvertLongToLuid(SE_SECURITY_PRIVILEGE);
|
|||
|
Privileges.Privilege[0].Attributes = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the caller can be given the requested access
|
|||
|
// to the new object
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
AliasContext->GrantedAccess = DesiredAccess;
|
|||
|
|
|||
|
RtlMapGenericMask(
|
|||
|
&AliasContext->GrantedAccess,
|
|||
|
&SampObjectInformation[SampAliasObjectType].GenericMapping
|
|||
|
);
|
|||
|
|
|||
|
if ((SampObjectInformation[SampAliasObjectType].InvalidMappedAccess &
|
|||
|
AliasContext->GrantedAccess) != 0) {
|
|||
|
NtStatus = STATUS_ACCESS_DENIED;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
AliasContext = NULL;
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the V1_fixed attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
V1Fixed.RelativeId = NewAccountRid;
|
|||
|
|
|||
|
NtStatus = SampSetFixedAttributes(
|
|||
|
AliasContext,
|
|||
|
(PVOID *)&V1Fixed
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Set the SECURITY DESCRIPTOR attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampGetNewAccountSecurity(
|
|||
|
SampAliasObjectType,
|
|||
|
FALSE, // Not member of ADMINISTRATORS alias
|
|||
|
DomainContext->TrustedClient,
|
|||
|
FALSE, //RestrictCreatorAccess
|
|||
|
NewAccountRid,
|
|||
|
&NewSecurityDescriptor,
|
|||
|
&NewSecurityDescriptorLength
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetAccessAttribute(
|
|||
|
AliasContext,
|
|||
|
SAMP_ALIAS_SECURITY_DESCRIPTOR,
|
|||
|
NewSecurityDescriptor,
|
|||
|
NewSecurityDescriptorLength
|
|||
|
);
|
|||
|
|
|||
|
MIDL_user_free( NewSecurityDescriptor );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the NAME attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
AliasContext,
|
|||
|
SAMP_ALIAS_NAME,
|
|||
|
(PUNICODE_STRING)AccountName
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the AdminComment attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
AliasContext,
|
|||
|
SAMP_ALIAS_ADMIN_COMMENT,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the MEMBERS attribute (with no members)
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUlongArrayAttribute(
|
|||
|
AliasContext,
|
|||
|
SAMP_ALIAS_MEMBERS,
|
|||
|
NULL,
|
|||
|
0,
|
|||
|
0
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If we created an object, dereference it. Write out its attributes
|
|||
|
// if everything was created OK.
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object, write out any change to current xaction.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT(AliasContext != NULL);
|
|||
|
NtStatus = SampDeReferenceContext( AliasContext, TRUE );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (AliasContext != NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object, ignore changes
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( AliasContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Commit changes and notify netlogon
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
NtStatus = SampCommitAndRetainWriteLock();
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
SampNotifyNetlogonOfDelta(
|
|||
|
SecurityDbNew,
|
|||
|
SecurityDbObjectSamAlias,
|
|||
|
*RelativeId,
|
|||
|
(PUNICODE_STRING) NULL,
|
|||
|
(DWORD) FALSE, // Replicate immediately
|
|||
|
NULL // Delta data
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// Generate audit here for local group creation
|
|||
|
// here.
|
|||
|
//
|
|||
|
|
|||
|
if (SampDoAccountAuditing(DomainContext->DomainIndex)) {
|
|||
|
|
|||
|
LsaIAuditSamEvent(
|
|||
|
STATUS_SUCCESS,
|
|||
|
SE_AUDITID_LOCAL_GROUP_CREATED, // AuditId
|
|||
|
Domain->Sid, // Domain SID
|
|||
|
NULL, // Member Rid (not used)
|
|||
|
NULL, // Member Sid (not used)
|
|||
|
(PUNICODE_STRING)AccountName, // Account Name
|
|||
|
&Domain->ExternalName, // Domain
|
|||
|
&AliasContext->TypeBody.User.Rid, // Account Rid
|
|||
|
&Privileges // Privileges used
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Return the context handle on success
|
|||
|
// Delete the context block and return a NULL handle on failure
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
ASSERT(AliasContext != NULL);
|
|||
|
(*AliasHandle) = AliasContext;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (AliasContext != NULL) {
|
|||
|
SampDeleteContext(AliasContext);
|
|||
|
}
|
|||
|
|
|||
|
(*AliasHandle) = (SAMPR_HANDLE)0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Release the lock
|
|||
|
//
|
|||
|
|
|||
|
if ( !WriteLockHeld ) {
|
|||
|
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamrCreateAliasInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN PRPC_UNICODE_STRING AccountName,
|
|||
|
IN ACCESS_MASK DesiredAccess,
|
|||
|
OUT SAMPR_HANDLE *AliasHandle,
|
|||
|
OUT PULONG RelativeId
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This is just a wrapper for SampCreateAliasInDomain() that ensures that
|
|||
|
RelativeId points to a RID of zero first.
|
|||
|
|
|||
|
A non-zero RID means that SampCreateAliasInDomain() was called by
|
|||
|
SamICreateAccountByRid(), which specifies a RID to be used.
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
Same as SampCreateAliasInDomain().
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
Same as SampCreateAliasInDomain().
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
|
|||
|
(*RelativeId) = 0;
|
|||
|
|
|||
|
NtStatus = SampCreateAliasInDomain(
|
|||
|
DomainHandle,
|
|||
|
AccountName,
|
|||
|
DesiredAccess,
|
|||
|
FALSE,
|
|||
|
AliasHandle,
|
|||
|
RelativeId
|
|||
|
);
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS SamrEnumerateAliasesInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
|
|||
|
OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
|
|||
|
IN ULONG PreferedMaximumLength,
|
|||
|
OUT PULONG CountReturned
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API lists all the aliases defined in the account database.
|
|||
|
Since there may be more aliass than can fit into a buffer, the
|
|||
|
caller is provided with a handle that can be used across calls to
|
|||
|
the API. On the initial call, EnumerationContext should point to a
|
|||
|
SAM_ENUMERATE_HANDLE variable that is set to 0.
|
|||
|
|
|||
|
If the API returns STATUS_MORE_ENTRIES, then the API should be
|
|||
|
called again with EnumerationContext. When the API returns
|
|||
|
STATUS_SUCCESS or any error return, the context becomes invalid for
|
|||
|
future use.
|
|||
|
|
|||
|
This API requires DOMAIN_LIST_ALIASES access to the Domain object.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
EnumerationContext - API specific handle to allow multiple calls
|
|||
|
(see below). This is a zero based index.
|
|||
|
|
|||
|
Buffer - Receives a pointer to the buffer containing the
|
|||
|
requested information. The information returned is
|
|||
|
structured as an array of SAM_RID_ENUMERATION 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.
|
|||
|
|
|||
|
CountReturned - Number of entries returned.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully, and there
|
|||
|
are no addition entries.
|
|||
|
|
|||
|
STATUS_MORE_ENTRIES - There are more entries, so call again.
|
|||
|
This is a successful return.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have privilege required to
|
|||
|
request that data.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus;
|
|||
|
|
|||
|
NtStatus = SampEnumerateAccountNamesCommon(
|
|||
|
DomainHandle,
|
|||
|
SampAliasObjectType,
|
|||
|
EnumerationContext,
|
|||
|
Buffer,
|
|||
|
PreferedMaximumLength,
|
|||
|
0L, // no filter
|
|||
|
CountReturned
|
|||
|
);
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS SamrRemoveMemberFromForeignDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN PRPC_SID MemberId
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine removes an account (group or user) from all aliases in
|
|||
|
the given domain. It is meant to be called in domains OTHER than
|
|||
|
domain in which the account was created.
|
|||
|
|
|||
|
This is typically called just before deleting the account from the
|
|||
|
domain in which it was created.
|
|||
|
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
MemberId - The SID of the account being removed.
|
|||
|
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
|||
|
access to complete the operation.
|
|||
|
|
|||
|
STATUS_SPECIAL_ACCOUNT - This operation may not be performed on
|
|||
|
builtin accounts.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
PSAMP_OBJECT DomainContext = NULL;
|
|||
|
PULONG Membership = NULL;
|
|||
|
PSID DomainSid = NULL;
|
|||
|
ULONG MembershipCount, MemberRid, i;
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
|
|||
|
if ( !NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// 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)) {
|
|||
|
|
|||
|
if ( !DomainContext->TrustedClient ) {
|
|||
|
|
|||
|
//
|
|||
|
// Return error if the SID passed in is for a builtin account.
|
|||
|
// This may seem overly restrictive, but this API is meant to
|
|||
|
// be called before deleting a user, and since deleting
|
|||
|
// builtin accounts isn't allowed, it makes sense for this to
|
|||
|
// fail too.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampSplitSid(
|
|||
|
MemberId,
|
|||
|
&DomainSid,
|
|||
|
&MemberRid );
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
MIDL_user_free( DomainSid );
|
|||
|
|
|||
|
NtStatus = SampIsAccountBuiltIn( MemberRid );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampRemoveAccountFromAllAliases(
|
|||
|
MemberId,
|
|||
|
TRUE, // verify caller is allowed to do this
|
|||
|
DomainHandle,
|
|||
|
&MembershipCount,
|
|||
|
&Membership
|
|||
|
);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
}
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
IgnoreStatus = STATUS_SUCCESS;
|
|||
|
|
|||
|
for ( i = 0;
|
|||
|
( ( i < MembershipCount ) && ( NT_SUCCESS( IgnoreStatus ) ) );
|
|||
|
i++ ) {
|
|||
|
|
|||
|
//
|
|||
|
// Notify netlogon once for each alias that the account was
|
|||
|
// removed from. Netlogon requires that ModifiedCount be
|
|||
|
// incremented each time; Commit increments ModifiedCount,
|
|||
|
// so we do each notification after a commit.
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampCommitAndRetainWriteLock();
|
|||
|
|
|||
|
if ( i == 0 ) {
|
|||
|
|
|||
|
//
|
|||
|
// The first commit is the one that commits all the
|
|||
|
// important changes, so we'll save it's status to return
|
|||
|
// to the caller.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = IgnoreStatus;
|
|||
|
|
|||
|
//
|
|||
|
// Update the Cached Alias Information if necessary.
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampAlRemoveAccountFromAllAliases(
|
|||
|
MemberId,
|
|||
|
FALSE,
|
|||
|
DomainHandle,
|
|||
|
NULL,
|
|||
|
NULL
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
if ( NT_SUCCESS( IgnoreStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Notify if we were able to increment the modified count
|
|||
|
// (which is done by SampCommitAndRetainWriteLock()).
|
|||
|
//
|
|||
|
|
|||
|
SAM_DELTA_DATA DeltaData;
|
|||
|
|
|||
|
//
|
|||
|
// Fill in id of member being removed
|
|||
|
//
|
|||
|
|
|||
|
DeltaData.AliasMemberId.MemberSid = MemberId;
|
|||
|
|
|||
|
SampNotifyNetlogonOfDelta(
|
|||
|
SecurityDbChangeMemberDel,
|
|||
|
SecurityDbObjectSamAlias,
|
|||
|
Membership[i],
|
|||
|
(PUNICODE_STRING) NULL,
|
|||
|
(DWORD) FALSE, // Replicate immediately
|
|||
|
&DeltaData
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( Membership != NULL ) {
|
|||
|
|
|||
|
MIDL_user_free( Membership );
|
|||
|
}
|
|||
|
|
|||
|
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampCreateUserInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN PRPC_UNICODE_STRING AccountName,
|
|||
|
IN ULONG AccountType,
|
|||
|
IN ACCESS_MASK DesiredAccess,
|
|||
|
IN BOOLEAN WriteLockHeld,
|
|||
|
OUT SAMPR_HANDLE *UserHandle,
|
|||
|
OUT PULONG GrantedAccess,
|
|||
|
IN OUT PULONG RelativeId
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API adds a new user to the account database. The account is
|
|||
|
created in a disabled state. Default information is assigned to all
|
|||
|
fields except the account name. A password must be provided before
|
|||
|
the account may be enabled, unless the PasswordNotRequired control
|
|||
|
field is set.
|
|||
|
|
|||
|
This api may be used in either of two ways:
|
|||
|
|
|||
|
1) An administrative utility may use this api to create
|
|||
|
any type of user account. In this case, the DomainHandle
|
|||
|
is expected to be open for DOMAIN_CREATE_USER access.
|
|||
|
|
|||
|
2) A non-administrative user may use this api to create
|
|||
|
a machine account. In this case, the caller is expected
|
|||
|
to have the SE_CREATE_MACHINE_ACCOUNT_PRIV privilege
|
|||
|
and the DomainHandle is expected to be open for DOMAIN_LOOKUP
|
|||
|
access.
|
|||
|
|
|||
|
|
|||
|
For the normal administrative model ( #1 above), the creator will
|
|||
|
be assigned as the owner of the created user account. Furthermore,
|
|||
|
the new account will be give USER_WRITE access to itself.
|
|||
|
|
|||
|
For the special machine-account creation model (#2 above), the
|
|||
|
"Administrators" will be assigned as the owner of the account.
|
|||
|
Furthermore, the new account will be given NO access to itself.
|
|||
|
Instead, the creator of the account will be give USER_WRITE and
|
|||
|
DELETE access to the account.
|
|||
|
|
|||
|
|
|||
|
This call returns a handle to the newly created user that may be
|
|||
|
used for successive operations on the user. This handle may be
|
|||
|
closed with the SamCloseHandle() API. If a machine account is
|
|||
|
being created using model #2 above, then this handle will have
|
|||
|
only USER_WRITE and DELETE access. Otherwise, it will be open
|
|||
|
for USER_ALL_ACCESS.
|
|||
|
|
|||
|
|
|||
|
A newly created user will automatically be made a member of the
|
|||
|
DOMAIN_USERS group.
|
|||
|
|
|||
|
A newly created user will have the following initial field value
|
|||
|
settings. If another value is desired, it must be explicitly
|
|||
|
changed using the user object manipulation services.
|
|||
|
|
|||
|
UserName - the name of the account will be as specified in the
|
|||
|
creation API.
|
|||
|
|
|||
|
FullName - will be null.
|
|||
|
|
|||
|
UserComment - will be null.
|
|||
|
|
|||
|
Parameters - will be null.
|
|||
|
|
|||
|
CountryCode - will be zero.
|
|||
|
|
|||
|
UserId - will be a uniquelly allocated ID.
|
|||
|
|
|||
|
PrimaryGroupId - Will be DOMAIN_USERS.
|
|||
|
|
|||
|
PasswordLastSet - will be the time the account was created.
|
|||
|
|
|||
|
HomeDirectory - will be null.
|
|||
|
|
|||
|
HomeDirectoryDrive - will be null.
|
|||
|
|
|||
|
UserAccountControl - will have the following flags set:
|
|||
|
|
|||
|
UserAccountDisable,
|
|||
|
UserPasswordNotRequired,
|
|||
|
and the passed account type.
|
|||
|
|
|||
|
|
|||
|
ScriptPath - will be null.
|
|||
|
|
|||
|
WorkStations - will be null.
|
|||
|
|
|||
|
CaseInsensitiveDbcs - will be null.
|
|||
|
|
|||
|
CaseSensitiveUnicode - will be null.
|
|||
|
|
|||
|
LastLogon - will be zero delta time.
|
|||
|
|
|||
|
LastLogoff - will be zero delta time
|
|||
|
|
|||
|
AccountExpires - will be very far into the future.
|
|||
|
|
|||
|
BadPasswordCount - will be negative 1 (-1).
|
|||
|
|
|||
|
LastBadPasswordTime - will be SampHasNeverTime ( [High,Low] = [0,0] ).
|
|||
|
|
|||
|
LogonCount - will be negative 1 (-1).
|
|||
|
|
|||
|
AdminCount - will be zero.
|
|||
|
|
|||
|
AdminComment - will be null.
|
|||
|
|
|||
|
Password - will be "".
|
|||
|
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
AccountName - Points to the name of the new account. A case-insensitive
|
|||
|
comparison must not find a group or user with this name already defined.
|
|||
|
|
|||
|
AccountType - Indicates what type of account is being created.
|
|||
|
Exactly one account type must be provided:
|
|||
|
|
|||
|
USER_INTERDOMAIN_TRUST_ACCOUNT
|
|||
|
USER_WORKSTATION_TRUST_ACCOUNT
|
|||
|
USER_SERVER_TRUST_ACCOUNT
|
|||
|
USER_TEMP_DUPLICATE_ACCOUNT
|
|||
|
USER_NORMAL_ACCOUNT
|
|||
|
USER_MACHINE_ACCOUNT_MASK
|
|||
|
|
|||
|
|
|||
|
DesiredAccess - Is an access mask indicating which access types
|
|||
|
are desired to the user.
|
|||
|
|
|||
|
UserHandle - Receives a handle referencing the newly created
|
|||
|
user. This handle will be required in successive calls to
|
|||
|
operate on the user.
|
|||
|
|
|||
|
GrantedAccess - Receives the accesses actually granted to via
|
|||
|
the UserHandle.
|
|||
|
|
|||
|
RelativeId - Receives the relative ID of the newly created user
|
|||
|
account. The SID of the new user account is this relative ID
|
|||
|
value prefixed with the domain's SID value.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
|||
|
access to complete the operation.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
|||
|
|
|||
|
STATUS_GROUP_EXISTS - The name is already in use as a group.
|
|||
|
|
|||
|
STATUS_USER_EXISTS - The name is already in use as a user.
|
|||
|
|
|||
|
STATUS_ALIAS_EXISTS - The name is already in use as an alias.
|
|||
|
|
|||
|
STATUS_INVALID_ACCOUNT_NAME - The name was poorly formed, e.g.
|
|||
|
contains non-printable characters.
|
|||
|
|
|||
|
STATUS_INVALID_DOMAIN_STATE - The domain server is not in the
|
|||
|
correct state (disabled or enabled) to perform the requested
|
|||
|
operation. The domain server must be enabled before users
|
|||
|
can be created in it.
|
|||
|
|
|||
|
STATUS_INVALID_DOMAIN_ROLE - The domain server is serving the
|
|||
|
incorrect role (primary or backup) to perform the requested
|
|||
|
operation. The domain server must be a primary server to
|
|||
|
create user accounts.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS
|
|||
|
NtStatus,
|
|||
|
IgnoreStatus;
|
|||
|
|
|||
|
PSAMP_OBJECT
|
|||
|
DomainContext,
|
|||
|
UserContext,
|
|||
|
GroupContext;
|
|||
|
|
|||
|
SAMP_OBJECT_TYPE
|
|||
|
FoundType;
|
|||
|
|
|||
|
PSAMP_DEFINED_DOMAINS
|
|||
|
Domain;
|
|||
|
|
|||
|
SAMP_V1_0A_FIXED_LENGTH_GROUP
|
|||
|
GroupV1Fixed;
|
|||
|
|
|||
|
ULONG
|
|||
|
NewAccountRid,
|
|||
|
NewSecurityDescriptorLength;
|
|||
|
|
|||
|
UNICODE_STRING
|
|||
|
KeyName;
|
|||
|
|
|||
|
PSECURITY_DESCRIPTOR
|
|||
|
NewSecurityDescriptor;
|
|||
|
|
|||
|
SAMP_V1_0A_FIXED_LENGTH_USER
|
|||
|
V1aFixed;
|
|||
|
|
|||
|
GROUP_MEMBERSHIP
|
|||
|
DomainUsersMember;
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
DomainPasswordInformationAccessible = FALSE,
|
|||
|
PrivilegedMachineAccountCreate = FALSE;
|
|||
|
|
|||
|
PRIVILEGE_SET
|
|||
|
Privileges;
|
|||
|
|
|||
|
PPRIVILEGE_SET
|
|||
|
PPrivileges = NULL; // No privileges in audit by default
|
|||
|
|
|||
|
|
|||
|
ACCESS_MASK
|
|||
|
AccessRestriction = USER_ALL_ACCESS |
|
|||
|
ACCESS_SYSTEM_SECURITY; // No access restrictions by default
|
|||
|
|
|||
|
DomainUsersMember.RelativeId = DOMAIN_GROUP_RID_USERS;
|
|||
|
DomainUsersMember.Attributes = (SE_GROUP_MANDATORY |
|
|||
|
SE_GROUP_ENABLED |
|
|||
|
SE_GROUP_ENABLED_BY_DEFAULT);
|
|||
|
|
|||
|
|
|||
|
if (UserHandle == NULL) {
|
|||
|
return(STATUS_INVALID_PARAMETER);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Make sure a name was provided
|
|||
|
//
|
|||
|
|
|||
|
if (AccountName == NULL) {
|
|||
|
return(STATUS_INVALID_ACCOUNT_NAME);
|
|||
|
}
|
|||
|
if (AccountName->Length > AccountName->MaximumLength) {
|
|||
|
return(STATUS_INVALID_ACCOUNT_NAME);
|
|||
|
}
|
|||
|
if (AccountName->Buffer == NULL) {
|
|||
|
return(STATUS_INVALID_ACCOUNT_NAME);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if ( !WriteLockHeld ) {
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to domain object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DOMAIN_CREATE_USER, // DesiredAccess
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// if we don't have DOMAIN_CREATE_USER access, then see
|
|||
|
// if we are creating a machine account and try for DOMAIN_LOOKUP.
|
|||
|
// If this works, then we can see if the client has
|
|||
|
// SE_CREATE_MACHINE_ACCOUNT_PRIVILEGE.
|
|||
|
//
|
|||
|
|
|||
|
if ( (NtStatus == STATUS_ACCESS_DENIED) &&
|
|||
|
(AccountType == USER_WORKSTATION_TRUST_ACCOUNT) ) {
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DOMAIN_LOOKUP, // DesiredAccess
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
NtStatus = SampRtlWellKnownPrivilegeCheck(
|
|||
|
TRUE, // ImpersonateClient
|
|||
|
SE_MACHINE_ACCOUNT_PRIVILEGE,
|
|||
|
NULL); // ClientId - optional
|
|||
|
if (NtStatus == STATUS_PRIVILEGE_NOT_HELD) {
|
|||
|
NtStatus = STATUS_ACCESS_DENIED;
|
|||
|
}
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Clients creating accounts in this fashion are limited
|
|||
|
// in what they can do to the account.
|
|||
|
//
|
|||
|
|
|||
|
AccessRestriction = DELETE |
|
|||
|
USER_WRITE |
|
|||
|
USER_FORCE_PASSWORD_CHANGE;
|
|||
|
PrivilegedMachineAccountCreate = TRUE;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
} else {
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
UserContext = NULL;
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// If the domain handle allows reading the password parameters,
|
|||
|
// note that now (best to call LookupContext early because of
|
|||
|
// side effects; data will be copied to the user's context later)
|
|||
|
// to make life easy for SampGetUserDomainPasswordInformation().
|
|||
|
//
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
IgnoreStatus = SampLookupContext(
|
|||
|
DomainHandle,
|
|||
|
DOMAIN_READ_PASSWORD_PARAMETERS, // DesiredAccess
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( IgnoreStatus ) ) {
|
|||
|
|
|||
|
DomainPasswordInformationAccessible = TRUE;
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainHandle, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the name is valid and not already in use before we
|
|||
|
// use it to create the new alias.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampValidateNewAccountName((PUNICODE_STRING)AccountName);
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if ( (*RelativeId) == 0 ) {
|
|||
|
|
|||
|
//
|
|||
|
// Allocate a new account RID
|
|||
|
//
|
|||
|
|
|||
|
NewAccountRid = Domain->CurrentFixed.NextRid;
|
|||
|
|
|||
|
Domain->CurrentFixed.NextRid += 1;
|
|||
|
(*RelativeId) = NewAccountRid;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// A RID was passed in, so we want to use that rather than
|
|||
|
// select a new one.
|
|||
|
//
|
|||
|
|
|||
|
NewAccountRid = (*RelativeId);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Increment the User count
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAdjustAccountCount(SampUserObjectType, TRUE );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Create the registry key that has the User's name.
|
|||
|
// This simply serves as a name to RID mapping. Save
|
|||
|
// the name when finished; we'll put it in the context.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountKeyName(
|
|||
|
SampUserObjectType,
|
|||
|
&KeyName,
|
|||
|
(PUNICODE_STRING)AccountName
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = RtlAddActionToRXact(
|
|||
|
SampRXactContext,
|
|||
|
RtlRXactOperationSetValue,
|
|||
|
&KeyName,
|
|||
|
NewAccountRid,
|
|||
|
NULL,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
SampFreeUnicodeString(&KeyName);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Now create a User context block
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
SampUserObjectType,
|
|||
|
NewAccountRid,
|
|||
|
DomainContext->TrustedClient,
|
|||
|
FALSE, // Account exists
|
|||
|
&UserContext
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// The existing reference count of 1 is for RPC.
|
|||
|
// Reference the context again for the writes we're
|
|||
|
// about to do to initialize it.
|
|||
|
//
|
|||
|
|
|||
|
SampReferenceContext( UserContext );
|
|||
|
|
|||
|
//
|
|||
|
// Stash away the password info accessible flag
|
|||
|
//
|
|||
|
|
|||
|
UserContext->TypeBody.User.DomainPasswordInformationAccessible =
|
|||
|
DomainPasswordInformationAccessible;
|
|||
|
|
|||
|
//
|
|||
|
// If MAXIMUM_ALLOWED is requested, add GENERIC_ALL
|
|||
|
//
|
|||
|
|
|||
|
if (DesiredAccess & MAXIMUM_ALLOWED) {
|
|||
|
|
|||
|
DesiredAccess |= GENERIC_ALL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If ACCESS_SYSTEM_SECURITY is requested and we are
|
|||
|
// a non-trusted client, check that we have
|
|||
|
// SE_SECURITY_PRIVILEGE.
|
|||
|
//
|
|||
|
|
|||
|
if ((DesiredAccess & ACCESS_SYSTEM_SECURITY) &&
|
|||
|
(!DomainContext->TrustedClient)) {
|
|||
|
|
|||
|
NtStatus = SampRtlWellKnownPrivilegeCheck(
|
|||
|
TRUE,
|
|||
|
SE_SECURITY_PRIVILEGE,
|
|||
|
NULL
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
if (NtStatus == STATUS_PRIVILEGE_NOT_HELD) {
|
|||
|
|
|||
|
NtStatus = STATUS_ACCESS_DENIED;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the caller can be given the requested access
|
|||
|
// to the new object
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
UserContext->GrantedAccess = DesiredAccess;
|
|||
|
|
|||
|
RtlMapGenericMask(
|
|||
|
&UserContext->GrantedAccess,
|
|||
|
&SampObjectInformation[SampUserObjectType].GenericMapping
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
if ((SampObjectInformation[SampUserObjectType].InvalidMappedAccess
|
|||
|
& UserContext->GrantedAccess) != 0) {
|
|||
|
|
|||
|
NtStatus = STATUS_ACCESS_DENIED;
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Restrict access if necessary
|
|||
|
//
|
|||
|
|
|||
|
UserContext->GrantedAccess &= AccessRestriction;
|
|||
|
(*GrantedAccess) = UserContext->GrantedAccess;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
UserContext = NULL;
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// If the GROUP we're going to put this user in
|
|||
|
// is part of an ADMIN alias, we must set the ACL
|
|||
|
// on this user account to disallow access by
|
|||
|
// account operators. Get group info to determine
|
|||
|
// whether it's in an ADMIN alias or not.
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampCreateAccountContext(
|
|||
|
SampGroupObjectType,
|
|||
|
DOMAIN_GROUP_RID_USERS,
|
|||
|
TRUE, // TrustedClient,
|
|||
|
TRUE, // Account exists
|
|||
|
&GroupContext
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SampRetrieveGroupV1Fixed(
|
|||
|
GroupContext,
|
|||
|
&GroupV1Fixed
|
|||
|
);
|
|||
|
|
|||
|
SampDeleteContext(GroupContext);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the V1_fixed attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
V1aFixed.Revision = SAMP_REVISION;
|
|||
|
|
|||
|
V1aFixed.CountryCode = 0;
|
|||
|
V1aFixed.CodePage = 0;
|
|||
|
V1aFixed.BadPasswordCount = 0;
|
|||
|
V1aFixed.LogonCount = 0;
|
|||
|
V1aFixed.AdminCount = (GroupV1Fixed.AdminCount != 0) ? 1 : 0;
|
|||
|
V1aFixed.OperatorCount = (GroupV1Fixed.OperatorCount != 0) ? 1 : 0;
|
|||
|
V1aFixed.Unused1 = 0;
|
|||
|
V1aFixed.Unused2 = 0;
|
|||
|
V1aFixed.UserAccountControl = (USER_PASSWORD_NOT_REQUIRED |
|
|||
|
AccountType);
|
|||
|
//
|
|||
|
// Disable the account unless this is a special creation
|
|||
|
// in which the creator won't be able to enable the account.
|
|||
|
//
|
|||
|
|
|||
|
if (!PrivilegedMachineAccountCreate) {
|
|||
|
V1aFixed.UserAccountControl |= USER_ACCOUNT_DISABLED;
|
|||
|
}
|
|||
|
|
|||
|
V1aFixed.UserId = NewAccountRid;
|
|||
|
V1aFixed.PrimaryGroupId = DOMAIN_GROUP_RID_USERS;
|
|||
|
V1aFixed.LastLogon = SampHasNeverTime;
|
|||
|
V1aFixed.LastLogoff = SampHasNeverTime;
|
|||
|
V1aFixed.PasswordLastSet = SampHasNeverTime;
|
|||
|
V1aFixed.AccountExpires = SampWillNeverTime;
|
|||
|
V1aFixed.LastBadPasswordTime = SampHasNeverTime;
|
|||
|
|
|||
|
NtStatus = SampSetFixedAttributes(
|
|||
|
UserContext,
|
|||
|
(PVOID *)&V1aFixed
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Set the SECURITY_DESCRIPTOR attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Build a security descriptor to protect the User.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampGetNewAccountSecurity(
|
|||
|
SampUserObjectType,
|
|||
|
(BOOLEAN) (((GroupV1Fixed.AdminCount != 0) ||
|
|||
|
(GroupV1Fixed.OperatorCount != 0)) ? TRUE : FALSE),
|
|||
|
DomainContext->TrustedClient,
|
|||
|
PrivilegedMachineAccountCreate,
|
|||
|
NewAccountRid,
|
|||
|
&NewSecurityDescriptor,
|
|||
|
&NewSecurityDescriptorLength
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetAccessAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_SECURITY_DESCRIPTOR,
|
|||
|
NewSecurityDescriptor,
|
|||
|
NewSecurityDescriptorLength
|
|||
|
);
|
|||
|
|
|||
|
MIDL_user_free( NewSecurityDescriptor );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the ACCOUNT_NAME attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_ACCOUNT_NAME,
|
|||
|
(PUNICODE_STRING)AccountName
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the FULL_NAME attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_FULL_NAME,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the AdminComment attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_ADMIN_COMMENT,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the UserComment attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_USER_COMMENT,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the Parameters attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_PARAMETERS,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the HomeDirectory attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_HOME_DIRECTORY,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the HomeDirectoryDrive attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_HOME_DIRECTORY_DRIVE,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the ScriptPath attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_SCRIPT_PATH,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the ProfilePath attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_PROFILE_PATH,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the WorkStations attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_WORKSTATIONS,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the LogonHours attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
LOGON_HOURS LogonHours;
|
|||
|
|
|||
|
LogonHours.UnitsPerWeek = 0;
|
|||
|
LogonHours.LogonHours = NULL;
|
|||
|
|
|||
|
NtStatus = SampSetLogonHoursAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_LOGON_HOURS,
|
|||
|
&LogonHours
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the Groups attribute (with membership in DomainUsers only)
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetLargeIntArrayAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_GROUPS,
|
|||
|
(PLARGE_INTEGER)&DomainUsersMember,
|
|||
|
1
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the CaseInsensitiveDbcs attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_DBCS_PWD,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Create the CaseSensitiveUnicode key
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_UNICODE_PWD,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the NtPasswordHistory attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_NT_PWD_HISTORY,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Set the LmPasswordHistory attribute
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_LM_PWD_HISTORY,
|
|||
|
&SampNullString
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Add this new user to the DomainUsers group
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampAddUserToGroup( DOMAIN_GROUP_RID_USERS, NewAccountRid );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// If we created an object, dereference it. Write out its attributes
|
|||
|
// if everything was created OK.
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object, write out any change to current xaction.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT(UserContext != NULL);
|
|||
|
NtStatus = SampDeReferenceContext( UserContext, TRUE );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (UserContext != NULL) {
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object, ignore changes
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( UserContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Commit changes and notify netlogon
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Commit the changes; hold on to the write lock for now.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCommitAndRetainWriteLock();
|
|||
|
|
|||
|
//
|
|||
|
// If we can't commit the mess for some reason, then delete
|
|||
|
// the new context block and return null for the context handle.
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
|
|||
|
SAMP_ACCOUNT_DISPLAY_INFO AccountInfo;
|
|||
|
|
|||
|
//
|
|||
|
// Update the display information
|
|||
|
//
|
|||
|
|
|||
|
AccountInfo.Name = *((PUNICODE_STRING)AccountName);
|
|||
|
AccountInfo.Rid = NewAccountRid;
|
|||
|
AccountInfo.AccountControl = V1aFixed.UserAccountControl;
|
|||
|
RtlInitUnicodeString(&AccountInfo.Comment, NULL);
|
|||
|
RtlInitUnicodeString(&AccountInfo.FullName, NULL);
|
|||
|
|
|||
|
IgnoreStatus = SampUpdateDisplayInformation(NULL,
|
|||
|
&AccountInfo,
|
|||
|
SampUserObjectType);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
//
|
|||
|
// Audit the creation before we free the write lock
|
|||
|
// so that we have access to the context block.
|
|||
|
//
|
|||
|
|
|||
|
if (SampDoAccountAuditing(UserContext->DomainIndex)) {
|
|||
|
|
|||
|
if (PrivilegedMachineAccountCreate) {
|
|||
|
|
|||
|
//
|
|||
|
// Set up the privilege set for auditing
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
Privileges.PrivilegeCount = 1;
|
|||
|
Privileges.Control = 0;
|
|||
|
ASSERT(ANYSIZE_ARRAY >= 1);
|
|||
|
Privileges.Privilege[0].Attributes = SE_PRIVILEGE_USED_FOR_ACCESS;
|
|||
|
Privileges.Privilege[0].Luid = RtlConvertUlongToLuid( SE_MACHINE_ACCOUNT_PRIVILEGE);
|
|||
|
PPrivileges = &Privileges;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
LsaIAuditSamEvent(
|
|||
|
STATUS_SUCCESS,
|
|||
|
SE_AUDITID_USER_CREATED, // AuditId
|
|||
|
Domain->Sid, // Domain SID
|
|||
|
NULL, // Member Rid (not used)
|
|||
|
NULL, // Member Sid (not used)
|
|||
|
(PUNICODE_STRING)AccountName, // Account Name
|
|||
|
&Domain->ExternalName, // Domain
|
|||
|
&NewAccountRid, // Account Rid
|
|||
|
PPrivileges // Privileges used
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Notify netlogon if a machine account was created.
|
|||
|
//
|
|||
|
|
|||
|
if ( ( V1aFixed.UserAccountControl &
|
|||
|
USER_MACHINE_ACCOUNT_MASK ) != 0 ) {
|
|||
|
|
|||
|
//
|
|||
|
// This was a machine account. Let
|
|||
|
// NetLogon know of the change.
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = I_NetNotifyMachineAccount(
|
|||
|
NewAccountRid,
|
|||
|
SampDefinedDomains[SampTransactionDomainIndex].Sid,
|
|||
|
0,
|
|||
|
V1aFixed.UserAccountControl,
|
|||
|
(PUNICODE_STRING)AccountName
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Notify netlogon of changes
|
|||
|
//
|
|||
|
|
|||
|
SampNotifyNetlogonOfDelta(
|
|||
|
SecurityDbNew,
|
|||
|
SecurityDbObjectSamUser,
|
|||
|
*RelativeId,
|
|||
|
(PUNICODE_STRING) NULL,
|
|||
|
(DWORD) FALSE, // Replicate immediately
|
|||
|
NULL // Delta data
|
|||
|
);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Return the context handle on success
|
|||
|
// Delete the context block and return a NULL handle on failure
|
|||
|
//
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
ASSERT(UserContext != NULL);
|
|||
|
(*UserHandle) = UserContext;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if (UserContext != NULL) {
|
|||
|
SampDeleteContext(UserContext);
|
|||
|
}
|
|||
|
|
|||
|
(*UserHandle) = (SAMPR_HANDLE)0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Release the lock
|
|||
|
//
|
|||
|
|
|||
|
if ( !WriteLockHeld ) {
|
|||
|
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS SamrCreateUser2InDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN PRPC_UNICODE_STRING AccountName,
|
|||
|
IN ULONG AccountType,
|
|||
|
IN ACCESS_MASK DesiredAccess,
|
|||
|
OUT SAMPR_HANDLE *UserHandle,
|
|||
|
OUT PULONG GrantedAccess,
|
|||
|
OUT PULONG RelativeId
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This is just a wrapper for SampCreateUserInDomain() that ensures
|
|||
|
RelativeId points to a RID of zero first. It also guarantees
|
|||
|
that AccountType is valid.
|
|||
|
|
|||
|
A non-zero RID means that SampCreateUserInDomain() was called by
|
|||
|
SamICreateAccountByRid(), which specifies a RID to be used.
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
Same as SampCreateUserInDomain() except AccountType maps to
|
|||
|
AccountControl.
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
Same as SampCreateUserInDomain().
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
|
|||
|
(*RelativeId) = 0;
|
|||
|
|
|||
|
//
|
|||
|
// Make sure one, and only one, account type flag is set.
|
|||
|
//
|
|||
|
|
|||
|
switch (AccountType) {
|
|||
|
|
|||
|
case USER_NORMAL_ACCOUNT :
|
|||
|
case USER_WORKSTATION_TRUST_ACCOUNT :
|
|||
|
case USER_INTERDOMAIN_TRUST_ACCOUNT :
|
|||
|
case USER_SERVER_TRUST_ACCOUNT :
|
|||
|
case USER_TEMP_DUPLICATE_ACCOUNT :
|
|||
|
|
|||
|
//
|
|||
|
// AccountType is valid
|
|||
|
//
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
default :
|
|||
|
|
|||
|
//
|
|||
|
// Bad account type value specified
|
|||
|
//
|
|||
|
|
|||
|
return( STATUS_INVALID_PARAMETER );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampCreateUserInDomain(
|
|||
|
DomainHandle,
|
|||
|
AccountName,
|
|||
|
AccountType,
|
|||
|
DesiredAccess,
|
|||
|
FALSE,
|
|||
|
UserHandle,
|
|||
|
GrantedAccess,
|
|||
|
RelativeId
|
|||
|
);
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS SamrCreateUserInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN PRPC_UNICODE_STRING AccountName,
|
|||
|
IN ACCESS_MASK DesiredAccess,
|
|||
|
OUT SAMPR_HANDLE *UserHandle,
|
|||
|
OUT PULONG RelativeId
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This is just a wrapper for SampCreateUserInDomain() that ensures that
|
|||
|
RelativeId points to a RID of zero first.
|
|||
|
|
|||
|
A non-zero RID means that SampCreateUserInDomain() was called by
|
|||
|
SamICreateAccountByRid(), which specifies a RID to be used.
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
Same as SampCreateUserInDomain() except AccountType is NORMAL_USER.
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
Same as SampCreateUserInDomain().
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
NTSTATUS
|
|||
|
NtStatus;
|
|||
|
|
|||
|
ULONG
|
|||
|
GrantedAccess;
|
|||
|
|
|||
|
(*RelativeId) = 0;
|
|||
|
|
|||
|
NtStatus = SampCreateUserInDomain(
|
|||
|
DomainHandle,
|
|||
|
AccountName,
|
|||
|
USER_NORMAL_ACCOUNT,
|
|||
|
DesiredAccess,
|
|||
|
FALSE,
|
|||
|
UserHandle,
|
|||
|
&GrantedAccess,
|
|||
|
RelativeId
|
|||
|
);
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS SamrEnumerateUsersInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN OUT PSAM_ENUMERATE_HANDLE EnumerationContext,
|
|||
|
IN ULONG UserAccountControl,
|
|||
|
OUT PSAMPR_ENUMERATION_BUFFER *Buffer,
|
|||
|
IN ULONG PreferedMaximumLength,
|
|||
|
OUT PULONG CountReturned
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API lists all the users defined in the account database. Since
|
|||
|
there may be more users than can fit into a buffer, the caller is
|
|||
|
provided with a handle that can be used across calls to the API. On
|
|||
|
the initial call, EnumerationContext should point to a
|
|||
|
SAM_ENUMERATE_HANDLE variable that is set to 0.
|
|||
|
|
|||
|
If the API returns STATUS_MORE_ENTRIES, then the API should be
|
|||
|
called again with EnumerationContext. When the API returns
|
|||
|
STATUS_SUCCESS or any error return, the handle becomes invalid for
|
|||
|
future use.
|
|||
|
|
|||
|
This API requires DOMAIN_LIST_USERS access to the Domain object.
|
|||
|
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
EnumerationContext - API specific handle to allow multiple calls.
|
|||
|
This is a zero based index.
|
|||
|
|
|||
|
UserAccountControl - Provides enumeration filtering information. Any
|
|||
|
characteristics specified here will cause that type of User account
|
|||
|
to be included in the enumeration process.
|
|||
|
|
|||
|
Buffer - Receives a pointer to the buffer containing the
|
|||
|
requested information. The information returned is
|
|||
|
structured as an array of SAM_RID_ENUMERATION 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.
|
|||
|
|
|||
|
CountReturned - Number of entries returned.
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully, and there
|
|||
|
are no additional entries.
|
|||
|
|
|||
|
STATUS_MORE_ENTRIES - There are more entries, so call again.
|
|||
|
This is a successful return.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have privilege required to
|
|||
|
request that data.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The handle passed is invalid.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus;
|
|||
|
|
|||
|
NtStatus = SampEnumerateAccountNamesCommon(
|
|||
|
DomainHandle,
|
|||
|
SampUserObjectType,
|
|||
|
EnumerationContext,
|
|||
|
Buffer,
|
|||
|
PreferedMaximumLength,
|
|||
|
UserAccountControl,
|
|||
|
CountReturned
|
|||
|
);
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS SamrLookupNamesInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN ULONG Count,
|
|||
|
IN RPC_UNICODE_STRING Names[],
|
|||
|
OUT PSAMPR_ULONG_ARRAY RelativeIds,
|
|||
|
OUT PSAMPR_ULONG_ARRAY Use
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API attempts to find relative IDs corresponding to name
|
|||
|
strings. If a name can not be mapped to a relative ID, a zero is
|
|||
|
placed in the corresponding relative ID array entry, and translation
|
|||
|
continues.
|
|||
|
|
|||
|
DOMAIN_LOOKUP access to the domain is needed to use this service.
|
|||
|
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
Count - Number of names to translate.
|
|||
|
|
|||
|
Names - Pointer to an array of Count UNICODE_STRINGs that contain
|
|||
|
the names to map to relative IDs. Case-insensitive
|
|||
|
comparisons of these names will be performed for the lookup
|
|||
|
operation.
|
|||
|
|
|||
|
RelativeIds - Receives an array of Count Relative IDs.
|
|||
|
The relative ID of the nth name will be the nth entry in this
|
|||
|
array. Any names that could not be translated will have a
|
|||
|
zero relative ID.
|
|||
|
|
|||
|
RelativeIds - Receives a pointer to a SAMPR_RETURNED_ULONG_ARRAY structure.
|
|||
|
The nth entry in the array associated with this structure
|
|||
|
contains the RID of the nth name looked up.
|
|||
|
When this information is no longer needed, the caller is responsible
|
|||
|
for deallocating each returned block (including the
|
|||
|
SAMPR_ULONG_ARRAY structure itself) using SamFreeMemory().
|
|||
|
|
|||
|
Use - Receives a pointer to a SAMPR_RETURNED_ULONG_ARRAY structure.
|
|||
|
The nth entry in the array associated with this structure
|
|||
|
contains the SID_NAME_USE of the nth name looked up.
|
|||
|
When this information is no longer needed, the caller is responsible
|
|||
|
for deallocating each returned block (including the
|
|||
|
SAMPR_ULONG_ARRAY structure itself) using SamFreeMemory().
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
|||
|
access to complete the operation.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The domain handle passed is invalid.
|
|||
|
|
|||
|
STATUS_SOME_NOT_MAPPED - Some of the names provided could not be
|
|||
|
mapped. This is a successful return.
|
|||
|
|
|||
|
STATUS_NONE_MAPPED - No names could be mapped. This is an error
|
|||
|
return.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
UNICODE_STRING KeyName;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
HANDLE TempHandle;
|
|||
|
LARGE_INTEGER IgnoreTimeStamp;
|
|||
|
ULONG i, KeyValueLength, UnMappedCount;
|
|||
|
ULONG ApproximateTotalLength;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Make sure we understand what RPC is doing for (to) us.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT (Use != NULL);
|
|||
|
ASSERT (Use->Element == NULL);
|
|||
|
ASSERT (RelativeIds != NULL);
|
|||
|
ASSERT (RelativeIds->Element == NULL);
|
|||
|
|
|||
|
Use->Count = 0;
|
|||
|
RelativeIds->Count = 0;
|
|||
|
|
|||
|
|
|||
|
if (Count == 0) {
|
|||
|
return(STATUS_SUCCESS);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the parameter values are within reasonable bounds
|
|||
|
//
|
|||
|
|
|||
|
if (Count > SAM_MAXIMUM_LOOKUP_COUNT) {
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
ApproximateTotalLength = (Count*(sizeof(ULONG) + sizeof(SID_NAME_USE)));
|
|||
|
|
|||
|
//
|
|||
|
// Do the return test inside this loop to avoid overflow problems
|
|||
|
// summing up the name lengths.
|
|||
|
//
|
|||
|
|
|||
|
for ( i=0; i<Count; i++) {
|
|||
|
ApproximateTotalLength += (ULONG)Names[i].MaximumLength;
|
|||
|
if ( ApproximateTotalLength > SAMP_MAXIMUM_MEMORY_TO_USE ) {
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Allocate the return buffers
|
|||
|
//
|
|||
|
|
|||
|
Use->Element = MIDL_user_allocate( Count * sizeof(ULONG) );
|
|||
|
if (Use->Element == NULL) {
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
|
|||
|
RelativeIds->Element = MIDL_user_allocate( Count * sizeof(ULONG) );
|
|||
|
if (RelativeIds->Element == NULL) {
|
|||
|
MIDL_user_free( Use->Element);
|
|||
|
Use->Element = NULL; // required to RPC doesn't free it again.
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
Use->Count = Count;
|
|||
|
RelativeIds->Count = Count;
|
|||
|
|
|||
|
SampAcquireReadLock();
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DOMAIN_LOOKUP,
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
UnMappedCount = Count;
|
|||
|
for ( i=0; i<Count; i++) {
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Search the groups for a match
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountKeyName(
|
|||
|
SampGroupObjectType,
|
|||
|
&KeyName,
|
|||
|
(PUNICODE_STRING)&Names[i]
|
|||
|
);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&TempHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
UnMappedCount -= 1;
|
|||
|
Use->Element[i] = SidTypeGroup;
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
TempHandle,
|
|||
|
&RelativeIds->Element[i],
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreTimeStamp
|
|||
|
);
|
|||
|
IgnoreStatus = NtClose( TempHandle );
|
|||
|
ASSERT( NT_SUCCESS(IgnoreStatus) );
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
goto unexpected_error;
|
|||
|
}
|
|||
|
ASSERT(KeyValueLength == 0);
|
|||
|
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Search the aliases for a match
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountKeyName(
|
|||
|
SampAliasObjectType,
|
|||
|
&KeyName,
|
|||
|
(PUNICODE_STRING)&Names[i]
|
|||
|
);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&TempHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
UnMappedCount -= 1;
|
|||
|
Use->Element[i] = SidTypeAlias;
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
TempHandle,
|
|||
|
&RelativeIds->Element[i],
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreTimeStamp
|
|||
|
);
|
|||
|
IgnoreStatus = NtClose( TempHandle );
|
|||
|
ASSERT( NT_SUCCESS(IgnoreStatus) );
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
goto unexpected_error;
|
|||
|
}
|
|||
|
ASSERT(KeyValueLength == 0);
|
|||
|
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Search the user for a match
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountKeyName(
|
|||
|
SampUserObjectType,
|
|||
|
&KeyName,
|
|||
|
(PUNICODE_STRING)&Names[i]
|
|||
|
);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&TempHandle,
|
|||
|
(KEY_READ),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
UnMappedCount -= 1;
|
|||
|
Use->Element[i] = SidTypeUser;
|
|||
|
KeyValueLength = 0;
|
|||
|
NtStatus = RtlpNtQueryValueKey(
|
|||
|
TempHandle,
|
|||
|
&RelativeIds->Element[i],
|
|||
|
NULL,
|
|||
|
&KeyValueLength,
|
|||
|
&IgnoreTimeStamp
|
|||
|
);
|
|||
|
IgnoreStatus = NtClose( TempHandle );
|
|||
|
ASSERT( NT_SUCCESS(IgnoreStatus) );
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
goto unexpected_error;
|
|||
|
}
|
|||
|
ASSERT(KeyValueLength == 0);
|
|||
|
|
|||
|
} else if(NtStatus == STATUS_OBJECT_NAME_NOT_FOUND) {
|
|||
|
|
|||
|
//
|
|||
|
// This is fine. It just means that we don't
|
|||
|
// have an account with the name being looked up.
|
|||
|
//
|
|||
|
|
|||
|
Use->Element[i] = SidTypeUnknown;
|
|||
|
RelativeIds->Element[i] = 0;
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus) &&
|
|||
|
NtStatus != STATUS_INVALID_ACCOUNT_NAME) {
|
|||
|
goto unexpected_error;
|
|||
|
}
|
|||
|
|
|||
|
} // end_for
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
|
|||
|
if (UnMappedCount == Count) {
|
|||
|
NtStatus = STATUS_NONE_MAPPED;
|
|||
|
} else {
|
|||
|
if (UnMappedCount > 0) {
|
|||
|
NtStatus = STATUS_SOME_NOT_MAPPED;
|
|||
|
} else {
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free the read lock
|
|||
|
//
|
|||
|
|
|||
|
SampReleaseReadLock();
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// If the status isn't one of the expected return values,
|
|||
|
// then deallocate the return memory block
|
|||
|
//
|
|||
|
|
|||
|
if ( ( NtStatus != STATUS_SUCCESS ) &&
|
|||
|
( NtStatus != STATUS_SOME_NOT_MAPPED ) ) {
|
|||
|
|
|||
|
Use->Count = 0;
|
|||
|
MIDL_user_free( Use->Element );
|
|||
|
Use->Element = NULL;
|
|||
|
RelativeIds->Count = 0;
|
|||
|
MIDL_user_free( RelativeIds->Element );
|
|||
|
RelativeIds->Element = NULL;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
|
|||
|
|
|||
|
unexpected_error:
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
|
|||
|
//
|
|||
|
// Free the read lock
|
|||
|
//
|
|||
|
|
|||
|
SampReleaseReadLock();
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Don't return any memory
|
|||
|
//
|
|||
|
|
|||
|
Use->Count = 0;
|
|||
|
MIDL_user_free( Use->Element );
|
|||
|
Use->Element = NULL; // Required so RPC doesn't try to free the element
|
|||
|
RelativeIds->Count = 0;
|
|||
|
MIDL_user_free( RelativeIds->Element );
|
|||
|
RelativeIds->Element = NULL; // Required so RPC doesn't try to free the element
|
|||
|
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS SamrLookupIdsInDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN ULONG Count,
|
|||
|
IN PULONG RelativeIds,
|
|||
|
OUT PSAMPR_RETURNED_USTRING_ARRAY Names,
|
|||
|
OUT PSAMPR_ULONG_ARRAY Use
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This API maps a number of relative IDs to their corresponding names.
|
|||
|
If a relative ID can not be mapped, a NULL value is placed in the slot
|
|||
|
for the UNICODE_STRING, and STATUS_SOME_NOT_MAPPED is returned.
|
|||
|
If none of the IDs can be mapped, then all array entries will contain
|
|||
|
NULL values and STATUS_NONE_MAPPED is returned.
|
|||
|
|
|||
|
DOMAIN_LOOKUP access to the domain is needed to use this service.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Parameters:
|
|||
|
|
|||
|
DomainHandle - A domain handle returned from a previous call to
|
|||
|
SamOpenDomain.
|
|||
|
|
|||
|
Count - Provides the number of relative IDs to translate.
|
|||
|
|
|||
|
RelativeIds - Array of Count relative IDs to be mapped.
|
|||
|
|
|||
|
Names - Receives a pointer to an allocated SAMPR_UNICODE_STRING_ARRAY.
|
|||
|
The nth entry in the array of names pointed to by this structure
|
|||
|
corresonds to the nth relative id looked up.
|
|||
|
Each name string buffer will be in a separate block of memory
|
|||
|
allocated by this routine. When these names are no longer
|
|||
|
needed, the caller is responsible for deallocating each
|
|||
|
returned block (including the SAMPR_RETURNED_USTRING_ARRAY structure
|
|||
|
itself) using SamFreeMemory().
|
|||
|
|
|||
|
Use - Receives a pointer to a SAMPR_ULONG_ARRAY structure.
|
|||
|
The nth entry in the array associated with this structure
|
|||
|
contains the SID_NAME_USE of the nth relative ID looked up.
|
|||
|
When this information is no longer needed, the caller is responsible
|
|||
|
for deallocating this memory using SamFreeMemory().
|
|||
|
|
|||
|
Return Values:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully.
|
|||
|
|
|||
|
STATUS_ACCESS_DENIED - Caller does not have the appropriate
|
|||
|
access to complete the operation.
|
|||
|
|
|||
|
STATUS_INVALID_HANDLE - The domain handle passed is invalid.
|
|||
|
|
|||
|
STATUS_SOME_NOT_MAPPED - Some of the names provided could not be
|
|||
|
mapped. This is a successful return.
|
|||
|
|
|||
|
STATUS_NONE_MAPPED - No names could be mapped. This is an error
|
|||
|
return.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
SAMP_OBJECT_TYPE ObjectType;
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
ULONG i, UnMappedCount;
|
|||
|
ULONG TotalLength;
|
|||
|
PSAMP_MEMORY NextMemory;
|
|||
|
SAMP_MEMORY MemoryHead;
|
|||
|
|
|||
|
BOOLEAN LengthLimitReached = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// Used for tracking allocated memory so we can deallocate it on
|
|||
|
// error
|
|||
|
//
|
|||
|
|
|||
|
MemoryHead.Memory = NULL;
|
|||
|
MemoryHead.Next = NULL;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Make sure we understand what RPC is doing for (to) us.
|
|||
|
//
|
|||
|
|
|||
|
ASSERT (RelativeIds != NULL);
|
|||
|
ASSERT (Use != NULL);
|
|||
|
ASSERT (Use->Element == NULL);
|
|||
|
ASSERT (Names != NULL);
|
|||
|
ASSERT (Names->Element == NULL);
|
|||
|
|
|||
|
Use->Count = 0;
|
|||
|
Names->Count = 0;
|
|||
|
|
|||
|
if (Count == 0) {
|
|||
|
return(STATUS_SUCCESS);
|
|||
|
}
|
|||
|
|
|||
|
TotalLength = (Count*(sizeof(ULONG) + sizeof(UNICODE_STRING)));
|
|||
|
|
|||
|
if ( TotalLength > SAMP_MAXIMUM_MEMORY_TO_USE ) {
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
|
|||
|
if (Count == 0) {
|
|||
|
return(STATUS_SUCCESS);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Allocate the return buffers
|
|||
|
//
|
|||
|
|
|||
|
Use->Element = MIDL_user_allocate( Count * sizeof(ULONG) );
|
|||
|
if (Use->Element == NULL) {
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
Names->Element = MIDL_user_allocate( Count * sizeof(UNICODE_STRING) );
|
|||
|
if (Names->Element == NULL) {
|
|||
|
MIDL_user_free( Use->Element);
|
|||
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|||
|
}
|
|||
|
|
|||
|
Use->Count = Count;
|
|||
|
Names->Count = Count;
|
|||
|
|
|||
|
SampAcquireReadLock();
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
DOMAIN_LOOKUP,
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
UnMappedCount = Count;
|
|||
|
for ( i=0; i<Count; i++) {
|
|||
|
|
|||
|
//
|
|||
|
// allocate a block to track a name allocated for this mapping
|
|||
|
//
|
|||
|
|
|||
|
NextMemory = MIDL_user_allocate( sizeof(SAMP_MEMORY) );
|
|||
|
if (NextMemory == NULL) {
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
goto unexpected_error;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampLookupAccountName(
|
|||
|
RelativeIds[i],
|
|||
|
(PUNICODE_STRING)&Names->Element[i],
|
|||
|
&ObjectType
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
goto unexpected_error;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
switch (ObjectType) {
|
|||
|
|
|||
|
case SampUserObjectType:
|
|||
|
case SampGroupObjectType:
|
|||
|
case SampAliasObjectType:
|
|||
|
|
|||
|
//
|
|||
|
// We found the account
|
|||
|
//
|
|||
|
|
|||
|
UnMappedCount -= 1;
|
|||
|
|
|||
|
NextMemory->Memory = (PVOID)&Names->Element[i].Buffer;
|
|||
|
NextMemory->Next = MemoryHead.Next;
|
|||
|
MemoryHead.Next = NextMemory;
|
|||
|
|
|||
|
switch (ObjectType) {
|
|||
|
|
|||
|
case SampUserObjectType:
|
|||
|
Use->Element[i] = SidTypeUser;
|
|||
|
break;
|
|||
|
|
|||
|
case SampGroupObjectType:
|
|||
|
Use->Element[i] = SidTypeGroup;
|
|||
|
break;
|
|||
|
|
|||
|
case SampAliasObjectType:
|
|||
|
Use->Element[i] = SidTypeAlias;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
|
|||
|
case SampUnknownObjectType:
|
|||
|
|
|||
|
//
|
|||
|
// Hmmm - don't know what this rid is. It's either been
|
|||
|
// deleted, or a bogus RID.
|
|||
|
//
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
|
|||
|
|
|||
|
if ( ( RelativeIds[i] >= SAMP_RESTRICTED_ACCOUNT_COUNT ) &&
|
|||
|
( RelativeIds[i] < Domain->CurrentFixed.NextRid ) ) {
|
|||
|
|
|||
|
Use->Element[i] = SidTypeDeletedAccount;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
Use->Element[i] = SidTypeUnknown;
|
|||
|
}
|
|||
|
|
|||
|
Names->Element[i].Length = 0;
|
|||
|
Names->Element[i].MaximumLength = 0;
|
|||
|
Names->Element[i].Buffer = NULL;
|
|||
|
MIDL_user_free( NextMemory );
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
|
|||
|
ASSERT(FALSE); // unexpected object type returned
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
} // end_for
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
|
|||
|
if (UnMappedCount == Count) {
|
|||
|
NtStatus = STATUS_NONE_MAPPED;
|
|||
|
} else {
|
|||
|
if (UnMappedCount > 0) {
|
|||
|
NtStatus = STATUS_SOME_NOT_MAPPED;
|
|||
|
} else {
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free the read lock
|
|||
|
//
|
|||
|
|
|||
|
SampReleaseReadLock();
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Free all the memory tracking blocks
|
|||
|
//
|
|||
|
|
|||
|
NextMemory = MemoryHead.Next;
|
|||
|
while ( NextMemory != NULL ) {
|
|||
|
MemoryHead.Next = NextMemory->Next;
|
|||
|
MIDL_user_free( NextMemory );
|
|||
|
NextMemory = MemoryHead.Next;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
|
|||
|
|
|||
|
unexpected_error:
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
|
|||
|
//
|
|||
|
// Free the read lock
|
|||
|
//
|
|||
|
|
|||
|
SampReleaseReadLock();
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Free all the memory tracking blocks - and the memory they point to.
|
|||
|
//
|
|||
|
|
|||
|
Use->Count = 0;
|
|||
|
Names->Count = 0;
|
|||
|
MIDL_user_free( Use->Element );
|
|||
|
MIDL_user_free( Names->Element );
|
|||
|
NextMemory = MemoryHead.Next;
|
|||
|
while ( NextMemory != NULL ) {
|
|||
|
if (NextMemory->Memory != NULL) {
|
|||
|
MIDL_user_free( NextMemory->Memory );
|
|||
|
}
|
|||
|
MemoryHead.Next = NextMemory->Next;
|
|||
|
MIDL_user_free( NextMemory );
|
|||
|
NextMemory = MemoryHead.Next;
|
|||
|
}
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// private services //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampOpenDomainKey(
|
|||
|
IN PSAMP_OBJECT DomainContext,
|
|||
|
IN PRPC_SID DomainId
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service attempts to open the root registry key of the domain with
|
|||
|
the specified SID. The root name and key handle are put in the
|
|||
|
passed domain context.
|
|||
|
|
|||
|
|
|||
|
If successful, and the domain key is opened, then the opened domain is
|
|||
|
established as the transaction domain (using SampSetTransactionDomain()).
|
|||
|
|
|||
|
THIS SERVICE MUST BE CALLED WITH THE SampLock() HELD FOR READ OR
|
|||
|
WRITE ACCESS.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainContext - Context in which root namd and handle are stored.
|
|||
|
|
|||
|
DomainId - Specifies the SID of the domain to open.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The domain has been openned.
|
|||
|
|
|||
|
STATUS_NO_SUCH_DOMAIN - The domain object could not be found.
|
|||
|
|
|||
|
STATUS_INSUFFICIENT_RESOURCES - The domain object could not be openned
|
|||
|
due to the lack of some resource (probably memory).
|
|||
|
|
|||
|
STATUS_INVALID_SID - The sid provided as the domain identifier is not
|
|||
|
a valid SID structure.
|
|||
|
|
|||
|
Other errors that might be returned are values returned by:
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
ULONG i;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Make sure the SID provided is legitimate...
|
|||
|
//
|
|||
|
|
|||
|
if ( !RtlValidSid(DomainId)) {
|
|||
|
NtStatus = STATUS_INVALID_SID;
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// Set our default completion status
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_NO_SUCH_DOMAIN;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Search the list of defined domains for a match.
|
|||
|
//
|
|||
|
|
|||
|
for (i = 0; i<SampDefinedDomainsCount; i++ ) {
|
|||
|
|
|||
|
if (RtlEqualSid( DomainId, SampDefinedDomains[i].Sid)) {
|
|||
|
|
|||
|
//
|
|||
|
// Copy the found name and handle into the context
|
|||
|
// Note we reference the key handle in the defined_domains
|
|||
|
// structure directly since it is not closed
|
|||
|
// when the context is deleted.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext->RootKey = SampDefinedDomains[i].Context->RootKey;
|
|||
|
DomainContext->RootName = SampDefinedDomains[i].Context->RootName;
|
|||
|
DomainContext->DomainIndex = i;
|
|||
|
|
|||
|
//
|
|||
|
// Set the transaction domain to the one found
|
|||
|
//
|
|||
|
|
|||
|
SampSetTransactionDomain( i );
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
break; // out of for
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
// //
|
|||
|
// Routines available to other SAM modules //
|
|||
|
// //
|
|||
|
///////////////////////////////////////////////////////////////////////////////
|
|||
|
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
SampInitializeDomainObject( VOID )
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service performs initialization functions related to the Domain
|
|||
|
object class.
|
|||
|
|
|||
|
This involves:
|
|||
|
|
|||
|
1) Openning the DOMAINS registry key.
|
|||
|
|
|||
|
2) Obtaining the name of each domain (builtin and account)
|
|||
|
and opening that domain.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
TRUE - Indicates initialization was performed successfully.
|
|||
|
|
|||
|
FALSE - Indicates initialization was not performed successfully.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus;
|
|||
|
ULONG DefinedDomainsSize, i, j, k;
|
|||
|
BOOLEAN ReturnStatus = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// Open all domains and keep information about each in memory for
|
|||
|
// somewhat fast processing and less complicated code strewn throughout.
|
|||
|
//
|
|||
|
// This concept will work in the future
|
|||
|
// but we will have to allow dynamic re-sizing of this array
|
|||
|
// when domains can be added and/or deleted. For the first
|
|||
|
// revision, there exactly 2 domains and they can't be deleted.
|
|||
|
//
|
|||
|
|
|||
|
SampDefinedDomainsCount = 2;
|
|||
|
DefinedDomainsSize = SampDefinedDomainsCount * sizeof(SAMP_DEFINED_DOMAINS);
|
|||
|
SampDefinedDomains = MIDL_user_allocate( DefinedDomainsSize );
|
|||
|
|
|||
|
//
|
|||
|
// Get the BUILTIN and ACCOUNT domain information from the LSA
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampSetDomainPolicy();
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
return(FALSE);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Now prepare each of these domains
|
|||
|
//
|
|||
|
|
|||
|
i = 0; // Index into DefinedDomains array
|
|||
|
k = SampDefinedDomainsCount;
|
|||
|
for (j=0; j<k; j++) {
|
|||
|
NtStatus = SampInitializeSingleDomain( i );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
i++;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// If a domain didn't initialize, shift the last
|
|||
|
// domain into its slot (assuming this isn't the last
|
|||
|
// domain). Don't try to free the name buffers on error.
|
|||
|
// The builtin domain's name is not in an allocated buffer.
|
|||
|
//
|
|||
|
//
|
|||
|
|
|||
|
if (i != (SampDefinedDomainsCount-1)) {
|
|||
|
|
|||
|
SampDefinedDomains[i] =
|
|||
|
SampDefinedDomains[SampDefinedDomainsCount-1];
|
|||
|
|
|||
|
SampDefinedDomains[SampDefinedDomainsCount-1].ExternalName.Buffer = NULL;
|
|||
|
SampDefinedDomains[SampDefinedDomainsCount-1].InternalName.Buffer = NULL;
|
|||
|
SampDefinedDomains[SampDefinedDomainsCount-1].Sid = NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// And reduce the number of defined domains we have
|
|||
|
//
|
|||
|
|
|||
|
SampDefinedDomainsCount --;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// We might not have successfully initialized all domains,
|
|||
|
// so set the final tally accordingly.
|
|||
|
//
|
|||
|
|
|||
|
#if DBG
|
|||
|
if (SampDefinedDomainsCount != 2) {
|
|||
|
NTSTATUS IgnoreStatus;
|
|||
|
if (SampDefinedDomainsCount == 0) {
|
|||
|
|
|||
|
|
|||
|
DbgPrint("\n\n");
|
|||
|
DbgPrint("SAMSS: Neither of the two SAM domains has initialized.\n");
|
|||
|
DbgPrint(" This will not prevent the system from booting,\n");
|
|||
|
DbgPrint(" but logons will be prohibited and the system will\n");
|
|||
|
DbgPrint(" not be usable.\n");
|
|||
|
DbgPrint("\n\n");
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
DbgPrint("\n\n");
|
|||
|
DbgPrint("SAMSS: Only one of the two SAM domains has initialized.\n");
|
|||
|
DbgPrint(" This will not prevent the system from booting,\n");
|
|||
|
DbgPrint(" but may result in abnormal logon or user security\n");
|
|||
|
DbgPrint(" context behaviour.\n\n");
|
|||
|
DbgPrint(" One typical cause of this is changing your machine\n");
|
|||
|
DbgPrint(" from a WinNt system to a LanManNT system (or vica versa).\n");
|
|||
|
DbgPrint(" You will need to delete (or rename) your existing\n");
|
|||
|
DbgPrint(" SAM database and generate a new one.\n");
|
|||
|
DbgPrint("\n\n");
|
|||
|
|
|||
|
IgnoreStatus = NtClose(SampDefinedDomains[0].Context->RootKey);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
SampFreeUnicodeString(&SampDefinedDomains[0].Context->RootName);
|
|||
|
}
|
|||
|
DbgPrint(" NOTE: in the end-product this will prevent booting.\n");
|
|||
|
DbgPrint(" For development, the System account is still\n");
|
|||
|
DbgPrint(" usable.\n");
|
|||
|
DbgPrint("\n\n");
|
|||
|
DbgPrint(" You probably want to logon as SYSTEM and run BLDSAM2.EXE\n\n");
|
|||
|
|
|||
|
//
|
|||
|
// Allow the existing SAM database to be moved or deleted
|
|||
|
// by closing handles that are still open within it.
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = NtClose( SampKey );
|
|||
|
}
|
|||
|
#endif //DBG
|
|||
|
|
|||
|
|
|||
|
return(TRUE);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampInitializeSingleDomain(
|
|||
|
ULONG Index
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service opens a single domain that is expected to be in the
|
|||
|
SAM database.
|
|||
|
|
|||
|
The name and SID of the DefinedDomains array entry are expected
|
|||
|
to be filled in by the caller.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Index - An index into the DefinedDomains array. This array
|
|||
|
contains information about the domain being openned,
|
|||
|
including its name. The remainder of this array entry
|
|||
|
is filled in by this routine.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
PSID Sid;
|
|||
|
PSAMP_V1_0A_FIXED_LENGTH_DOMAIN V1aFixed;
|
|||
|
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Initialize everything we might have to cleanup on error
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = NULL;
|
|||
|
|
|||
|
//
|
|||
|
// Initialize the user & group context block list heads
|
|||
|
//
|
|||
|
|
|||
|
InitializeListHead( &SampDefinedDomains[Index].UserContextHead );
|
|||
|
InitializeListHead( &SampDefinedDomains[Index].GroupContextHead );
|
|||
|
InitializeListHead( &SampDefinedDomains[Index].AliasContextHead );
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Create a context for this domain object.
|
|||
|
// We'll keep this context around until SAM is shutdown
|
|||
|
// We store the context handle in the defined_domains structure.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = SampCreateContext( SampDomainObjectType, TRUE );
|
|||
|
|
|||
|
if ( DomainContext == NULL ) {
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
goto error_cleanup;
|
|||
|
}
|
|||
|
|
|||
|
DomainContext->DomainIndex = Index;
|
|||
|
|
|||
|
//
|
|||
|
// Create the name of the root key name of this domain in the registry.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildDomainKeyName(
|
|||
|
&DomainContext->RootName,
|
|||
|
&SampDefinedDomains[Index].InternalName
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
DomainContext->RootName.Buffer = NULL;
|
|||
|
goto error_cleanup;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Open the root key and store the handle in the context
|
|||
|
//
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&DomainContext->RootName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&DomainContext->RootKey,
|
|||
|
(KEY_READ | KEY_WRITE),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
#if DBG
|
|||
|
DbgPrint("SAMSS: Failed to open %Z Domain.\n",
|
|||
|
&SampDefinedDomains[Index].ExternalName);
|
|||
|
#endif //DBG
|
|||
|
DomainContext->RootKey = INVALID_HANDLE_VALUE;
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Get the fixed length data for the domain and store in
|
|||
|
// the defined_domain structure
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampGetFixedAttributes(
|
|||
|
DomainContext,
|
|||
|
FALSE, // Don't make copy
|
|||
|
(PVOID *)&V1aFixed
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
#if DBG
|
|||
|
DbgPrint("SAMSS: Failed to get fixed attributes for %Z Domain.\n",
|
|||
|
&SampDefinedDomains[Index].ExternalName);
|
|||
|
#endif //DBG
|
|||
|
|
|||
|
goto error_cleanup;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
RtlMoveMemory(
|
|||
|
&SampDefinedDomains[Index].UnmodifiedFixed,
|
|||
|
V1aFixed,
|
|||
|
sizeof(*V1aFixed)
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Get the sid attribute of the domain
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampGetSidAttribute(
|
|||
|
DomainContext,
|
|||
|
SAMP_DOMAIN_SID,
|
|||
|
FALSE,
|
|||
|
&Sid
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
#if DBG
|
|||
|
DbgPrint("SAMSS: Failed to get SID attribute for %Z Domain.\n",
|
|||
|
&SampDefinedDomains[Index].ExternalName);
|
|||
|
#endif //DBG
|
|||
|
goto error_cleanup;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Make sure this sid agrees with the one we were passed
|
|||
|
//
|
|||
|
|
|||
|
if (RtlEqualSid(Sid, SampDefinedDomains[Index].Sid) != TRUE) {
|
|||
|
#if DBG
|
|||
|
DbgPrint("SAMSS: Database corruption for %Z Domain.\n",
|
|||
|
&SampDefinedDomains[Index].ExternalName);
|
|||
|
#endif //DBG
|
|||
|
NtStatus = STATUS_INVALID_ID_AUTHORITY;
|
|||
|
goto error_cleanup;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Build security descriptors for use in user and group account creations
|
|||
|
// in this domain.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampInitializeDomainDescriptors( Index );
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
goto error_cleanup;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Intialize the cached display information
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampInitializeDisplayInformation( Index );
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Store the context handle in the defined_domain structure
|
|||
|
//
|
|||
|
|
|||
|
SampDefinedDomains[Index].Context = DomainContext;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
|
|||
|
error_cleanup:
|
|||
|
|
|||
|
#if DBG
|
|||
|
DbgPrint(" Status is 0x%lx \n", NtStatus);
|
|||
|
#endif //DBG
|
|||
|
|
|||
|
|
|||
|
if (DomainContext != 0) {
|
|||
|
|
|||
|
SampFreeUnicodeString(&DomainContext->RootName);
|
|||
|
|
|||
|
if (DomainContext->RootKey != INVALID_HANDLE_VALUE) {
|
|||
|
|
|||
|
IgnoreStatus = NtClose(DomainContext->RootKey);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampSetDomainPolicy(
|
|||
|
)
|
|||
|
/*++
|
|||
|
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine sets the names and SIDs for the builtin and account domains.
|
|||
|
The builtin account domain has a well known name and SID.
|
|||
|
The account domain has these stored in the Policy database.
|
|||
|
|
|||
|
|
|||
|
It places the information for the builtin domain in
|
|||
|
SampDefinedDomains[0] and the information for the account
|
|||
|
domain in SampDefinedDomains[1].
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
None.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
PPOLICY_ACCOUNT_DOMAIN_INFO PolicyAccountDomainInfo;
|
|||
|
SID_IDENTIFIER_AUTHORITY BuiltinAuthority = SECURITY_NT_AUTHORITY;
|
|||
|
|
|||
|
//
|
|||
|
// Builtin domain - Well-known External Name and SID
|
|||
|
// Constant Internal Name
|
|||
|
|
|||
|
RtlInitUnicodeString( &SampDefinedDomains[0].InternalName, L"Builtin");
|
|||
|
RtlInitUnicodeString( &SampDefinedDomains[0].ExternalName, L"Builtin");
|
|||
|
|
|||
|
SampDefinedDomains[0].Sid = RtlAllocateHeap(RtlProcessHeap(), 0,RtlLengthRequiredSid( 1 ));
|
|||
|
ASSERT( SampDefinedDomains[0].Sid != NULL );
|
|||
|
RtlInitializeSid(
|
|||
|
SampDefinedDomains[0].Sid, &BuiltinAuthority, 1 );
|
|||
|
*(RtlSubAuthoritySid( SampDefinedDomains[0].Sid, 0 )) = SECURITY_BUILTIN_DOMAIN_RID;
|
|||
|
|
|||
|
//
|
|||
|
// Account domain - Configurable External Name and Sid
|
|||
|
// The External Name is held in the LSA Policy
|
|||
|
// Database. It is equal to the Domain Name for DC's
|
|||
|
// or the Computer Name for Workstations.
|
|||
|
// Constant Internal Name
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampGetAccountDomainInfo( &PolicyAccountDomainInfo );
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
SampDefinedDomains[1].Sid = PolicyAccountDomainInfo->DomainSid;
|
|||
|
SampDefinedDomains[1].ExternalName = PolicyAccountDomainInfo->DomainName;
|
|||
|
RtlInitUnicodeString( &SampDefinedDomains[1].InternalName, L"Account");
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampReInitializeSingleDomain(
|
|||
|
ULONG Index
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service reinitializes a single domain after a registry hive refresh.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Index - An index into the DefinedDomains array.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS : The domain was re-initialized successfully.
|
|||
|
|
|||
|
Other failure codes.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
PSAMP_V1_0A_FIXED_LENGTH_DOMAIN V1aFixed;
|
|||
|
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[Index];
|
|||
|
|
|||
|
//
|
|||
|
// Create a context for this domain object.
|
|||
|
// We'll keep this context around until SAM is shutdown
|
|||
|
// We store the context handle in the defined_domains structure.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = SampCreateContext( SampDomainObjectType, TRUE );
|
|||
|
|
|||
|
if ( DomainContext == NULL ) {
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
goto error_cleanup;
|
|||
|
}
|
|||
|
|
|||
|
DomainContext->DomainIndex = Index;
|
|||
|
|
|||
|
//
|
|||
|
// Create the name of the root key name of this domain in the registry.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildDomainKeyName(
|
|||
|
&DomainContext->RootName,
|
|||
|
&SampDefinedDomains[Index].InternalName
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
RtlInitUnicodeString(&DomainContext->RootName, NULL);
|
|||
|
goto error_cleanup;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Open the root key and store the handle in the context
|
|||
|
//
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&DomainContext->RootName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&DomainContext->RootKey,
|
|||
|
(KEY_READ | KEY_WRITE),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAMSS: Failed to open %Z Domain.\n", &SampDefinedDomains[Index].ExternalName));
|
|||
|
DomainContext->RootKey = INVALID_HANDLE_VALUE;
|
|||
|
goto error_cleanup;
|
|||
|
}
|
|||
|
|
|||
|
KdPrint(("SAM New domain %d key : 0x%lx\n", Index, DomainContext->RootKey));
|
|||
|
|
|||
|
//
|
|||
|
// Get the fixed length data for the domain and store in
|
|||
|
// the defined_domain structure
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampGetFixedAttributes(
|
|||
|
DomainContext,
|
|||
|
FALSE, // Don't make copy
|
|||
|
(PVOID *)&V1aFixed
|
|||
|
);
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
KdPrint(("SAMSS: Failed to get fixed attributes for %Z Domain.\n", &SampDefinedDomains[Index].ExternalName));
|
|||
|
goto error_cleanup;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Copy the fixed-length data into our in-memory data area for this domain.
|
|||
|
//
|
|||
|
|
|||
|
RtlMoveMemory(
|
|||
|
&SampDefinedDomains[Index].UnmodifiedFixed,
|
|||
|
V1aFixed,
|
|||
|
sizeof(*V1aFixed)
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Delete any cached display information
|
|||
|
//
|
|||
|
|
|||
|
{
|
|||
|
ULONG OldTransactionDomainIndex = SampTransactionDomainIndex;
|
|||
|
SampTransactionDomainIndex = Index;
|
|||
|
|
|||
|
NtStatus = SampMarkDisplayInformationInvalid(SampUserObjectType);
|
|||
|
NtStatus = SampMarkDisplayInformationInvalid(SampGroupObjectType);
|
|||
|
|
|||
|
SampTransactionDomainIndex = OldTransactionDomainIndex;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Store the context handle in the defined_domain structure
|
|||
|
//
|
|||
|
|
|||
|
SampDeleteContext(Domain->Context);
|
|||
|
Domain->Context = DomainContext;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Close all account context registry handles for existing
|
|||
|
// open contexts
|
|||
|
//
|
|||
|
|
|||
|
SampInvalidateContextListKeys(&Domain->UserContextHead, TRUE);
|
|||
|
SampInvalidateContextListKeys(&Domain->GroupContextHead, TRUE);
|
|||
|
SampInvalidateContextListKeys(&Domain->AliasContextHead, TRUE);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
|
|||
|
error_cleanup:
|
|||
|
|
|||
|
KdPrint((" Status is 0x%lx \n", NtStatus));
|
|||
|
|
|||
|
if (DomainContext != NULL) {
|
|||
|
SampDeleteContext(DomainContext);
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampCollisionError(
|
|||
|
IN SAMP_OBJECT_TYPE ObjectType
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine is called by SamICreateAccountByRid when there is a
|
|||
|
name or RID collision. It accepts the type of account which caused
|
|||
|
the collision, and returns the appropriate error status.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ObjectType - The type of account that has the same Rid or Name (but
|
|||
|
not both) as the account that was to be created.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_USER_EXISTS - An object with the specified name could not be
|
|||
|
created because a User account with that name or RID already exists.
|
|||
|
|
|||
|
STATUS_GROUP_EXISTS - An object with the specified name could not be
|
|||
|
created because a Group account with that name or RID already exists.
|
|||
|
|
|||
|
STATUS_ALIAS_EXISTS - An object with the specified name could not be
|
|||
|
created because an Alias account with that name or RID already exists.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
//
|
|||
|
// Name collision. Return offending RID and appropriate
|
|||
|
// error code.
|
|||
|
//
|
|||
|
|
|||
|
switch ( ObjectType ) {
|
|||
|
|
|||
|
case SampAliasObjectType: {
|
|||
|
|
|||
|
return STATUS_ALIAS_EXISTS;
|
|||
|
}
|
|||
|
|
|||
|
case SampGroupObjectType: {
|
|||
|
|
|||
|
return STATUS_GROUP_EXISTS;
|
|||
|
}
|
|||
|
|
|||
|
case SampUserObjectType: {
|
|||
|
|
|||
|
return STATUS_USER_EXISTS;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamICreateAccountByRid(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN SAM_ACCOUNT_TYPE AccountType,
|
|||
|
IN ULONG RelativeId,
|
|||
|
IN PRPC_UNICODE_STRING AccountName,
|
|||
|
IN ACCESS_MASK DesiredAccess,
|
|||
|
OUT SAMPR_HANDLE *AccountHandle,
|
|||
|
OUT ULONG *ConflictingAccountRid
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service creates a user, group or alias account with a specific
|
|||
|
RID value.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainHandle - A handle to an open domain.
|
|||
|
|
|||
|
AccountType - Specifies which type of account is being created.
|
|||
|
|
|||
|
RelativeId - The relative ID to be assigned to the account. If an
|
|||
|
account of the specified type and specified RID value and
|
|||
|
specified name already exists, then it will be opened. If an
|
|||
|
account exists with any of this information in conflict, then an
|
|||
|
error will be returned indicating what the problem is.
|
|||
|
|
|||
|
AccountName - The name to assign to the account. If an account of
|
|||
|
the specified type and specified RID value and specified name
|
|||
|
already exists, then it will be opened. If an account exists with
|
|||
|
any of this information in conflict, then an error will be returned
|
|||
|
indicating what the problem is.
|
|||
|
|
|||
|
DesiredAccess - Specifies the accesses desired to the account object.
|
|||
|
|
|||
|
AccountHandle - Recieves a handle to the account object.
|
|||
|
|
|||
|
ConflictingAccountRid - If another account with the same name or RID
|
|||
|
prevents this account from being created, then this will receive
|
|||
|
the RID of the conflicting account (in the case of conflicting
|
|||
|
RIDs, this means that we return the RID that was passed in).
|
|||
|
The error value indicates the type of the account.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The object has been successfully opened or created.
|
|||
|
|
|||
|
STATUS_OBJECT_TYPE_MISMATCH - The specified object type did not match
|
|||
|
the type of the object found with the specified RID.
|
|||
|
|
|||
|
STATUS_USER_EXISTS - An object with the specified name could not be
|
|||
|
created because a User account with that name already exists.
|
|||
|
|
|||
|
STATUS_GROUP_EXISTS - An object with the specified name could not be
|
|||
|
created because a Group account with that name already exists.
|
|||
|
|
|||
|
STATUS_ALIAS_EXISTS - An object with the specified name could not be
|
|||
|
created because an Alias account with that name already exists.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
UNICODE_STRING KeyName;
|
|||
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|||
|
SAM_ACCOUNT_TYPE ObjectType, SecondObjectType, ThirdObjectType;
|
|||
|
SID_NAME_USE SidNameUse;
|
|||
|
HANDLE KeyHandle;
|
|||
|
NTSTATUS NtStatus, IgnoreStatus,
|
|||
|
NotFoundStatus, FoundButWrongStatus;
|
|||
|
ACCESS_MASK GrantedAccess;
|
|||
|
|
|||
|
ASSERT( RelativeId != 0 );
|
|||
|
|
|||
|
switch ( AccountType ) {
|
|||
|
|
|||
|
case SamObjectUser: {
|
|||
|
|
|||
|
ObjectType = SampUserObjectType;
|
|||
|
SecondObjectType = SampAliasObjectType;
|
|||
|
ThirdObjectType = SampGroupObjectType;
|
|||
|
NotFoundStatus = STATUS_NO_SUCH_USER;
|
|||
|
FoundButWrongStatus = STATUS_USER_EXISTS;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case SamObjectGroup: {
|
|||
|
|
|||
|
ObjectType = SampGroupObjectType;
|
|||
|
SecondObjectType = SampAliasObjectType;
|
|||
|
ThirdObjectType = SampUserObjectType;
|
|||
|
NotFoundStatus = STATUS_NO_SUCH_GROUP;
|
|||
|
FoundButWrongStatus = STATUS_GROUP_EXISTS;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case SamObjectAlias: {
|
|||
|
|
|||
|
ObjectType = SampAliasObjectType;
|
|||
|
SecondObjectType = SampGroupObjectType;
|
|||
|
ThirdObjectType = SampUserObjectType;
|
|||
|
NotFoundStatus = STATUS_NO_SUCH_ALIAS;
|
|||
|
FoundButWrongStatus = STATUS_ALIAS_EXISTS;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
default: {
|
|||
|
|
|||
|
return( STATUS_INVALID_PARAMETER );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// See if the account specified already exists.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
|
|||
|
if ( !NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
0,
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampLookupAccountRid(
|
|||
|
ObjectType,
|
|||
|
(PUNICODE_STRING)AccountName,
|
|||
|
NotFoundStatus,
|
|||
|
ConflictingAccountRid,
|
|||
|
&SidNameUse
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// The NAME exists; now we have to check the RID.
|
|||
|
//
|
|||
|
|
|||
|
if ( (*ConflictingAccountRid) == RelativeId ) {
|
|||
|
|
|||
|
//
|
|||
|
// The correct account already exists, so just open it.
|
|||
|
//
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
NtStatus = SampOpenAccount(
|
|||
|
ObjectType,
|
|||
|
DomainHandle,
|
|||
|
DesiredAccess,
|
|||
|
RelativeId,
|
|||
|
TRUE, //we already have the lock
|
|||
|
AccountHandle
|
|||
|
);
|
|||
|
|
|||
|
goto Done;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// An account with the given name, but a different RID, exists.
|
|||
|
// Return error.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = FoundButWrongStatus;
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if ( NtStatus == NotFoundStatus ) {
|
|||
|
|
|||
|
//
|
|||
|
// Account doesn't exist, that's good
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Check for name collision with 2nd object type
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampLookupAccountRid(
|
|||
|
SecondObjectType,
|
|||
|
(PUNICODE_STRING)AccountName,
|
|||
|
STATUS_UNSUCCESSFUL,
|
|||
|
ConflictingAccountRid,
|
|||
|
&SidNameUse
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// The name was found; return an error.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampCollisionError( SecondObjectType );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if ( NtStatus == STATUS_UNSUCCESSFUL ) {
|
|||
|
|
|||
|
//
|
|||
|
// Account doesn't exist, that's good
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Check for name collision with 3rd object type
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampLookupAccountRid(
|
|||
|
ThirdObjectType,
|
|||
|
(PUNICODE_STRING)AccountName,
|
|||
|
STATUS_UNSUCCESSFUL,
|
|||
|
ConflictingAccountRid,
|
|||
|
&SidNameUse
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SampCollisionError( ThirdObjectType );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if ( NtStatus == STATUS_UNSUCCESSFUL ) {
|
|||
|
|
|||
|
//
|
|||
|
// Account doesn't exist, that's good
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// We didn't find the name as an alias, group or user.
|
|||
|
// Now, check to see if the RID is already in use. First,
|
|||
|
// check to see if the specified RID is already in use as
|
|||
|
// the specified account type, but with a different name.
|
|||
|
//
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
NtStatus = SampOpenAccount(
|
|||
|
ObjectType,
|
|||
|
DomainHandle,
|
|||
|
DesiredAccess,
|
|||
|
RelativeId,
|
|||
|
TRUE, //we already have the lock
|
|||
|
AccountHandle
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// The RID is in use, but the name is wrong.
|
|||
|
//
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
IgnoreStatus = SamrCloseHandle( AccountHandle );
|
|||
|
ASSERT( NT_SUCCESS( IgnoreStatus ) );
|
|||
|
|
|||
|
*ConflictingAccountRid = RelativeId;
|
|||
|
|
|||
|
NtStatus = FoundButWrongStatus;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// RID not in use, that's good
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Now check to see if the RID is already in use, but in
|
|||
|
// an account type other than the one specified. (type2)
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountSubKeyName(
|
|||
|
SecondObjectType,
|
|||
|
&KeyName,
|
|||
|
RelativeId,
|
|||
|
NULL // Don't give a sub-key name
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&KeyHandle,
|
|||
|
(KEY_READ | KEY_WRITE),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// There is an account with the specified RID.
|
|||
|
// Return error.
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = NtClose( KeyHandle );
|
|||
|
ASSERT( NT_SUCCESS( IgnoreStatus ) );
|
|||
|
|
|||
|
*ConflictingAccountRid = RelativeId;
|
|||
|
|
|||
|
NtStatus = SampCollisionError( SecondObjectType );
|
|||
|
|
|||
|
} else {
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Now check to see if the RID is already in use, but in
|
|||
|
// an account type other than the one specified. (type3)
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampBuildAccountSubKeyName(
|
|||
|
ThirdObjectType,
|
|||
|
&KeyName,
|
|||
|
RelativeId,
|
|||
|
NULL // Don't give a sub-key name
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
InitializeObjectAttributes(
|
|||
|
&ObjectAttributes,
|
|||
|
&KeyName,
|
|||
|
OBJ_CASE_INSENSITIVE,
|
|||
|
SampKey,
|
|||
|
NULL
|
|||
|
);
|
|||
|
|
|||
|
NtStatus = RtlpNtOpenKey(
|
|||
|
&KeyHandle,
|
|||
|
(KEY_READ | KEY_WRITE),
|
|||
|
&ObjectAttributes,
|
|||
|
0
|
|||
|
);
|
|||
|
|
|||
|
SampFreeUnicodeString( &KeyName );
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// There is an account with the specified
|
|||
|
// RID. Return error.
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = NtClose( KeyHandle );
|
|||
|
ASSERT( NT_SUCCESS( IgnoreStatus ) );
|
|||
|
|
|||
|
*ConflictingAccountRid = RelativeId;
|
|||
|
|
|||
|
NtStatus = SampCollisionError( ThirdObjectType );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// We haven't found a conflicting account, so go ahead
|
|||
|
// and create this one with the name and RID specified.
|
|||
|
//
|
|||
|
|
|||
|
switch ( AccountType ) {
|
|||
|
|
|||
|
case SamObjectUser: {
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
NtStatus = SampCreateUserInDomain(
|
|||
|
DomainHandle,
|
|||
|
AccountName,
|
|||
|
USER_NORMAL_ACCOUNT,
|
|||
|
DesiredAccess,
|
|||
|
TRUE,
|
|||
|
AccountHandle,
|
|||
|
&GrantedAccess,
|
|||
|
&RelativeId
|
|||
|
);
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case SamObjectGroup: {
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
NtStatus = SampCreateGroupInDomain(
|
|||
|
DomainHandle,
|
|||
|
AccountName,
|
|||
|
DesiredAccess,
|
|||
|
TRUE,
|
|||
|
AccountHandle,
|
|||
|
&RelativeId
|
|||
|
);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case SamObjectAlias: {
|
|||
|
|
|||
|
SampTransactionWithinDomain = FALSE;
|
|||
|
|
|||
|
NtStatus = SampCreateAliasInDomain(
|
|||
|
DomainHandle,
|
|||
|
AccountName,
|
|||
|
DesiredAccess,
|
|||
|
TRUE,
|
|||
|
AccountHandle,
|
|||
|
&RelativeId
|
|||
|
);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// We may have created a new account. Set the domain's RID marker,
|
|||
|
// if necessary, to make sure we don't reuse the RID we just created.
|
|||
|
//
|
|||
|
|
|||
|
PSAMP_DEFINED_DOMAINS Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
|
|||
|
|
|||
|
if ( RelativeId >= Domain->CurrentFixed.NextRid ) {
|
|||
|
Domain->CurrentFixed.NextRid = RelativeId + 1;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
Done:
|
|||
|
//
|
|||
|
// De-reference the domain object
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SampReleaseWriteLock( TRUE );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamIGetSerialNumberDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
OUT PLARGE_INTEGER ModifiedCount,
|
|||
|
OUT PLARGE_INTEGER CreationTime
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine retrieves the creation time and modified count of the
|
|||
|
domain. This information is used as a serial number for the domain.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainHandle - Handle to the domain being replicated.
|
|||
|
|
|||
|
ModifiedCount - Retrieves the current count of modifications to the
|
|||
|
domain.
|
|||
|
|
|||
|
CreationTime - Receives the date/time the domain was created.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The service has completed successfully.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
NTSTATUS NtStatus;
|
|||
|
NTSTATUS IgnoreStatus;
|
|||
|
|
|||
|
SampAcquireReadLock();
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
0L,
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
|
|||
|
|
|||
|
(*ModifiedCount) = Domain->UnmodifiedFixed.ModifiedCount;
|
|||
|
(*CreationTime) = Domain->UnmodifiedFixed.CreationTime;
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the domain object
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
SampReleaseReadLock();
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamISetSerialNumberDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle,
|
|||
|
IN PLARGE_INTEGER ModifiedCount,
|
|||
|
IN PLARGE_INTEGER CreationTime,
|
|||
|
IN BOOLEAN StartOfFullSync
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine causes the creation time and modified count of the
|
|||
|
domain to be replaced. This information is used as a serial number
|
|||
|
for the domain.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainHandle - Handle to the domain being replicated.
|
|||
|
|
|||
|
ModifiedCount - Provides the current count of modifications to the
|
|||
|
domain.
|
|||
|
|
|||
|
CreationTime - Provides the date/time the domain was created.
|
|||
|
|
|||
|
StartOfFullSync - This boolean indicates whether a full sync is being
|
|||
|
initiated. If this is TRUE, then a full sync is to follow and
|
|||
|
all existing domain information may be discarded. If this is
|
|||
|
FALSE, then only specific domain information is to follow and all
|
|||
|
changes must not violate statndard SAM operation behaviour.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The service has completed successfully.
|
|||
|
|
|||
|
Other failures may be returned from SampReleaseWriteLock().
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
LARGE_INTEGER LargeOne, AdjustedModifiedCount;
|
|||
|
NTSTATUS NtStatus, TmpStatus, IgnoreStatus;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
|
|||
|
UNREFERENCED_PARAMETER( StartOfFullSync );
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
|
|||
|
if ( !NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)DomainHandle;
|
|||
|
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
0L,
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
|
|||
|
|
|||
|
//
|
|||
|
// Now set the Domain's ModifiedCount and CreationTime to the values
|
|||
|
// specified.
|
|||
|
//
|
|||
|
|
|||
|
Domain->CurrentFixed.CreationTime = (*CreationTime);
|
|||
|
|
|||
|
if ( SampDefinedDomains[SampTransactionDomainIndex].CurrentFixed.ServerRole
|
|||
|
== DomainServerRoleBackup ) {
|
|||
|
|
|||
|
//
|
|||
|
// Go ahead and use the ModifiedCount that was passed in.
|
|||
|
// Since this is a BDC, the commit code will not increment
|
|||
|
// the ModifiedCount.
|
|||
|
//
|
|||
|
|
|||
|
Domain->CurrentFixed.ModifiedCount = (*ModifiedCount);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// This is a PDC, so the commit code will increment the
|
|||
|
// ModifiedCount before writing it out to disk. So decrement
|
|||
|
// it here so that it ends up at the right value.
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
AdjustedModifiedCount.QuadPart = ModifiedCount->QuadPart - 1 ;
|
|||
|
|
|||
|
Domain->CurrentFixed.ModifiedCount = AdjustedModifiedCount;
|
|||
|
}
|
|||
|
|
|||
|
if ( !( ModifiedCount->QuadPart == 0) ||
|
|||
|
!StartOfFullSync ) {
|
|||
|
|
|||
|
//
|
|||
|
// If ModifiedCount is non-zero, we must be ending a full
|
|||
|
// or partial replication of the database...or perhaps we've
|
|||
|
// just finished a 128k chunk over a WAN or somesuch. Let's
|
|||
|
// ask to flush this stuff out to disk right away, rather
|
|||
|
// than waiting for the flush thread to get around to it.
|
|||
|
//
|
|||
|
|
|||
|
FlushImmediately = TRUE;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
SampDiagPrint( DISPLAY_ROLE_CHANGES,
|
|||
|
("SAM: SamISetSerialNumberDomain\n"
|
|||
|
" Old ModifiedId: [0x%lx, 0x%lx]\n"
|
|||
|
" New ModifiedId: [0x%lx, 0x%lx]\n",
|
|||
|
Domain->UnmodifiedFixed.ModifiedCount.HighPart,
|
|||
|
Domain->UnmodifiedFixed.ModifiedCount.LowPart,
|
|||
|
Domain->CurrentFixed.ModifiedCount.HighPart,
|
|||
|
Domain->CurrentFixed.ModifiedCount.LowPart )
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the domain object
|
|||
|
// Don't save changes - the domain fixed info will be written
|
|||
|
// out when the write lock is released.
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
|
|||
|
|
|||
|
NtStatus = SampReleaseWriteLock( TRUE );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
TmpStatus = SampReleaseWriteLock( FALSE );
|
|||
|
}
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampGetPrivateUserData(
|
|||
|
PSAMP_OBJECT UserContext,
|
|||
|
OUT PULONG DataLength,
|
|||
|
OUT PVOID *Data
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service is used during replication of private user
|
|||
|
type-specific information. It reads the private user information from
|
|||
|
the registry, and adjusts it if necessary (ie if the password history
|
|||
|
value is smaller than it used to be).
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
UserContext - A handle to a User.
|
|||
|
|
|||
|
DataLength - The length of the data returned.
|
|||
|
|
|||
|
Data - Receives a pointer to a buffer of length DataLength allocated
|
|||
|
and returned by SAM. The buffer must be freed to the process
|
|||
|
heap when it is no longer needed.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - Indicates the service completed successfully.
|
|||
|
|
|||
|
STATUS_INVALID_PARAMETER_1 - The object type of the provided handle
|
|||
|
does not support this operation.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
UNICODE_STRING TempString;
|
|||
|
PSAMI_PRIVATE_DATA_PASSWORD_TYPE PasswordData;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
PVOID BufferPointer;
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ UserContext->DomainIndex ];
|
|||
|
|
|||
|
//
|
|||
|
// Return data length as the maximum possible for this domain
|
|||
|
// - the size of the structure, plus the maximum size of the
|
|||
|
// NT and LM password histories.
|
|||
|
//
|
|||
|
|
|||
|
*DataLength = ( ( Domain->UnmodifiedFixed.PasswordHistoryLength )
|
|||
|
* ENCRYPTED_NT_OWF_PASSWORD_LENGTH ) +
|
|||
|
( ( Domain->UnmodifiedFixed.PasswordHistoryLength ) *
|
|||
|
ENCRYPTED_LM_OWF_PASSWORD_LENGTH ) +
|
|||
|
sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE );
|
|||
|
|
|||
|
*Data = MIDL_user_allocate( *DataLength );
|
|||
|
|
|||
|
if ( *Data == NULL ) {
|
|||
|
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
PasswordData = (PSAMI_PRIVATE_DATA_PASSWORD_TYPE)*Data;
|
|||
|
PasswordData->DataType = 0; // set correctly when we're done
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_DBCS_PWD,
|
|||
|
FALSE,
|
|||
|
&TempString
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
PasswordData->CaseInsensitiveDbcs = TempString;
|
|||
|
|
|||
|
RtlCopyMemory(
|
|||
|
&(PasswordData->CaseInsensitiveDbcsBuffer),
|
|||
|
PasswordData->CaseInsensitiveDbcs.Buffer,
|
|||
|
PasswordData->CaseInsensitiveDbcs.Length );
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_UNICODE_PWD,
|
|||
|
FALSE,
|
|||
|
&TempString
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
PasswordData->CaseSensitiveUnicode = TempString;
|
|||
|
|
|||
|
RtlCopyMemory(
|
|||
|
&(PasswordData->CaseSensitiveUnicodeBuffer),
|
|||
|
PasswordData->CaseSensitiveUnicode.Buffer,
|
|||
|
PasswordData->CaseSensitiveUnicode.Length );
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_NT_PWD_HISTORY,
|
|||
|
FALSE,
|
|||
|
&TempString
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// If history is too long, must shorten here
|
|||
|
//
|
|||
|
|
|||
|
PasswordData->NtPasswordHistory = TempString;
|
|||
|
|
|||
|
if ( PasswordData->NtPasswordHistory.Length > (USHORT)
|
|||
|
( Domain->UnmodifiedFixed.PasswordHistoryLength
|
|||
|
* ENCRYPTED_NT_OWF_PASSWORD_LENGTH ) ) {
|
|||
|
|
|||
|
PasswordData->NtPasswordHistory.Length = (USHORT)
|
|||
|
( Domain->UnmodifiedFixed.PasswordHistoryLength
|
|||
|
* ENCRYPTED_NT_OWF_PASSWORD_LENGTH );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Put the body of the Nt password history
|
|||
|
// immediately following the structure.
|
|||
|
//
|
|||
|
|
|||
|
BufferPointer = (PVOID)(((PCHAR)PasswordData) +
|
|||
|
sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE ) );
|
|||
|
|
|||
|
RtlCopyMemory(
|
|||
|
BufferPointer,
|
|||
|
PasswordData->NtPasswordHistory.Buffer,
|
|||
|
PasswordData->NtPasswordHistory.Length );
|
|||
|
|
|||
|
NtStatus = SampGetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_LM_PWD_HISTORY,
|
|||
|
FALSE,
|
|||
|
&TempString
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
PasswordData->LmPasswordHistory = TempString;
|
|||
|
|
|||
|
if ( PasswordData->LmPasswordHistory.Length > (USHORT)
|
|||
|
( Domain->UnmodifiedFixed.PasswordHistoryLength
|
|||
|
* ENCRYPTED_LM_OWF_PASSWORD_LENGTH ) ) {
|
|||
|
|
|||
|
PasswordData->LmPasswordHistory.Length = (USHORT)
|
|||
|
( Domain->UnmodifiedFixed.PasswordHistoryLength
|
|||
|
* ENCRYPTED_LM_OWF_PASSWORD_LENGTH );
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Put the body of the Lm password history
|
|||
|
// immediately following the Nt password
|
|||
|
// history.
|
|||
|
//
|
|||
|
|
|||
|
BufferPointer = (PVOID)(((PCHAR)(BufferPointer)) +
|
|||
|
PasswordData->NtPasswordHistory.Length );
|
|||
|
|
|||
|
RtlCopyMemory(
|
|||
|
BufferPointer,
|
|||
|
PasswordData->LmPasswordHistory.Buffer,
|
|||
|
PasswordData->LmPasswordHistory.Length );
|
|||
|
|
|||
|
PasswordData->DataType = SamPrivateDataPassword;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamIGetPrivateData(
|
|||
|
IN SAMPR_HANDLE SamHandle,
|
|||
|
IN PSAMI_PRIVATE_DATA_TYPE PrivateDataType,
|
|||
|
OUT PBOOLEAN SensitiveData,
|
|||
|
OUT PULONG DataLength,
|
|||
|
OUT PVOID *Data
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service is used to replicate private object type-specific
|
|||
|
information. This information must be replicated for each instance
|
|||
|
of the object type that is replicated.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
SamHandle - A handle to a Domain, User, Group or Alias.
|
|||
|
|
|||
|
PrivateDataType - Indicates which private data is being retrieved.
|
|||
|
The data type must correspond to the type of object that the
|
|||
|
handle is to.
|
|||
|
|
|||
|
SensitiveData - Indicates that the data returned must be encrypted
|
|||
|
before being sent anywhere.
|
|||
|
|
|||
|
DataLength - The length of the data returned.
|
|||
|
|
|||
|
Data - Receives a pointer to a buffer of length DataLength allocated
|
|||
|
and returned by SAM. The buffer must be freed to the process
|
|||
|
heap when it is no longer needed.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - Indicates the service completed successfully.
|
|||
|
|
|||
|
STATUS_INVALID_PARAMETER_1 - The object type of the provided handle
|
|||
|
does not support this operation.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
|
|||
|
SampAcquireReadLock();
|
|||
|
|
|||
|
switch ( *PrivateDataType ) {
|
|||
|
|
|||
|
case SamPrivateDataNextRid: {
|
|||
|
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)SamHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
0L,
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
PSAMI_PRIVATE_DATA_NEXTRID_TYPE NextRidData;
|
|||
|
|
|||
|
//
|
|||
|
// Return the domain's NextRid.
|
|||
|
//
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
|
|||
|
|
|||
|
*Data = MIDL_user_allocate( sizeof( SAMI_PRIVATE_DATA_NEXTRID_TYPE ) );
|
|||
|
|
|||
|
if ( *Data == NULL ) {
|
|||
|
|
|||
|
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NextRidData = (PSAMI_PRIVATE_DATA_NEXTRID_TYPE)*Data;
|
|||
|
NextRidData->NextRid = Domain->CurrentFixed.NextRid;
|
|||
|
NextRidData->DataType = SamPrivateDataNextRid;
|
|||
|
}
|
|||
|
|
|||
|
*DataLength = sizeof( SAMI_PRIVATE_DATA_NEXTRID_TYPE );
|
|||
|
|
|||
|
*SensitiveData = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( DomainContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case SamPrivateDataPassword: {
|
|||
|
|
|||
|
PSAMP_OBJECT UserContext;
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
UserContext = (PSAMP_OBJECT)SamHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
UserContext,
|
|||
|
0L,
|
|||
|
SampUserObjectType, //ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
NtStatus = SampGetPrivateUserData(
|
|||
|
UserContext,
|
|||
|
DataLength,
|
|||
|
Data
|
|||
|
);
|
|||
|
|
|||
|
*SensitiveData = TRUE;
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object
|
|||
|
//
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext( UserContext, FALSE );
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
default: {
|
|||
|
|
|||
|
//
|
|||
|
// Since caller is trusted, assume we've got a version mismatch
|
|||
|
// or somesuch.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_NOT_IMPLEMENTED;
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Free the read lock
|
|||
|
//
|
|||
|
|
|||
|
SampReleaseReadLock();
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampSetPrivateUserData(
|
|||
|
PSAMP_OBJECT UserContext,
|
|||
|
IN ULONG DataLength,
|
|||
|
IN PVOID Data
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service is used to replicate private user type-specific
|
|||
|
information. It writes the private data (passwords and password
|
|||
|
histories) to the registry.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
UserContext - Handle to a User object.
|
|||
|
|
|||
|
DataLength - The length of the data being set.
|
|||
|
|
|||
|
Data - A pointer to a buffer of length DataLength containing the
|
|||
|
private data.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully.
|
|||
|
|
|||
|
STATUS_INVALID_PARAMETER_1 - The object type of the provided handle
|
|||
|
does not support this operation.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
PSAMI_PRIVATE_DATA_PASSWORD_TYPE PasswordData;
|
|||
|
BOOLEAN ReplicateImmediately = FALSE;
|
|||
|
|
|||
|
ASSERT( Data != NULL );
|
|||
|
|
|||
|
if ( ( Data != NULL ) &&
|
|||
|
( DataLength >= sizeof(SAMI_PRIVATE_DATA_PASSWORD_TYPE) ) ) {
|
|||
|
|
|||
|
PasswordData = (PSAMI_PRIVATE_DATA_PASSWORD_TYPE)Data;
|
|||
|
|
|||
|
PasswordData->CaseInsensitiveDbcs.Buffer = (PWSTR)
|
|||
|
(&(PasswordData->CaseInsensitiveDbcsBuffer));
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_DBCS_PWD,
|
|||
|
&(PasswordData->CaseInsensitiveDbcs)
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
PasswordData->CaseSensitiveUnicode.Buffer = (PWSTR)
|
|||
|
(&(PasswordData->CaseSensitiveUnicodeBuffer));
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_UNICODE_PWD,
|
|||
|
&(PasswordData->CaseSensitiveUnicode)
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
PasswordData->NtPasswordHistory.Buffer =
|
|||
|
(PWSTR)(((PCHAR)PasswordData) +
|
|||
|
sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE ) );
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_NT_PWD_HISTORY,
|
|||
|
&(PasswordData->NtPasswordHistory)
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
PasswordData->LmPasswordHistory.Buffer =
|
|||
|
(PWSTR)(((PCHAR)(PasswordData->NtPasswordHistory.Buffer)) +
|
|||
|
PasswordData->NtPasswordHistory.Length );
|
|||
|
|
|||
|
NtStatus = SampSetUnicodeStringAttribute(
|
|||
|
UserContext,
|
|||
|
SAMP_USER_LM_PWD_HISTORY,
|
|||
|
&(PasswordData->LmPasswordHistory)
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NtStatus = STATUS_INVALID_PARAMETER;
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamISetPrivateData(
|
|||
|
IN SAMPR_HANDLE SamHandle,
|
|||
|
IN ULONG DataLength,
|
|||
|
IN PVOID Data
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service is used to replicate private object type-specific
|
|||
|
information. This information must be replicated for each instance
|
|||
|
of the object type that is replicated.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
SamHandle - Handle to a Domain, User, Group or Alias object. See
|
|||
|
SamIGetPrivateInformation() for a list of supported object
|
|||
|
types.
|
|||
|
|
|||
|
DataLength - The length of the data being set.
|
|||
|
|
|||
|
Data - A pointer to a buffer of length DataLength containing the
|
|||
|
private data.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The Service completed successfully.
|
|||
|
|
|||
|
STATUS_INVALID_PARAMETER_1 - The object type of the provided handle
|
|||
|
does not support this operation.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus, IgnoreStatus;
|
|||
|
SAMP_OBJECT_TYPE FoundType;
|
|||
|
BOOLEAN ReplicateImmediately = FALSE;
|
|||
|
|
|||
|
ASSERT( Data != NULL );
|
|||
|
|
|||
|
NtStatus = SampAcquireWriteLock();
|
|||
|
|
|||
|
if ( !NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
switch ( *((PSAMI_PRIVATE_DATA_TYPE)(Data)) ) {
|
|||
|
|
|||
|
case SamPrivateDataNextRid: {
|
|||
|
|
|||
|
PSAMP_OBJECT DomainContext;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
DomainContext = (PSAMP_OBJECT)SamHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
DomainContext,
|
|||
|
0L,
|
|||
|
SampDomainObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// Set the domain's NextRid.
|
|||
|
//
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ DomainContext->DomainIndex ];
|
|||
|
|
|||
|
if ( ( Data != NULL ) &&
|
|||
|
( DataLength == sizeof(SAMI_PRIVATE_DATA_NEXTRID_TYPE) ) ) {
|
|||
|
|
|||
|
PSAMI_PRIVATE_DATA_NEXTRID_TYPE NextRidData;
|
|||
|
|
|||
|
//
|
|||
|
// We can trust Data to be a valid pointer; since our
|
|||
|
// caller is trusted.
|
|||
|
//
|
|||
|
|
|||
|
NextRidData = (PSAMI_PRIVATE_DATA_NEXTRID_TYPE)Data;
|
|||
|
|
|||
|
//
|
|||
|
// We used to set the domain's NextRid here. But we've
|
|||
|
// decided that, rather than trying to replicate an exact
|
|||
|
// copy of the database, we're going to try to patch any
|
|||
|
// problems as we replicate. To ensure that we don't
|
|||
|
// create any problems on the way, we want to make sure
|
|||
|
// that the NextRid value on a BDC is NEVER decreased.
|
|||
|
// Not that it matters; nobody calls this anyway. So the
|
|||
|
// Get/SetPrivateData code for domains could be removed.
|
|||
|
//
|
|||
|
|
|||
|
// Domain->CurrentFixed.NextRid = NextRidData->NextRid;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
NtStatus = STATUS_INVALID_PARAMETER;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// De-reference the object
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SampDeReferenceContext(
|
|||
|
DomainContext,
|
|||
|
TRUE
|
|||
|
);
|
|||
|
} else {
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext(
|
|||
|
DomainContext,
|
|||
|
FALSE
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
case SamPrivateDataPassword: {
|
|||
|
|
|||
|
PSAMP_OBJECT UserContext;
|
|||
|
|
|||
|
//
|
|||
|
// Validate type of, and access to object.
|
|||
|
//
|
|||
|
|
|||
|
UserContext = (PSAMP_OBJECT)SamHandle;
|
|||
|
NtStatus = SampLookupContext(
|
|||
|
UserContext,
|
|||
|
0L,
|
|||
|
SampUserObjectType, // ExpectedType
|
|||
|
&FoundType
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SampSetPrivateUserData(
|
|||
|
UserContext,
|
|||
|
DataLength,
|
|||
|
Data
|
|||
|
);
|
|||
|
//
|
|||
|
// De-reference the object, adding attribute changes to the
|
|||
|
// RXACT if everything went OK.
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SampDeReferenceContext(
|
|||
|
UserContext,
|
|||
|
TRUE
|
|||
|
);
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
IgnoreStatus = SampDeReferenceContext(
|
|||
|
UserContext,
|
|||
|
FALSE
|
|||
|
);
|
|||
|
ASSERT(NT_SUCCESS(IgnoreStatus));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
default: {
|
|||
|
|
|||
|
//
|
|||
|
// We've either got a version mismatch, or the caller passed us
|
|||
|
// bad data, or SamIGetPrivateData() never finished getting the
|
|||
|
// data into the buffer.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_INVALID_PARAMETER;
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Release the write lock - commit only if successful.
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS(NtStatus) ) {
|
|||
|
|
|||
|
NtStatus = SampReleaseWriteLock( TRUE );
|
|||
|
|
|||
|
//
|
|||
|
// No need to call SampNotifyNetlogonOfDelta, since the replicator
|
|||
|
// is the one that made this call.
|
|||
|
//
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
IgnoreStatus = SampReleaseWriteLock( FALSE );
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamrTestPrivateFunctionsDomain(
|
|||
|
IN SAMPR_HANDLE DomainHandle
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service is called to test functions that are normally only
|
|||
|
accessible inside the security process.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainHandle - Handle to the domain being tested.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The tests completed successfully.
|
|||
|
|
|||
|
Any errors are as propogated from the tests.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
#if SAM_SERVER_TESTS
|
|||
|
|
|||
|
LARGE_INTEGER ModifiedCount1;
|
|||
|
LARGE_INTEGER CreationTime1;
|
|||
|
PSAMP_DEFINED_DOMAINS Domain;
|
|||
|
NTSTATUS NtStatus, TmpStatus;
|
|||
|
SAMI_PRIVATE_DATA_TYPE DataType = SamPrivateDataNextRid;
|
|||
|
SAMI_PRIVATE_DATA_NEXTRID_TYPE LocalNextRidData;
|
|||
|
PSAMI_PRIVATE_DATA_NEXTRID_TYPE NextRidData1 = NULL;
|
|||
|
PSAMI_PRIVATE_DATA_NEXTRID_TYPE NextRidData2 = NULL;
|
|||
|
PVOID NextRidDataPointer = NULL;
|
|||
|
ULONG DataLength = 0;
|
|||
|
BOOLEAN SensitiveData = TRUE;
|
|||
|
|
|||
|
Domain = &SampDefinedDomains[ ((PSAMP_OBJECT)DomainHandle)->DomainIndex ];
|
|||
|
|
|||
|
//
|
|||
|
// Test SamIGetSerialNumberDomain(). Just do a GET to make sure we
|
|||
|
// don't blow up.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SamIGetSerialNumberDomain(
|
|||
|
DomainHandle,
|
|||
|
&ModifiedCount1,
|
|||
|
&CreationTime1 );
|
|||
|
|
|||
|
//
|
|||
|
// Test SamISetSerialNumberDomain().
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
LARGE_INTEGER ModifiedCount2;
|
|||
|
LARGE_INTEGER ModifiedCount3;
|
|||
|
LARGE_INTEGER CreationTime2;
|
|||
|
LARGE_INTEGER CreationTime3;
|
|||
|
|
|||
|
//
|
|||
|
// Try a simple SET to make sure we don't blow up.
|
|||
|
//
|
|||
|
|
|||
|
ModifiedCount2.HighPart = 7;
|
|||
|
ModifiedCount2.LowPart = 4;
|
|||
|
CreationTime2.HighPart = 6;
|
|||
|
CreationTime2.LowPart = 9;
|
|||
|
|
|||
|
NtStatus = SamISetSerialNumberDomain(
|
|||
|
DomainHandle,
|
|||
|
&ModifiedCount2,
|
|||
|
&CreationTime2,
|
|||
|
FALSE );
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Now do a GET to see if our SET worked.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SamIGetSerialNumberDomain(
|
|||
|
DomainHandle,
|
|||
|
&ModifiedCount3,
|
|||
|
&CreationTime3 );
|
|||
|
|
|||
|
if ( ( CreationTime2.HighPart != CreationTime3.HighPart ) ||
|
|||
|
( CreationTime2.LowPart != CreationTime3.LowPart ) ) {
|
|||
|
|
|||
|
NtStatus = STATUS_DATA_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Do another SET to put CreationTime back where it should
|
|||
|
// be. ModifiedCount will be 1 too big, so what.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SamISetSerialNumberDomain(
|
|||
|
DomainHandle,
|
|||
|
&ModifiedCount1,
|
|||
|
&CreationTime1,
|
|||
|
FALSE );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Test SamIGetPrivateData().
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SamIGetPrivateData(
|
|||
|
DomainHandle,
|
|||
|
&DataType,
|
|||
|
&SensitiveData,
|
|||
|
&DataLength,
|
|||
|
&NextRidDataPointer );
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NextRidData1 = (PSAMI_PRIVATE_DATA_NEXTRID_TYPE)NextRidDataPointer;
|
|||
|
|
|||
|
if ( ( DataLength != sizeof( SAMI_PRIVATE_DATA_NEXTRID_TYPE ) ) ||
|
|||
|
( SensitiveData != FALSE ) ||
|
|||
|
( NextRidData1->DataType != SamPrivateDataNextRid ) ||
|
|||
|
( NextRidData1->NextRid != Domain->CurrentFixed.NextRid ) ) {
|
|||
|
|
|||
|
NtStatus = STATUS_DATA_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// //
|
|||
|
// // Test SamISetPrivateData().
|
|||
|
// //
|
|||
|
// // NO, don't test it, since it no longer does anything. We don't
|
|||
|
// // ever want NextRid to get set, because we never want it to get
|
|||
|
// // smaller.
|
|||
|
//
|
|||
|
// if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
//
|
|||
|
// //
|
|||
|
// // First do a random domain set to make sure we don't blow up.
|
|||
|
// //
|
|||
|
//
|
|||
|
// LocalNextRidData.DataType = SamPrivateDataNextRid;
|
|||
|
// LocalNextRidData.NextRid = 34567;
|
|||
|
//
|
|||
|
// NtStatus = SamISetPrivateData(
|
|||
|
// DomainHandle,
|
|||
|
// sizeof( SAMI_PRIVATE_DATA_NEXTRID_TYPE ),
|
|||
|
// &LocalNextRidData
|
|||
|
// );
|
|||
|
//
|
|||
|
// if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
//
|
|||
|
// //
|
|||
|
// // Now do a domain get to make sure our set worked.
|
|||
|
// //
|
|||
|
//
|
|||
|
// NtStatus = SamIGetPrivateData(
|
|||
|
// DomainHandle,
|
|||
|
// &DataType,
|
|||
|
// &SensitiveData,
|
|||
|
// &DataLength,
|
|||
|
// &NextRidDataPointer );
|
|||
|
//
|
|||
|
// if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
//
|
|||
|
// //
|
|||
|
// // Verify the data is as we set it.
|
|||
|
// //
|
|||
|
//
|
|||
|
// NextRidData2 = (PSAMI_PRIVATE_DATA_NEXTRID_TYPE)NextRidDataPointer;
|
|||
|
//
|
|||
|
// if ( NextRidData2->NextRid != LocalNextRidData.NextRid ) {
|
|||
|
//
|
|||
|
// NtStatus = STATUS_DATA_ERROR;
|
|||
|
// }
|
|||
|
//
|
|||
|
// //
|
|||
|
// // Now do a domain set to restore things to their original state.
|
|||
|
// //
|
|||
|
//
|
|||
|
// TmpStatus = SamISetPrivateData(
|
|||
|
// DomainHandle,
|
|||
|
// sizeof( SAMI_PRIVATE_DATA_NEXTRID_TYPE ),
|
|||
|
// NextRidData1
|
|||
|
// );
|
|||
|
//
|
|||
|
// if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
//
|
|||
|
// NtStatus = TmpStatus;
|
|||
|
// }
|
|||
|
// }
|
|||
|
// }
|
|||
|
//
|
|||
|
// if ( NextRidData1 != NULL ) {
|
|||
|
//
|
|||
|
// MIDL_user_free( NextRidData1 );
|
|||
|
// }
|
|||
|
//
|
|||
|
// if ( NextRidData2 != NULL ) {
|
|||
|
//
|
|||
|
// MIDL_user_free( NextRidData2 );
|
|||
|
// }
|
|||
|
// }
|
|||
|
|
|||
|
//
|
|||
|
// Test SamICreateAccountByRid().
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
RPC_UNICODE_STRING AccountNameU;
|
|||
|
RPC_UNICODE_STRING AccountName2U;
|
|||
|
SAMPR_HANDLE UserAccountHandle;
|
|||
|
SAMPR_HANDLE BadAccountHandle;
|
|||
|
SAMPR_HANDLE GroupAccountHandle;
|
|||
|
NTSTATUS TmpStatus;
|
|||
|
ULONG RelativeId = 1111;
|
|||
|
ULONG ConflictingAccountRid;
|
|||
|
BOOLEAN AllTestsCompleted = FALSE;
|
|||
|
|
|||
|
//
|
|||
|
// Create a unique account - a user with a known name and RID.
|
|||
|
//
|
|||
|
|
|||
|
RtlInitUnicodeString( &AccountNameU, L"USER1SRV" );
|
|||
|
RtlInitUnicodeString( &AccountName2U, L"USER2SRV" );
|
|||
|
|
|||
|
NtStatus = SamICreateAccountByRid(
|
|||
|
DomainHandle,
|
|||
|
SamObjectUser,
|
|||
|
RelativeId,
|
|||
|
&AccountNameU,
|
|||
|
USER_ALL_ACCESS,
|
|||
|
&UserAccountHandle,
|
|||
|
&ConflictingAccountRid );
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// User is open. Close it, and make the same call as above to
|
|||
|
// make sure that the user gets opened. We'll need it open
|
|||
|
// later to delete it anyway.
|
|||
|
//
|
|||
|
|
|||
|
TmpStatus = SamrCloseHandle( &UserAccountHandle );
|
|||
|
ASSERT( NT_SUCCESS( TmpStatus ) );
|
|||
|
|
|||
|
NtStatus = SamICreateAccountByRid(
|
|||
|
DomainHandle,
|
|||
|
SamObjectUser,
|
|||
|
RelativeId,
|
|||
|
&AccountName,
|
|||
|
USER_ALL_ACCESS,
|
|||
|
&UserAccountHandle,
|
|||
|
&ConflictingAccountRid );
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Make same call as above, but with a different RID.
|
|||
|
// Should get an error because of the name collision.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SamICreateAccountByRid(
|
|||
|
DomainHandle,
|
|||
|
SamObjectUser,
|
|||
|
RelativeId + 1,
|
|||
|
&AccountName,
|
|||
|
0L,
|
|||
|
&BadAccountHandle,
|
|||
|
&ConflictingAccountRid );
|
|||
|
|
|||
|
if ( NtStatus == STATUS_USER_EXISTS ) {
|
|||
|
|
|||
|
//
|
|||
|
// Make same call as above, but with a different name. Should
|
|||
|
// get an error because of the RID collision.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SamICreateAccountByRid(
|
|||
|
DomainHandle,
|
|||
|
SamObjectUser,
|
|||
|
RelativeId,
|
|||
|
&AccountName2,
|
|||
|
0L,
|
|||
|
&BadAccountHandle,
|
|||
|
&ConflictingAccountRid );
|
|||
|
|
|||
|
if ( NtStatus == STATUS_USER_EXISTS ) {
|
|||
|
|
|||
|
//
|
|||
|
// Create a different type - a group - with the
|
|||
|
// user's RID. Should get an error because of
|
|||
|
// the RID collision.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SamICreateAccountByRid(
|
|||
|
DomainHandle,
|
|||
|
SamObjectGroup,
|
|||
|
RelativeId,
|
|||
|
&AccountName,
|
|||
|
0L,
|
|||
|
&BadAccountHandle,
|
|||
|
&ConflictingAccountRid );
|
|||
|
|
|||
|
if ( NtStatus == STATUS_USER_EXISTS ) {
|
|||
|
|
|||
|
//
|
|||
|
// Try a different type - a group - with a
|
|||
|
// different name, but still the same RID.
|
|||
|
// This should still fail due to the RID
|
|||
|
// collision.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SamICreateAccountByRid(
|
|||
|
DomainHandle,
|
|||
|
SamObjectGroup,
|
|||
|
RelativeId,
|
|||
|
&AccountName2,
|
|||
|
0L,
|
|||
|
&BadAccountHandle,
|
|||
|
&ConflictingAccountRid );
|
|||
|
|
|||
|
if ( NtStatus == STATUS_USER_EXISTS ) {
|
|||
|
|
|||
|
//
|
|||
|
// Create a group with the user's name, but
|
|||
|
// a different RID. This should fail
|
|||
|
// because of the name collision.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SamICreateAccountByRid(
|
|||
|
DomainHandle,
|
|||
|
SamObjectGroup,
|
|||
|
RelativeId + 1,
|
|||
|
&AccountName,
|
|||
|
GROUP_ALL_ACCESS,
|
|||
|
&GroupAccountHandle,
|
|||
|
&ConflictingAccountRid );
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Ack! This shouldn't have happened.
|
|||
|
// Close and delete the group we just created.
|
|||
|
//
|
|||
|
|
|||
|
TmpStatus = SamrDeleteGroup( &GroupAccountHandle );
|
|||
|
ASSERT( NT_SUCCESS( TmpStatus ) );
|
|||
|
NtStatus = STATUS_UNSUCCESSFUL;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
if ( NtStatus == STATUS_USER_EXISTS ) {
|
|||
|
|
|||
|
NtStatus = STATUS_SUCCESS;
|
|||
|
AllTestsCompleted = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now delete the user.
|
|||
|
//
|
|||
|
|
|||
|
TmpStatus = SamrDeleteUser( &UserAccountHandle );
|
|||
|
ASSERT( NT_SUCCESS( TmpStatus ) );
|
|||
|
}
|
|||
|
|
|||
|
if ( ( !AllTestsCompleted ) && ( NtStatus == STATUS_SUCCESS ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// STATUS_SUCCESS means everything succeeded (it was set after
|
|||
|
// the last one succeeded) or a test that was supposed to fail
|
|||
|
// didn't. If the former, set an error.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = STATUS_UNSUCCESSFUL;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
|
|||
|
#else
|
|||
|
|
|||
|
return( STATUS_NOT_IMPLEMENTED );
|
|||
|
|
|||
|
#endif // SAM_SERVER_TESTS
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SamrTestPrivateFunctionsUser(
|
|||
|
IN SAMPR_HANDLE UserHandle
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This service is called to test functions that are normally only
|
|||
|
accessible inside the security process.
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
UserHandle - Handle to the user being tested.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - The tests completed successfully.
|
|||
|
|
|||
|
Any errors are as propogated from the tests.
|
|||
|
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
|
|||
|
#if SAM_SERVER_TESTS
|
|||
|
|
|||
|
UNICODE_STRING WorkstationsU, LogonWorkstationU;
|
|||
|
LOGON_HOURS LogonHours;
|
|||
|
PVOID LogonHoursPointer, WorkstationsPointer;
|
|||
|
LARGE_INTEGER LogoffTime, KickoffTime;
|
|||
|
NTSTATUS NtStatus, TmpStatus;
|
|||
|
SAMI_PRIVATE_DATA_TYPE DataType = SamPrivateDataPassword;
|
|||
|
PVOID PasswordDataPointer = NULL;
|
|||
|
PCHAR BufferPointer;
|
|||
|
ULONG OriginalDataLength = 0;
|
|||
|
ULONG DataLength = 0;
|
|||
|
USHORT i;
|
|||
|
BOOLEAN SensitiveData = FALSE;
|
|||
|
SAMI_PRIVATE_DATA_PASSWORD_TYPE LocalPasswordData;
|
|||
|
PSAMI_PRIVATE_DATA_PASSWORD_TYPE PasswordData1;
|
|||
|
PSAMI_PRIVATE_DATA_PASSWORD_TYPE PasswordData2;
|
|||
|
PUSER_ALL_INFORMATION All = NULL;
|
|||
|
PUSER_ALL_INFORMATION All2 = NULL;
|
|||
|
|
|||
|
// --------------------------------------------------------------
|
|||
|
// Test Query and SetInformationUser for UserAllInformation level
|
|||
|
//
|
|||
|
// The handle is passed to us from user space. Make it look like
|
|||
|
// a trusted handle so we can test the trusted stuff.
|
|||
|
//
|
|||
|
|
|||
|
((PSAMP_OBJECT)(UserHandle))->TrustedClient = TRUE;
|
|||
|
|
|||
|
NtStatus = SamrQueryInformationUser(
|
|||
|
UserHandle,
|
|||
|
UserAllInformation,
|
|||
|
(PSAMPR_USER_INFO_BUFFER *)&All
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Now change some of the data, and set it
|
|||
|
//
|
|||
|
|
|||
|
RtlInitUnicodeString( (PUNICODE_STRING)(&All->FullName), L"FullName );
|
|||
|
|
|||
|
RtlInitUnicodeString( (PUNICODE_STRING)(&All->HomeDirectory), L"HomeDirectory" );
|
|||
|
|
|||
|
RtlInitUnicodeString(
|
|||
|
(PUNICODE_STRING)(&All->HomeDirectoryDrive),
|
|||
|
L"HomeDirectoryDrive"
|
|||
|
);
|
|||
|
|
|||
|
RtlInitUnicodeString(
|
|||
|
(PUNICODE_STRING)(&All->ScriptPath),
|
|||
|
L"ScriptPath"
|
|||
|
);
|
|||
|
|
|||
|
RtlInitUnicodeString(
|
|||
|
(PUNICODE_STRING)(&All->ProfilePath),
|
|||
|
L"ProfilePath"
|
|||
|
);
|
|||
|
|
|||
|
RtlInitUnicodeString(
|
|||
|
(PUNICODE_STRING)(&All->AdminComment),
|
|||
|
L"AdminComment"
|
|||
|
);
|
|||
|
|
|||
|
RtlInitUnicodeString(
|
|||
|
(PUNICODE_STRING)(&All->WorkStations),
|
|||
|
L"WorkStations"
|
|||
|
);
|
|||
|
|
|||
|
RtlInitUnicodeString(
|
|||
|
(PUNICODE_STRING)(&All->UserComment),
|
|||
|
L"UserComment"
|
|||
|
);
|
|||
|
|
|||
|
RtlInitUnicodeString(
|
|||
|
(PUNICODE_STRING)(&All->Parameters),
|
|||
|
L"Parameters"
|
|||
|
);
|
|||
|
|
|||
|
RtlInitUnicodeString(
|
|||
|
(PUNICODE_STRING)(&All->NtPassword),
|
|||
|
L"12345678"
|
|||
|
);
|
|||
|
|
|||
|
RtlInitUnicodeString(
|
|||
|
(PUNICODE_STRING)(&All->LmPassword),
|
|||
|
L"87654321"
|
|||
|
);
|
|||
|
|
|||
|
All->BadPasswordCount = 5;
|
|||
|
All->LogonCount = 6;
|
|||
|
All->CountryCode = 7;
|
|||
|
All->CodePage = 8;
|
|||
|
|
|||
|
All->PasswordExpired = TRUE;
|
|||
|
All->NtPasswordPresent = TRUE;
|
|||
|
All->LmPasswordPresent = TRUE;
|
|||
|
|
|||
|
All->LogonHours.UnitsPerWeek = 7;
|
|||
|
|
|||
|
All->WhichFields =
|
|||
|
USER_ALL_FULLNAME |
|
|||
|
USER_ALL_HOMEDIRECTORY |
|
|||
|
USER_ALL_HOMEDIRECTORYDRIVE |
|
|||
|
USER_ALL_SCRIPTPATH |
|
|||
|
USER_ALL_PROFILEPATH |
|
|||
|
USER_ALL_ADMINCOMMENT |
|
|||
|
USER_ALL_WORKSTATIONS |
|
|||
|
USER_ALL_USERCOMMENT |
|
|||
|
USER_ALL_PARAMETERS |
|
|||
|
USER_ALL_BADPASSWORDCOUNT |
|
|||
|
USER_ALL_LOGONCOUNT |
|
|||
|
USER_ALL_COUNTRYCODE |
|
|||
|
USER_ALL_CODEPAGE |
|
|||
|
USER_ALL_PASSWORDEXPIRED |
|
|||
|
USER_ALL_LMPASSWORDPRESENT |
|
|||
|
USER_ALL_NTPASSWORDPRESENT |
|
|||
|
USER_ALL_LOGONHOURS;
|
|||
|
|
|||
|
NtStatus = SamrSetInformationUser(
|
|||
|
UserHandle,
|
|||
|
UserAllInformation,
|
|||
|
(PSAMPR_USER_INFO_BUFFER)All
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = SamrQueryInformationUser(
|
|||
|
UserHandle,
|
|||
|
UserAllInformation,
|
|||
|
(PSAMPR_USER_INFO_BUFFER *)&All2
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Verify that queried info is as we set it
|
|||
|
//
|
|||
|
|
|||
|
if (
|
|||
|
|
|||
|
//
|
|||
|
// Fields that we didn't touch. Note that private
|
|||
|
// data and PasswordMustChange will change anyway
|
|||
|
// due to password changes.
|
|||
|
//
|
|||
|
|
|||
|
( All2->WhichFields != (USER_ALL_READ_GENERAL_MASK |
|
|||
|
USER_ALL_READ_LOGON_MASK |
|
|||
|
USER_ALL_READ_ACCOUNT_MASK |
|
|||
|
USER_ALL_READ_PREFERENCES_MASK |
|
|||
|
USER_ALL_READ_TRUSTED_MASK) ) ||
|
|||
|
( !(All->LastLogon.QuadPart == All2->LastLogon.QuadPart) ) ||
|
|||
|
( !(All->LastLogoff.QuadPart == All2->LastLogoff.QuadPart) ) ||
|
|||
|
( !(All->PasswordLastSet.QuadPart == All2->PasswordLastSet.QuadPart) ) ||
|
|||
|
( !(All->AccountExpires.QuadPart == All2->AccountExpires.QuadPart) ) ||
|
|||
|
( !(All->PasswordCanChange.QuadPart == All2->PasswordCanChange.QuadPart) ) ||
|
|||
|
( (All->PasswordMustChange.QuadPart == All2->PasswordMustChange.QuadPart) ) ||
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->UserName),
|
|||
|
&(All2->UserName),
|
|||
|
FALSE) != 0) ||
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->PrivateData),
|
|||
|
&(All2->PrivateData),
|
|||
|
FALSE) == 0) ||
|
|||
|
( All->SecurityDescriptor.Length !=
|
|||
|
All2->SecurityDescriptor.Length ) ||
|
|||
|
( All->UserId != All2->UserId ) ||
|
|||
|
( All->PrimaryGroupId != All2->PrimaryGroupId ) ||
|
|||
|
( All->UserAccountControl != All2->UserAccountControl ) ||
|
|||
|
( All->PrivateDataSensitive !=
|
|||
|
All2->PrivateDataSensitive ) ||
|
|||
|
|
|||
|
// Fields that we changed
|
|||
|
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->FullName),
|
|||
|
&(All2->FullName),
|
|||
|
FALSE) != 0) ||
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->HomeDirectory),
|
|||
|
&(All2->HomeDirectory),
|
|||
|
FALSE) != 0) ||
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->HomeDirectoryDrive),
|
|||
|
&(All2->HomeDirectoryDrive),
|
|||
|
FALSE) != 0) ||
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->ScriptPath),
|
|||
|
&(All2->ScriptPath),
|
|||
|
FALSE) != 0) ||
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->ProfilePath),
|
|||
|
&(All2->ProfilePath),
|
|||
|
FALSE) != 0) ||
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->AdminComment),
|
|||
|
&(All2->AdminComment),
|
|||
|
FALSE) != 0) ||
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->WorkStations),
|
|||
|
&(All2->WorkStations),
|
|||
|
FALSE) != 0) ||
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->UserComment),
|
|||
|
&(All2->UserComment),
|
|||
|
FALSE) != 0) ||
|
|||
|
(RtlCompareUnicodeString(
|
|||
|
&(All->Parameters),
|
|||
|
&(All2->Parameters),
|
|||
|
FALSE) != 0) ||
|
|||
|
( All->BadPasswordCount != All2->BadPasswordCount ) ||
|
|||
|
( All->LogonCount != All2->LogonCount ) ||
|
|||
|
( All->CountryCode != All2->CountryCode ) ||
|
|||
|
( All->CodePage != All2->CodePage ) ||
|
|||
|
( All->PasswordExpired != All2->PasswordExpired ) ||
|
|||
|
( All->LmPasswordPresent != All2->LmPasswordPresent ) ||
|
|||
|
( All->NtPasswordPresent != All2->NtPasswordPresent ) ||
|
|||
|
( All->LogonHours.UnitsPerWeek !=
|
|||
|
All2->LogonHours.UnitsPerWeek )
|
|||
|
) {
|
|||
|
|
|||
|
NtStatus = STATUS_DATA_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
MIDL_user_free( All2 );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
MIDL_user_free( All );
|
|||
|
}
|
|||
|
|
|||
|
if ( !NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
// --------------------------------------------------------------
|
|||
|
// Test SamIAccountRestrictions
|
|||
|
// NOTE: We really should have more tests for this
|
|||
|
//
|
|||
|
|
|||
|
RtlInitUnicodeString( &WorkstationsU, L"machine1,CHADS2 chads1" );
|
|||
|
|
|||
|
NtStatus = SamrSetInformationUser(
|
|||
|
UserHandle,
|
|||
|
UserWorkStationsInformation,
|
|||
|
(PSAMPR_USER_INFO_BUFFER) &WorkstationsU
|
|||
|
);
|
|||
|
ASSERT( NT_SUCCESS( NtStatus ) ) ;
|
|||
|
|
|||
|
LogonHours.UnitsPerWeek = 168;
|
|||
|
LogonHours.LogonHours = MIDL_user_allocate( 21 );
|
|||
|
ASSERT( LogonHours.LogonHours != NULL );
|
|||
|
|
|||
|
for ( i = 0; i < 21; i++ ) {
|
|||
|
|
|||
|
LogonHours.LogonHours[i] = 0xa1;
|
|||
|
}
|
|||
|
|
|||
|
NtStatus = SamrSetInformationUser(
|
|||
|
UserHandle,
|
|||
|
UserLogonHoursInformation,
|
|||
|
(PSAMPR_USER_INFO_BUFFER)&LogonHours
|
|||
|
);
|
|||
|
ASSERT( NT_SUCCESS( NtStatus ) ) ;
|
|||
|
|
|||
|
LogonHoursPointer = NULL;
|
|||
|
|
|||
|
NtStatus = SamrQueryInformationUser(
|
|||
|
UserHandle,
|
|||
|
UserLogonHoursInformation,
|
|||
|
(PSAMPR_USER_INFO_BUFFER *)&LogonHoursPointer
|
|||
|
);
|
|||
|
ASSERT( NT_SUCCESS( NtStatus ) ) ;
|
|||
|
|
|||
|
WorkstationsPointer = NULL;
|
|||
|
|
|||
|
NtStatus = SamrQueryInformationUser(
|
|||
|
UserHandle,
|
|||
|
UserWorkStationsInformation,
|
|||
|
(PSAMPR_USER_INFO_BUFFER *)&WorkstationsPointer
|
|||
|
);
|
|||
|
ASSERT( NT_SUCCESS( NtStatus ) ) ;
|
|||
|
|
|||
|
RtlInitUnicodeString( &WorkstationsU, L"ChadS2" );
|
|||
|
|
|||
|
NtStatus = SamIAccountRestrictions(
|
|||
|
UserHandle,
|
|||
|
&LogonWorkstation,
|
|||
|
WorkstationsPointer,
|
|||
|
LogonHoursPointer,
|
|||
|
&LogoffTime,
|
|||
|
&KickoffTime
|
|||
|
);
|
|||
|
|
|||
|
if ( NtStatus == STATUS_INVALID_LOGON_HOURS ) {
|
|||
|
|
|||
|
//
|
|||
|
// We hate to use 0xff all the time as a test value, but using
|
|||
|
// 0xA1 as a test value means that this test may fail depending
|
|||
|
// on when it runs. So only IF we get this error, will we try
|
|||
|
// again with 0xff as the logon hours.
|
|||
|
//
|
|||
|
|
|||
|
LogonHours.UnitsPerWeek = 168;
|
|||
|
|
|||
|
for ( i = 0; i < 21; i++ ) {
|
|||
|
|
|||
|
LogonHours.LogonHours[i] = 0xff;
|
|||
|
}
|
|||
|
|
|||
|
NtStatus = SamrSetInformationUser(
|
|||
|
UserHandle,
|
|||
|
UserLogonHoursInformation,
|
|||
|
(PSAMPR_USER_INFO_BUFFER)&LogonHours
|
|||
|
);
|
|||
|
ASSERT( NT_SUCCESS( NtStatus ) ) ;
|
|||
|
|
|||
|
MIDL_user_free( LogonHoursPointer );
|
|||
|
LogonHoursPointer = NULL;
|
|||
|
|
|||
|
NtStatus = SamrQueryInformationUser(
|
|||
|
UserHandle,
|
|||
|
UserLogonHoursInformation,
|
|||
|
(PSAMPR_USER_INFO_BUFFER *)&LogonHoursPointer
|
|||
|
);
|
|||
|
ASSERT( NT_SUCCESS( NtStatus ) ) ;
|
|||
|
|
|||
|
NtStatus = SamIAccountRestrictions(
|
|||
|
UserHandle,
|
|||
|
&LogonWorkstationU,
|
|||
|
WorkstationsPointer,
|
|||
|
LogonHoursPointer,
|
|||
|
&LogoffTime,
|
|||
|
&KickoffTime
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
MIDL_user_free( LogonHours.LogonHours );
|
|||
|
|
|||
|
MIDL_user_free( LogonHoursPointer );
|
|||
|
MIDL_user_free( WorkstationsPointer );
|
|||
|
|
|||
|
if ( !NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
}
|
|||
|
|
|||
|
// --------------------------------------------------------------
|
|||
|
// Test SamIGetPrivateData
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SamIGetPrivateData(
|
|||
|
UserHandle,
|
|||
|
&DataType,
|
|||
|
&SensitiveData,
|
|||
|
&OriginalDataLength,
|
|||
|
&PasswordDataPointer );
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
PasswordData1 = (PSAMI_PRIVATE_DATA_PASSWORD_TYPE)PasswordDataPointer;
|
|||
|
|
|||
|
if ( ( !( OriginalDataLength >= sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE ) ) ) ||
|
|||
|
( SensitiveData != TRUE ) ||
|
|||
|
( PasswordData1->DataType != SamPrivateDataPassword ) ) {
|
|||
|
|
|||
|
NtStatus = STATUS_DATA_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// --------------------------------------------------------------
|
|||
|
// Now test SamISetPrivateData() for user objects.
|
|||
|
//
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// First do a random user set to make sure we don't blow up.
|
|||
|
//
|
|||
|
|
|||
|
LocalPasswordData.DataType = SamPrivateDataPassword;
|
|||
|
|
|||
|
LocalPasswordData.CaseInsensitiveDbcs.Length = ENCRYPTED_LM_OWF_PASSWORD_LENGTH;
|
|||
|
LocalPasswordData.CaseInsensitiveDbcs.MaximumLength = ENCRYPTED_LM_OWF_PASSWORD_LENGTH;
|
|||
|
LocalPasswordData.CaseInsensitiveDbcs.Buffer = (PWSTR)&(LocalPasswordData.CaseInsensitiveDbcsBuffer);
|
|||
|
|
|||
|
BufferPointer = (PCHAR)&(LocalPasswordData.CaseInsensitiveDbcsBuffer);
|
|||
|
|
|||
|
for ( i = 0; i < ENCRYPTED_LM_OWF_PASSWORD_LENGTH; i++ ) {
|
|||
|
|
|||
|
*BufferPointer++ = (CHAR)(i + 12);
|
|||
|
}
|
|||
|
|
|||
|
LocalPasswordData.CaseSensitiveUnicode.Length = ENCRYPTED_NT_OWF_PASSWORD_LENGTH;
|
|||
|
LocalPasswordData.CaseSensitiveUnicode.MaximumLength = ENCRYPTED_NT_OWF_PASSWORD_LENGTH;
|
|||
|
LocalPasswordData.CaseSensitiveUnicode.Buffer = (PWSTR)&(LocalPasswordData.CaseSensitiveUnicodeBuffer);
|
|||
|
|
|||
|
BufferPointer = (PCHAR)(&LocalPasswordData.CaseSensitiveUnicodeBuffer);
|
|||
|
|
|||
|
for ( i = 0; i < ENCRYPTED_NT_OWF_PASSWORD_LENGTH; i++ ) {
|
|||
|
|
|||
|
*BufferPointer++ = (CHAR)(i + 47);
|
|||
|
}
|
|||
|
|
|||
|
LocalPasswordData.LmPasswordHistory.Length = 0;
|
|||
|
LocalPasswordData.LmPasswordHistory.MaximumLength = 0;
|
|||
|
LocalPasswordData.LmPasswordHistory.Buffer = (PWSTR)
|
|||
|
( &LocalPasswordData + sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE ) );
|
|||
|
|
|||
|
LocalPasswordData.NtPasswordHistory.Length = 0;
|
|||
|
LocalPasswordData.NtPasswordHistory.MaximumLength = 0;
|
|||
|
LocalPasswordData.NtPasswordHistory.Buffer = (PWSTR)
|
|||
|
( &LocalPasswordData + sizeof( SAMI_PRIVATE_DATA_PASSWORD_TYPE ) );
|
|||
|
|
|||
|
NtStatus = SamISetPrivateData(
|
|||
|
UserHandle,
|
|||
|
sizeof( LocalPasswordData ),
|
|||
|
&LocalPasswordData
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Now do a user get to make sure our set worked.
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SamIGetPrivateData(
|
|||
|
UserHandle,
|
|||
|
&DataType,
|
|||
|
&SensitiveData,
|
|||
|
&DataLength,
|
|||
|
&PasswordDataPointer );
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Verify the data is as we set it.
|
|||
|
//
|
|||
|
|
|||
|
PasswordData2 = (PSAMI_PRIVATE_DATA_PASSWORD_TYPE)PasswordDataPointer;
|
|||
|
|
|||
|
if ( ( PasswordData2->DataType != LocalPasswordData.DataType ) ||
|
|||
|
|
|||
|
( PasswordData2->CaseInsensitiveDbcs.Length != LocalPasswordData.CaseInsensitiveDbcs.Length ) ||
|
|||
|
|
|||
|
( PasswordData2->CaseSensitiveUnicode.Length != LocalPasswordData.CaseSensitiveUnicode.Length ) ||
|
|||
|
|
|||
|
( PasswordData2->LmPasswordHistory.Length != LocalPasswordData.LmPasswordHistory.Length ) ||
|
|||
|
|
|||
|
( PasswordData2->NtPasswordHistory.Length != LocalPasswordData.NtPasswordHistory.Length ) ||
|
|||
|
|
|||
|
( RtlCompareMemory(
|
|||
|
&LocalPasswordData.CaseInsensitiveDbcsBuffer,
|
|||
|
&(PasswordData2->CaseInsensitiveDbcsBuffer),
|
|||
|
ENCRYPTED_LM_OWF_PASSWORD_LENGTH) != ENCRYPTED_LM_OWF_PASSWORD_LENGTH ) ||
|
|||
|
|
|||
|
( RtlCompareMemory(
|
|||
|
&LocalPasswordData.CaseSensitiveUnicodeBuffer,
|
|||
|
&(PasswordData2->CaseSensitiveUnicodeBuffer),
|
|||
|
ENCRYPTED_NT_OWF_PASSWORD_LENGTH) != ENCRYPTED_NT_OWF_PASSWORD_LENGTH )
|
|||
|
|
|||
|
) {
|
|||
|
|
|||
|
NtStatus = STATUS_DATA_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Now do a user set to restore things to their original state.
|
|||
|
//
|
|||
|
|
|||
|
TmpStatus = SamISetPrivateData(
|
|||
|
UserHandle,
|
|||
|
OriginalDataLength,
|
|||
|
PasswordData1
|
|||
|
);
|
|||
|
|
|||
|
if ( NT_SUCCESS( NtStatus ) ) {
|
|||
|
|
|||
|
NtStatus = TmpStatus;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( PasswordData1 != NULL ) {
|
|||
|
|
|||
|
MIDL_user_free( PasswordData1 );
|
|||
|
}
|
|||
|
|
|||
|
if ( PasswordData2 != NULL ) {
|
|||
|
|
|||
|
MIDL_user_free( PasswordData2 );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return( NtStatus );
|
|||
|
|
|||
|
#else
|
|||
|
|
|||
|
return( STATUS_NOT_IMPLEMENTED );
|
|||
|
|
|||
|
#endif // SAM_SERVER_TESTS
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SampBuildDomainKeyName(
|
|||
|
OUT PUNICODE_STRING DomainKeyName,
|
|||
|
IN PUNICODE_STRING DomainName OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
This routine builds the name of a domain 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 domain.
|
|||
|
|
|||
|
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 domain.
|
|||
|
|
|||
|
|
|||
|
For example, given a DomainName of "ABC_DOMAIN" this would
|
|||
|
yield a resultant DomainKeyName of "DOMAINS\ABC_DOMAIN"
|
|||
|
|
|||
|
|
|||
|
|
|||
|
All allocation for this string will be done using MIDL_user_allocate.
|
|||
|
Any deallocations will be done using MIDL_user_free.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
DomainKeyName - 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.
|
|||
|
|
|||
|
|
|||
|
DomainName - The name of the domain. This string is not modified.
|
|||
|
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
STATUS_SUCCESS - DomainKeyName points at the full key name.
|
|||
|
|
|||
|
--*/
|
|||
|
{
|
|||
|
NTSTATUS NtStatus;
|
|||
|
USHORT TotalLength, DomainNameLength;
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Allocate a buffer large enough to hold the entire name.
|
|||
|
// Only count the domain name if it is passed.
|
|||
|
//
|
|||
|
|
|||
|
DomainNameLength = 0;
|
|||
|
if (ARGUMENT_PRESENT(DomainName)) {
|
|||
|
DomainNameLength = DomainName->Length + SampBackSlash.Length;
|
|||
|
}
|
|||
|
|
|||
|
TotalLength = SampNameDomains.Length +
|
|||
|
DomainNameLength +
|
|||
|
(USHORT)(sizeof(UNICODE_NULL)); // for null terminator
|
|||
|
|
|||
|
NtStatus = SampInitUnicodeString( DomainKeyName, TotalLength );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( DomainKeyName, &SampNameDomains);
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
if (ARGUMENT_PRESENT(DomainName)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString( DomainKeyName, &SampBackSlash );
|
|||
|
if (NT_SUCCESS(NtStatus)) {
|
|||
|
|
|||
|
//
|
|||
|
// "DOMAINS\(domain name)"
|
|||
|
//
|
|||
|
|
|||
|
NtStatus = SampAppendUnicodeString(
|
|||
|
DomainKeyName,
|
|||
|
DomainName
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//
|
|||
|
// Clean-up on failure
|
|||
|
//
|
|||
|
|
|||
|
if (!NT_SUCCESS(NtStatus)) {
|
|||
|
SampFreeUnicodeString( DomainKeyName );
|
|||
|
}
|
|||
|
|
|||
|
return(NtStatus);
|
|||
|
|
|||
|
}
|