8152 lines
232 KiB
C
8152 lines
232 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;
|
||
|
||
SAMTRACE("SamrOpenDomain");
|
||
|
||
//
|
||
// 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;
|
||
|
||
SAMTRACE("SamrQueryInformationDomain");
|
||
|
||
#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
|
||
|
||
SAMTRACE("SamrSetInformationDomain");
|
||
|
||
//
|
||
// 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;
|
||
|
||
SAMTRACE("SampCreateGroupInDomain");
|
||
|
||
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;
|
||
|
||
SAMTRACE("SamrCreateGroupInDomain");
|
||
|
||
(*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;
|
||
|
||
SAMTRACE("SamrEnumerateGroupsInDomain");
|
||
|
||
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;
|
||
|
||
SAMTRACE("SampCreateAliasInDomain");
|
||
|
||
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;
|
||
|
||
SAMTRACE("SamrCreateAliasInDomain");
|
||
|
||
(*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;
|
||
|
||
SAMTRACE("SamrEnumerateAliasesInDomain");
|
||
|
||
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;
|
||
|
||
SAMTRACE("SamrRemoveMemberFromForiegnDomain");
|
||
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
|
||
|
||
|
||
SAMTRACE("SampCreateUserInDomain");
|
||
|
||
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 = 0;
|
||
V1aFixed.OperatorCount = 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) ? FALSE : TRUE),
|
||
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;
|
||
|
||
SAMTRACE("SamrCreateUser2InDomain");
|
||
|
||
(*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;
|
||
|
||
SAMTRACE("SamrCreateUserInDomain");
|
||
|
||
(*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;
|
||
|
||
SAMTRACE("SamrEnumerateUsersInDomain");
|
||
|
||
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;
|
||
|
||
|
||
SAMTRACE("SamrLookupNamesInDomain");
|
||
|
||
|
||
//
|
||
// 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
|
||
);
|
||
|
||
SampDumpNtOpenKey((KEY_READ), &ObjectAttributes, 0);
|
||
|
||
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
|
||
);
|
||
|
||
SampDumpRtlpNtQueryValueKey(&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
|
||
);
|
||
|
||
SampDumpNtOpenKey((KEY_READ), &ObjectAttributes, 0);
|
||
|
||
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
|
||
);
|
||
|
||
SampDumpRtlpNtQueryValueKey(&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
|
||
);
|
||
|
||
|
||
SampDumpNtOpenKey((KEY_READ),
|
||
&ObjectAttributes,
|
||
0);
|
||
|
||
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
|
||
);
|
||
|
||
SampDumpRtlpNtQueryValueKey(&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;
|
||
|
||
|
||
SAMTRACE("SamrLookupIdsInDomain");
|
||
|
||
//
|
||
// 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;
|
||
|
||
|
||
|
||
SAMTRACE("SampOpenDomainKey");
|
||
|
||
|
||
//
|
||
// 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;
|
||
|
||
|
||
SAMTRACE("SampInitializeDomainObject");
|
||
|
||
//
|
||
// 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;
|
||
|
||
#if DBG
|
||
SID *Sid1, *Sid2;
|
||
#endif
|
||
|
||
SAMTRACE("SampInitializeSingleDomain");
|
||
|
||
|
||
//
|
||
// 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
|
||
);
|
||
|
||
SampDumpNtOpenKey((KEY_READ | KEY_WRITE), &ObjectAttributes, 0);
|
||
|
||
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);
|
||
|
||
Sid1 = Sid; Sid2 = SampDefinedDomains[Index].Sid;
|
||
|
||
DbgPrint("Sid1 Revision = %d\n", Sid1->Revision);
|
||
DbgPrint("Sid1 SubAuthorityCount = %d\n", Sid1->SubAuthorityCount);
|
||
DbgPrint("Sid1 IdentifierAuthority = %d\n", Sid1->IdentifierAuthority.Value[0]);
|
||
DbgPrint("Sid1 IdentifierAuthority = %d\n", Sid1->IdentifierAuthority.Value[1]);
|
||
DbgPrint("Sid1 IdentifierAuthority = %d\n", Sid1->IdentifierAuthority.Value[2]);
|
||
DbgPrint("Sid1 IdentifierAuthority = %d\n", Sid1->IdentifierAuthority.Value[3]);
|
||
DbgPrint("Sid1 IdentifierAuthority = %d\n", Sid1->IdentifierAuthority.Value[4]);
|
||
DbgPrint("Sid1 IdentifierAuthority = %d\n", Sid1->IdentifierAuthority.Value[5]);
|
||
DbgPrint("Sid1 SubAuthority = %lu\n", Sid1->SubAuthority[0]);
|
||
|
||
DbgPrint("Sid2 Revision = %d\n", Sid2->Revision);
|
||
DbgPrint("Sid2 SubAuthorityCount = %d\n", Sid2->SubAuthorityCount);
|
||
DbgPrint("Sid2 IdentifierAuthority = %d\n", Sid2->IdentifierAuthority.Value[0]);
|
||
DbgPrint("Sid2 IdentifierAuthority = %d\n", Sid2->IdentifierAuthority.Value[1]);
|
||
DbgPrint("Sid2 IdentifierAuthority = %d\n", Sid2->IdentifierAuthority.Value[2]);
|
||
DbgPrint("Sid2 IdentifierAuthority = %d\n", Sid2->IdentifierAuthority.Value[3]);
|
||
DbgPrint("Sid2 IdentifierAuthority = %d\n", Sid2->IdentifierAuthority.Value[4]);
|
||
DbgPrint("Sid2 IdentifierAuthority = %d\n", Sid2->IdentifierAuthority.Value[5]);
|
||
DbgPrint("Sid2 SubAuthority = %lu\n", Sid2->SubAuthority[0]);
|
||
#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
|
||
//
|
||
|
||
// MURLIS 6/11/96 -- Store the Context handle in the
|
||
// defined domains structure, before calling Initialize Display
|
||
// information as Enumerate Account Names will need this information
|
||
// to make the decision wether the domain is in the DS or in
|
||
// Registry.
|
||
|
||
SampDefinedDomains[Index].Context = DomainContext;
|
||
|
||
NtStatus = SampInitializeDisplayInformation( Index );
|
||
|
||
|
||
|
||
if (!NT_SUCCESS(NtStatus)) {
|
||
|
||
//
|
||
// NULL out the context handle in the defined_domain structure
|
||
//
|
||
|
||
SampDefinedDomains[Index].Context = NULL;
|
||
goto error_cleanup;
|
||
}
|
||
|
||
|
||
|
||
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;
|
||
|
||
SAMTRACE("SampSetDomainPolicy");
|
||
|
||
//
|
||
// 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;
|
||
|
||
SAMTRACE("SampReinitializeSingleDomain");
|
||
|
||
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
|
||
);
|
||
|
||
SampDumpNtOpenKey((KEY_READ | KEY_WRITE), &ObjectAttributes, 0);
|
||
|
||
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.
|
||
|
||
--*/
|
||
{
|
||
|
||
SAMTRACE("SampCollisionError");
|
||
|
||
//
|
||
// 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;
|
||
|
||
SAMTRACE("SamICreateAccountByRid");
|
||
|
||
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(
|
||
DomainContext,
|
||
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(
|
||
DomainContext,
|
||
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(
|
||
DomainContext,
|
||
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
|
||
);
|
||
|
||
SampDumpNtOpenKey((KEY_READ | KEY_WRITE), &ObjectAttributes, 0);
|
||
|
||
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
|
||
);
|
||
|
||
SampDumpNtOpenKey((KEY_READ | KEY_WRITE), &ObjectAttributes, 0);
|
||
|
||
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;
|
||
|
||
SAMTRACE("SamIGetSerialNumberDomain");
|
||
|
||
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 );
|
||
|
||
SAMTRACE("SamISetSerialNumberDomain");
|
||
|
||
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;
|
||
|
||
|
||
SAMTRACE("SampGetPrivateUserData");
|
||
|
||
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;
|
||
|
||
SAMTRACE("SamIGetPrivateData");
|
||
|
||
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;
|
||
|
||
SAMTRACE("SampSetPrivateUserData");
|
||
|
||
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;
|
||
|
||
SAMTRACE("SamISetPrivateData");
|
||
|
||
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;
|
||
|
||
SAMTRACE("SamrTestPrivateFunctionsDomain");
|
||
|
||
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;
|
||
|
||
SAMTRACE("SamrTestPrivateFunctionsUser");
|
||
|
||
// --------------------------------------------------------------
|
||
// 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;
|
||
|
||
SAMTRACE("SampBuildDomainKeyName");
|
||
|
||
//
|
||
// 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);
|
||
|
||
}
|