You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
802 lines
19 KiB
802 lines
19 KiB
/*++
|
|
|
|
Copyright (c) 1997 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
reglayer.c
|
|
|
|
Abstract:
|
|
|
|
Implemntation of LSA/Registry interface and support routines
|
|
|
|
Author:
|
|
|
|
Mac McLain (MacM) Jan 17, 1997
|
|
|
|
Environment:
|
|
|
|
User Mode
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include <lsapch2.h>
|
|
#include <dbp.h>
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegReadObjectSD(
|
|
IN LSAPR_HANDLE ObjectHandle,
|
|
OUT PSECURITY_DESCRIPTOR *ppSD
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function will ready the security descriptor from the specified object
|
|
|
|
Arguments:
|
|
|
|
ObjectHandle - Object to read the SD from
|
|
ppSD -- Where the allocated security descriptor is returned. Allocated via
|
|
LsapAllocateLsaHeap.
|
|
|
|
Return Value:
|
|
|
|
Pointer to allocated memory on success or NULL on failure
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
ULONG SecurityDescriptorLength = 0;
|
|
|
|
Status = LsapDbReadAttributeObject(
|
|
ObjectHandle,
|
|
&LsapDbNames[SecDesc],
|
|
NULL,
|
|
&SecurityDescriptorLength
|
|
);
|
|
|
|
if ( NT_SUCCESS(Status ) ) {
|
|
|
|
//
|
|
// Allocate a buffer from the Lsa Heap for the existing object's SD.
|
|
//
|
|
|
|
*ppSD = LsapAllocateLsaHeap( SecurityDescriptorLength );
|
|
|
|
if ( *ppSD == NULL ) {
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Read the SD. It is the value of the SecDesc subkey.
|
|
//
|
|
|
|
Status = LsapDbReadAttributeObject(
|
|
ObjectHandle,
|
|
&LsapDbNames[SecDesc],
|
|
*ppSD,
|
|
&SecurityDescriptorLength
|
|
);
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
LsapFreeLsaHeap( *ppSD );
|
|
*ppSD = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegGetPhysicalObjectName(
|
|
IN PLSAP_DB_OBJECT_INFORMATION ObjectInformation,
|
|
IN PUNICODE_STRING LogicalNameU,
|
|
OUT OPTIONAL PUNICODE_STRING PhysicalNameU
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function returns the Physical Name of an object
|
|
given an object information buffer. Memory will be allocated for
|
|
the Unicode String Buffers that will receive the name(s).
|
|
|
|
|
|
The Physical Name of an object is the full path of the object relative
|
|
to the root ot the Database. It is computed by concatenating the Physical
|
|
Name of the Container Object (if any), the Classifying Directory
|
|
corresponding to the object type id, and the Logical Name of the
|
|
object.
|
|
|
|
<Physical Name of Object> =
|
|
[<Physical Name of Container Object> "\"]
|
|
[<Classifying Directory> "\"] <Logical Name of Object>
|
|
|
|
If there is no Container Object (as in the case of the Policy object)
|
|
the <Physical Name of Container Object> and following \ are omitted.
|
|
If there is no Classifying Directory (as in the case of the Policy object)
|
|
the <Classifying Directory> and following \ are omitted. If neither
|
|
Container Object not Classifying Directory exist, the Logical and Physical
|
|
names coincide.
|
|
|
|
Note that memory is allocated by this routine for the output
|
|
Unicode string buffer(s). When the output Unicode String(s) are no
|
|
longer needed, the memory must be freed by call(s) to
|
|
RtlFreeUnicodeString().
|
|
|
|
|
|
Arguments:
|
|
|
|
ObjectInformation - Pointer to object information containing as a minimum
|
|
the object's Logical Name, Container Object's handle and object type
|
|
id.
|
|
|
|
LogicalNameU - Optional pointer to Unicode String structure which will
|
|
receive the Logical Name of the object. A buffer will be allocated
|
|
by this routine for the name text. This memory must be freed when no
|
|
longer needed by calling RtlFreeUnicodeString() wiht a pointer such
|
|
as LogicalNameU to the Unicode String structure.
|
|
|
|
PhysicalNameU - Optional pointer to Unicode String structure which will
|
|
receive the Physical Name of the object. A buffer will be allocated by
|
|
this routine for the name text. This memory must be freed when no
|
|
longer needed by calling RtlFreeUnicodeString() with a pointer such as
|
|
PhysicalNameU to the Unicode String structure.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
STATUS_INSUFFICIENT_RESOURCES - Insufficient system resources to
|
|
allocate the name string buffer for the Physical Name or
|
|
Logical Name.
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
PUNICODE_STRING ContainerPhysicalNameU = NULL;
|
|
PUNICODE_STRING ClassifyingDirU = NULL;
|
|
UNICODE_STRING IntermediatePath1U;
|
|
PUNICODE_STRING JoinedPath1U = &IntermediatePath1U;
|
|
LSAP_DB_OBJECT_TYPE_ID ObjectTypeId = ObjectInformation->ObjectTypeId;
|
|
POBJECT_ATTRIBUTES ObjectAttributes = &ObjectInformation->ObjectAttributes;
|
|
|
|
//
|
|
// Initialize
|
|
//
|
|
|
|
RtlInitUnicodeString( &IntermediatePath1U, NULL );
|
|
|
|
//
|
|
// The Physical Name of the object is requested. Construct this
|
|
// in stages. First, get the Container Object Physical Name from
|
|
// the handle stored inside ObjectAttributes.
|
|
//
|
|
|
|
if (ObjectAttributes->RootDirectory != NULL) {
|
|
|
|
ContainerPhysicalNameU =
|
|
&(((LSAP_DB_HANDLE)
|
|
ObjectAttributes->RootDirectory)->PhysicalNameU);
|
|
}
|
|
|
|
//
|
|
// Next, get the Classifying Directory name appropriate to the
|
|
// object type.
|
|
//
|
|
|
|
if (LsapDbContDirs[ObjectTypeId].Length != 0) {
|
|
|
|
ClassifyingDirU = &LsapDbContDirs[ObjectTypeId];
|
|
}
|
|
|
|
//
|
|
// Now join the Physical Name of the Container Object and Classifying
|
|
// Directory together. If there is no Container Object and no
|
|
// Classifying Directory, just set the result to NULL.
|
|
//
|
|
|
|
if (ContainerPhysicalNameU == NULL && ClassifyingDirU == NULL) {
|
|
|
|
JoinedPath1U = NULL;
|
|
|
|
} else {
|
|
|
|
Status = LsapDbJoinSubPaths(
|
|
ContainerPhysicalNameU,
|
|
ClassifyingDirU,
|
|
JoinedPath1U
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto GetNamesError;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now join the Physical Name of the Containing Object, Classifying
|
|
// Directory and Logical Name of the object together. Note that
|
|
// JoinedPath1U may be NULL, but LogicalNameU is never NULL.
|
|
//
|
|
|
|
Status = LsapDbJoinSubPaths(
|
|
JoinedPath1U,
|
|
LogicalNameU,
|
|
PhysicalNameU
|
|
);
|
|
|
|
if (JoinedPath1U != NULL) {
|
|
|
|
RtlFreeUnicodeString( JoinedPath1U );
|
|
JoinedPath1U = NULL; // so we don't try to free it again
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
goto GetNamesError;
|
|
}
|
|
|
|
goto GetNamesFinish;
|
|
|
|
GetNamesError:
|
|
|
|
//
|
|
// If necessary, free any string buffer allocated to JoinedPath1U
|
|
//
|
|
|
|
if (JoinedPath1U != NULL) {
|
|
|
|
RtlFreeUnicodeString( JoinedPath1U );
|
|
}
|
|
|
|
GetNamesFinish:
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegOpenObject(
|
|
IN LSAP_DB_HANDLE ObjectHandle,
|
|
IN ULONG OpenMode,
|
|
OUT PVOID *pvKey
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Opens the object in the LSA registry database
|
|
|
|
Arguments:
|
|
|
|
ObjectHandle - Internal LSA object handle
|
|
|
|
OpenMode - How to open the object
|
|
|
|
pvKey - Where the key is returned
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
OBJECT_ATTRIBUTES OpenKeyObjectAttributes;
|
|
|
|
//
|
|
// Setup Object Attributes structure for opening the Registry key of
|
|
// the object. Specify as path the Physical Name of the object, this
|
|
// being the path of the object's Registry Key relative to the
|
|
// LSA Database root key.
|
|
//
|
|
|
|
InitializeObjectAttributes(
|
|
&OpenKeyObjectAttributes,
|
|
&(ObjectHandle->PhysicalNameU),
|
|
OBJ_CASE_INSENSITIVE,
|
|
LsapDbState.DbRootRegKeyHandle,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Now attempt to open the object's Registry Key. Store the Registry
|
|
// Key handle in the object's handle.
|
|
//
|
|
|
|
Status = RtlpNtOpenKey(
|
|
(PHANDLE) pvKey,
|
|
KEY_READ | KEY_WRITE,
|
|
&OpenKeyObjectAttributes,
|
|
0L
|
|
);
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegOpenTransaction(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function starts a transaction within the LSA Database.
|
|
|
|
WARNING: The Lsa Database must be in the locked state when this function
|
|
is called.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
Result codes are those returned from the Registry Transaction
|
|
Package.
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegOpenTransaction\n" ));
|
|
|
|
LsapDbLockAcquire( &LsapDbState.RegistryLock );
|
|
|
|
ASSERT( LsapDbState.RegistryTransactionOpen == FALSE );
|
|
|
|
Status = RtlStartRXact(LsapDbState.RXactContext);
|
|
|
|
ASSERT( NT_SUCCESS( Status ) || Status == STATUS_NO_MEMORY );
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
LsapDbState.RegistryTransactionOpen = TRUE;
|
|
LsapDbState.RegistryModificationCount = 0;
|
|
} else {
|
|
|
|
LsapDbLockRelease( &LsapDbState.RegistryLock );
|
|
}
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegOpenTransaction: 0x%lx\n", Status ));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegApplyTransaction(
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function applies a transaction within the LSA Database.
|
|
|
|
WARNING: The Lsa Database must be in the locked state when this function
|
|
is called.
|
|
|
|
Arguments:
|
|
|
|
ObjectHandle - Handle to an LSA object. This is expected to have
|
|
already been validated.
|
|
|
|
Options - Specifies optional actions to be taken. The following
|
|
options are recognized, other options relevant to calling routines
|
|
are ignored.
|
|
|
|
LSAP_DB_OMIT_REPLICATOR_NOTIFICATION - Omit notification to
|
|
Replicator.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
Result codes are those returned from the Registry Transaction
|
|
Package.
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegApplyTransaction\n" ));
|
|
|
|
//
|
|
ASSERT( LsapDbState.RegistryTransactionOpen == TRUE );
|
|
|
|
//
|
|
// Apply the Registry Transaction.
|
|
//
|
|
|
|
if ( LsapDbState.RegistryModificationCount > 0 ) {
|
|
|
|
Status = RtlApplyRXact(LsapDbState.RXactContext);
|
|
|
|
} else {
|
|
|
|
Status = RtlAbortRXact(LsapDbState.RXactContext);
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
LsapDbState.RegistryTransactionOpen = FALSE;
|
|
LsapDbState.RegistryModificationCount = 0;
|
|
|
|
LsapDbLockRelease( &LsapDbState.RegistryLock );
|
|
}
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegApplyTransaction: 0x%lx\n", Status ));
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegAbortTransaction(
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function aborts a transaction within the LSA Database.
|
|
|
|
WARNING: The Lsa Database must be in the locked state when this function
|
|
is called.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Standard Nt Result Code
|
|
|
|
Result codes are those returned from the Registry Transaction
|
|
Package.
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegAbortTransaction\n" ));
|
|
|
|
ASSERT( LsapDbState.RegistryTransactionOpen == TRUE );
|
|
|
|
Status = RtlAbortRXact(LsapDbState.RXactContext);
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
LsapDbState.RegistryTransactionOpen = FALSE;
|
|
LsapDbState.RegistryModificationCount = 0;
|
|
}
|
|
|
|
ASSERT( NT_SUCCESS( Status ) );
|
|
|
|
LsapDbLockRelease( &LsapDbState.RegistryLock );
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegAbortTransaction: 0x%lx\n", Status ));
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegCreateObject(
|
|
IN PUNICODE_STRING ObjectPath,
|
|
IN LSAP_DB_OBJECT_TYPE_ID ObjectType
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegCreateObject\n" ));
|
|
|
|
Status = RtlAddActionToRXact(
|
|
LsapDbState.RXactContext,
|
|
RtlRXactOperationSetValue,
|
|
ObjectPath,
|
|
ObjectType,
|
|
NULL, // No Key Value needed
|
|
0L
|
|
);
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
LsapDbState.RegistryModificationCount++;
|
|
}
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegCreateObjectL 0x%lx\n", Status ));
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegDeleteObject(
|
|
IN PUNICODE_STRING ObjectPath
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegDeleteObject\n" ));
|
|
|
|
Status = RtlAddActionToRXact(
|
|
LsapDbState.RXactContext,
|
|
RtlRXactOperationDelete,
|
|
ObjectPath,
|
|
0L,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
LsapDbState.RegistryModificationCount++;
|
|
}
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegDeleteObject: 0x%lx\n", Status ));
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegWriteAttribute(
|
|
IN PUNICODE_STRING AttributePath,
|
|
IN PVOID pvAttribute,
|
|
IN ULONG AttributeLength
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegWriteAttribute\n" ));
|
|
|
|
Status = RtlAddActionToRXact(
|
|
LsapDbState.RXactContext,
|
|
RtlRXactOperationSetValue,
|
|
AttributePath,
|
|
0L,
|
|
pvAttribute,
|
|
AttributeLength
|
|
);
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
LsapDbState.RegistryModificationCount++;
|
|
}
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegWriteAttribute: 0x%lx\n", Status ));
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegDeleteAttribute(
|
|
IN PUNICODE_STRING AttributePath,
|
|
IN BOOLEAN DeleteSecurely,
|
|
IN ULONG AttributeLength
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Deletes a registry attribute
|
|
|
|
Arguments:
|
|
|
|
AttributePath full pathname of attribute to delete
|
|
|
|
DeleteSecurely fill value with zero prior to deletion?
|
|
|
|
AttributeLength number of bytes to fill with zero (must be equal to
|
|
actual length of the attribute for secure deletion to
|
|
work); ignored if DeleteSecurely is FALSE
|
|
|
|
Returns:
|
|
|
|
STATUS_SUCCESS if happy
|
|
|
|
STATUS_ error code otherwise
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
PBYTE Buffer = NULL;
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegDeleteAttribute\n" ));
|
|
|
|
if ( DeleteSecurely &&
|
|
AttributeLength > 0 ) {
|
|
|
|
Buffer = ( PBYTE )LsapAllocateLsaHeap( AttributeLength );
|
|
|
|
if ( Buffer == NULL ) {
|
|
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
//
|
|
// NOTE: LsapAllocateLsaHeap returns memory that is zero-filled
|
|
// but even if it didn't, filling the secret value with
|
|
// random junk is just as good
|
|
//
|
|
|
|
Status = LsapRegWriteAttribute(
|
|
AttributePath,
|
|
Buffer,
|
|
AttributeLength
|
|
);
|
|
|
|
if ( !NT_SUCCESS( Status )) {
|
|
|
|
LsapFreeLsaHeap( Buffer );
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
Status = RtlAddActionToRXact(
|
|
LsapDbState.RXactContext,
|
|
RtlRXactOperationDelete,
|
|
AttributePath,
|
|
0L,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
LsapDbState.RegistryModificationCount++;
|
|
}
|
|
|
|
LsapDsDebugOut(( DEB_FTRACE, "LsapRegDeleteAttribute: 0x%lx\n", Status ));
|
|
|
|
LsapFreeLsaHeap( Buffer );
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
LsapRegReadAttribute(
|
|
IN LSAPR_HANDLE ObjectHandle,
|
|
IN PUNICODE_STRING AttributeName,
|
|
IN OPTIONAL PVOID AttributeValue,
|
|
IN OUT PULONG AttributeValueLength
|
|
)
|
|
{
|
|
//
|
|
// The LSA Database is implemented as a subtree of the Configuration
|
|
// Registry. In this implementation, Lsa Database objects correspond
|
|
// to Registry keys and "attributes" and their "values" correspond to
|
|
// Registry "subkeys" and "values" of the Registry key representing the
|
|
// object.
|
|
//
|
|
|
|
NTSTATUS Status, SecondaryStatus;
|
|
ULONG SubKeyValueActualLength;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
HANDLE SubKeyHandle = NULL;
|
|
LSAP_DB_HANDLE InternalHandle = (LSAP_DB_HANDLE) ObjectHandle;
|
|
|
|
//
|
|
// Reading an attribute of an object is simpler than writing one,
|
|
// because the Registry Transaction package is not used. Since an
|
|
// attribute is stored as the value of a subkey of the object's
|
|
// Registry Key, we can simply call the Registry API RtlpNtReadKey
|
|
// specifying the relative name of the subkey and the parent key's
|
|
// handle.
|
|
//
|
|
// Prior to opening the subkey in the Registry, setup ObjectAttributes
|
|
// containing the SubKey name and the Registry Handle for the LSA Database
|
|
// Root.
|
|
//
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
AttributeName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
InternalHandle->KeyHandle,
|
|
NULL
|
|
);
|
|
|
|
//
|
|
// Open the subkey
|
|
//
|
|
|
|
Status = RtlpNtOpenKey(
|
|
&SubKeyHandle,
|
|
KEY_READ,
|
|
&ObjectAttributes,
|
|
0L
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
SubKeyHandle = NULL; //For error processing
|
|
return(Status);
|
|
}
|
|
|
|
//
|
|
// Now query the size of the buffer required to read the subkey's
|
|
// value.
|
|
//
|
|
|
|
SubKeyValueActualLength = *AttributeValueLength;
|
|
|
|
//
|
|
// If a NULL buffer parameter has been supplied or the size of the
|
|
// buffer given is 0, this is just a size query.
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT(AttributeValue) || *AttributeValueLength == 0) {
|
|
|
|
Status = RtlpNtQueryValueKey(
|
|
SubKeyHandle,
|
|
NULL,
|
|
NULL,
|
|
&SubKeyValueActualLength,
|
|
NULL
|
|
);
|
|
|
|
if ((Status == STATUS_BUFFER_OVERFLOW) || NT_SUCCESS(Status)) {
|
|
|
|
*AttributeValueLength = SubKeyValueActualLength;
|
|
Status = STATUS_SUCCESS;
|
|
goto ReadAttError;
|
|
|
|
} else {
|
|
|
|
goto ReadAttError;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Supplied buffer is large enough to hold the SubKey's value.
|
|
// Query the value.
|
|
//
|
|
|
|
Status = RtlpNtQueryValueKey(
|
|
SubKeyHandle,
|
|
NULL,
|
|
AttributeValue,
|
|
&SubKeyValueActualLength,
|
|
NULL
|
|
);
|
|
|
|
if( (Status == STATUS_BUFFER_OVERFLOW) || NT_SUCCESS(Status) ) {
|
|
|
|
//
|
|
// Return the length of the Sub Key.
|
|
//
|
|
|
|
*AttributeValueLength = SubKeyValueActualLength;
|
|
}
|
|
|
|
ReadAttFinish:
|
|
|
|
//
|
|
// If necessary, close the Sub Key
|
|
//
|
|
|
|
if (SubKeyHandle != NULL) {
|
|
|
|
SecondaryStatus = NtClose( SubKeyHandle );
|
|
}
|
|
|
|
return(Status);
|
|
|
|
ReadAttError:
|
|
|
|
goto ReadAttFinish;
|
|
}
|