Leaked source code of windows server 2003
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.
 
 
 
 
 
 

913 lines
25 KiB

// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
//
// Copyright (c) 1985-2000 Microsoft Corporation
//
// This file is part of the Microsoft Research IPv6 Network Protocol Stack.
// You should have received a copy of the Microsoft End-User License Agreement
// for this software along with this release; see the file "license.txt".
// If not, please see http://www.research.microsoft.com/msripv6/license.htm,
// or write to Microsoft Research, One Microsoft Way, Redmond, WA 98052-6399.
//
// Abstract:
//
// This source file contains the routines to access the NT Registry for
// configuration info.
//
#include <oscfg.h>
#include <ndis.h>
#include <ip6imp.h>
#include "ip6def.h"
#include <ntddip6.h>
#include <string.h>
#include <wchar.h>
#include "ntreg.h"
#define WORK_BUFFER_SIZE 512
#ifdef ALLOC_PRAGMA
//
// This code is pagable.
//
#pragma alloc_text(PAGE, GetRegDWORDValue)
#pragma alloc_text(PAGE, SetRegDWORDValue)
#pragma alloc_text(PAGE, InitRegDWORDParameter)
#pragma alloc_text(PAGE, OpenRegKey)
#if 0
#pragma alloc_text(PAGE, GetRegStringValue)
#pragma alloc_text(PAGE, GetRegSZValue)
#pragma alloc_text(PAGE, GetRegMultiSZValue)
#endif
#endif // ALLOC_PRAGMA
WCHAR Tcpip6Parameters[] = L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\" TCPIPV6_NAME L"\\Parameters";
//* OpenRegKey
//
// Opens a Registry key and returns a handle to it.
//
// Returns (plus other failure codes):
// STATUS_OBJECT_NAME_NOT_FOUND
// STATUS_SUCCESS
//
NTSTATUS
OpenRegKey(
PHANDLE HandlePtr, // Where to write the opened handle.
HANDLE Parent,
const WCHAR *KeyName, // Name of Registry key to open.
OpenRegKeyAction Action)
{
NTSTATUS Status;
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING UKeyName;
PAGED_CODE();
RtlInitUnicodeString(&UKeyName, KeyName);
memset(&ObjectAttributes, 0, sizeof(OBJECT_ATTRIBUTES));
InitializeObjectAttributes(&ObjectAttributes, &UKeyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
Parent, NULL);
switch (Action) {
case OpenRegKeyRead:
Status = ZwOpenKey(HandlePtr, KEY_READ, &ObjectAttributes);
break;
case OpenRegKeyCreate:
Status = ZwCreateKey(HandlePtr, KEY_WRITE, &ObjectAttributes,
0, // TitleIndex
NULL, // Class
REG_OPTION_NON_VOLATILE,
NULL); // Disposition
break;
case OpenRegKeyDeleting:
Status = ZwOpenKey(HandlePtr, KEY_ALL_ACCESS, &ObjectAttributes);
break;
default:
ABORT();
Status = STATUS_INVALID_PARAMETER;
break;
}
return Status;
}
//* RegDeleteValue
//
// Deletes a value from the key.
//
NTSTATUS
RegDeleteValue(
HANDLE KeyHandle,
const WCHAR *ValueName)
{
NTSTATUS status;
UNICODE_STRING UValueName;
PAGED_CODE();
RtlInitUnicodeString(&UValueName, ValueName);
status = ZwDeleteValueKey(KeyHandle, &UValueName);
return status;
}
//* GetRegDWORDValue
//
// Reads a REG_DWORD value from the registry into the supplied variable.
//
NTSTATUS // Returns: STATUS_SUCCESS or an appropriate failure code.
GetRegDWORDValue(
HANDLE KeyHandle, // Open handle to the parent key of the value to read.
const WCHAR *ValueName, // Name of the value to read.
PULONG ValueData) // Variable into which to read the data.
{
NTSTATUS status;
ULONG resultLength;
PKEY_VALUE_FULL_INFORMATION keyValueFullInformation;
UCHAR keybuf[WORK_BUFFER_SIZE];
UNICODE_STRING UValueName;
PAGED_CODE();
RtlInitUnicodeString(&UValueName, ValueName);
keyValueFullInformation = (PKEY_VALUE_FULL_INFORMATION)keybuf;
RtlZeroMemory(keyValueFullInformation, sizeof(keyValueFullInformation));
status = ZwQueryValueKey(KeyHandle, &UValueName, KeyValueFullInformation,
keyValueFullInformation, WORK_BUFFER_SIZE,
&resultLength);
if (NT_SUCCESS(status)) {
if (keyValueFullInformation->Type != REG_DWORD) {
status = STATUS_INVALID_PARAMETER_MIX;
} else {
*ValueData = *((ULONG UNALIGNED *)
((PCHAR)keyValueFullInformation +
keyValueFullInformation->DataOffset));
}
}
return status;
}
//* SetRegDWORDValue
//
// Writes the contents of a variable to a REG_DWORD value.
//
NTSTATUS // Returns: STATUS_SUCCESS or an appropriate failure code.
SetRegDWORDValue(
HANDLE KeyHandle, // Open handle to the parent key of the value to write.
const WCHAR *ValueName, // Name of the value to write.
ULONG ValueData) // Variable from which to write the data.
{
NTSTATUS status;
UNICODE_STRING UValueName;
PAGED_CODE();
RtlInitUnicodeString(&UValueName, ValueName);
status = ZwSetValueKey(KeyHandle, &UValueName, 0, REG_DWORD,
&ValueData, sizeof ValueData);
return status;
}
//* SetRegQUADValue
//
// Writes the contents of a variable to a REG_BINARY value.
//
NTSTATUS // Returns: STATUS_SUCCESS or an appropriate failure code.
SetRegQUADValue(
HANDLE KeyHandle, // Open handle to the parent key of the value to write.
const WCHAR *ValueName, // Name of the value to write.
const LARGE_INTEGER *ValueData) // Variable from which to write the data.
{
NTSTATUS status;
UNICODE_STRING UValueName;
PAGED_CODE();
RtlInitUnicodeString(&UValueName, ValueName);
status = ZwSetValueKey(KeyHandle, &UValueName, 0, REG_BINARY,
(void *)ValueData, sizeof *ValueData);
return status;
}
//* GetRegIPAddrValue
//
// Reads a REG_SZ value from the registry into the supplied variable.
//
NTSTATUS // Returns: STATUS_SUCCESS or an appropriate failure code.
GetRegIPAddrValue(
HANDLE KeyHandle, // Open handle to the parent key of the value to read.
const WCHAR *ValueName, // Name of the value to read.
IPAddr *Addr) // Variable into which to read the data.
{
NTSTATUS status;
ULONG resultLength;
PKEY_VALUE_PARTIAL_INFORMATION info;
UCHAR keybuf[WORK_BUFFER_SIZE];
UNICODE_STRING UValueName;
WCHAR *string;
PAGED_CODE();
RtlInitUnicodeString(&UValueName, ValueName);
info = (PKEY_VALUE_PARTIAL_INFORMATION)keybuf;
status = ZwQueryValueKey(KeyHandle, &UValueName,
KeyValuePartialInformation,
info, WORK_BUFFER_SIZE,
&resultLength);
if (! NT_SUCCESS(status))
return status;
if (info->Type != REG_SZ)
return STATUS_INVALID_PARAMETER_MIX;
string = (WCHAR *)info->Data;
if ((info->DataLength < sizeof(WCHAR)) ||
(string[(info->DataLength/sizeof(WCHAR)) - 1] != UNICODE_NULL))
return STATUS_INVALID_PARAMETER_MIX;
if (! ParseV4Address(string, &string, Addr) ||
(*string != UNICODE_NULL))
return STATUS_INVALID_PARAMETER;
return STATUS_SUCCESS;
}
//* SetRegIPAddrValue
//
// Writes the contents of a variable to a REG_SZ value.
//
NTSTATUS // Returns: STATUS_SUCCESS or an appropriate failure code.
SetRegIPAddrValue(
HANDLE KeyHandle, // Open handle to the parent key of the value to write.
const WCHAR *ValueName, // Name of the value to write.
IPAddr Addr) // Variable from which to write the data.
{
NTSTATUS status;
UNICODE_STRING UValueName;
char AddrStr[16];
WCHAR ValueData[16];
uint len;
PAGED_CODE();
RtlInitUnicodeString(&UValueName, ValueName);
FormatV4AddressWorker(AddrStr, Addr);
for (len = 0;; len++) {
if ((ValueData[len] = (WCHAR)AddrStr[len]) == UNICODE_NULL)
break;
}
status = ZwSetValueKey(KeyHandle, &UValueName, 0, REG_SZ,
ValueData, (len + 1) * sizeof(WCHAR));
return status;
}
#if 0
//* GetRegStringValue
//
// Reads a REG_*_SZ string value from the Registry into the supplied
// key value buffer. If the buffer string buffer is not large enough,
// it is reallocated.
//
NTSTATUS // Returns: STATUS_SUCCESS or an appropriate failure code.
GetRegStringValue(
HANDLE KeyHandle, // Open handle to the parent key of the value to read.
const WCHAR *ValueName, // Name of the value to read.
PKEY_VALUE_PARTIAL_INFORMATION *ValueData, // Destination of read data.
PUSHORT ValueSize) // Size of the ValueData buffer. Updated on output.
{
NTSTATUS status;
ULONG resultLength;
UNICODE_STRING UValueName;
PAGED_CODE();
RtlInitUnicodeString(&UValueName, ValueName);
status = ZwQueryValueKey(KeyHandle, &UValueName,
KeyValuePartialInformation, *ValueData,
(ULONG) *ValueSize, &resultLength);
if ((status == STATUS_BUFFER_OVERFLOW) ||
(status == STATUS_BUFFER_TOO_SMALL)) {
PVOID temp;
//
// Free the old buffer and allocate a new one of the
// appropriate size.
//
ASSERT(resultLength > (ULONG) *ValueSize);
if (resultLength <= 0xFFFF) {
temp = ExAllocatePool(NonPagedPool, resultLength);
if (temp != NULL) {
if (*ValueData != NULL) {
ExFreePool(*ValueData);
}
*ValueData = temp;
*ValueSize = (USHORT) resultLength;
status = ZwQueryValueKey(KeyHandle, &UValueName,
KeyValuePartialInformation,
*ValueData, *ValueSize,
&resultLength);
ASSERT((status != STATUS_BUFFER_OVERFLOW) &&
(status != STATUS_BUFFER_TOO_SMALL));
} else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
} else {
status = STATUS_BUFFER_TOO_SMALL;
}
}
return status;
}
#endif // 0
#if 0
//* GetRegMultiSZValue
//
// Reads a REG_MULTI_SZ string value from the Registry into the supplied
// Unicode string. If the Unicode string buffer is not large enough,
// it is reallocated.
//
NTSTATUS // Returns: STATUS_SUCCESS or an appropriate failure code.
GetRegMultiSZValue(
HANDLE KeyHandle, // Open handle to parent key of value to read.
const WCHAR *ValueName, // Name of value to read.
PUNICODE_STRING ValueData) // Destination string for the value data.
{
NTSTATUS status;
ULONG resultLength;
PKEY_VALUE_PARTIAL_INFORMATION keyValuePartialInformation;
UNICODE_STRING UValueName;
PAGED_CODE();
ValueData->Length = 0;
status = GetRegStringValue(KeyHandle, ValueName,
(PKEY_VALUE_PARTIAL_INFORMATION *)
&(ValueData->Buffer),
&(ValueData->MaximumLength));
if (NT_SUCCESS(status)) {
keyValuePartialInformation =
(PKEY_VALUE_PARTIAL_INFORMATION) ValueData->Buffer;
if (keyValuePartialInformation->Type == REG_MULTI_SZ) {
ValueData->Length = (USHORT)
keyValuePartialInformation->DataLength;
RtlCopyMemory(ValueData->Buffer,
&(keyValuePartialInformation->Data),
ValueData->Length);
} else {
status = STATUS_INVALID_PARAMETER_MIX;
}
}
return status;
} // GetRegMultiSZValue
#endif // 0
#if 0
//* GetRegSZValue
//
// Reads a REG_SZ string value from the Registry into the supplied
// Unicode string. If the Unicode string buffer is not large enough,
// it is reallocated.
//
NTSTATUS // Returns: STATUS_SUCCESS or an appropriate failure code.
GetRegSZValue(
HANDLE KeyHandle, // Open handle to the parent key of the value to read.
const WCHAR *ValueName, // Name of the value to read.
PUNICODE_STRING ValueData, // Destination string for the value data.
PULONG ValueType) // On return, contains Registry type of value read.
{
NTSTATUS status;
ULONG resultLength;
PKEY_VALUE_PARTIAL_INFORMATION keyValuePartialInformation;
UNICODE_STRING UValueName;
PAGED_CODE();
ValueData->Length = 0;
status = GetRegStringValue(KeyHandle, ValueName,
(PKEY_VALUE_PARTIAL_INFORMATION *)
&(ValueData->Buffer),
&(ValueData->MaximumLength));
if (NT_SUCCESS(status)) {
keyValuePartialInformation =
(PKEY_VALUE_PARTIAL_INFORMATION)ValueData->Buffer;
if ((keyValuePartialInformation->Type == REG_SZ) ||
(keyValuePartialInformation->Type == REG_EXPAND_SZ)) {
WCHAR *src;
WCHAR *dst;
ULONG dataLength;
*ValueType = keyValuePartialInformation->Type;
dataLength = keyValuePartialInformation->DataLength;
ASSERT(dataLength <= ValueData->MaximumLength);
dst = ValueData->Buffer;
src = (PWCHAR) &(keyValuePartialInformation->Data);
while (ValueData->Length <= dataLength) {
if ((*dst++ = *src++) == UNICODE_NULL) {
break;
}
ValueData->Length += sizeof(WCHAR);
}
if (ValueData->Length < (ValueData->MaximumLength - 1)) {
ValueData->Buffer[ValueData->Length/sizeof(WCHAR)] =
UNICODE_NULL;
}
} else {
status = STATUS_INVALID_PARAMETER_MIX;
}
}
return status;
}
#endif // 0
//* InitRegDWORDParameter
//
// Reads a REG_DWORD parameter from the Registry into a variable. If the
// read fails, the variable is initialized to a default.
//
VOID
InitRegDWORDParameter(
HANDLE RegKey, // Open handle to the parent key of the value to read.
const WCHAR *ValueName, // The name of the value to read.
UINT *Value, // Destination variable into which to read the data.
UINT DefaultValue) // Default to assign if the read fails.
{
PAGED_CODE();
if ((RegKey == NULL) ||
!NT_SUCCESS(GetRegDWORDValue(RegKey, ValueName, (PULONG)Value))) {
//
// These registry parameters override the defaults, so their
// absence is not an error.
//
*Value = DefaultValue;
}
}
//* InitRegQUADParameter
//
// Reads a REG_BINARY value from the registry into the supplied variable.
//
// Upon failure, the variable is left untouched.
//
VOID
InitRegQUADParameter(
HANDLE RegKey, // Open handle to the parent key of the value to read.
const WCHAR *ValueName, // Name of the value to read.
LARGE_INTEGER *Value) // Variable into which to read the data.
{
NTSTATUS status;
ULONG resultLength;
UCHAR keybuf[WORK_BUFFER_SIZE];
PKEY_VALUE_PARTIAL_INFORMATION value =
(PKEY_VALUE_PARTIAL_INFORMATION) keybuf;
UNICODE_STRING UValueName;
PAGED_CODE();
if (RegKey == NULL)
return;
RtlInitUnicodeString(&UValueName, ValueName);
status = ZwQueryValueKey(RegKey, &UValueName,
KeyValuePartialInformation,
value, WORK_BUFFER_SIZE,
&resultLength);
if (NT_SUCCESS(status) &&
(value->Type == REG_BINARY) &&
(value->DataLength == sizeof *Value)) {
RtlCopyMemory(Value, value->Data, sizeof *Value);
}
}
#if 0
//* EnumRegMultiSz
//
// Parses a REG_MULTI_SZ string and returns the specified substring.
//
// Note: This code is called at raised IRQL. It is not pageable.
//
const WCHAR *
EnumRegMultiSz(
IN const WCHAR *MszString, // Pointer to the REG_MULTI_SZ string.
IN ULONG MszStringLength, // Length of above, including terminating null.
IN ULONG StringIndex) // Index number of substring to return.
{
const WCHAR *string = MszString;
if (MszStringLength < (2 * sizeof(WCHAR))) {
return NULL;
}
//
// Find the start of the desired string.
//
while (StringIndex) {
while (MszStringLength >= sizeof(WCHAR)) {
MszStringLength -= sizeof(WCHAR);
if (*string++ == UNICODE_NULL) {
break;
}
}
//
// Check for index out of range.
//
if (MszStringLength < (2 * sizeof(UNICODE_NULL))) {
return NULL;
}
StringIndex--;
}
if (MszStringLength < (2 * sizeof(UNICODE_NULL))) {
return NULL;
}
return string;
}
#endif // 0
//* OpenTopLevelRegKey
//
// Given the name of a top-level registry key (under Parameters),
// opens the registry key.
//
// Callable from thread context, not DPC context.
//
NTSTATUS
OpenTopLevelRegKey(const WCHAR *Name,
OUT HANDLE *RegKey, OpenRegKeyAction Action)
{
HANDLE ParametersKey;
NTSTATUS Status;
PAGED_CODE();
Status = OpenRegKey(&ParametersKey, NULL, Tcpip6Parameters,
OpenRegKeyRead);
if (! NT_SUCCESS(Status))
return Status;
Status = OpenRegKey(RegKey, ParametersKey, Name, Action);
ZwClose(ParametersKey);
return Status;
}
//* DeleteTopLevelRegKey
//
// Given the name of a top-level registry key (under Parameters),
// deletes the registry key and all subkeys and values.
//
// Callable from thread context, not DPC context.
//
NTSTATUS
DeleteTopLevelRegKey(const WCHAR *Name)
{
HANDLE RegKey;
NTSTATUS Status;
Status = OpenTopLevelRegKey(Name, &RegKey, OpenRegKeyDeleting);
if (! NT_SUCCESS(Status)) {
//
// If the registry key does not exist, that's OK.
//
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
Status = STATUS_SUCCESS;
}
else {
//
// DeleteRegKey always closes the key.
//
Status = DeleteRegKey(RegKey);
}
return Status;
}
//* EnumRegKeyIndex
//
// Enumerates the specified subkey of the registry key.
// Calls the callback function on the subkey.
//
// Callable from thread context, not DPC context.
//
NTSTATUS
EnumRegKeyIndex(
HANDLE RegKey,
uint Index,
EnumRegKeysCallback Callback,
void *Context)
{
KEY_BASIC_INFORMATION *Info;
uint InfoLength;
uint ResultLength;
NTSTATUS Status;
PAGED_CODE();
#if DBG
//
// Start with no buffer, to exercise the retry code.
//
Info = NULL;
InfoLength = 0;
#else
//
// Start with a decent-sized buffer.
//
ResultLength = WORK_BUFFER_SIZE;
goto AllocBuffer;
#endif
//
// Get basic information about the subkey.
//
for (;;) {
//
// The documentation for ZwEnumerateKey says
// that it returns STATUS_BUFFER_TOO_SMALL
// to indicate that the buffer is too small
// but it can also return STATUS_BUFFER_OVERFLOW.
//
Status = ZwEnumerateKey(RegKey, Index, KeyBasicInformation,
Info, InfoLength, (PULONG)&ResultLength);
if (NT_SUCCESS(Status)) {
break;
}
else if ((Status == STATUS_BUFFER_TOO_SMALL) ||
(Status == STATUS_BUFFER_OVERFLOW)) {
//
// We need a larger buffer.
// Leave space for a null character at the end.
//
#if DBG
if (Info != NULL)
ExFreePool(Info);
#else
ExFreePool(Info);
AllocBuffer:
#endif
Info = ExAllocatePool(PagedPool, ResultLength+sizeof(WCHAR));
if (Info == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorReturn;
}
InfoLength = ResultLength;
}
else
goto ErrorReturn;
}
//
// Null-terminate the name and call the callback function.
//
Info->Name[Info->NameLength/sizeof(WCHAR)] = UNICODE_NULL;
Status = (*Callback)(Context, RegKey, Info->Name);
ErrorReturn:
if (Info != NULL)
ExFreePool(Info);
return Status;
}
//* EnumRegKeys
//
// Enumerate the subkeys of the specified registry key.
// Calls the callback function for each subkey.
//
// Callable from thread context, not DPC context.
//
NTSTATUS
EnumRegKeys(
HANDLE RegKey,
EnumRegKeysCallback Callback,
void *Context)
{
uint Index;
NTSTATUS Status;
PAGED_CODE();
for (Index = 0;; Index++) {
Status = EnumRegKeyIndex(RegKey, Index, Callback, Context);
if (! NT_SUCCESS(Status)) {
if (Status == STATUS_NO_MORE_ENTRIES)
Status = STATUS_SUCCESS;
break;
}
}
return Status;
}
#define MAX_DELETE_REGKEY_ATTEMPTS 10
typedef struct DeleteRegKeyContext {
struct DeleteRegKeyContext *Next;
HANDLE RegKey;
uint Attempts;
} DeleteRegKeyContext;
//* DeleteRegKeyCallback
//
// Opens a subkey of the parent and pushes a new record onto the list.
//
NTSTATUS
DeleteRegKeyCallback(
void *Context,
HANDLE ParentKey,
WCHAR *SubKeyName)
{
DeleteRegKeyContext **pList = (DeleteRegKeyContext **) Context;
DeleteRegKeyContext *Record;
HANDLE SubKey;
NTSTATUS Status;
PAGED_CODE();
Status = OpenRegKey(&SubKey, ParentKey, SubKeyName, OpenRegKeyDeleting);
if (! NT_SUCCESS(Status))
return Status;
Record = ExAllocatePool(PagedPool, sizeof *Record);
if (Record == NULL) {
ZwClose(SubKey);
return STATUS_INSUFFICIENT_RESOURCES;
}
Record->RegKey = SubKey;
Record->Attempts = 0;
Record->Next = *pList;
*pList = Record;
return STATUS_SUCCESS;
}
//* DeleteRegKey
//
// Deletes a registry key and all subkeys.
//
// Uses depth-first iterative traversal instead of recursion,
// to avoid blowing out the kernel stack.
//
// Always closes the supplied registry key, even upon failure.
//
// Callable from thread context, not DPC context.
//
NTSTATUS
DeleteRegKey(HANDLE RegKey)
{
DeleteRegKeyContext *List;
DeleteRegKeyContext *This;
NTSTATUS Status;
PAGED_CODE();
//
// Start the iteration by creating a record for the parent key.
//
List = ExAllocatePool(PagedPool, sizeof *List);
if (List == NULL) {
ZwClose(RegKey);
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ErrorReturn;
}
List->Next = NULL;
List->RegKey = RegKey;
List->Attempts = 0;
while ((This = List) != NULL) {
//
// Try to delete the key at the front of the list.
//
This->Attempts++;
Status = ZwDeleteKey(This->RegKey);
if (NT_SUCCESS(Status)) {
//
// Remove the key from the list and repeat.
//
List = This->Next;
ZwClose(This->RegKey);
ExFreePool(This);
continue;
}
//
// If the deletion failed for some reason
// other than the presence of subkeys, stop now.
//
if (Status != STATUS_CANNOT_DELETE)
goto ErrorReturn;
//
// Limit the number of attempts to delete a key,
// to avoid an infinite loop. However we do want
// to try more than once, in case there is concurrent
// activity.
//
if (This->Attempts >= MAX_DELETE_REGKEY_ATTEMPTS)
goto ErrorReturn;
//
// Enumerate the child keys, pushing them on the list
// in front of the parent key.
//
Status = EnumRegKeys(This->RegKey, DeleteRegKeyCallback, &List);
if (! NT_SUCCESS(Status))
goto ErrorReturn;
//
// After the child keys are deleted, we will try again
// to delete the parent key.
//
}
return STATUS_SUCCESS;
ErrorReturn:
//
// Cleanup remaining records.
//
while ((This = List) != NULL) {
List = This->Next;
ZwClose(This->RegKey);
ExFreePool(This);
}
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_NTOS_ERROR,
"DeleteRegKey(%p) failed %x\n", RegKey, Status));
return Status;
}