1425 lines
39 KiB
C
1425 lines
39 KiB
C
/*++
|
||
|
||
|
||
|
||
Copyright (c) 1991 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
Regkey.c
|
||
|
||
Abstract:
|
||
|
||
This module contains the server side implementation for the Win32
|
||
Registry APIs to open, create, flush and close keys. That is:
|
||
|
||
- BaseRegCloseKey
|
||
- BaseRegCreateKey
|
||
- BaseRegFlushKey
|
||
- BaseRegOpenKey
|
||
|
||
Author:
|
||
|
||
David J. Gilman (davegi) 15-Nov-1991
|
||
|
||
Notes:
|
||
|
||
These notes apply to the Win32 Registry API implementation as a whole
|
||
and not just to this module.
|
||
|
||
On the client side, modules contain RPC wrappers for both the new
|
||
Win32 and compatible Win 3.1 APIs. The Win 3.1 wrappers generally
|
||
supply default parameters before calling the Win32 wrappers. In some
|
||
cases they may need to call multiple Win32 wrappers in order to
|
||
function correctly (e.g. RegSetValue sometimes needs to call
|
||
RegCreateKeyEx). The Win32 wrappers are quite thin and usually do
|
||
nothing more than map a predefined handle to a real handle and perform
|
||
ANSI<->Unicode translations. In some cases (e.g. RegCreateKeyEx) the
|
||
wrapper also converts some argument (e.g. SECURITY_ATTRIBUTES) to an
|
||
RPCable representation. In both the Win 3.1 and Win32 cases ANSI and
|
||
Unicode implementations are provided.
|
||
|
||
On the server side, there is one entry point for each of the Win32
|
||
APIs. Each contains an identical interface with the client side
|
||
wrapper with the exception that all string / count arguments are
|
||
passed as a single counted Unicode string. Pictorially, for an API
|
||
named "F":
|
||
|
||
RegWin31FA() RegWin31FW() (client side)
|
||
|
||
| |
|
||
| |
|
||
| |
|
||
| |
|
||
V V
|
||
|
||
RegWin32FExA() RegWin32FExW()
|
||
|
||
| |
|
||
^ ^
|
||
v v (RPC)
|
||
| |
|
||
| |
|
||
+----> BaseRegF() <---+ (server side)
|
||
|
||
|
||
This yields smaller code (as the string conversion is done only once
|
||
per API) at the cost of slightly higher maintenance (i.e. Win 3.1
|
||
default parameter replacement and Win32 string conversions must be
|
||
manually kept in synch).
|
||
|
||
Another option would be to have a calling sequence that looks like,
|
||
|
||
RegWin31FA() RegWin31FW()
|
||
|
||
| |
|
||
| |
|
||
| |
|
||
V V
|
||
|
||
RegWin32FExA() -----> RegWin32FExW()
|
||
|
||
and have the RegWin32FExW() API perform all of the actual work. This
|
||
method is generally less efficient. It requires the RegWin32FExA()
|
||
API to convert its ANSI string arguments to counted Unicode strings,
|
||
extract the buffers to call the RegWin32FExW() API only to have it
|
||
rebuild a counted Unicode string. However in some cases (e.g.
|
||
RegConnectRegistry) where a counted Unicode string was not needed in
|
||
the Unicode API this method is used.
|
||
|
||
Details of an API's functionality, arguments and return value can be
|
||
found in the base implementations (e.g. BaseRegF()). All other
|
||
function headers contain only minimal routine descriptions and no
|
||
descriptions of their arguments or return value.
|
||
|
||
The comment string "Win3.1ism" indicates special code for Win 3.1
|
||
compatability.
|
||
|
||
Throughout the implementation the following variable names are used
|
||
and always refer to the same thing:
|
||
|
||
Obja - An OBJECT_ATTRIBUTES structure.
|
||
Status - A NTSTATUS value.
|
||
Error - A Win32 Registry error code (n.b. one of the error
|
||
values is ERROR_SUCCESS).
|
||
|
||
--*/
|
||
|
||
#include <rpc.h>
|
||
#include <string.h>
|
||
#include <wchar.h>
|
||
#include "regrpc.h"
|
||
#include "localreg.h"
|
||
#include "regclass.h"
|
||
#include "regecls.h"
|
||
#include "regsec.h"
|
||
#include <malloc.h>
|
||
|
||
#ifdef LOCAL
|
||
#include "tsappcmp.h"
|
||
|
||
#ifdef LEAK_TRACK
|
||
#include "regleak.h"
|
||
#endif // LEAK_TRACK
|
||
|
||
#endif
|
||
|
||
NTSTATUS
|
||
BaseRegCreateMultipartKey(
|
||
IN HKEY hkDestKey,
|
||
IN PUNICODE_STRING pDestSubKey,
|
||
IN PUNICODE_STRING lpClass OPTIONAL,
|
||
IN DWORD dwOptions,
|
||
IN REGSAM samDesired,
|
||
IN PRPC_SECURITY_ATTRIBUTES pRpcSecurityAttributes OPTIONAL,
|
||
OUT PHKEY phkResult,
|
||
OUT LPDWORD lpdwDisposition OPTIONAL,
|
||
ULONG Attributes);
|
||
|
||
|
||
|
||
BOOL
|
||
InitializeRegCreateKey(
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function was used to initialize a critical section that no longer
|
||
exists. This critical section was used when a key name '\', and multiple
|
||
multiple keys were to be created. The API used the wcstok defined in the
|
||
kernel, which was not multi-threaded safe.
|
||
|
||
This function now will always return TRUE. It will not be removed from the code
|
||
to avoid change in the rpc interface.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
Returns TRUE always.
|
||
|
||
--*/
|
||
|
||
{
|
||
return( TRUE );
|
||
|
||
}
|
||
|
||
|
||
|
||
BOOL
|
||
CleanupRegCreateKey(
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function was used to clean up a critical section that no longer
|
||
exists. This critical section was used when a key name '\', and multiple
|
||
multiple keys were to be created. The API used the wcstok defined in the
|
||
kernel, which was not multi-threaded safe.
|
||
|
||
This function now will always return TRUE. It will not be removed from the code
|
||
to avoid change in the rpc interface.
|
||
|
||
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
Returns TRUE if the cleanup succeeds.
|
||
|
||
--*/
|
||
|
||
{
|
||
return( TRUE );
|
||
}
|
||
|
||
|
||
|
||
error_status_t
|
||
BaseRegCloseKeyInternal(
|
||
IN OUT PHKEY phKey
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Closes a key handle.
|
||
|
||
Arguments:
|
||
|
||
phKey - Supplies a handle to an open key to be closed.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS (0) for success; error-code for failure.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS Status;
|
||
#if defined(LEAK_TRACK)
|
||
BOOL fTrack;
|
||
#endif // defined(LEAK_TRACK)
|
||
|
||
//
|
||
// Call out to Perflib if the HKEY is HKEY_PERFOMANCE_DATA.
|
||
//
|
||
|
||
if(( *phKey == HKEY_PERFORMANCE_DATA ) ||
|
||
( *phKey == HKEY_PERFORMANCE_TEXT ) ||
|
||
( *phKey == HKEY_PERFORMANCE_NLSTEXT )) {
|
||
|
||
Status = PerfRegCloseKey( phKey );
|
||
return (error_status_t)Status;
|
||
}
|
||
|
||
ASSERT( IsPredefinedRegistryHandle( *phKey ) == FALSE );
|
||
|
||
#ifdef LOCAL
|
||
//
|
||
// now we need to remove any state for registry key enumeration associated
|
||
// with this key if it's a class registration parent
|
||
//
|
||
if (REG_CLASS_IS_SPECIAL_KEY(*phKey)) {
|
||
|
||
// this may not succeed since someone could have already removed this key
|
||
(void) EnumTableRemoveKey(
|
||
&gClassesEnumTable,
|
||
*phKey,
|
||
ENUM_TABLE_REMOVEKEY_CRITERIA_ANYTHREAD);
|
||
}
|
||
|
||
#if defined(LEAK_TRACK)
|
||
|
||
if (g_RegLeakTraceInfo.bEnableLeakTrack) {
|
||
fTrack = RegLeakTableIsTrackedObject(&gLeakTable, *phKey);
|
||
}
|
||
|
||
#endif // defined(LEAK_TRACK)
|
||
|
||
#endif // LOCAL
|
||
|
||
|
||
Status = NtClose( *phKey );
|
||
|
||
if( NT_SUCCESS( Status )) {
|
||
|
||
#ifdef LOCAL
|
||
#if defined(LEAK_TRACK)
|
||
|
||
if (g_RegLeakTraceInfo.bEnableLeakTrack) {
|
||
if (fTrack) {
|
||
(void) UnTrackObject(*phKey);
|
||
}
|
||
}
|
||
|
||
#endif // defined(LEAK_TRACK)
|
||
#endif // LOCAL
|
||
|
||
//
|
||
// Set the handle to NULL so that RPC knows that it has been closed.
|
||
//
|
||
*phKey = NULL;
|
||
|
||
return ERROR_SUCCESS;
|
||
|
||
} else {
|
||
|
||
return (error_status_t)RtlNtStatusToDosError( Status );
|
||
}
|
||
}
|
||
|
||
|
||
|
||
error_status_t
|
||
BaseRegCloseKey(
|
||
IN OUT PHKEY phKey
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Closes a key handle.
|
||
|
||
Arguments:
|
||
|
||
phKey - Supplies a handle to an open key to be closed.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS (0) for success; error-code for failure.
|
||
|
||
--*/
|
||
|
||
{
|
||
error_status_t Error;
|
||
|
||
#ifndef LOCAL
|
||
RPC_STATUS _rpcstatus = RpcImpersonateClient( NULL );
|
||
#if DBG
|
||
if( _rpcstatus != ERROR_SUCCESS ) {
|
||
DbgPrint("WINREG: BaseRegCloseKey: Failed to impersonate in process %p, thread %p, for handle %p \n",NtCurrentProcess(),NtCurrentThread(),*phKey);
|
||
}
|
||
#endif
|
||
#endif //LOCAL
|
||
|
||
Error = BaseRegCloseKeyInternal(phKey);
|
||
|
||
#ifndef LOCAL
|
||
|
||
#if DBG
|
||
if( _rpcstatus != ERROR_SUCCESS ) {
|
||
DbgPrint("WINREG: BaseRegCloseKeyInternal without impersonation returned %lx\n",Error);
|
||
}
|
||
#endif
|
||
|
||
if (_rpcstatus == ERROR_SUCCESS) {
|
||
RpcRevertToSelf();
|
||
}
|
||
#endif
|
||
|
||
return Error;
|
||
}
|
||
|
||
|
||
error_status_t
|
||
BaseRegCreateKey(
|
||
IN HKEY hKey,
|
||
IN PUNICODE_STRING lpSubKey,
|
||
IN PUNICODE_STRING lpClass OPTIONAL,
|
||
IN DWORD dwOptions,
|
||
IN REGSAM samDesired,
|
||
IN PRPC_SECURITY_ATTRIBUTES pRpcSecurityAttributes OPTIONAL,
|
||
OUT PHKEY phkResult,
|
||
OUT LPDWORD lpdwDisposition OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Create a new key, with the specified name, or open an already existing
|
||
key. RegCreateKeyExW is atomic, meaning that one can use it to create
|
||
a key as a lock. If a second caller creates the same key, the call
|
||
will return a value that says whether the key already existed or not,
|
||
and thus whether the caller "owns" the "lock" or not. RegCreateKeyExW
|
||
does NOT truncate an existing entry, so the lock entry may contain
|
||
data.
|
||
|
||
Arguments:
|
||
|
||
hKey - Supplies a handle to an open key. The lpSubKey key path
|
||
parameter is relative to this key handle. Any of the predefined
|
||
reserved handle values or a previously opened key handle may be used
|
||
for hKey.
|
||
|
||
lpSubKey - Supplies the downward key path to the key to create.
|
||
lpSubKey is always relative to the key specified by hKey.
|
||
This parameter may not be NULL.
|
||
|
||
lpClass - Supplies the class (object type) of this key. Ignored if
|
||
the key already exists. No class is associated with this key if
|
||
this parameter is NULL.
|
||
|
||
dwOptions - Supplies special options. Only one is currently defined:
|
||
|
||
REG_VOLATILE - Specifies that this key should not be preserved
|
||
across reboot. The default is not volatile. This is ignored
|
||
if the key already exists.
|
||
|
||
WARNING: All descendent keys of a volatile key are also volatile.
|
||
|
||
samDesired - Supplies the requested security access mask. This
|
||
access mask describes the desired security access to the newly
|
||
created key.
|
||
|
||
lpSecurityAttributes - Supplies a pointer to a SECURITY_ATTRIBUTES
|
||
structure for the newly created key. This parameter is ignored
|
||
if NULL or not supported by the OS.
|
||
|
||
phkResult - Returns an open handle to the newly created key.
|
||
|
||
lpdwDisposition - Returns the disposition state, which can be one of:
|
||
|
||
REG_CREATED_NEW_KEY - the key did not exist and was created.
|
||
|
||
REG_OPENED_EXISTING_KEY - the key already existed, and was simply
|
||
opened without being changed.
|
||
|
||
This parameter is ignored if NULL.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS (0) for success; error-code for failure.
|
||
|
||
If successful, RegCreateKeyEx creates the new key (or opens the key if
|
||
it already exists), and returns an open handle to the newly created
|
||
key in phkResult. Newly created keys have no value; RegSetValue, or
|
||
RegSetValueEx must be called to set values. hKey must have been
|
||
opened for KEY_CREATE_SUB_KEY access.
|
||
|
||
--*/
|
||
|
||
{
|
||
OBJECT_ATTRIBUTES Obja;
|
||
ULONG Attributes;
|
||
NTSTATUS Status;
|
||
#if DBG
|
||
HANDLE DebugKey = hKey;
|
||
#endif
|
||
HKEY hkDestKey;
|
||
UNICODE_STRING DestClassSubkey;
|
||
PUNICODE_STRING pDestSubkey;
|
||
DWORD dwDisposition;
|
||
BOOL fRetryOnAccessDenied;
|
||
BOOL fRetried;
|
||
BOOL fTrySingleCreate;
|
||
#if LOCAL
|
||
SKeySemantics keyinfo;
|
||
BYTE rgNameInfoBuf[REG_MAX_CLASSKEY_LEN];
|
||
REGSAM OriginalSam = samDesired;
|
||
UNICODE_STRING TmpStr = *lpSubKey; //used to keep original SubKey string
|
||
|
||
|
||
memset(&keyinfo, 0, sizeof(keyinfo));
|
||
#endif
|
||
|
||
ASSERT( IsPredefinedRegistryHandle( hKey ) == FALSE );
|
||
ASSERT( lpSubKey->Length > 0 );
|
||
|
||
DestClassSubkey.Buffer = NULL;
|
||
|
||
//
|
||
// For class registrations, retry on access denied in machine hive --
|
||
// if we do retry, this will be set to FALSE so we only retry once
|
||
//
|
||
fRetryOnAccessDenied = TRUE;
|
||
fRetried = FALSE;
|
||
|
||
//
|
||
// First attempt should do create with a single ntcreatekey call
|
||
// If that doesn't work, this gets set to false so we remember if we
|
||
// have to retry for access denied in the machine hive
|
||
//
|
||
fTrySingleCreate = TRUE;
|
||
|
||
hkDestKey = NULL;
|
||
pDestSubkey = NULL;
|
||
|
||
|
||
//
|
||
// Quick check for a "restricted" handle
|
||
//
|
||
|
||
if ( REGSEC_CHECK_HANDLE( hKey ) )
|
||
{
|
||
if ( ! REGSEC_CHECK_PATH( hKey, lpSubKey ) )
|
||
{
|
||
return( ERROR_ACCESS_DENIED );
|
||
}
|
||
|
||
hKey = REGSEC_CLEAR_HANDLE( hKey );
|
||
}
|
||
|
||
//
|
||
// Check for malformed arguments from malicious clients
|
||
//
|
||
|
||
if ((lpSubKey->Length < sizeof(UNICODE_NULL)) ||
|
||
(lpSubKey->Buffer == NULL) ||
|
||
((lpSubKey->Length % sizeof(WCHAR)) != 0) ||
|
||
(lpSubKey->Buffer[lpSubKey->Length / sizeof(WCHAR) - 1] != L'\0')) {
|
||
return(ERROR_INVALID_PARAMETER);
|
||
}
|
||
|
||
//
|
||
// Impersonate the client.
|
||
//
|
||
|
||
RPC_IMPERSONATE_CLIENT( NULL );
|
||
|
||
//
|
||
// Initialize the variable that will contain the handle to NULL
|
||
// to ensure that in case of error the API will not return a
|
||
// bogus handle. This is required otherwise RPC will get confused.
|
||
// Note that RPC should have already initialized it to 0.
|
||
//
|
||
*phkResult = NULL;
|
||
|
||
//
|
||
// Subtract the NULLs from the Length of the provided strings.
|
||
// These were added on the client side so that the NULLs were
|
||
// transmitted by RPC.
|
||
//
|
||
lpSubKey->Length -= sizeof( UNICODE_NULL );
|
||
|
||
if( lpSubKey->Buffer[0] == ( WCHAR )'\\' ) {
|
||
//
|
||
// Do not accept a key name that starts with '\', even though
|
||
// the code below would handle it. This is to ensure that
|
||
// RegCreateKeyEx and RegOpenKeyEx will behave in the same way
|
||
// when they get a key name that starts with '\'.
|
||
//
|
||
Status = STATUS_OBJECT_PATH_INVALID;
|
||
goto cleanup;
|
||
}
|
||
|
||
if ( lpClass->Length > 0 ) {
|
||
lpClass->Length -= sizeof( UNICODE_NULL );
|
||
}
|
||
|
||
//
|
||
// Determine the correct set of attributes.
|
||
//
|
||
|
||
Attributes = OBJ_CASE_INSENSITIVE;
|
||
|
||
if( ARGUMENT_PRESENT( pRpcSecurityAttributes )) {
|
||
|
||
if( pRpcSecurityAttributes->bInheritHandle ) {
|
||
|
||
Attributes |= OBJ_INHERIT;
|
||
}
|
||
}
|
||
|
||
if (dwOptions & REG_OPTION_OPEN_LINK) {
|
||
Attributes |= OBJ_OPENLINK;
|
||
}
|
||
|
||
#ifdef LOCAL
|
||
if (REG_CLASS_IS_SPECIAL_KEY(hKey) ||
|
||
( (gdwRegistryExtensionFlags & TERMSRV_ENABLE_PER_USER_CLASSES_REDIRECTION )
|
||
&& ExtractClassKey(&hKey,lpSubKey) ) ) {
|
||
|
||
//
|
||
// Find more information
|
||
// about this key -- the most important piece of information
|
||
// is whether it's a class registration key
|
||
//
|
||
keyinfo._pFullPath = (PKEY_NAME_INFORMATION) rgNameInfoBuf;
|
||
keyinfo._cbFullPath = sizeof(rgNameInfoBuf);
|
||
keyinfo._fAllocedNameBuf = FALSE;
|
||
|
||
//
|
||
// see if this is a class registration
|
||
//
|
||
Status = BaseRegGetKeySemantics(hKey, lpSubKey, &keyinfo);
|
||
|
||
// if we can't determine what type of key this is, leave
|
||
if (!NT_SUCCESS(Status)) {
|
||
goto cleanup;
|
||
}
|
||
|
||
Status = BaseRegMapClassRegistrationKey(
|
||
hKey,
|
||
lpSubKey,
|
||
&keyinfo,
|
||
&DestClassSubkey,
|
||
&fRetryOnAccessDenied,
|
||
&hkDestKey,
|
||
&pDestSubkey);
|
||
|
||
if (!NT_SUCCESS(Status)) {
|
||
goto cleanup;
|
||
}
|
||
|
||
} else
|
||
#endif // LOCAL
|
||
{
|
||
#ifdef LOCAL
|
||
memset(&keyinfo, 0, sizeof(keyinfo));
|
||
#endif // LOCAL
|
||
|
||
hkDestKey = hKey;
|
||
pDestSubkey = lpSubKey;
|
||
}
|
||
|
||
|
||
for (;;) {
|
||
|
||
#ifdef LOCAL
|
||
|
||
Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
||
|
||
if (fTrySingleCreate)
|
||
{
|
||
#endif
|
||
|
||
//
|
||
// Validate the security descriptor.
|
||
//
|
||
if( ARGUMENT_PRESENT( pRpcSecurityAttributes ) &&
|
||
(pRpcSecurityAttributes->RpcSecurityDescriptor.lpSecurityDescriptor ||
|
||
pRpcSecurityAttributes->RpcSecurityDescriptor.cbInSecurityDescriptor))
|
||
{
|
||
if( !RtlValidRelativeSecurityDescriptor((PSECURITY_DESCRIPTOR)(pRpcSecurityAttributes->RpcSecurityDescriptor.lpSecurityDescriptor),
|
||
pRpcSecurityAttributes->RpcSecurityDescriptor.cbInSecurityDescriptor,
|
||
0 )) {
|
||
//
|
||
// We were passed a bogus security descriptor to set. Bail out
|
||
//
|
||
|
||
Status = STATUS_INVALID_PARAMETER;
|
||
goto cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Try to create the specified key. This will work if there is only
|
||
// one key being created or if the key already exists. If more than
|
||
// one key needs to be created, this will fail and we will have to
|
||
// do all the complicated stuff to create each intermediate key.
|
||
//
|
||
InitializeObjectAttributes(&Obja,
|
||
pDestSubkey,
|
||
Attributes,
|
||
hkDestKey,
|
||
ARGUMENT_PRESENT( pRpcSecurityAttributes )
|
||
? pRpcSecurityAttributes
|
||
->RpcSecurityDescriptor.lpSecurityDescriptor
|
||
: NULL);
|
||
Status = NtCreateKey(phkResult,
|
||
samDesired,
|
||
&Obja,
|
||
0,
|
||
lpClass,
|
||
dwOptions,
|
||
&dwDisposition);
|
||
|
||
|
||
#ifdef LOCAL
|
||
if (gpfnTermsrvCreateRegEntry && NT_SUCCESS(Status) && (dwDisposition == REG_CREATED_NEW_KEY)) {
|
||
//
|
||
// Terminal Server application compatiblity
|
||
// Store the newly created key in the Terminal Server registry tracking database
|
||
//
|
||
|
||
gpfnTermsrvCreateRegEntry(*phkResult,
|
||
&Obja,
|
||
0,
|
||
lpClass,
|
||
dwOptions);
|
||
}
|
||
|
||
}
|
||
|
||
#ifdef CLASSES_RETRY_ON_ACCESS_DENIED
|
||
if (fTrySingleCreate && (STATUS_ACCESS_DENIED == Status) && keyinfo._fCombinedClasses &&
|
||
fRetryOnAccessDenied ) {
|
||
|
||
Status = BaseRegMapClassOnAccessDenied(
|
||
&keyinfo,
|
||
&hkDestKey,
|
||
pDestSubkey,
|
||
&fRetryOnAccessDenied);
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
fRetried = TRUE;
|
||
continue;
|
||
}
|
||
|
||
// we failed for some reason -- exit
|
||
break;
|
||
|
||
}
|
||
#else
|
||
//if (it's terminal server; we're trying to create single key;
|
||
//we've got ASSESS_DENIED trying to create key
|
||
//a key we want to create is HKCR subkey(keyinfo._fCombinedClasses!=0);
|
||
//Registry flag is set to allow per user classes redirection.
|
||
//(fRetryOnAccessDenied !=0 - means that parent key is not in the user hive))
|
||
//then try to create the key in the user hive.
|
||
if ( (gdwRegistryExtensionFlags & TERMSRV_ENABLE_PER_USER_CLASSES_REDIRECTION)
|
||
&& fTrySingleCreate && (STATUS_ACCESS_DENIED == Status)
|
||
&& keyinfo._fCombinedClasses && fRetryOnAccessDenied
|
||
) {
|
||
|
||
|
||
if (DestClassSubkey.Buffer) {
|
||
RegClassHeapFree(DestClassSubkey.Buffer);
|
||
DestClassSubkey.Buffer=NULL;
|
||
}
|
||
|
||
Status = BaseRegMapClassOnAccessDenied(
|
||
&keyinfo,
|
||
&hkDestKey,
|
||
pDestSubkey,
|
||
&fRetryOnAccessDenied);
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
fRetried = TRUE;
|
||
continue;
|
||
}
|
||
|
||
// we failed for some reason -- exit
|
||
break;
|
||
|
||
}
|
||
#endif // CLASSES_RETRY_ON_ACCESS_DENIED
|
||
#endif // LOCAL
|
||
|
||
fTrySingleCreate = FALSE;
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
|
||
if (lpdwDisposition) {
|
||
*lpdwDisposition = dwDisposition;
|
||
}
|
||
|
||
} else {
|
||
|
||
Status = BaseRegCreateMultipartKey(
|
||
hkDestKey,
|
||
pDestSubkey,
|
||
lpClass,
|
||
dwOptions,
|
||
samDesired,
|
||
pRpcSecurityAttributes,
|
||
phkResult,
|
||
lpdwDisposition,
|
||
Attributes);
|
||
}
|
||
|
||
#ifdef LOCAL
|
||
#ifdef CLASSES_RETRY_ON_ACCESS_DENIED
|
||
if ((STATUS_ACCESS_DENIED == Status) && keyinfo._fCombinedClasses &&
|
||
fRetryOnAccessDenied ) {
|
||
|
||
Status = BaseRegMapClassOnAccessDenied(
|
||
&keyinfo,
|
||
&hkDestKey,
|
||
pDestSubkey,
|
||
&fRetryOnAccessDenied);
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
fRetried = TRUE;
|
||
continue;
|
||
}
|
||
|
||
break;
|
||
|
||
}
|
||
#else
|
||
//We've tried to create single key and failed (Status !=STATUS_ACCESS_DENIED)
|
||
//then we tried to create multipart key and got access denied
|
||
//thus we've got here
|
||
if ( (gdwRegistryExtensionFlags & TERMSRV_ENABLE_PER_USER_CLASSES_REDIRECTION)
|
||
&& (STATUS_ACCESS_DENIED == Status)
|
||
&& keyinfo._fCombinedClasses && fRetryOnAccessDenied
|
||
) {
|
||
|
||
if (DestClassSubkey.Buffer) {
|
||
RegClassHeapFree(DestClassSubkey.Buffer);
|
||
DestClassSubkey.Buffer=NULL;
|
||
}
|
||
|
||
Status = BaseRegMapClassOnAccessDenied(
|
||
&keyinfo,
|
||
&hkDestKey,
|
||
pDestSubkey,
|
||
&fRetryOnAccessDenied);
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
fRetried = TRUE;
|
||
continue;
|
||
}
|
||
|
||
break;
|
||
|
||
}
|
||
#endif // CLASSES_RETRY_ON_ACCESS_DENIED
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
if (keyinfo._fCombinedClasses) {
|
||
// mark this key as part of hkcr
|
||
*phkResult = REG_CLASS_SET_SPECIAL_KEY(*phkResult);
|
||
}
|
||
}
|
||
|
||
#endif // LOCAL
|
||
|
||
break;
|
||
}
|
||
|
||
cleanup:
|
||
|
||
|
||
#ifdef CLASSES_RETRY_ON_ACCESS_DENIED
|
||
//
|
||
// Memory was allocated if we retried, so free it
|
||
//
|
||
if (fRetried && pDestSubkey->Buffer) {
|
||
RtlFreeHeap(RtlProcessHeap(), 0, pDestSubkey->Buffer);
|
||
pDestSubkey->Buffer = NULL;
|
||
}
|
||
#endif // CLASSES_RETRY_ON_ACCESS_DENIED
|
||
|
||
if (hkDestKey && (hkDestKey != hKey)) {
|
||
NtClose(hkDestKey);
|
||
}
|
||
|
||
#ifdef LOCAL
|
||
if (DestClassSubkey.Buffer) {
|
||
RegClassHeapFree(DestClassSubkey.Buffer);
|
||
}
|
||
|
||
BaseRegReleaseKeySemantics(&keyinfo);
|
||
|
||
*lpSubKey = TmpStr; //restore original SubKey string
|
||
#endif // LOCAL
|
||
|
||
if (NT_SUCCESS(Status)) {
|
||
#ifdef LOCAL
|
||
#if defined(LEAK_TRACK)
|
||
|
||
if (g_RegLeakTraceInfo.bEnableLeakTrack) {
|
||
(void) TrackObject(*phkResult);
|
||
}
|
||
|
||
#endif // defined(LEAK_TRACK)
|
||
#endif LOCAL
|
||
// disabled, for the case where we specifically close a predefined key inside
|
||
// RegOpenKeyExA and RegOpenKeyExW
|
||
//ASSERT( *phkResult != DebugKey );
|
||
}
|
||
|
||
RPC_REVERT_TO_SELF();
|
||
|
||
return (error_status_t)RtlNtStatusToDosError( Status );
|
||
|
||
}
|
||
|
||
|
||
|
||
NTSTATUS
|
||
BaseRegCreateMultipartKey(
|
||
IN HKEY hkDestKey,
|
||
IN PUNICODE_STRING pDestSubKey,
|
||
IN PUNICODE_STRING lpClass OPTIONAL,
|
||
IN DWORD dwOptions,
|
||
IN REGSAM samDesired,
|
||
IN PRPC_SECURITY_ATTRIBUTES pRpcSecurityAttributes OPTIONAL,
|
||
OUT PHKEY phkResult,
|
||
OUT LPDWORD lpdwDisposition OPTIONAL,
|
||
ULONG Attributes)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function creates registry keys for which multiple path components
|
||
are nonexistent. It parses the key path and creates each intermediate
|
||
subkey.
|
||
|
||
Arguments:
|
||
|
||
See BaseRegCreateKey.
|
||
|
||
Return Value:
|
||
|
||
Returns STATUS_SUCCESS on success, other NTSTATUS if failed.
|
||
|
||
--*/
|
||
{
|
||
LPWSTR KeyBuffer;
|
||
ULONG NumberOfSubKeys;
|
||
LPWSTR p;
|
||
ULONG i;
|
||
LPWSTR Token;
|
||
UNICODE_STRING KeyName;
|
||
HANDLE TempHandle1;
|
||
HANDLE TempHandle2;
|
||
OBJECT_ATTRIBUTES Obja;
|
||
NTSTATUS Status;
|
||
DWORD dwDisposition;
|
||
#ifdef LOCAL
|
||
REGSAM OriginalSam = samDesired;
|
||
#endif // LOCAL
|
||
|
||
dwDisposition = REG_OPENED_EXISTING_KEY;
|
||
TempHandle1 = NULL;
|
||
|
||
//
|
||
// Win3.1ism - Loop through each '\' separated component in the
|
||
// supplied sub key and create a key for each component. This is
|
||
// guaranteed to work at least once because lpSubKey was validated
|
||
// on the client side.
|
||
//
|
||
|
||
|
||
//
|
||
// Initialize the buffer to be tokenized.
|
||
//
|
||
|
||
KeyBuffer = pDestSubKey->Buffer;
|
||
|
||
//
|
||
// Find out the number of subkeys to be created
|
||
//
|
||
NumberOfSubKeys = 1;
|
||
p = KeyBuffer;
|
||
while ( ( p = wcschr( p, ( WCHAR )'\\' ) ) != NULL ) {
|
||
p++;
|
||
NumberOfSubKeys++;
|
||
}
|
||
|
||
for( i = 0, Token = KeyBuffer; i < NumberOfSubKeys; i++ ) {
|
||
|
||
ASSERT(Token != NULL);
|
||
|
||
if( ( *Token == ( WCHAR )'\\' ) &&
|
||
( i != NumberOfSubKeys - 1 ) ) {
|
||
//
|
||
// If the first character of the key name is '\', and the key
|
||
// is not the last to be created, then ignore this key name.
|
||
// This condition can happen if the key name contains
|
||
// consecutive '\'.
|
||
// This behavior is consistent with the one we had in the past
|
||
// when the API used wcstok() to get the key names.
|
||
// Note that if the key name is an empty string, we return a handle
|
||
// that is different than hKey, even though both point to the same
|
||
// key. This is by design.
|
||
//
|
||
Token++;
|
||
continue;
|
||
}
|
||
|
||
//
|
||
// Convert the token to a counted Unicode string.
|
||
//
|
||
KeyName.Buffer = Token;
|
||
if (i == NumberOfSubKeys - 1) {
|
||
KeyName.Length = wcslen(Token)*sizeof(WCHAR);
|
||
} else {
|
||
KeyName.Length = (USHORT)(wcschr(Token, ( WCHAR )'\\') - Token)*sizeof(WCHAR);
|
||
}
|
||
|
||
//
|
||
// Remember the intermediate handle (NULL the first time through).
|
||
//
|
||
|
||
TempHandle2 = TempHandle1;
|
||
|
||
{
|
||
//
|
||
// Initialize the OBJECT_ATTRIBUTES structure, close the
|
||
// intermediate key and create or open the key.
|
||
//
|
||
|
||
InitializeObjectAttributes(
|
||
&Obja,
|
||
&KeyName,
|
||
Attributes,
|
||
hkDestKey,
|
||
ARGUMENT_PRESENT( pRpcSecurityAttributes )
|
||
? pRpcSecurityAttributes
|
||
->RpcSecurityDescriptor.lpSecurityDescriptor
|
||
: NULL
|
||
);
|
||
|
||
Status = NtCreateKey(
|
||
&TempHandle1,
|
||
( i == NumberOfSubKeys - 1 )? samDesired :
|
||
(samDesired & KEY_WOW64_RES) | MAXIMUM_ALLOWED,
|
||
&Obja,
|
||
0,
|
||
lpClass,
|
||
dwOptions,
|
||
&dwDisposition
|
||
);
|
||
|
||
if (NT_SUCCESS(Status) && lpdwDisposition) {
|
||
*lpdwDisposition = dwDisposition;
|
||
}
|
||
|
||
#ifdef LOCAL
|
||
// This code is in Hydra 4. We have disabled this for NT 5
|
||
// for now till we are sure that its needed to get some imporatant
|
||
// app to work on Hydra 5. Otherwise this should be removed
|
||
if ( gdwRegistryExtensionFlags & TERMSRV_ENABLE_ACCESS_FLAG_MODIFICATION ) {
|
||
|
||
// For Terminal Server only.
|
||
// Some apps try to create/open the key with all of the access bits
|
||
// turned on. We'll mask off the ones they don't have access to by
|
||
// default, (at least under HKEY_LOCAL_MACHINE\Software) and try to
|
||
// open the key again.
|
||
if (Status == STATUS_ACCESS_DENIED) {
|
||
//MAXIMUM_ALLOWED does not include ACCESS_SYSTEM_SECURITY
|
||
//so if user asks for this permission, we need to add it.
|
||
//It could result in ACCESS_DENIED error but for
|
||
//TS App. Compat. it is not important.
|
||
Status = NtCreateKey(
|
||
&TempHandle1,
|
||
(samDesired & (KEY_WOW64_RES | ACCESS_SYSTEM_SECURITY) ) | MAXIMUM_ALLOWED,
|
||
&Obja,
|
||
0,
|
||
lpClass,
|
||
dwOptions,
|
||
&dwDisposition);
|
||
|
||
// Give app back the original error
|
||
if (!NT_SUCCESS(Status)) {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
if (lpdwDisposition) {
|
||
*lpdwDisposition = dwDisposition;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
if (gpfnTermsrvCreateRegEntry && NT_SUCCESS(Status) && (dwDisposition == REG_CREATED_NEW_KEY)) {
|
||
|
||
//
|
||
// Terminal Server application compatiblity
|
||
// Store the newly created key in the Terminal Server registry tracking database
|
||
//
|
||
gpfnTermsrvCreateRegEntry(TempHandle1,
|
||
&Obja,
|
||
0,
|
||
lpClass,
|
||
dwOptions);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
//
|
||
// Initialize the next object directory (i.e. parent key) handle.
|
||
//
|
||
|
||
hkDestKey = TempHandle1;
|
||
|
||
//
|
||
// Close the intermediate key.
|
||
// This fails the first time through the loop since the
|
||
// handle is NULL.
|
||
//
|
||
|
||
if( TempHandle2 != NULL ) {
|
||
NtClose( TempHandle2 );
|
||
}
|
||
|
||
//
|
||
// If creating the key failed, map and return the error.
|
||
//
|
||
|
||
if( ! NT_SUCCESS( Status )) {
|
||
return Status;
|
||
}
|
||
|
||
Token = wcschr( Token, ( WCHAR )'\\') + 1;
|
||
|
||
}
|
||
|
||
//
|
||
// Only set the return value once we know we've
|
||
// succeeded.
|
||
//
|
||
*phkResult = hkDestKey;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
|
||
|
||
error_status_t
|
||
BaseRegFlushKey(
|
||
IN HKEY hKey
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Flush changes to backing store. Flush will not return until the data
|
||
has been written to backing store. It will flush all the attributes
|
||
of a single key. Closing a key without flushing it will NOT abort
|
||
changes.
|
||
|
||
Arguments:
|
||
|
||
hKey - Supplies a handle to the open key.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS (0) for success; error-code for failure.
|
||
|
||
If successful, RegFlushKey will flush to backing store any changes
|
||
made to the key.
|
||
|
||
Notes:
|
||
|
||
RegFlushKey may also flush other data in the Registry, and therefore
|
||
can be expensive, it should not be called gratuitously.
|
||
|
||
--*/
|
||
|
||
{
|
||
if ((hKey == HKEY_PERFORMANCE_DATA) ||
|
||
(hKey == HKEY_PERFORMANCE_TEXT) ||
|
||
(hKey == HKEY_PERFORMANCE_NLSTEXT)) {
|
||
return(ERROR_SUCCESS);
|
||
}
|
||
|
||
ASSERT( IsPredefinedRegistryHandle( hKey ) == FALSE );
|
||
|
||
|
||
//
|
||
// Call the Nt Api to flush the key, map the NTSTATUS code to a
|
||
// Win32 Registry error code and return.
|
||
//
|
||
|
||
return (error_status_t)RtlNtStatusToDosError( NtFlushKey( hKey ));
|
||
}
|
||
|
||
error_status_t
|
||
BaseRegOpenKey(
|
||
IN HKEY hKey,
|
||
IN PUNICODE_STRING lpSubKey,
|
||
IN DWORD dwOptions,
|
||
IN REGSAM samDesired,
|
||
OUT PHKEY phkResult
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Open a key for access, returning a handle to the key. If the key is
|
||
not present, it is not created (see RegCreateKeyExW).
|
||
|
||
Arguments:
|
||
|
||
hKey - Supplies a handle to an open key. The lpSubKey pathname
|
||
parameter is relative to this key handle. Any of the predefined
|
||
reserved handle values or a previously opened key handle may be used
|
||
for hKey. NULL is not permitted.
|
||
|
||
lpSubKey - Supplies the downward key path to the key to open.
|
||
lpSubKey is always relative to the key specified by hKey.
|
||
|
||
dwOptions -- reserved.
|
||
|
||
samDesired -- This access mask describes the desired security access
|
||
for the key.
|
||
|
||
phkResult -- Returns the handle to the newly opened key.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS (0) for success; error-code for failure.
|
||
|
||
If successful, RegOpenKeyEx will return the handle to the newly opened
|
||
key in phkResult.
|
||
|
||
--*/
|
||
|
||
{
|
||
OBJECT_ATTRIBUTES Obja;
|
||
NTSTATUS Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
||
error_status_t ret = ERROR_SUCCESS;
|
||
#ifdef LOCAL
|
||
UNICODE_STRING TmpStr = *lpSubKey; //used to keep original SubKey string
|
||
#endif
|
||
|
||
UNREFERENCED_PARAMETER( dwOptions );
|
||
|
||
ASSERT( IsPredefinedRegistryHandle( hKey ) == FALSE );
|
||
|
||
//
|
||
// Need to NULL this out param for compat with NT4, even though SDK
|
||
// does not define this out param on api failure -- bad apps were written
|
||
// which rely on this. Used to get NULLed by call to NtOpenKey, but since
|
||
// we don't always call that now, we need to do this here in user mode. Also
|
||
// need an exception wrapper since NtOpenKey would simply return an error if
|
||
// the pointer were invalid, whereas in user mode we access violate if we simply
|
||
// assign -- yet another fix needed for app compatibility as some apps on NT 4
|
||
// were actually passing in a bad pointer and ignoring the error returned
|
||
// by the api as part of their normal operation.
|
||
//
|
||
|
||
__try {
|
||
|
||
*phkResult = NULL;
|
||
|
||
} __except ( EXCEPTION_EXECUTE_HANDLER ) {
|
||
|
||
Status = GetExceptionCode();
|
||
|
||
#if DBG
|
||
DbgPrint( "WINREG Error: Exception %x in BaseRegOpenKey\n",
|
||
Status );
|
||
#endif
|
||
ret = RtlNtStatusToDosError( Status );
|
||
}
|
||
|
||
//
|
||
// This will only be true if there was an exception above --
|
||
// return the exception code as an error
|
||
//
|
||
|
||
if (ERROR_SUCCESS != ret) {
|
||
return ret;
|
||
}
|
||
|
||
//
|
||
// Quick check for a "restricted" handle
|
||
//
|
||
|
||
if ( REGSEC_CHECK_HANDLE( hKey ) )
|
||
{
|
||
if ( ! REGSEC_CHECK_PATH( hKey, lpSubKey ) )
|
||
{
|
||
return( ERROR_ACCESS_DENIED );
|
||
}
|
||
|
||
hKey = REGSEC_CLEAR_HANDLE( hKey );
|
||
}
|
||
|
||
//
|
||
// Impersonate the client.
|
||
//
|
||
|
||
RPC_IMPERSONATE_CLIENT( NULL );
|
||
|
||
//
|
||
// Subtract the NULLs from the Length of the provided string.
|
||
// This was added on the client side so that the NULL was
|
||
// transmited by RPC.
|
||
//
|
||
lpSubKey->Length -= sizeof( UNICODE_NULL );
|
||
|
||
//
|
||
// Initialize the OBJECT_ATTRIBUTES structure and open the key.
|
||
//
|
||
|
||
InitializeObjectAttributes(
|
||
&Obja,
|
||
lpSubKey,
|
||
dwOptions & REG_OPTION_OPEN_LINK ? (OBJ_OPENLINK | OBJ_CASE_INSENSITIVE)
|
||
: OBJ_CASE_INSENSITIVE,
|
||
hKey,
|
||
NULL
|
||
);
|
||
|
||
#ifdef LOCAL
|
||
if ( REG_CLASS_IS_SPECIAL_KEY(hKey) ||
|
||
( (gdwRegistryExtensionFlags & TERMSRV_ENABLE_PER_USER_CLASSES_REDIRECTION)
|
||
&& ExtractClassKey(&hKey,lpSubKey) ) ) {
|
||
Status = BaseRegOpenClassKey(
|
||
hKey,
|
||
lpSubKey,
|
||
dwOptions,
|
||
samDesired,
|
||
phkResult);
|
||
|
||
} else
|
||
#endif // LOCAL
|
||
{
|
||
//
|
||
// Obja was initialized above
|
||
//
|
||
|
||
Status = NtOpenKey(
|
||
phkResult,
|
||
samDesired,
|
||
&Obja);
|
||
}
|
||
|
||
RPC_REVERT_TO_SELF();
|
||
|
||
ret = (error_status_t)RtlNtStatusToDosError( Status );
|
||
|
||
#ifdef LOCAL
|
||
if (STATUS_ACCESS_DENIED == Status)
|
||
{
|
||
//If key could not be opened with SamDesired access
|
||
//open it with MAXIMUM_ALLOWED.
|
||
//do it only if it's terminal server and proper
|
||
//flag is set in the registry.
|
||
if ( gdwRegistryExtensionFlags & TERMSRV_ENABLE_ACCESS_FLAG_MODIFICATION )
|
||
{
|
||
{
|
||
//MAXIMUM_ALLOWED does not include ACCESS_SYSTEM_SECURITY
|
||
//so if user asks for this permission, we need to add it.
|
||
//It could result in ACCESS_DENIED error but for
|
||
//TS App. Compat. it is not important.
|
||
if(REG_CLASS_IS_SPECIAL_KEY(hKey))
|
||
{
|
||
Status = BaseRegOpenClassKey(
|
||
hKey,
|
||
lpSubKey,
|
||
dwOptions,
|
||
(samDesired & (KEY_WOW64_RES | ACCESS_SYSTEM_SECURITY)) | MAXIMUM_ALLOWED,
|
||
phkResult);
|
||
}
|
||
else
|
||
{
|
||
Status = NtOpenKey(
|
||
phkResult,
|
||
(samDesired & (KEY_WOW64_RES | ACCESS_SYSTEM_SECURITY)) | MAXIMUM_ALLOWED,
|
||
&Obja);
|
||
}
|
||
|
||
|
||
// Give app back the original error
|
||
if (!NT_SUCCESS(Status)) {
|
||
Status = STATUS_ACCESS_DENIED;
|
||
}
|
||
ret = (error_status_t)RtlNtStatusToDosError( Status );
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
|
||
if ((!REG_CLASS_IS_SPECIAL_KEY(hKey)) && !NT_SUCCESS(Status) && gpfnTermsrvOpenRegEntry) {
|
||
|
||
//
|
||
// Obja was initialized above
|
||
//
|
||
|
||
if (gpfnTermsrvOpenRegEntry(phkResult,
|
||
samDesired,
|
||
&Obja)) {
|
||
Status = STATUS_SUCCESS;
|
||
ret = (error_status_t)RtlNtStatusToDosError( Status );
|
||
}
|
||
}
|
||
#if defined(LEAK_TRACK)
|
||
|
||
if (g_RegLeakTraceInfo.bEnableLeakTrack) {
|
||
if (ERROR_SUCCESS == ret) {
|
||
(void) TrackObject(*phkResult);
|
||
}
|
||
}
|
||
|
||
#endif (LEAK_TRACK)
|
||
|
||
*lpSubKey = TmpStr; //Restore original SubKey string
|
||
|
||
#endif // LOCAL
|
||
|
||
return ret;
|
||
}
|
||
|
||
//
|
||
// BaseRegGetVersion - new for Chicago to determine what version a registry
|
||
// key is connected to.
|
||
//
|
||
|
||
error_status_t
|
||
BaseRegGetVersion(
|
||
IN HKEY hKey,
|
||
OUT LPDWORD lpdwVersion
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
New for Win95, allows a caller to determine what version a registry
|
||
key is connected to.
|
||
|
||
Arguments:
|
||
|
||
hKey - Supplies a handle to an open key.
|
||
|
||
lpdwVersion - Returns the registry version.
|
||
|
||
Return Value:
|
||
|
||
Returns ERROR_SUCCESS (0) for success;
|
||
|
||
If successful, BaseRegGetVersion returns the registry version in lpdwVersion
|
||
|
||
--*/
|
||
{
|
||
if (lpdwVersion != NULL) {
|
||
*lpdwVersion = REMOTE_REGISTRY_VERSION;
|
||
return(ERROR_SUCCESS);
|
||
}
|
||
//
|
||
// ERROR_NOACCESS is kind of a weird thing to return,
|
||
// but we want to return something different in the
|
||
// NULL case because that is how we tell whether we
|
||
// are talking to a Win95 machine. Win95's implementation
|
||
// of BaseRegGetVersion does not actually fill in the
|
||
// version. It just returns ERROR_SUCCESS or
|
||
// ERROR_INVALID_PARAMETER.
|
||
//
|
||
return(ERROR_NOACCESS);
|
||
}
|
||
|